Commit cd12944d authored by catch's avatar catch
Browse files

Issue #3129762 by amateescu, adriancid, msuthars, Alexj12: Creating an...

Issue #3129762 by amateescu, adriancid, msuthars, Alexj12: Creating an unpublished entity in a workspace does not set the workspace field on the revision
parent 40d5e404
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -136,8 +136,10 @@ public function entityPresave(EntityInterface $entity) {
      // become the default revision only when it is replicated to the default
      // workspace.
      $entity->isDefaultRevision(FALSE);
    }

    // Track the workspaces in which the new revision was saved.
    if (!$entity->isSyncing()) {
      $field_name = $entity_type->getRevisionMetadataKey('workspace');
      $entity->{$field_name}->target_id = $this->workspaceManager->getActiveWorkspace()->id();
    }
+11 −10
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
namespace Drupal\workspaces\Plugin\Validation\Constraint;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\workspaces\WorkspaceAssociationInterface;
use Drupal\Core\State\StateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
@@ -14,20 +14,20 @@
class DeletedWorkspaceConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {

  /**
   * The workspace association service.
   * The state service.
   *
   * @var \Drupal\workspaces\WorkspaceAssociationInterface
   * @var \Drupal\Core\State\StateInterface
   */
  protected $workspaceAssociation;
  protected $state;

  /**
   * Creates a new DeletedWorkspaceConstraintValidator instance.
   *
   * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
   *   The workspace association service.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   */
  public function __construct(WorkspaceAssociationInterface $workspace_association) {
    $this->workspaceAssociation = $workspace_association;
  public function __construct(StateInterface $state) {
    $this->state = $state;
  }

  /**
@@ -35,7 +35,7 @@ public function __construct(WorkspaceAssociationInterface $workspace_association
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('workspaces.association')
      $container->get('state')
    );
  }

@@ -49,7 +49,8 @@ public function validate($value, Constraint $constraint) {
      return;
    }

    if ($this->workspaceAssociation->getTrackedEntities($value->getEntity()->id())) {
    $deleted_workspace_ids = $this->state->get('workspace.deleted', []);
    if (isset($deleted_workspace_ids[$value->getEntity()->id()])) {
      $this->context->addViolation($constraint->message);
    }
  }
+46 −2
Original line number Diff line number Diff line
@@ -186,13 +186,21 @@ public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_i
    $id_field = $table_mapping->getColumnNames($entity_type->getKey('id'))['value'];
    $revision_id_field = $table_mapping->getColumnNames($entity_type->getKey('revision'))['value'];

    $workspace_tree = $this->workspaceRepository->loadTree();
    if (isset($workspace_tree[$workspace_id])) {
      $workspace_candidates = array_merge([$workspace_id], $workspace_tree[$workspace_id]['ancestors']);
    }
    else {
      $workspace_candidates = [$workspace_id];
    }

    $query = $this->database->select($entity_type->getRevisionTable(), 'revision');
    $query->leftJoin($entity_type->getBaseTable(), 'base', "[revision].[$id_field] = [base].[$id_field]");

    $query
      ->fields('revision', [$revision_id_field, $id_field])
      ->condition("revision.$workspace_field", $workspace_id)
      ->where("[revision].[$revision_id_field] > [base].[$revision_id_field]")
      ->condition("revision.$workspace_field", $workspace_candidates, 'IN')
      ->where("[revision].[$revision_id_field] >= [base].[$revision_id_field]")
      ->orderBy("revision.$revision_id_field", 'ASC');

    // Restrict the result to a set of entity ID's if provided.
@@ -203,6 +211,42 @@ public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_i
    return $query->execute()->fetchAllKeyed();
  }

  /**
   * {@inheritdoc}
   */
  public function getAssociatedInitialRevisions(string $workspace_id, string $entity_type_id, array $entity_ids = []) {
    /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
    $storage = $this->entityTypeManager->getStorage($entity_type_id);

    // If the entity type is not using core's default entity storage, we can't
    // assume the table mapping layout so we have to return only the latest
    // tracked revisions.
    if (!$storage instanceof SqlContentEntityStorage) {
      return $this->getTrackedEntities($workspace_id, $entity_type_id, $entity_ids)[$entity_type_id];
    }

    $entity_type = $storage->getEntityType();
    $table_mapping = $storage->getTableMapping();
    $workspace_field = $table_mapping->getColumnNames($entity_type->get('revision_metadata_keys')['workspace'])['target_id'];
    $id_field = $table_mapping->getColumnNames($entity_type->getKey('id'))['value'];
    $revision_id_field = $table_mapping->getColumnNames($entity_type->getKey('revision'))['value'];

    $query = $this->database->select($entity_type->getBaseTable(), 'base');
    $query->leftJoin($entity_type->getRevisionTable(), 'revision', "[base].[$revision_id_field] = [revision].[$revision_id_field]");

    $query
      ->fields('base', [$revision_id_field, $id_field])
      ->condition("revision.$workspace_field", $workspace_id, '=')
      ->orderBy("base.$revision_id_field", 'ASC');

    // Restrict the result to a set of entity ID's if provided.
    if ($entity_ids) {
      $query->condition("base.$id_field", $entity_ids, 'IN');
    }

    return $query->execute()->fetchAllKeyed();
  }

  /**
   * {@inheritdoc}
   */
+17 −0
Original line number Diff line number Diff line
@@ -74,6 +74,23 @@ public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entit
   */
  public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_ids = NULL);

