Skip to content
Snippets Groups Projects
Verified Commit 686537e1 authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #3242564 by amateescu, larowlan, catch, Fabianx, longwave: Workspaces...

Issue #3242564 by amateescu, larowlan, catch, Fabianx, longwave: Workspaces can't be published from the command line
parent 40b09f3b
Branches
Tags
26 merge requests!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!4289Issue #1344552 by marcingy, Niklas Fiekas, Ravi.J, aleevas, Eduardo Morales...,!4114Issue #2707291: Disable body-level scrolling when a dialog is open as a modal,!4100Issue #3249600: Add support for PHP 8.1 Enums as allowed values for list_* data types,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1484Exposed filters get values from URL when Ajax is on,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1162Issue #3100350: Unable to save '/' root path alias,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!925Issue #2339235: Remove taxonomy hard dependency on node module,!877Issue #2708101: Default value for link text is not saved,!872Draft: Issue #3221319: Race condition when creating menu links and editing content deletes menu links,!844Resolve #3036010 "Updaters",!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
Showing
with 392 additions and 88 deletions
......@@ -5,7 +5,6 @@
* Contains content_moderation.module.
*/
use Drupal\content_moderation\ContentModerationState;
use Drupal\content_moderation\EntityOperations;
use Drupal\content_moderation\EntityTypeInfo;
use Drupal\content_moderation\ContentPreprocess;
......@@ -30,7 +29,6 @@
use Drupal\Core\Action\Plugin\Action\PublishAction;
use Drupal\Core\Action\Plugin\Action\UnpublishAction;
use Drupal\workflows\Entity\Workflow;
use Drupal\workspaces\WorkspaceInterface;
use Drupal\views\Entity\View;
/**
......@@ -274,65 +272,6 @@ function content_moderation_entity_field_access($operation, FieldDefinitionInter
return AccessResult::neutral();
}
/**
* Implements hook_ENTITY_TYPE_access() for the 'workspace' entity type.
*
* Prevents a workspace to be published if there are any pending revisions in a
* moderation state that doesn't create default revisions.
*/
function content_moderation_workspace_access(WorkspaceInterface $workspace, $operation, AccountInterface $account) {
if ($operation !== 'publish') {
return AccessResult::neutral();
}
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
$workspace_association = \Drupal::service('workspaces.association');
$entity_type_manager = \Drupal::entityTypeManager();
$tracked_revisions = $workspace_association->getTrackedEntities($workspace->id());
// Extract all the second-level keys (revision IDs) of the two-dimensional
// array.
$tracked_revision_ids = array_reduce(array_map('array_keys', $tracked_revisions), 'array_merge', []);
// Gather a list of moderation states that don't create a default revision.
$workflow_non_default_states = [];
foreach ($entity_type_manager->getStorage('workflow')->loadByProperties(['type' => 'content_moderation']) as $workflow) {
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModerationInterface $workflow_type */
$workflow_type = $workflow->getTypePlugin();
// Find all workflows which are moderating entity types of the same type to
// those that are tracked by the workspace.
if (array_intersect($workflow_type->getEntityTypes(), array_keys($tracked_revisions))) {
$workflow_non_default_states[$workflow->id()] = array_filter(array_map(function (ContentModerationState $state) {
return !$state->isDefaultRevisionState() ? $state->id() : NULL;
}, $workflow_type->getStates()));
}
}
// Check if any revisions that are about to be published are in a non-default
// revision moderation state.
$query = $entity_type_manager->getStorage('content_moderation_state')->getQuery()
->allRevisions()
->accessCheck(FALSE);
$query->condition('content_entity_revision_id', $tracked_revision_ids, 'IN');
$workflow_condition_group = $query->orConditionGroup();
foreach ($workflow_non_default_states as $workflow_id => $non_default_states) {
$group = $query->andConditionGroup()
->condition('workflow', $workflow_id, '=')
->condition('moderation_state', $non_default_states, 'IN');
$workflow_condition_group->condition($group);
}
$query->condition($workflow_condition_group);
if ($count = $query->count()->execute()) {
$message = \Drupal::translation()->formatPlural($count, 'The @label workspace can not be published because it contains 1 item in an unpublished moderation state.', 'The @label workspace can not be published because it contains @count items in an unpublished moderation state.', [
'@label' => $workspace->label(),
]);
return AccessResult::forbidden((string) $message);
}
}
/**
* Implements hook_theme().
*/
......
......@@ -20,3 +20,8 @@ services:
arguments: ['@entity_type.manager']
tags:
- { name: event_subscriber }
content_moderation.workspace_subscriber:
class: Drupal\content_moderation\EventSubscriber\WorkspaceSubscriber
arguments: ['@entity_type.manager', '@?workspaces.association']
tags:
- { name: event_subscriber }
<?php
namespace Drupal\content_moderation\EventSubscriber;
use Drupal\content_moderation\ContentModerationState;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\workspaces\Event\WorkspacePrePublishEvent;
use Drupal\workspaces\Event\WorkspacePublishEvent;
use Drupal\workspaces\WorkspaceAssociationInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Checks whether a workspace is publishable, and prevents publishing if needed.
*/
class WorkspaceSubscriber implements EventSubscriberInterface {
/**
* Constructs a new WorkspaceSubscriber instance.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager service.
* @param \Drupal\workspaces\WorkspaceAssociationInterface $workspaceAssociation
* The workspace association service.
*/
public function __construct(
protected readonly EntityTypeManagerInterface $entityTypeManager,
protected readonly WorkspaceAssociationInterface $workspaceAssociation
) {}
/**
* Prevents a workspace from being published based on certain conditions.
*
* @param \Drupal\workspaces\Event\WorkspacePublishEvent $event
* The workspace publish event.
*/
public function onWorkspacePrePublish(WorkspacePublishEvent $event): void {
// Prevent a workspace from being published if there are any pending
// revisions in a moderation state that doesn't create default revisions.
$workspace = $event->getWorkspace();
$tracked_revisions = $this->workspaceAssociation->getTrackedEntities($workspace->id());
// Extract all the second-level keys (revision IDs) of the two-dimensional
// array.
$tracked_revision_ids = array_reduce(array_map('array_keys', $tracked_revisions), 'array_merge', []);
// Gather a list of moderation states that don't create a default revision.
$workflow_non_default_states = [];
foreach ($this->entityTypeManager->getStorage('workflow')->loadByProperties(['type' => 'content_moderation']) as $workflow) {
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModerationInterface $workflow_type */
$workflow_type = $workflow->getTypePlugin();
// Find all workflows which are moderating entity types of the same type
// to those that are tracked by the workspace.
if (array_intersect($workflow_type->getEntityTypes(), array_keys($tracked_revisions))) {
$workflow_non_default_states[$workflow->id()] = array_filter(array_map(function (ContentModerationState $state) {
return !$state->isDefaultRevisionState() ? $state->id() : NULL;
}, $workflow_type->getStates()));
}
}
// Check if any revisions that are about to be published are in a
// non-default revision moderation state.
$query = $this->entityTypeManager->getStorage('content_moderation_state')->getQuery()
->allRevisions()
->accessCheck(FALSE);
$query->condition('content_entity_revision_id', $tracked_revision_ids, 'IN');
$workflow_condition_group = $query->orConditionGroup();
foreach ($workflow_non_default_states as $workflow_id => $non_default_states) {
$group = $query->andConditionGroup()
->condition('workflow', $workflow_id, '=')
->condition('moderation_state', $non_default_states, 'IN');
$workflow_condition_group->condition($group);
}
$query->condition($workflow_condition_group);
if ($count = $query->count()->execute()) {
$message = \Drupal::translation()->formatPlural($count, 'The @label workspace can not be published because it contains 1 item in an unpublished moderation state.', 'The @label workspace can not be published because it contains @count items in an unpublished moderation state.', [
'@label' => $workspace->label(),
]);
$event->stopPublishing();
$event->setPublishingStoppedReason((string) $message);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[WorkspacePrePublishEvent::class][] = ['onWorkspacePrePublish'];
return $events;
}
}
......@@ -10,7 +10,7 @@
use Drupal\Tests\workspaces\Kernel\WorkspaceTestTrait;
use Drupal\workflows\Entity\Workflow;
use Drupal\workflows\WorkflowInterface;
use Drupal\workspaces\WorkspaceAccessException;
use Drupal\workspaces\WorkspacePublishException;
/**
* Tests that Workspaces and Content Moderation work together properly.
......@@ -118,7 +118,7 @@ public function testContentModerationIntegrationWithWorkspaces() {
$this->workspaces['stage']->publish();
$this->fail('The expected exception was not thrown.');
}
catch (WorkspaceAccessException $e) {
catch (WorkspacePublishException $e) {
$this->assertEquals('The Stage workspace can not be published because it contains 3 items in an unpublished moderation state.', $e->getMessage());
}
......@@ -130,7 +130,7 @@ public function testContentModerationIntegrationWithWorkspaces() {
$this->workspaces['stage']->publish();
$this->fail('The expected exception was not thrown.');
}
catch (WorkspaceAccessException $e) {
catch (WorkspacePublishException $e) {
$this->assertEquals('The Stage workspace can not be published because it contains 2 items in an unpublished moderation state.', $e->getMessage());
}
......@@ -142,7 +142,7 @@ public function testContentModerationIntegrationWithWorkspaces() {
$this->workspaces['stage']->publish();
$this->fail('The expected exception was not thrown.');
}
catch (WorkspaceAccessException $e) {
catch (WorkspacePublishException $e) {
$this->assertEquals('The Stage workspace can not be published because it contains 1 item in an unpublished moderation state.', $e->getMessage());
}
......
<?php
namespace Drupal\workspaces\Event;
/**
* Defines the post-publish event class.
*/
class WorkspacePostPublishEvent extends WorkspacePublishEvent {
}
<?php
namespace Drupal\workspaces\Event;
/**
* Defines the pre-publish event class.
*/
class WorkspacePrePublishEvent extends WorkspacePublishEvent {
}
<?php
namespace Drupal\workspaces\Event;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\workspaces\WorkspaceInterface;
use Drupal\Component\EventDispatcher\Event;
/**
* Defines the workspace publish event.
*/
abstract class WorkspacePublishEvent extends Event {
/**
* The IDs of the entities that are being published.
*/
protected readonly array $publishedRevisionIds;
/**
* Whether an event subscriber requested the publishing to be stopped.
*/
protected bool $publishingStopped = FALSE;
/**
* The reason why publishing stopped. For use in messages.
*/
protected string $publishingStoppedReason = '';
/**
* Constructs a new WorkspacePublishEvent.
*
* @param \Drupal\workspaces\WorkspaceInterface $workspace
* The workspace.
* @param array $published_revision_ids
* The IDs of the entities that are being published.
*/
public function __construct(
protected readonly WorkspaceInterface $workspace,
array $published_revision_ids
) {
$this->publishedRevisionIds = $published_revision_ids;
}
/**
* Gets the workspace.
*
* @return \Drupal\workspaces\WorkspaceInterface
* The workspace.
*/
public function getWorkspace(): WorkspaceInterface {
return $this->workspace;
}
/**
* Gets the entity IDs that are being published as part of the workspace.
*
* @return array
* Returns a multidimensional array where the first level keys are entity
* type IDs and the values are an array of entity IDs keyed by revision IDs.
*/
public function getPublishedRevisionIds(): array {
return $this->publishedRevisionIds;
}
/**
* Determines whether a subscriber requested the publishing to be stopped.
*
* @return bool
* TRUE if the publishing of the workspace should be stopped, FALSE
* otherwise.
*/
public function isPublishingStopped(): bool {
return $this->publishingStopped;
}
/**
* Signals that the workspace publishing should be aborted.
*
* @return $this
*/
public function stopPublishing(): static {
$this->publishingStopped = TRUE;
return $this;
}
/**
* Gets the reason for stopping the workspace publication.
*
* @return string|\Drupal\Core\StringTranslation\TranslatableMarkup
* The reason for stopping the workspace publication or an empty string if
* no reason is provided.
*/
public function getPublishingStoppedReason(): string|TranslatableMarkup {
return $this->publishingStoppedReason;
}
/**
* Sets the reason for stopping the workspace publication.
*
* @param string|\Stringable $reason
* The reason for stopping the workspace publication.
*
* @return $this
*/
public function setPublishingStoppedReason(string|\Stringable $reason): static {
$this->publishingStoppedReason = $reason;
return $this;
}
}
......@@ -6,11 +6,14 @@
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\workspaces\Event\WorkspacePostPublishEvent;
use Drupal\workspaces\Event\WorkspacePublishEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Provides a class for CRUD operations on workspace associations.
*/
class WorkspaceAssociation implements WorkspaceAssociationInterface {
class WorkspaceAssociation implements WorkspaceAssociationInterface, EventSubscriberInterface {
/**
* The table for the workspace association storage.
......@@ -208,6 +211,7 @@ public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity) {
* {@inheritdoc}
*/
public function postPublish(WorkspaceInterface $workspace) {
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use the \Drupal\workspaces\Event\WorkspacePostPublishEvent event instead. See https://www.drupal.org/node/3242573', E_USER_DEPRECATED);
$this->deleteAssociations($workspace->id());
}
......@@ -263,4 +267,23 @@ public function initializeWorkspace(WorkspaceInterface $workspace) {
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
// Workspace association records cleanup should happen as late as possible.
$events[WorkspacePostPublishEvent::class][] = ['onPostPublish', -500];
return $events;
}
/**
* Triggers clean-up operations after a workspace is published.
*
* @param \Drupal\workspaces\Event\WorkspacePublishEvent $event
* The workspace publish event.
*/
public function onPostPublish(WorkspacePublishEvent $event): void {
$this->deleteAssociations($event->getWorkspace()->id());
}
}
......@@ -91,6 +91,11 @@ public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity);
*
* @param \Drupal\workspaces\WorkspaceInterface $workspace
* A workspace entity.
*
* @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use the
* \Drupal\workspaces\Event\WorkspacePostPublishEvent event instead.
*
* @see https://www.drupal.org/node/3242573
*/
public function postPublish(WorkspaceInterface $workspace);
......
......@@ -5,6 +5,7 @@
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* Defines a factory class for workspace operations.
......@@ -52,7 +53,14 @@ class WorkspaceOperationFactory {
protected $cacheTagsInvalidator;
/**
* Constructs a new WorkspacePublisher.
* An event dispatcher instance to use for configuration events.
*
* @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* Constructs a new WorkspaceOperationFactory.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
......@@ -64,13 +72,20 @@ class WorkspaceOperationFactory {
* The workspace association service.
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
* The cache tags invalidator service.
* @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association, CacheTagsInvalidatorInterface $cache_tags_invalidator) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association, CacheTagsInvalidatorInterface $cache_tags_invalidator, EventDispatcherInterface $event_dispatcher = NULL) {
$this->entityTypeManager = $entity_type_manager;
$this->database = $database;
$this->workspaceManager = $workspace_manager;
$this->workspaceAssociation = $workspace_association;
$this->cacheTagsInvalidator = $cache_tags_invalidator;
if (!$event_dispatcher) {
@trigger_error('The event dispatcher service should be passed to WorkspaceOperationFactory::__construct() since 10.1.0. This will be required in Drupal 11.0.0.', E_USER_DEPRECATED);
$event_dispatcher = \Drupal::service('event_dispatcher');
}
$this->eventDispatcher = $event_dispatcher;
}
/**
......@@ -83,7 +98,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Con
* A workspace publisher object.
*/
public function getPublisher(WorkspaceInterface $source) {
return new WorkspacePublisher($this->entityTypeManager, $this->database, $this->workspaceManager, $this->workspaceAssociation, $source);
return new WorkspacePublisher($this->entityTypeManager, $this->database, $this->workspaceManager, $this->workspaceAssociation, $this->eventDispatcher, $source);
}
/**
......
<?php
namespace Drupal\workspaces;
/**
* An exception thrown when a workspace can not be published.
*/
class WorkspacePublishException extends WorkspaceAccessException {
}
......@@ -2,10 +2,11 @@
namespace Drupal\workspaces;
use Drupal\Core\Access\AccessResultReasonInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\workspaces\Event\WorkspacePostPublishEvent;
use Drupal\workspaces\Event\WorkspacePrePublishEvent;
/**
* Default implementation of the workspace publisher.
......@@ -51,6 +52,13 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
*/
protected $workspaceAssociation;
/**
* The event dispatcher.
*
* @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* Constructs a new WorkspacePublisher.
*
......@@ -62,14 +70,22 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
* The workspace manager.
* @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
* The workspace association service.
* @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
* @param \Drupal\workspaces\WorkspaceInterface $source
* The source workspace entity.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association, WorkspaceInterface $source) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association, $event_dispatcher, WorkspaceInterface $source = NULL) {
$this->entityTypeManager = $entity_type_manager;
$this->database = $database;
$this->workspaceManager = $workspace_manager;
$this->workspaceAssociation = $workspace_association;
if ($event_dispatcher instanceof WorkspaceInterface) {
@trigger_error('Calling WorkspacePublisher::__construct() without the $event_dispatcher argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3242573', E_USER_DEPRECATED);
$source = $event_dispatcher;
$event_dispatcher = \Drupal::service('event_dispatcher');
}
$this->eventDispatcher = $event_dispatcher;
$this->sourceWorkspace = $source;
}
......@@ -77,23 +93,28 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Con
* {@inheritdoc}
*/
public function publish() {
$publish_access = $this->sourceWorkspace->access('publish', NULL, TRUE);
if (!$publish_access->isAllowed()) {
$message = $publish_access instanceof AccessResultReasonInterface ? $publish_access->getReason() : '';
throw new WorkspaceAccessException($message);
if ($this->sourceWorkspace->hasParent()) {
throw new WorkspacePublishException('Only top-level workspaces can be published.');
}
if ($this->checkConflictsOnTarget()) {
throw new WorkspaceConflictException();
}
$tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->sourceWorkspace->id());
$event = new WorkspacePrePublishEvent($this->sourceWorkspace, $tracked_entities);
$this->eventDispatcher->dispatch($event);
if ($event->isPublishingStopped()) {
throw new WorkspacePublishException((string) $event->getPublishingStoppedReason());
}
try {
$transaction = $this->database->startTransaction();
// @todo Handle the publishing of a workspace with a batch operation in
// https://www.drupal.org/node/2958752.
$this->workspaceManager->executeOutsideWorkspace(function () {
foreach ($this->getDifferringRevisionIdsOnSource() as $entity_type_id => $revision_difference) {
$this->workspaceManager->executeOutsideWorkspace(function () use ($tracked_entities) {
foreach ($tracked_entities as $entity_type_id => $revision_difference) {
$entity_revisions = $this->entityTypeManager->getStorage($entity_type_id)
->loadMultipleRevisions(array_keys($revision_difference));
$default_revisions = $this->entityTypeManager->getStorage($entity_type_id)
......@@ -125,8 +146,8 @@ public function publish() {
throw $e;
}
// Notify the workspace association that a workspace has been published.
$this->workspaceAssociation->postPublish($this->sourceWorkspace);
$event = new WorkspacePostPublishEvent($this->sourceWorkspace, $tracked_entities);
$this->eventDispatcher->dispatch($event);
}
/**
......
......@@ -6,7 +6,6 @@
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\workspaces\Entity\Workspace;
use Drupal\workspaces\WorkspaceAccessException;
/**
* Tests access on workspaces.
......@@ -104,15 +103,13 @@ public function testPublishWorkspaceAccess() {
$workspace->save();
// Check that, by default, an admin user is allowed to publish a workspace.
$workspace->publish();
$this->assertTrue($workspace->access('publish'));
// Simulate an external factor which decides that a workspace can not be
// published.
\Drupal::state()->set('workspace_access_test.result.publish', AccessResult::forbidden());
\Drupal::entityTypeManager()->getAccessControlHandler('workspace')->resetCache();
$this->expectException(WorkspaceAccessException::class);
$workspace->publish();
$this->assertFalse($workspace->access('publish'));
}
/**
......
<?php
namespace Drupal\Tests\workspaces\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\workspaces\Entity\Workspace;
/**
* @coversDefaultClass \Drupal\workspaces\WorkspaceAssociation
* @group legacy
*/
class WorkspaceAssociationDeprecationTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['path_alias', 'user', 'workspaces'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('workspace');
$this->installSchema('workspaces', ['workspace_association']);
}
/**
* @covers ::postPublish
*/
public function testPostPublishDeprecation(): void {
$this->expectDeprecation('Drupal\workspaces\WorkspaceAssociation::postPublish() is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use the \Drupal\workspaces\Event\WorkspacePostPublishEvent event instead. See https://www.drupal.org/node/3242573');
$workspace = Workspace::create([
'id' => 'test',
'label' => 'Test',
]);
$workspace->save();
\Drupal::service('workspaces.association')->postPublish($workspace);
}
}
......@@ -4,6 +4,7 @@
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Form\FormState;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\entity_test\Entity\EntityTestMulRevPub;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
......@@ -15,7 +16,7 @@
use Drupal\views\Tests\ViewResultAssertionTrait;
use Drupal\views\Views;
use Drupal\workspaces\Entity\Workspace;
use Drupal\workspaces\WorkspaceAccessException;
use Drupal\workspaces\WorkspacePublishException;
/**
* Tests a complete publishing scenario across different workspaces.
......@@ -526,11 +527,31 @@ public function testWorkspaceHierarchy() {
$this->assertWorkspaceAssociation($expected_workspace_association, 'node');
// Check that a workspace that is not at the top level can not be published.
$this->expectException(WorkspaceAccessException::class);
$this->expectException(WorkspacePublishException::class);
$this->expectExceptionMessage('Only top-level workspaces can be published.');
$this->workspaces['dev']->publish();
}
/**
* Tests workspace publishing as anonymous user, simulating a CLI request.
*/
public function testCliPublishing() {
$this->initializeWorkspacesModule();
$this->switchToWorkspace('stage');
// Add a workspace-specific revision to a pre-existing node.
$node = $this->entityTypeManager->getStorage('node')->load(2);
$node->title->value = 'stage - 2 - r3 - published';
$node->save();
// Switch to an anonymous user account and the 'Live' workspace.
\Drupal::service('account_switcher')->switchTo(new AnonymousUserSession());
\Drupal::service('workspaces.manager')->switchToLive();
// Publish the workspace as anonymous, simulating a CLI request.
$this->workspaces['stage']->publish();
}
/**
* Tests entity query overrides without any conditions.
*/
......
......@@ -6,12 +6,13 @@ services:
- { name: service_id_collector, tag: workspace_negotiator }
workspaces.operation_factory:
class: Drupal\workspaces\WorkspaceOperationFactory
arguments: ['@entity_type.manager', '@database', '@workspaces.manager', '@workspaces.association', '@cache_tags.invalidator']
arguments: ['@entity_type.manager', '@database', '@workspaces.manager', '@workspaces.association', '@cache_tags.invalidator', '@event_dispatcher']
workspaces.association:
class: Drupal\workspaces\WorkspaceAssociation
arguments: ['@database', '@entity_type.manager', '@workspaces.repository']
tags:
- { name: backend_overridable }
- { name: event_subscriber }
workspaces.repository:
class: Drupal\workspaces\WorkspaceRepository
arguments: ['@entity_type.manager', '@cache.default']
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment