Commit 4240fa27 authored by catch's avatar catch
Browse files

fix: #3619974 A view in a workspace shows default revision values for non-translatable entity types

By: amateescu
By: gabesullice
By: plach
parent dde124da
Loading
Loading
Loading
Loading
+2 −0
Changes for core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRevPub.php: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
use Drupal\entity_test\EntityTestDeleteForm;
use Drupal\entity_test\EntityTestForm;
use Drupal\entity_test\EntityTestViewBuilder as TestViewBuilder;
use Drupal\views\EntityViewsData;

/**
 * Defines the test entity class.
@@ -44,6 +45,7 @@
      'revision-delete' => RevisionDeleteForm::class,
      'revision-revert' => RevisionRevertForm::class,
    ],
    'views_data' => EntityViewsData::class,
    'route_provider' => [
      'html' => DefaultHtmlRouteProvider::class,
      'revision' => RevisionHtmlRouteProvider::class,
+10 −2
Changes for core/modules/workspaces/src/Hook/ViewsOperations.php: 10 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -157,8 +157,16 @@ protected function alterQueryForEntityType(Sql $query, EntityTypeInterface $enti

    $base_entity_table = $entity_type->isTranslatable() ? $entity_type->getDataTable() : $entity_type->getBaseTable();

    $base_fields = array_diff($table_mapping->getFieldNames($entity_type->getBaseTable()), [$entity_type->getKey('langcode')]);
    $revisionable_fields = array_diff($table_mapping->getFieldNames($entity_type->getRevisionDataTable()), $base_fields);
    // Only revisionable fields can change in a pending revision, so that's
    // what we need to read from the revision tables below. Views identifies
    // them by column name, so a multi-column field contributes one entry per
    // column.
    $revisionable_fields = [];
    foreach ($table_mapping->getFieldNames($base_entity_table) as $field_name) {
      if ($field_storage_definitions[$field_name]->isRevisionable()) {
        $revisionable_fields = array_merge($revisionable_fields, array_values($table_mapping->getColumnNames($field_name)));
      }
    }

    // Go through and look to see if we have to modify fields and filters.
    foreach ($query->fields as &$field_info) {
+128 −0
Changes for core/modules/workspaces/tests/src/Kernel/WorkspaceViewsIntegrationTest.php: 128 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -14,7 +14,10 @@
use Drupal\Tests\node\Traits\NodeCreationTrait;
use Drupal\Tests\node\Traits\PromotedContentViewTestTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Entity\View;
use Drupal\views\Views;
use Drupal\views_ui\ViewUI;
use Drupal\workspaces\Entity\Workspace;
@@ -46,6 +49,7 @@ class WorkspaceViewsIntegrationTest extends ViewsKernelTestBase {
    'filter',
    'node',
    'language',
    'taxonomy',
    'text',
    'views_ui',
    'workspaces',
@@ -70,6 +74,8 @@ protected function setUp($import_test_views = TRUE): void {
    $this->entityTypeManager = \Drupal::entityTypeManager();

    $this->installEntitySchema('entity_test_mulrevpub');
    $this->installEntitySchema('entity_test_revpub');
    $this->installEntitySchema('taxonomy_term');
    $this->installEntitySchema('node');
    $this->installEntitySchema('user');
    $this->installEntitySchema('workspace');
@@ -188,6 +194,128 @@ public function testViewsQueryAlter(): void {
    $this->assertIdenticalResultset($view, $expected, ['nid' => 'nid']);
  }

  /**
   * Tests the views query alter for an entity type without a data table.
   */
  public function testViewsQueryAlterWithoutDataTable(): void {
    // This entity type has no data table, so the query reads the base table,
    // which holds revisionable and non-revisionable fields alike. Only the
    // revisionable ones (e.g. 'name') should be affected by the query alter.
    $storage = $this->entityTypeManager->getStorage('entity_test_revpub');
    $entity = $storage->create(['name' => 'live', 'non_rev_field' => 'shared']);
    $entity->save();

    Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save();

    $this->assertSame(['live'], $this->getRevPubViewResults('name', 'live'));
    $this->assertSame([], $this->getRevPubViewResults('name', 'stage'));

    $this->switchToWorkspace('stage');
    $entity->set('name', 'stage');
    $entity->save();

    // The pending revision holds the new name, so that is what the filter has
    // to match on, and what the field has to return.
    $this->assertSame(['stage'], $this->getRevPubViewResults('name', 'stage'));
    $this->assertSame([], $this->getRevPubViewResults('name', 'live'));

    // A non-revisionable field is shared by every revision and only exists on
    // the base table. Redirecting it to the revision table would query a column
    // that doesn't exist. The name still comes from the pending revision.
    $this->assertSame(['stage'], $this->getRevPubViewResults('non_rev_field', 'shared'));

    // Live still sees the default revision.
    $this->switchToLive();
    $this->assertSame(['live'], $this->getRevPubViewResults('name', 'live'));
    $this->assertSame([], $this->getRevPubViewResults('name', 'stage'));
  }

  /**
   * Returns the 'name' values of a view of 'entity_test_revpub' with a filter.
   *
   * @param string $field
   *   The field to filter on.
   * @param string $value
   *   The value to filter for.
   *
   * @return string[]
   *   The name of each row the view returned.
   */
  protected function getRevPubViewResults(string $field, string $value): array {
    $view = View::create([
      'id' => 'test_entity_test_revpub',
      'base_table' => 'entity_test_revpub',
      'base_field' => 'id',
    ]);
    $executable = Views::executableFactory()->get($view);
    $executable->setDisplay('default');
    $executable->addHandler('default', 'field', 'entity_test_revpub', 'name', []);
    // Views only selects the column when something sorts on it.
    $executable->addHandler('default', 'sort', 'entity_test_revpub', 'name', []);
    $executable->addHandler('default', 'filter', 'entity_test_revpub', $field, [
      'operator' => '=',
      'value' => $value,
    ]);
    $executable->execute();

    // The rendered value comes from the loaded entity, which is already the
    // pending revision, so read the selected column instead.
    return array_map(fn ($row) => (string) $row->entity_test_revpub_name, $executable->result);
  }

  /**
   * Tests the views query alter for a field stored in more than one column.
   */
  public function testViewsQueryAlterMultiColumnField(): void {
    Vocabulary::create(['vid' => 'tags', 'name' => 'Tags'])->save();
    $term = Term::create([
      'vid' => 'tags',
      'name' => 'Test term',
      'description' => ['value' => 'live text', 'format' => 'plain_text'],
    ]);
    $term->save();

    Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save();

    $this->assertSame(1, $this->countTermViewResults('live text'));
    $this->assertSame(0, $this->countTermViewResults('stage text'));

    $this->switchToWorkspace('stage');
    $term->set('description', ['value' => 'stage text', 'format' => 'plain_text']);
    $term->save();

    // Views names the column 'description__value', not 'description', so the
    // filter is only redirected when the column name is matched.
    $this->assertSame(1, $this->countTermViewResults('stage text'));
    $this->assertSame(0, $this->countTermViewResults('live text'));
  }

  /**
   * Counts the rows of a term view filtered on the description column.
   *
   * @param string $value
   *   The description value to filter for.
   *
   * @return int
   *   The number of rows the view returned.
   */
  protected function countTermViewResults(string $value): int {
    $view = View::create([
      'id' => 'test_taxonomy_term',
      'base_table' => 'taxonomy_term_field_data',
      'base_field' => 'tid',
    ]);
    $executable = Views::executableFactory()->get($view);
    $executable->setDisplay('default');
    $executable->addHandler('default', 'filter', 'taxonomy_term_field_data', 'description__value', [
      'operator' => '=',
      'value' => $value,
    ]);
    $executable->execute();

    return count($executable->result);
  }

  /**
   * Tests creating a view of workspace entities.
   *