Skip to content
Snippets Groups Projects
Commit 71a1ab74 authored by catch's avatar catch
Browse files

Issue #3000749 by amateescu, s_leu, dragos-dumi, tim.plunkett, jeremylichtman,...

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

(cherry picked from commit bf22a795)
parent 60b47296
No related branches found
No related tags found
22 merge requests!11185Issue #3477324 by andypost, alexpott: Fix usage of str_getcsv() and fgetcsv() for PHP 8.4,!10602Issue #3438769 by vinmayiswamy, antonnavi, michelle, amateescu: Sub workspace does not clear,!10301Issue #3469309 by mstrelan, smustgrave, moshe weitzman: Use one-time login...,!10187Issue #3487488 by dakwamine: ExtensionMimeTypeGuesser::guessMimeType must support file names with "0" (zero) like foo.0.zip,!9944Issue #3483353: Consider making the createCopy config action optionally fail...,!9929Issue #3445469 by pooja_sharma, smustgrave: Add additional test coverage for...,!9787Resolve issue 3479427 - bootstrap barrio issue under Windows,!9742Issue #3463908 by catch, quietone: Split OptionsFieldUiTest into two,!9526Issue #3458177 by mondrake, catch, quietone, godotislate, longwave, larowlan,...,!8738Issue #3424162 by camilledavis, dineshkumarbollu, smustgrave: Claro...,!8704Make greek characters available in ckeditor5,!8597Draft: Issue #3442259 by catch, quietone, dww: Reduce time of Migrate Upgrade tests...,!8533Issue #3446962 by kim.pepper: Remove incorrectly added...,!8517Issue #3443748 by NexusNovaz, smustgrave: Testcase creates false positive,!8325Update file Sort.php,!8095Expose document root on install,!7930Resolve #3427374 "Taxonomytid viewsargumentdefault plugin",!7627Issue #3439440 by nicxvan, Binoli Lalani, longwave: Remove country support from DateFormatter,!7445Issue #3440169: When using drupalGet(), provide an associative array for $headers,!6502Draft: Resolve #2938524 "Plach testing issue",!38582585169-10.1.x,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key
Pipeline #143717 passed with warnings
Showing
with 293 additions and 24 deletions
......@@ -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 @@ public function doBuildForm(array $form, FormStateInterface $form_state, Section
$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 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
$this->delta = $delta;
$this->isUpdate = is_null($plugin_id);
$this->pluginId = $plugin_id;
$this->markWorkspaceSafe($form_state);
$section = $this->getCurrentSection();
......
......@@ -17,6 +17,8 @@
*/
class DiscardLayoutChangesForm extends ConfirmFormBase {
use WorkspaceSafeFormTrait;
/**
* The layout tempstore repository.
*
......@@ -87,6 +89,7 @@ public function getCancelUrl() {
*/
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 @@
*/
class LayoutBuilderDisableForm extends ConfirmFormBase {
use WorkspaceSafeFormTrait;
/**
* The layout tempstore repository.
*
......@@ -92,6 +94,7 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
}
$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 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
$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 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
$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 @@ protected function init(FormStateInterface $form_state) {
*/
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 @@
*/
class RevertOverridesForm extends ConfirmFormBase {
use WorkspaceSafeFormTrait;
/**
* The layout tempstore repository.
*
......@@ -99,6 +101,7 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
}
$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);
......
<?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\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 @@ public function handlePreSave(EntityInterface $entity) {
$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 @@ protected function saveInlineBlockComponent(EntityInterface $entity, SectionComp
$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 @@ public static function create(ContainerInterface $container, array $configuratio
public function defaultConfiguration() {
return [
'view_mode' => 'full',
'block_id' => NULL,
'block_revision_id' => NULL,
'block_serialized' => NULL,
];
......@@ -289,6 +290,7 @@ public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE
$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 @@ protected function getLatestBlockEntityId() {
/**
* 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);
}
......
<?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);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment