diff --git a/core/modules/workspaces/src/Controller/WorkspacesHtmlEntityFormController.php b/core/modules/workspaces/src/Controller/WorkspacesHtmlEntityFormController.php index bb8fb33259695655e2a9f93cc7d3882697aa4c67..281d9ae696785a48912768b6e2e410ddb10856e2 100644 --- a/core/modules/workspaces/src/Controller/WorkspacesHtmlEntityFormController.php +++ b/core/modules/workspaces/src/Controller/WorkspacesHtmlEntityFormController.php @@ -9,6 +9,8 @@ use Drupal\Core\Form\FormInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\TypedData\TypedDataManagerInterface; +use Drupal\workspaces\Plugin\Validation\Constraint\EntityWorkspaceConflictConstraint; use Drupal\workspaces\WorkspaceInformationInterface; use Drupal\workspaces\WorkspaceManagerInterface; use Symfony\Component\HttpFoundation\Request; @@ -24,7 +26,8 @@ class WorkspacesHtmlEntityFormController extends FormController { public function __construct( protected readonly FormController $entityFormController, protected readonly WorkspaceManagerInterface $workspaceManager, - protected readonly WorkspaceInformationInterface $workspaceInfo + protected readonly WorkspaceInformationInterface $workspaceInfo, + protected readonly TypedDataManagerInterface $typedDataManager ) {} /** @@ -34,16 +37,42 @@ public function getContentResult(Request $request, RouteMatchInterface $route_ma $form_arg = $this->getFormArgument($route_match); $form_object = $this->getFormObject($route_match, $form_arg); + /** @var \Drupal\Core\Entity\EntityInterface $entity */ $entity = $form_object->getEntity(); if ($this->workspaceInfo->isEntitySupported($entity)) { $active_workspace = $this->workspaceManager->getActiveWorkspace(); + // Prepare a minimal render array in case we need to return it. + $build['#cache']['contexts'] = $entity->getCacheContexts(); + $build['#cache']['tags'] = $entity->getCacheTags(); + $build['#cache']['max-age'] = $entity->getCacheMaxAge(); + + // Prevent entities from being edited if they're tracked in workspace. + if ($form_object->getOperation() !== 'delete') { + $constraints = array_values(array_filter($entity->getTypedData()->getConstraints(), function ($constraint) { + return $constraint instanceof EntityWorkspaceConflictConstraint; + })); + + if (!empty($constraints)) { + $violations = $this->typedDataManager->getValidator()->validate( + $entity->getTypedData(), + $constraints[0] + ); + if (count($violations)) { + $build['#markup'] = $violations->get(0)->getMessage(); + + return $build; + } + } + } + // Prevent entities from being deleted in a workspace if they have a // published default revision. if ($form_object->getOperation() === 'delete' && $active_workspace && !$this->workspaceInfo->isEntityDeletable($entity, $active_workspace)) { $build['#markup'] = $this->t('This @entity_type_label can only be deleted in the Live workspace.', [ '@entity_type_label' => $entity->getEntityType()->getSingularLabel(), ]); + return $build; } } diff --git a/core/modules/workspaces/src/EntityOperations.php b/core/modules/workspaces/src/EntityOperations.php index b46929b3d025e9e9a256f67a1998ae8fda817c26..4324d30f703e169a245dabdfe73578c1f2f2d4ab 100644 --- a/core/modules/workspaces/src/EntityOperations.php +++ b/core/modules/workspaces/src/EntityOperations.php @@ -8,7 +8,6 @@ use Drupal\Core\Entity\RevisionableInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\workspaces\Plugin\Validation\Constraint\EntityWorkspaceConflictConstraint; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -319,17 +318,6 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, $f if ($this->workspaceManager->hasActiveWorkspace()) { $form['#entity_builders'][] = [static::class, 'entityFormEntityBuild']; } - - // Run the workspace conflict validation constraint when the entity form is - // being built so we can "disable" it early and display a message to the - // user, instead of allowing them to enter data that can never be saved. - foreach ($entity->validate()->getEntityViolations() as $violation) { - if ($violation->getConstraint() instanceof EntityWorkspaceConflictConstraint) { - $form['#markup'] = $violation->getMessage(); - $form['#access'] = FALSE; - continue; - } - } } /** diff --git a/core/modules/workspaces/tests/src/FunctionalJavascript/WorkspacesLayoutBuilderIntegrationTest.php b/core/modules/workspaces/tests/src/FunctionalJavascript/WorkspacesLayoutBuilderIntegrationTest.php index b4ed65d0961c3243db00d8d12553f1e392c7e9db..56c4249267e3f963476e7de3821f174af0a6648b 100644 --- a/core/modules/workspaces/tests/src/FunctionalJavascript/WorkspacesLayoutBuilderIntegrationTest.php +++ b/core/modules/workspaces/tests/src/FunctionalJavascript/WorkspacesLayoutBuilderIntegrationTest.php @@ -123,6 +123,10 @@ public function testBlocksInWorkspaces(): void { $assert_session->pageTextNotContains($second_block_body); $assert_session->pageTextContains('The DEFAULT block body'); + // Check the concurrent editing protection on the Layout Builder form. + $this->drupalGet('/node/1/layout'); + $assert_session->pageTextContains('The content is being edited in the Stage workspace. As a result, your changes cannot be saved.'); + $stage->publish(); $this->drupalGet('node/1'); $assert_session->pageTextNotContains('The DEFAULT block body'); diff --git a/core/modules/workspaces/workspaces.services.yml b/core/modules/workspaces/workspaces.services.yml index cb7cce900691569cf5b8c1809c25b65cb1734a82..f124d5e376c3f745d800dfc2770968d0d1dbe96f 100644 --- a/core/modules/workspaces/workspaces.services.yml +++ b/core/modules/workspaces/workspaces.services.yml @@ -94,5 +94,5 @@ services: decorates: controller.entity_form class: Drupal\workspaces\Controller\WorkspacesHtmlEntityFormController public: false - arguments: ['@.inner', '@workspaces.manager', '@workspaces.information'] + arguments: ['@.inner', '@workspaces.manager', '@workspaces.information', '@typed_data_manager'] Drupal\workspaces\Controller\WorkspacesHtmlEntityFormController: '@workspaces.controller.entity_form'