requestStack = $request_stack; $this->entityTypeManager = $entity_type_manager; $this->entityMemoryCache = $entity_memory_cache; $this->currentUser = $current_user; $this->state = $state; $this->logger = $logger; $this->classResolver = $class_resolver; $this->negotiatorIds = $negotiator_ids; } /** * {@inheritdoc} */ public function isEntityTypeSupported(EntityTypeInterface $entity_type) { if (!isset($this->blacklist[$entity_type->id()]) && $entity_type->entityClassImplements(EntityPublishedInterface::class) && $entity_type->isRevisionable()) { return TRUE; } $this->blacklist[$entity_type->id()] = $entity_type->id(); return FALSE; } /** * {@inheritdoc} */ public function getSupportedEntityTypes() { $entity_types = []; foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { if ($this->isEntityTypeSupported($entity_type)) { $entity_types[$entity_type_id] = $entity_type; } } return $entity_types; } /** * {@inheritdoc} */ public function getActiveWorkspace() { if (!isset($this->activeWorkspace)) { $request = $this->requestStack->getCurrentRequest(); foreach ($this->negotiatorIds as $negotiator_id) { $negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id); if ($negotiator->applies($request)) { if ($this->activeWorkspace = $negotiator->getActiveWorkspace($request)) { break; } } } } // The default workspace negotiator always returns a valid workspace. return $this->activeWorkspace; } /** * {@inheritdoc} */ public function setActiveWorkspace(WorkspaceInterface $workspace) { $this->doSwitchWorkspace($workspace); // Set the workspace on the proper negotiator. $request = $this->requestStack->getCurrentRequest(); foreach ($this->negotiatorIds as $negotiator_id) { $negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id); if ($negotiator->applies($request)) { $negotiator->setActiveWorkspace($workspace); break; } } return $this; } /** * Switches the current workspace. * * @param \Drupal\workspaces\WorkspaceInterface $workspace * The workspace to set as active. * * @throws \Drupal\workspaces\WorkspaceAccessException * Thrown when the current user doesn't have access to view the workspace. */ protected function doSwitchWorkspace(WorkspaceInterface $workspace) { // If the current user doesn't have access to view the workspace, they // shouldn't be allowed to switch to it. if (!$workspace->access('view') && !$workspace->isDefaultWorkspace()) { $this->logger->error('Denied access to view workspace %workspace_label for user %uid', [ '%workspace_label' => $workspace->label(), '%uid' => $this->currentUser->id(), ]); throw new WorkspaceAccessException('The user does not have permission to view that workspace.'); } $this->activeWorkspace = $workspace; // Clear the static entity cache for the supported entity types. $cache_tags_to_invalidate = array_map(function ($entity_type_id) { return 'entity.memory_cache:' . $entity_type_id; }, array_keys($this->getSupportedEntityTypes())); $this->entityMemoryCache->invalidateTags($cache_tags_to_invalidate); } /** * {@inheritdoc} */ public function executeInWorkspace($workspace_id, callable $function) { /** @var \Drupal\workspaces\WorkspaceInterface $workspace */ $workspace = $this->entityTypeManager->getStorage('workspace')->load($workspace_id); if (!$workspace) { throw new \InvalidArgumentException('The ' . $workspace_id . ' workspace does not exist.'); } $previous_active_workspace = $this->getActiveWorkspace(); $this->doSwitchWorkspace($workspace); $result = $function(); $this->doSwitchWorkspace($previous_active_workspace); return $result; } /** * {@inheritdoc} */ public function shouldAlterOperations(EntityTypeInterface $entity_type) { return $this->isEntityTypeSupported($entity_type) && !$this->getActiveWorkspace()->isDefaultWorkspace(); } /** * {@inheritdoc} */ public function purgeDeletedWorkspacesBatch() { $deleted_workspace_ids = $this->state->get('workspace.deleted', []); // Bail out early if there are no workspaces to purge. if (empty($deleted_workspace_ids)) { return; } $batch_size = Settings::get('entity_update_batch_size', 50); /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */ $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); // Get the first deleted workspace from the list and delete the revisions // associated with it, along with the workspace_association entries. $workspace_id = reset($deleted_workspace_ids); $workspace_association_ids = $this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size); if ($workspace_association_ids) { $workspace_associations = $workspace_association_storage->loadMultipleRevisions(array_keys($workspace_association_ids)); foreach ($workspace_associations as $workspace_association) { $associated_entity_storage = $this->entityTypeManager->getStorage($workspace_association->target_entity_type_id->value); // Delete the associated entity revision. if ($entity = $associated_entity_storage->loadRevision($workspace_association->target_entity_revision_id->value)) { if ($entity->isDefaultRevision()) { $entity->delete(); } else { $associated_entity_storage->deleteRevision($workspace_association->target_entity_revision_id->value); } } // Delete the workspace_association revision. if ($workspace_association->isDefaultRevision()) { $workspace_association->delete(); } else { $workspace_association_storage->deleteRevision($workspace_association->getRevisionId()); } } } // The purging operation above might have taken a long time, so we need to // request a fresh list of workspace association IDs. If it is empty, we can // go ahead and remove the deleted workspace ID entry from state. if (!$this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size)) { unset($deleted_workspace_ids[$workspace_id]); $this->state->set('workspace.deleted', $deleted_workspace_ids); } } /** * Gets a list of workspace association IDs to purge. * * @param string $workspace_id * The ID of the workspace. * @param int $batch_size * The maximum number of records that will be purged. * * @return array * An array of workspace association IDs, keyed by their revision IDs. */ protected function getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size) { return $this->entityTypeManager->getStorage('workspace_association') ->getQuery() ->allRevisions() ->accessCheck(FALSE) ->condition('workspace', $workspace_id) ->sort('revision_id', 'ASC') ->range(0, $batch_size) ->execute(); } }