  /**
   * Retrieves all content revisions that were created in a given workspace.
   *
   * @param string $workspace_id
   *   The ID of the workspace.
   * @param string $entity_type_id
   *   An entity type ID to find revisions for.
   * @param int[]|string[] $entity_ids
   *   (optional) An array of entity IDs to filter the results by. Defaults to
   *   an empty array.
   *
   * @return array
   *   Returns an array where the values are an array of entity IDs keyed by
   *   revision IDs.
   */
  public function getAssociatedInitialRevisions(string $workspace_id, string $entity_type_id, array $entity_ids = []);

  /**
   * Gets a list of workspace IDs in which an entity is tracked.
   *
+37 −8
Original line number Diff line number Diff line
@@ -316,31 +316,60 @@ public function purgeDeletedWorkspacesBatch() {
    // Get the first deleted workspace from the list and delete the revisions
    // associated with it, along with the workspace association records.
    $workspace_id = reset($deleted_workspace_ids);
    $tracked_entities = $this->workspaceAssociation->getTrackedEntities($workspace_id);

    $all_associated_revisions = [];
    foreach (array_keys($this->getSupportedEntityTypes()) as $entity_type_id) {
      $all_associated_revisions[$entity_type_id] = $this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id);
    }
    $all_associated_revisions = array_filter($all_associated_revisions);

    $count = 1;
    foreach ($tracked_entities as $entity_type_id => $entities) {
    foreach ($all_associated_revisions as $entity_type_id => $associated_revisions) {
      $associated_entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
      $associated_revisions = $this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id);

      // Sort the associated revisions in reverse ID order, so we can delete the
      // most recent revisions first.
      krsort($associated_revisions);

      // Get a list of default revisions tracked by the given workspace, because
      // they need to be handled differently than pending revisions.
      $initial_revision_ids = $this->workspaceAssociation->getAssociatedInitialRevisions($workspace_id, $entity_type_id);

      foreach (array_keys($associated_revisions) as $revision_id) {
        if ($count > $batch_size) {
          continue 2;
        }

        // If the workspace is tracking the entity's default revision (i.e. the
        // entity was created inside that workspace), we need to delete the
        // whole entity after all of its pending revisions are gone.
        if (isset($initial_revision_ids[$revision_id])) {
          $associated_entity_storage->delete([$associated_entity_storage->load($initial_revision_ids[$revision_id])]);
        }
        else {
          // Delete the associated entity revision.
          $associated_entity_storage->deleteRevision($revision_id);
        }
        $count++;
      }
      // Delete the workspace association entries.
      $this->workspaceAssociation->deleteAssociations($workspace_id, $entity_type_id, $entities);
    }

    // The purging operation above might have taken a long time, so we need to
    // request a fresh list of tracked entities. If it is empty, we can go ahead
    // and remove the deleted workspace ID entry from state.
    if (!$this->workspaceAssociation->getTrackedEntities($workspace_id)) {
    $has_associated_revisions = FALSE;
    foreach (array_keys($this->getSupportedEntityTypes()) as $entity_type_id) {
      if (!empty($this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id))) {
        $has_associated_revisions = TRUE;
        break;
      }
    }
    if (!$has_associated_revisions) {
      unset($deleted_workspace_ids[$workspace_id]);
      $this->state->set('workspace.deleted', $deleted_workspace_ids);

      // Delete any possible leftover association entries.
      $this->workspaceAssociation->deleteAssociations($workspace_id);
    }
  }

Loading