Issue #3000749 by amateescu, s_leu, dragos-dumi, tim.plunkett, jeremylichtman, smithmilner, velocis: Layout builder overrides on a single content item not allowed in a workspace
parent
6e03532191
commit
bf22a79551
|
@ -64,6 +64,9 @@ inline_block:
|
|||
view_mode:
|
||||
type: string
|
||||
label: 'View mode'
|
||||
block_id:
|
||||
type: integer
|
||||
label: 'Block ID'
|
||||
block_revision_id:
|
||||
type: integer
|
||||
label: 'Block revision ID'
|
||||
|
|
|
@ -35,6 +35,7 @@ abstract class ConfigureBlockFormBase extends FormBase implements BaseFormIdInte
|
|||
use ContextAwarePluginAssignmentTrait;
|
||||
use LayoutBuilderContextTrait;
|
||||
use LayoutRebuildTrait;
|
||||
use WorkspaceSafeFormTrait;
|
||||
|
||||
/**
|
||||
* The plugin being configured.
|
||||
|
@ -163,6 +164,7 @@ abstract class ConfigureBlockFormBase extends FormBase implements BaseFormIdInte
|
|||
$this->delta = $delta;
|
||||
$this->uuid = $component->getUuid();
|
||||
$this->block = $component->getPlugin();
|
||||
$this->markWorkspaceSafe($form_state);
|
||||
|
||||
$form_state->setTemporaryValue('gathered_contexts', $this->getPopulatedContexts($section_storage));
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ class ConfigureSectionForm extends FormBase {
|
|||
use LayoutBuilderContextTrait;
|
||||
use LayoutBuilderHighlightTrait;
|
||||
use LayoutRebuildTrait;
|
||||
use WorkspaceSafeFormTrait;
|
||||
|
||||
/**
|
||||
* The layout tempstore repository.
|
||||
|
@ -127,6 +128,7 @@ class ConfigureSectionForm extends FormBase {
|
|||
$this->delta = $delta;
|
||||
$this->isUpdate = is_null($plugin_id);
|
||||
$this->pluginId = $plugin_id;
|
||||
$this->markWorkspaceSafe($form_state);
|
||||
|
||||
$section = $this->getCurrentSection();
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
*/
|
||||
class DiscardLayoutChangesForm extends ConfirmFormBase {
|
||||
|
||||
use WorkspaceSafeFormTrait;
|
||||
|
||||
/**
|
||||
* The layout tempstore repository.
|
||||
*
|
||||
|
@ -87,6 +89,7 @@ class DiscardLayoutChangesForm extends ConfirmFormBase {
|
|||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) {
|
||||
$this->sectionStorage = $section_storage;
|
||||
$this->markWorkspaceSafe($form_state);
|
||||
// Mark this as an administrative page for JavaScript ("Back to site" link).
|
||||
$form['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
|
||||
return parent::buildForm($form, $form_state);
|
||||
|
|
|
@ -18,6 +18,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
*/
|
||||
class LayoutBuilderDisableForm extends ConfirmFormBase {
|
||||
|
||||
use WorkspaceSafeFormTrait;
|
||||
|
||||
/**
|
||||
* The layout tempstore repository.
|
||||
*
|
||||
|
@ -92,6 +94,7 @@ class LayoutBuilderDisableForm extends ConfirmFormBase {
|
|||
}
|
||||
|
||||
$this->sectionStorage = $section_storage;
|
||||
$this->markWorkspaceSafe($form_state);
|
||||
// Mark this as an administrative page for JavaScript ("Back to site" link).
|
||||
$form['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
|
||||
return parent::buildForm($form, $form_state);
|
||||
|
|
|
@ -22,6 +22,7 @@ abstract class LayoutRebuildConfirmFormBase extends ConfirmFormBase {
|
|||
use AjaxFormHelperTrait;
|
||||
use LayoutBuilderHighlightTrait;
|
||||
use LayoutRebuildTrait;
|
||||
use WorkspaceSafeFormTrait;
|
||||
|
||||
/**
|
||||
* The layout tempstore repository.
|
||||
|
@ -77,6 +78,7 @@ abstract class LayoutRebuildConfirmFormBase extends ConfirmFormBase {
|
|||
$this->sectionStorage = $section_storage;
|
||||
$this->delta = $delta;
|
||||
|
||||
$this->markWorkspaceSafe($form_state);
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
if ($this->isAjax()) {
|
||||
|
|
|
@ -24,6 +24,7 @@ class MoveBlockForm extends FormBase {
|
|||
use LayoutBuilderContextTrait;
|
||||
use LayoutBuilderHighlightTrait;
|
||||
use LayoutRebuildTrait;
|
||||
use WorkspaceSafeFormTrait;
|
||||
|
||||
/**
|
||||
* The section storage.
|
||||
|
@ -117,6 +118,7 @@ class MoveBlockForm extends FormBase {
|
|||
$this->delta = $delta;
|
||||
$this->uuid = $uuid;
|
||||
$this->region = $region;
|
||||
$this->markWorkspaceSafe($form_state);
|
||||
|
||||
$form['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockUpdateHighlightId($uuid);
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ class OverridesEntityForm extends ContentEntityForm {
|
|||
|
||||
use PreviewToggleTrait;
|
||||
use LayoutBuilderEntityFormTrait;
|
||||
use WorkspaceSafeFormTrait;
|
||||
|
||||
/**
|
||||
* Layout tempstore repository.
|
||||
|
@ -90,6 +91,7 @@ class OverridesEntityForm extends ContentEntityForm {
|
|||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) {
|
||||
$this->sectionStorage = $section_storage;
|
||||
$this->markWorkspaceSafe($form_state);
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
$form['#attributes']['class'][] = 'layout-builder-form';
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
*/
|
||||
class RevertOverridesForm extends ConfirmFormBase {
|
||||
|
||||
use WorkspaceSafeFormTrait;
|
||||
|
||||
/**
|
||||
* The layout tempstore repository.
|
||||
*
|
||||
|
@ -99,6 +101,7 @@ class RevertOverridesForm extends ConfirmFormBase {
|
|||
}
|
||||
|
||||
$this->sectionStorage = $section_storage;
|
||||
$this->markWorkspaceSafe($form_state);
|
||||
// Mark this as an administrative page for JavaScript ("Back to site" link).
|
||||
$form['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
|
||||
return parent::buildForm($form, $form_state);
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\layout_builder\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\layout_builder\SectionStorageInterface;
|
||||
use Drupal\workspaces\WorkspaceInformationInterface;
|
||||
|
||||
/**
|
||||
* Provides a trait that marks Layout Builder forms as workspace-safe.
|
||||
*/
|
||||
trait WorkspaceSafeFormTrait {
|
||||
|
||||
/**
|
||||
* The workspace information service.
|
||||
*/
|
||||
protected ?WorkspaceInformationInterface $workspaceInfo = NULL;
|
||||
|
||||
/**
|
||||
* Marks a form as workspace-safe, if possible.
|
||||
*
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state object.
|
||||
*/
|
||||
protected function markWorkspaceSafe(FormStateInterface $form_state): void {
|
||||
if (!\Drupal::hasService('workspaces.information')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$section_storage = $this->sectionStorage ?: $this->getSectionStorageFromFormState($form_state);
|
||||
if ($section_storage) {
|
||||
$context_definitions = $section_storage->getContextDefinitions();
|
||||
if (!empty($context_definitions['entity'])) {
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity */
|
||||
$entity = $section_storage->getContext('entity')->getContextValue();
|
||||
$supported = $entity && $this->getWorkspaceInfo()->isEntitySupported($entity);
|
||||
$ignored = $entity && $this->getWorkspaceInfo()->isEntityIgnored($entity);
|
||||
|
||||
if ($supported || $ignored) {
|
||||
$form_state->set('workspace_safe', TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the section storage from a form state object, if it exists.
|
||||
*
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state object.
|
||||
*
|
||||
* @return \Drupal\layout_builder\SectionStorageInterface|null
|
||||
* The section storage or NULL if it doesn't exist.
|
||||
*/
|
||||
protected function getSectionStorageFromFormState(FormStateInterface $form_state): ?SectionStorageInterface {
|
||||
foreach ($form_state->getBuildInfo()['args'] as $argument) {
|
||||
if ($argument instanceof SectionStorageInterface) {
|
||||
return $argument;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the workspace information service.
|
||||
*
|
||||
* @return \Drupal\workspaces\WorkspaceInformationInterface
|
||||
* The workspace information service.
|
||||
*/
|
||||
protected function getWorkspaceInfo(): WorkspaceInformationInterface {
|
||||
if (!$this->workspaceInfo) {
|
||||
$this->workspaceInfo = \Drupal::service('workspaces.information');
|
||||
}
|
||||
|
||||
return $this->workspaceInfo;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,7 +7,6 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\RevisionableInterface;
|
||||
use Drupal\Core\Entity\SynchronizableInterface;
|
||||
use Drupal\layout_builder\Plugin\Block\InlineBlock;
|
||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
|
@ -171,24 +170,6 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface {
|
|||
$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.
|
||||
*
|
||||
|
@ -252,7 +233,7 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface {
|
|||
$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);
|
||||
$this->usage->addUsage($post_save_configuration['block_id'], $entity);
|
||||
}
|
||||
$component->setConfiguration($post_save_configuration);
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ class InlineBlock extends BlockBase implements ContainerFactoryPluginInterface,
|
|||
public function defaultConfiguration() {
|
||||
return [
|
||||
'view_mode' => 'full',
|
||||
'block_id' => NULL,
|
||||
'block_revision_id' => NULL,
|
||||
'block_serialized' => NULL,
|
||||
];
|
||||
|
@ -289,6 +290,7 @@ class InlineBlock extends BlockBase implements ContainerFactoryPluginInterface,
|
|||
$block->setNewRevision();
|
||||
}
|
||||
$block->save();
|
||||
$this->configuration['block_id'] = $block->id();
|
||||
$this->configuration['block_revision_id'] = $block->getRevisionId();
|
||||
$this->configuration['block_serialized'] = NULL;
|
||||
}
|
||||
|
|
|
@ -112,18 +112,23 @@ abstract class InlineBlockTestBase extends WebDriverTestBase {
|
|||
/**
|
||||
* Removes an entity block from the layout but does not save the layout.
|
||||
*/
|
||||
protected function removeInlineBlockFromLayout() {
|
||||
protected function removeInlineBlockFromLayout($selector = NULL) {
|
||||
$selector = $selector ?? static::INLINE_BLOCK_LOCATOR;
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$block_text = $page->find('css', static::INLINE_BLOCK_LOCATOR)->getText();
|
||||
$block_text = $page->find('css', $selector)->getText();
|
||||
$this->assertNotEmpty($block_text);
|
||||
$assert_session->pageTextContains($block_text);
|
||||
$this->clickContextualLink(static::INLINE_BLOCK_LOCATOR, 'Remove block');
|
||||
$this->clickContextualLink($selector, 'Remove block');
|
||||
$assert_session->waitForElement('css', "#drupal-off-canvas input[value='Remove']");
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
|
||||
// Output the new HTML.
|
||||
$this->htmlOutput($page->getHtml());
|
||||
|
||||
$page->find('css', '#drupal-off-canvas')->pressButton('Remove');
|
||||
$assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas');
|
||||
$assert_session->assertNoElementAfterWait('css', static::INLINE_BLOCK_LOCATOR);
|
||||
$assert_session->assertNoElementAfterWait('css', $selector);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->pageTextNotContains($block_text);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\FunctionalJavascript;
|
||||
|
||||
use Drupal\Tests\layout_builder\FunctionalJavascript\InlineBlockTestBase;
|
||||
use Drupal\Tests\system\Traits\OffCanvasTestTrait;
|
||||
use Drupal\Tests\workspaces\Functional\WorkspaceTestUtilities;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests for layout editing in workspaces.
|
||||
*
|
||||
* @group layout_builder
|
||||
* @group workspaces
|
||||
* @group #slow
|
||||
*/
|
||||
class WorkspacesLayoutBuilderIntegrationTest extends InlineBlockTestBase {
|
||||
|
||||
use OffCanvasTestTrait;
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'starterkit_theme';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_ui',
|
||||
'workspaces',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
'create and edit custom blocks',
|
||||
'administer blocks',
|
||||
'administer content types',
|
||||
'administer workspaces',
|
||||
'view any workspace',
|
||||
'administer site configuration',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
]));
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
|
||||
// Enable layout builder.
|
||||
$this->drupalGet(static::FIELD_UI_PREFIX . '/display/default');
|
||||
$this->submitForm([
|
||||
'layout[enabled]' => TRUE,
|
||||
'layout[allow_custom]' => TRUE,
|
||||
], 'Save');
|
||||
$this->clickLink('Manage layout');
|
||||
$this->assertSession()->addressEquals(static::FIELD_UI_PREFIX . '/display/default/layout');
|
||||
// Add a basic block with the body field set.
|
||||
$this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
|
||||
$this->assertSaveLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests changing a layout/blocks inside a workspace.
|
||||
*/
|
||||
public function testBlocksInWorkspaces(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$this->drupalGet('node/2');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
$stage = Workspace::load('stage');
|
||||
$this->switchToWorkspace($stage);
|
||||
|
||||
// Confirm the block can be edited.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$new_block_body = 'The NEW block body';
|
||||
$this->configureInlineBlock('The DEFAULT block body', $new_block_body);
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains($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($new_block_body);
|
||||
|
||||
// Switch back to the live workspace and verify that the changes are not
|
||||
// visible there.
|
||||
$this->switchToLive();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains($new_block_body);
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
$this->switchToWorkspace($stage);
|
||||
// Add a basic block with the body field set.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$second_block_body = 'The 2nd block body';
|
||||
$this->addInlineBlockToLayout('2nd Block title', $second_block_body);
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains($second_block_body);
|
||||
$this->drupalGet('node/2');
|
||||
// Node 2 should use default layout.
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains($new_block_body);
|
||||
$assert_session->pageTextNotContains($second_block_body);
|
||||
|
||||
// Switch back to the live workspace and verify that the new added block is
|
||||
// not visible there.
|
||||
$this->switchToLive();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains($second_block_body);
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
$stage->publish();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$assert_session->pageTextContains($new_block_body);
|
||||
$assert_session->pageTextContains($second_block_body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that blocks can be deleted inside workspaces.
|
||||
*/
|
||||
public function testBlockDeletionInWorkspaces(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$stage = Workspace::load('stage');
|
||||
$this->switchToWorkspace($stage);
|
||||
|
||||
$this->drupalGet('node/1/layout');
|
||||
$workspace_block_content = 'The WORKSPACE block body';
|
||||
$this->addInlineBlockToLayout('Workspace block title', $workspace_block_content);
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextContains($workspace_block_content);
|
||||
|
||||
$this->switchToLive();
|
||||
$assert_session->pageTextNotContains($workspace_block_content);
|
||||
|
||||
$this->switchToWorkspace($stage);
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->removeInlineBlockFromLayout(static::INLINE_BLOCK_LOCATOR . ' ~ ' . static::INLINE_BLOCK_LOCATOR);
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains($workspace_block_content);
|
||||
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->removeInlineBlockFromLayout();
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains($workspace_block_content);
|
||||
|
||||
$this->switchToLive();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$stage->publish();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains($workspace_block_content);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue