clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Functional;
use Drupal\Component\Utility\DeprecationHelper;
use Drupal\node\Entity\Node;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
/**
* Tests the entity_reference_revisions configuration.
*
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsAdminTest extends BrowserTestBase {
use FieldUiTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = array(
'node',
'field',
'entity_reference_revisions',
'field_ui',
'block',
);
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create paragraphs and article content types.
$this->drupalCreateContentType(array('type' => 'entity_revisions', 'name' => 'Entity revisions'));
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
// Place the breadcrumb, tested in fieldUIAddNewField().
$this->drupalPlaceBlock('system_breadcrumb_block');
$admin_user = $this->drupalCreateUser(array(
'administer site configuration',
'administer nodes',
'create article content',
'create entity_revisions content',
'administer content types',
'administer node fields',
'administer node display',
'administer node form display',
'edit any article content',
));
$this->drupalLogin($admin_user);
}
/**
* Tests the entity reference revisions configuration.
*/
public function testEntityReferenceRevisions() {
// Create a test target node used as entity reference by another test node.
$node_target = Node::create([
'title' => 'Target node',
'type' => 'article',
'body' => 'Target body text',
'uuid' => '2d04c2b4-9c3d-4fa6-869e-ecb6fa5c9410',
]);
$node_target->save();
// Add an entity reference revisions field to entity_revisions content type
// with $node_target as default value.
$storage_edit = ['settings[target_type]' => 'node', 'cardinality' => '-1'];
$field_edit = [
'settings[handler_settings][target_bundles][article]' => TRUE,
'default_value_input[field_entity_reference_revisions][0][target_id]' => $node_target->label() . ' (' . $node_target->id() . ')',
];
if (version_compare(\Drupal::VERSION, '10.1', '>=')) {
$field_edit['set_default_value'] = TRUE;
}
static::fieldUIAddNewField('admin/structure/types/manage/entity_revisions', 'entity_reference_revisions', 'Entity reference revisions', 'entity_reference_revisions', $storage_edit, $field_edit);
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
$this->assertSession()->pageTextContains('Saved Entity reference revisions configuration.');
// Resave the target node, so that the default revision is not the one we
// want to use.
$revision_id = $node_target->getRevisionId();
$node_target_after = Node::load($node_target->id());
$node_target_after->setNewRevision();
$node_target_after->save();
$this->assertTrue($node_target_after->getRevisionId() != $revision_id);
// Create an article.
$title = $this->randomMachineName();
$edit = array(
'title[0][value]' => $title,
'body[0][value]' => 'Revision 1',
);
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains($title);
$this->assertSession()->pageTextContains('Revision 1');
$node = $this->drupalGetNodeByTitle($title);
// Check if when creating an entity revisions content the default entity
// reference is set, add also the above article as a new reference.
$this->drupalGet('node/add/entity_revisions');
$this->assertSession()->fieldValueEquals('field_entity_reference_revisions[0][target_id]', $node_target->label() . ' (' . $node_target->id() . ')');
$edit = [
'title[0][value]' => 'Entity reference revision content',
'field_entity_reference_revisions[1][target_id]' => $node->label() . ' (' . $node->id() . ')',
];
$this->submitForm($edit, 'Save');
$this->assertSession()->linkByHrefExists('node/' . $node_target->id());
$this->assertSession()->pageTextContains('Entity revisions Entity reference revision content has been created.');
$this->assertSession()->pageTextContains('Entity reference revision content');
$this->assertSession()->pageTextContains($title);
$this->assertSession()->pageTextContains('Revision 1');
// Create 2nd revision of the article.
$edit = array(
'body[0][value]' => 'Revision 2',
'revision' => TRUE,
);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains($title);
$this->assertSession()->pageTextContains('Revision 2');
// View the Entity reference content and make sure it still has revision 1.
$node = $this->drupalGetNodeByTitle('Entity reference revision content');
$this->drupalGet('node/' . $node->id());
$this->assertSession()->pageTextContains($title);
$this->assertSession()->pageTextContains('Revision 1');
$this->assertSession()->pageTextNotContains('Revision 2');
// Make sure the non-revisionable entities are not selectable as referenced
// entities.
$this->drupalGet('admin/structure/types/manage/entity_revisions/fields/add-field');
if (version_compare(\Drupal::VERSION, '10.2', '>=')) {
$selected_group = [
'new_storage_type' => 'reference',
];
// The DeprecationHelper class is available from Drupal Core 10.1.x,
// so no need for class_exists here.
$submit = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '10.3', fn() => "Continue", fn() => "Change field group");
$this->submitForm($selected_group, $submit);
$this->assertSession()->pageTextContains('Other (revisions)');
$edit = array(
'group_field_options_wrapper' => 'entity_reference_revisions',
'label' => 'Entity reference revisions field',
'field_name' => 'entity_ref_revisions_field',
);
$this->submitForm($edit, 'Continue');
$this->assertSession()->optionNotExists('field_storage[subform][settings][target_type]', 'user');
$this->assertSession()->optionExists('field_storage[subform][settings][target_type]', 'node');
}
else {
$edit = array(
'new_storage_type' => 'entity_reference_revisions',
'label' => 'Entity reference revisions field',
'field_name' => 'entity_ref_revisions_field',
);
$this->submitForm($edit, 'Save and continue');
$this->assertSession()->optionNotExists('edit-settings-target-type', 'user');
$this->assertSession()->optionExists('edit-settings-target-type', 'node');
}
// Check ERR default value and property definitions label are set properly.
$field_definition = $node->getFieldDefinition('field_entity_reference_revisions');
$default_value = $field_definition->toArray()['default_value'];
$this->assertEquals($node_target->uuid(), $default_value[0]['target_uuid']);
$this->assertEquals($revision_id, $default_value[0]['target_revision_id']);
$properties = $field_definition->getFieldStorageDefinition()->getPropertyDefinitions();
$this->assertEquals('Content revision ID', (string) $properties['target_revision_id']->getLabel());
$this->assertEquals('Content ID', (string) $properties['target_id']->getLabel());
$this->assertEquals('Content', (string) $properties['entity']->getLabel());
}
/**
* Tests target bundle settings for an entity reference revisions field.
*/
public function testMultipleTargetBundles() {
// Create a couple of content types for the ERR field to point to.
$target_types = [];
for ($i = 0; $i < 2; $i++) {
$target_types[$i] = $this->drupalCreateContentType([
'type' => strtolower($this->randomMachineName()),
'name' => 'Test type ' . $i
]);
}
// Create a new field that can point to either target content type.
$node_type_path = 'admin/structure/types/manage/entity_revisions';
// Generate a random field name, must be only lowercase characters.
$field_name = strtolower($this->randomMachineName());
$field_edit = [];
$storage_edit = ['settings[target_type]' => 'node', 'cardinality' => '-1'];
$field_edit['settings[handler_settings][target_bundles][' . $target_types[0]->id() . ']'] = TRUE;
$field_edit['settings[handler_settings][target_bundles][' . $target_types[1]->id() . ']'] = TRUE;
$this->fieldUIAddNewField($node_type_path, $field_name, 'Entity reference revisions', 'entity_reference_revisions', $storage_edit, $field_edit);
// Deleting one of these content bundles at this point should only delete
// that bundle's body field. Test that there is no second field that will
// be deleted.
$this->drupalGet('/admin/structure/types/manage/' . $target_types[0]->id() . '/delete');
$this->xpath('(//details[@id="edit-entity-deletes"]//ul[@data-drupal-selector="edit-field-config"]/li)[2]');
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Functional;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Component\Utility\Html;
use Drupal\node\Entity\Node;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
/**
* Tests the entity_reference_revisions autocomplete.
*
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsAutocompleteTest extends BrowserTestBase {
use FieldUiTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = array(
'block_content',
'node',
'field',
'entity_reference_revisions',
'field_ui',
);
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create article content type.
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
// Place the breadcrumb, tested in fieldUIAddNewField().
$this->drupalPlaceBlock('system_breadcrumb_block');
}
/**
* Test for autocomplete processing.
*
* Tests that processing does not crash when the entity types of the
* referenced entity and of the entity the field is attached to are different.
*/
public function testEntityReferenceRevisionsAutocompleteProcessing() {
$admin_permissions = array(
'administer site configuration',
'administer nodes',
'administer blocks',
'create article content',
'administer content types',
'administer node fields',
'administer node display',
'administer node form display',
'edit any article content',
);
if (version_compare(\Drupal::VERSION, '10.1', '>=')) {
$admin_permissions[] = 'administer block types';
$admin_permissions[] = 'administer block content';
}
$admin_user = $this->drupalCreateUser($admin_permissions);
$this->drupalLogin($admin_user);
// Create a custom block content bundle.
$this->createBlockContentType(array('type' => 'customblockcontent', 'name' => 'Custom Block Content'));
// Create entity reference revisions field attached to article.
static::fieldUIAddNewField(
'admin/structure/types/manage/article',
'entity_reference_revisions',
'Entity reference revisions',
'entity_reference_revisions',
array('settings[target_type]' => 'block_content', 'cardinality' => '-1'),
array('settings[handler_settings][target_bundles][customblockcontent]' => TRUE)
);
// Create custom block.
$block_label = $this->randomMachineName();
$block_content = $this->randomString();
$edit = array(
'info[0][value]' => $block_label,
'body[0][value]' => $block_content,
);
$this->drupalGet('block/add');
$this->submitForm($edit, 'Save');
$block = $this->drupalGetBlockByInfo($block_label);
// Create an article.
$title = $this->randomMachineName();
$edit = array(
'title[0][value]' => $title,
'body[0][value]' => 'Revision 1',
'field_entity_reference_revisions[0][target_id]' => $block_label . ' (' . $block->id() . ')',
);
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains($title);
$this->assertSession()->responseContains(Html::escape($block_content));
// Check if the block content is not deleted since there is no composite
// relationship.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$node = Node::load($node->id());
$node->delete();
$this->assertNotNull(BlockContent::load($block->id()));
}
/**
* Get a custom block from the database based on its title.
*
* @param $info
* A block title, usually generated by $this->randomMachineName().
* @param $reset
* (optional) Whether to reset the entity cache.
*
* @return \Drupal\block\BlockInterface
* A block entity matching $info.
*/
function drupalGetBlockByInfo($info, $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()->getStorage('block_content')->resetCache();
}
$blocks = \Drupal::entityTypeManager()->getStorage('block_content')->loadByProperties(array('info' => $info));
// Get the first block returned from the database.
$returned_block = reset($blocks);
return $returned_block;
}
/**
* Create a block_content bundle.
*
* @param $parameters
* An assoc array with name (human readable) and type (bundle machine name)
* as keys.
*/
function createBlockContentType($parameters) {
$label = $parameters['name'];
$machine_name = $parameters['type'];
$edit = array(
'label' => $label,
'id' => $machine_name,
'revision' => TRUE,
);
$block_link = version_compare(\Drupal::VERSION, "10", ">=") ? 'admin/structure/block-content/add': 'admin/structure/block/block-content/types/add';
$this->drupalGet($block_link);
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains($label);
}
}

View File

@@ -0,0 +1,131 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
/**
* Tests the entity_reference_revisions diff plugin.
*
* @group entity_reference_revisions
*
* @dependencies diff
*/
class EntityReferenceRevisionsDiffTest extends BrowserTestBase {
use FieldUiTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'block_content',
'node',
'field',
'entity_reference_revisions',
'field_ui',
'diff',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create article content type.
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
// Disable visual inline diff.
$config = $this->config('diff.settings')
->set('general_settings.layout_plugins.visual_inline.enabled', FALSE);
$config->save();
$admin_user = $this->drupalCreateUser([
'administer site configuration',
'administer nodes',
'administer content types',
'administer node fields',
'administer node display',
'administer node form display',
'view all revisions',
'edit any article content',
'create article content',
]);
$this->drupalLogin($admin_user);
$this->drupalPlaceBlock('system_breadcrumb_block');
}
/**
* Test for diff plugin of ERR.
*
* Tests that the diff is displayed when changes are made in an ERR field.
*/
public function testEntityReferenceRevisionsDiff() {
// Add an entity_reference_revisions field.
static::fieldUIAddNewField('admin/structure/types/manage/article', 'err_field', 'err_field', 'entity_reference_revisions', [
'settings[target_type]' => 'node',
'cardinality' => '-1',
], [
'settings[handler_settings][target_bundles][article]' => TRUE,
]);
// Create first referenced node.
$title_node_1 = 'referenced_node_1';
$edit = [
'title[0][value]' => $title_node_1,
'body[0][value]' => 'body_node_1',
];
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
// Create second referenced node.
$title_node_2 = 'referenced_node_2';
$edit = [
'title[0][value]' => $title_node_2,
'body[0][value]' => 'body_node_2',
];
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
// Create referencing node.
$title = 'referencing_node';
$node = $this->drupalGetNodeByTitle($title_node_1);
$edit = [
'title[0][value]' => $title,
'field_err_field[0][target_id]' => $title_node_1 . ' (' . $node->id() . ')',
];
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
// Check the plugin is set.
$this->drupalGet('admin/config/content/diff/fields');
$this->submitForm(['fields[node__field_err_field][plugin][type]' => 'entity_reference_revisions_field_diff_builder'], 'Save');
// Update the referenced node of the err field and create a new revision.
$node = $this->drupalGetNodeByTitle($title);
$referenced_node_new = $this->drupalGetNodeByTitle($title_node_2);
$edit = [
'field_err_field[0][target_id]' => $title_node_2 . ' (' . $referenced_node_new->id() . ')',
'revision' => TRUE,
];
$this->drupalGet('node/' . $node->id() . '/edit');
$this->submitForm($edit, 'Save');
// Compare the revisions of the referencing node.
$this->drupalGet('node/' . $node->id() . '/revisions');
$this->submitForm([], 'Compare selected revisions');
// Assert the field changes.
$this->assertSession()->responseContains('class="diff-context diff-deletedline">' . $title_node_1);
$this->assertSession()->responseContains('class="diff-context diff-addedline">' . $title_node_2);
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Functional;
use Drupal\node\Entity\Node;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
/**
* Tests the entity_reference_revisions configuration.
*
* @group entity_reference_revisions
* @requires module hal
*/
class EntityReferenceRevisionsNormalizerTest extends BrowserTestBase {
use FieldUiTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = array(
'node',
'field',
'entity_reference_revisions',
'field_ui',
'block',
'hal',
'serialization',
'rest',
);
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
if (version_compare(\Drupal::VERSION, '10', '>=')) {
$this->markTestSkipped('HAL support has been moved to hal module');
}
parent::setUp();
// Create paragraphs and article content types.
$this->drupalCreateContentType(array('type' => 'entity_revisions', 'name' => 'Entity revisions'));
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
// Place the breadcrumb, tested in fieldUIAddNewField().
$this->drupalPlaceBlock('system_breadcrumb_block');
}
/**
* Tests the entity reference revisions configuration.
*/
public function testEntityReferenceRevisions() {
$admin_user = $this->drupalCreateUser(array(
'administer site configuration',
'administer nodes',
'create article content',
'create entity_revisions content',
'administer content types',
'administer node fields',
'administer node display',
'administer node form display',
'edit any article content',
));
$this->drupalLogin($admin_user);
// Create entity reference revisions field.
static::fieldUIAddNewField('admin/structure/types/manage/entity_revisions', 'entity_reference_revisions', 'Entity reference revisions', 'entity_reference_revisions', array('settings[target_type]' => 'node', 'cardinality' => '-1'), array('settings[handler_settings][target_bundles][article]' => TRUE));
$this->assertSession()->pageTextContains('Saved Entity reference revisions configuration.');
// Create an article.
$title = $this->randomMachineName();
$edit = array(
'title[0][value]' => $title,
'body[0][value]' => 'Revision 1',
);
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains($title);
$this->assertSession()->pageTextContains('Revision 1');
$node = $this->drupalGetNodeByTitle($title);
// Create entity revisions content that includes the above article.
$err_title = 'Entity reference revision content';
$edit = array(
'title[0][value]' => $err_title,
'field_entity_reference_revisions[0][target_id]' => $node->label() . ' (' . $node->id() . ')',
);
$this->drupalGet('node/add/entity_revisions');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Entity revisions Entity reference revision content has been created.');
$err_node = $this->drupalGetNodeByTitle($err_title);
$this->assertSession()->pageTextContains($err_title);
$this->assertSession()->pageTextContains($title);
$this->assertSession()->pageTextContains('Revision 1');
// Create 2nd revision of the article.
$edit = array(
'body[0][value]' => 'Revision 2',
'revision' => TRUE,
);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->submitForm($edit, 'Save');
$serializer = $this->container->get('serializer');
$normalized = $serializer->normalize($err_node, 'hal_json');
$request = \Drupal::request();
$link_domain = $request->getSchemeAndHttpHost() . $request->getBasePath();
$this->assertEquals($err_node->field_entity_reference_revisions->target_revision_id, $normalized['_embedded'][$link_domain . '/rest/relation/node/entity_revisions/field_entity_reference_revisions'][0]['target_revision_id']);
$new_err_node = $serializer->denormalize($normalized, Node::class, 'hal_json');
$this->assertEquals($err_node->field_entity_reference_revisions->target_revision_id, $new_err_node->field_entity_reference_revisions->target_revision_id);
}
}

View File

@@ -0,0 +1,244 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Functional;
use Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship;
use Drupal\entity_host_relationship_test\Entity\EntityTestHostRelationship;
/**
* Tests orphan composite revisions are properly removed.
*
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsOrphanRemovalForBaseFieldDefinitionTest extends EntityReferenceRevisionsOrphanRemovalTest {
/**
* A user with administration access.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'node',
'field',
'entity_reference_revisions',
'entity_composite_relationship_test',
'entity_host_relationship_test',
];
/**
* {@inheritdoc}
*/
public function insertRevisionableData() {
/** @var \Drupal\node\NodeStorageInterface $entity_host_storage */
$entity_host_storage = \Drupal::entityTypeManager()->getStorage('entity_host_relationship_test');
// Scenario 1: A composite with a default revision that is referenced and an
// old revision that is not. Result: Only the old revision is deleted.
$composite_entity_first = EntityTestCompositeRelationship::create([
'name' => 'first not used, second used',
'parent_id' => 1000,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_first->save();
$composite_entity_first = EntityTestCompositeRelationship::load($composite_entity_first->id());
$composite_entity_first->setNewRevision(TRUE);
$composite_entity_first->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'First composite',
'entity' => $composite_entity_first,
]);
$entity_host->save();
// Scenario 2: A composite with an old revision that is used and a default
// revision that is not. Result: Nothing should be deleted.
$composite_entity_second = EntityTestCompositeRelationship::create([
'name' => 'first used, second not used',
]);
$composite_entity_second->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Second composite',
'entity' => $composite_entity_second,
]);
$entity_host->save();
$entity_host = $this->getEntityHostByName('Second composite');
$entity_host = $entity_host_storage->createRevision($entity_host);
$entity_host->set('entity', NULL);
$entity_host->save();
$composite_entity_second = EntityTestCompositeRelationship::load($composite_entity_second->id());
$composite_entity_second->setNewRevision(TRUE);
$composite_entity_second->save();
// Scenario 3: A composite with an old revision and a default revision both
// that are not used with empty parent fields. Result: Nothing should be
// deleted since we do not know if it is still used.
$composite_entity_third = EntityTestCompositeRelationship::create([
'name' => 'first not used, second not used',
]);
$composite_entity_third->save();
$composite_entity_third = EntityTestCompositeRelationship::load($composite_entity_third->id());
$composite_entity_third->setNewRevision(TRUE);
$composite_entity_third->save();
// Scenario 4: A composite with an old revision and a default revision both
// that are not used with filled parent fields. Result: Should first delete
// the old revision and then the default revision. Delete the entity too.
$composite_entity_fourth = EntityTestCompositeRelationship::create([
'name' => '1st filled not, 2nd filled not',
'parent_id' => 1001,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_fourth->save();
$composite_entity_fourth = EntityTestCompositeRelationship::load($composite_entity_fourth->id());
$composite_entity_fourth->setNewRevision(TRUE);
$composite_entity_fourth->set('parent_id', 1001);
$composite_entity_fourth->save();
// Scenario 5: A composite with many revisions and 2 at least used. Result:
// Delete all unused revisions.
$composite_entity_fifth = EntityTestCompositeRelationship::create([
'name' => '1st not, 2nd used, 3rd not, 4th',
'parent_id' => 1001,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_fifth->save();
$composite_entity_fifth = EntityTestCompositeRelationship::load($composite_entity_fifth->id());
$composite_entity_fifth->setNewRevision(TRUE);
$composite_entity_fifth->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Third composite',
'entity' => $composite_entity_fifth,
]);
$entity_host->save();
$entity_host = $this->getEntityHostByName('Second composite');
$entity_host = $entity_host_storage->createRevision($entity_host);
$entity_host->set('entity', NULL);
$entity_host->save();
$composite_entity_fifth = EntityTestCompositeRelationship::load($composite_entity_fifth->id());
$composite_entity_fifth->setNewRevision(TRUE);
$composite_entity_fifth->save();
$entity_host = $this->getEntityHostByName('Third composite');
$entity_host = $entity_host_storage->createRevision($entity_host);
$entity_host->set('entity', $composite_entity_fifth);
$entity_host->save();
// Scenario 6: A composite with wrong parent fields filled pointing to a non
// existent parent (Parent 1). However, Parent 2 references it. Result: Must
// not be deleted.
$entity_host = EntityTestHostRelationship::create([
'name' => 'DELETED composite',
]);
$entity_host->save();
$composite_entity_sixth = EntityTestCompositeRelationship::create([
'name' => 'wrong parent fields',
'parent_id' => $entity_host->id(),
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_sixth->save();
$entity_host->delete();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Fourth composite',
'entity' => $composite_entity_sixth,
]);
$entity_host->save();
}
/**
* {@inheritdoc}
*/
public function insertNonRevisionableData() {
// Scenario 1: A composite with a default revision that is referenced and an
// old revision that is not. Result: Only the old revision is deleted.
$composite_entity_first = EntityTestCompositeRelationship::create([
'name' => 'NR first not used, second used',
'parent_id' => 1001,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_first->save();
$composite_entity_first = EntityTestCompositeRelationship::load($composite_entity_first->id());
$composite_entity_first->setNewRevision(TRUE);
$composite_entity_first->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'First NR composite',
'entity' => $composite_entity_first,
]);
$entity_host->save();
// Scenario 2: A composite with an old revision that is used and a default
// revision that is not. Result: Nothing should be deleted.
$composite_entity_second = EntityTestCompositeRelationship::create([
'name' => 'NR first used, second not used',
]);
$composite_entity_second->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Second NR composite',
'entity' => $composite_entity_second,
]);
$entity_host->save();
$composite_entity_second = EntityTestCompositeRelationship::load($composite_entity_second->id());
$composite_entity_second->setNewRevision(TRUE);
$composite_entity_second->save();
// Scenario 3: A composite with many revisions and 2 at least used. Result:
// Delete all unused revisions.
$composite_entity_third = EntityTestCompositeRelationship::create([
'name' => 'NR 1st not, 2nd, 3rd not, 4th',
'parent_id' => 1001,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_third->save();
$composite_entity_third = EntityTestCompositeRelationship::load($composite_entity_third->id());
$composite_entity_third->setNewRevision(TRUE);
$composite_entity_third->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Third NR composite',
'entity' => $composite_entity_third,
]);
$entity_host->save();
$entity_host = $this->getEntityHostByName('Third NR composite');
$entity_host->set('entity', NULL);
$entity_host->save();
$composite_entity_third = EntityTestCompositeRelationship::load($composite_entity_third->id());
$composite_entity_third->setNewRevision(TRUE);
$composite_entity_third->save();
$entity_host = $this->getEntityHostByName('Third NR composite');
$entity_host->set('entity', $composite_entity_third);
$entity_host->save();
}
/**
* Get an entity host from the database based on its name.
*
* @param string $name
* A entity name.
* @param bool $reset
* (optional) Whether to reset the entity cache.
*
* @return \Drupal\Core\Entity\RevisionableInterface
* A revisionable entity matching $name.
*/
protected function getEntityHostByName($name, $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()->getStorage('entity_host_relationship_test')->resetCache();
}
$name = (string) $name;
/** @var \Drupal\Core\Entity\RevisionableInterface[] $entities */
$entities = \Drupal::entityTypeManager()
->getStorage('entity_host_relationship_test')
->loadByProperties(['name' => $name]);
// Load the first entity returned from the database.
return reset($entities);
}
}

View File

@@ -0,0 +1,372 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Functional;
use Drupal\Core\Site\Settings;
use Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\BrowserTestBase;
/**
* Tests orphan composite revisions are properly removed.
*
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsOrphanRemovalTest extends BrowserTestBase {
/**
* A user with administration access.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'node',
'field',
'entity_reference_revisions',
'entity_composite_relationship_test'
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([
'delete orphan revisions',
]);
$this->drupalLogin($this->adminUser);
$this->insertRevisionableData();
$this->insertNonRevisionableData();
}
/**
* Tests that revisions that are no longer used are properly deleted.
*/
public function testNotUsedRevisionDeletion() {
$entity_test_composite_storage = \Drupal::entityTypeManager()->getStorage('entity_test_composite');
$composite_entity_first = $entity_test_composite_storage->loadByProperties(['name' => 'first not used, second used']);
$composite_entity_first = reset($composite_entity_first);
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_first->id());
$composite_entity_second = $entity_test_composite_storage->loadByProperties(['name' => 'first used, second not used']);
$composite_entity_second = reset($composite_entity_second);
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_second->id());
$composite_entity_third = $entity_test_composite_storage->loadByProperties(['name' => 'first not used, second not used']);
$composite_entity_third = reset($composite_entity_third);
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_third->id());
$composite_entity_fourth = $entity_test_composite_storage->loadByProperties(['name' => '1st filled not, 2nd filled not']);
$composite_entity_fourth = reset($composite_entity_fourth);
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_fourth->id());
$composite_entity_fifth = $entity_test_composite_storage->loadByProperties(['name' => '1st not, 2nd used, 3rd not, 4th']);
$composite_entity_fifth = reset($composite_entity_fifth);
$this->assertRevisionCount(4, 'entity_test_composite', $composite_entity_fifth->id());
$composite_entity_sixth = $entity_test_composite_storage->loadByProperties(['name' => 'wrong parent fields']);
$composite_entity_sixth = reset($composite_entity_sixth);
$this->assertRevisionCount(1, 'entity_test_composite', $composite_entity_sixth->id());
// Test non revisionable parent entities.
$composite_entity_seventh = $entity_test_composite_storage->loadByProperties(['name' => 'NR first not used, second used']);
$composite_entity_seventh = reset($composite_entity_seventh);
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_seventh->id());
$composite_entity_eighth = $entity_test_composite_storage->loadByProperties(['name' => 'NR first used, second not used']);
$composite_entity_eighth = reset($composite_entity_eighth);
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_eighth->id());
$composite_entity_ninth = $entity_test_composite_storage->loadByProperties(['name' => 'NR 1st not, 2nd, 3rd not, 4th']);
$composite_entity_ninth = reset($composite_entity_ninth);
$this->assertRevisionCount(3, 'entity_test_composite', $composite_entity_ninth->id());
// Set the batch size to 1.
$settings = Settings::getInstance() ? Settings::getAll() : [];
$settings['entity_update_batch_size'] = 1;
new Settings($settings);
// Run the delete process through the form.
$this->runDeleteForm();
$this->assertSession()->pageTextContains('Test entity - composite relationship: Deleted 8 revisions (1 entities)');
$this->assertRevisionCount(1, 'entity_test_composite', $composite_entity_first->id());
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_second->id());
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_third->id());
$this->assertRevisionCount(0, 'entity_test_composite', $composite_entity_fourth->id());
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_fifth->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_entity_sixth->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_entity_seventh->id());
$this->assertRevisionCount(2, 'entity_test_composite', $composite_entity_eighth->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_entity_ninth->id());
}
/**
* Programmatically runs the 'Delete orphaned composite entities' form.
*/
public function runDeleteForm() {
$this->drupalGet('admin/config/system/delete-orphans');
$this->submitForm([], 'Delete orphaned composite revisions');
$this->checkForMetaRefresh();
}
/**
* Asserts the revision count of a certain entity.
*
* @param int $expected
* The expected count.
* @param string $entity_type_id
* The entity type ID, e.g. node.
* @param int $entity_id
* The entity ID.
*/
protected function assertRevisionCount($expected, $entity_type_id, $entity_id) {
$id_field = \Drupal::entityTypeManager()->getDefinition($entity_type_id)->getKey('id');
$revision_count = \Drupal::entityQuery($entity_type_id)
->condition($id_field, $entity_id)
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals($expected, $revision_count);
}
/**
* Inserts revisionable entities needed for testing.
*/
public function insertRevisionableData() {
/** @var \Drupal\node\NodeStorageInterface $node_storage */
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
NodeType::create(['type' => 'revisionable', 'new_revision' => TRUE])->save();
// Add a translatable field and a not translatable field to both content
// types.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_composite_entity',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => [
'target_type' => 'entity_test_composite'
],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'revisionable',
'translatable' => FALSE,
]);
$field->save();
// Scenario 1: A composite with a default revision that is referenced and an
// old revision that is not. Result: Only the old revision is deleted.
$composite_entity_first = EntityTestCompositeRelationship::create([
'name' => 'first not used, second used',
'parent_id' => 1000,
'parent_type' => 'node',
'parent_field_name' => 'field_composite_entity',
]);
$composite_entity_first->save();
$composite_entity_first = EntityTestCompositeRelationship::load($composite_entity_first->id());
$composite_entity_first->setNewRevision(TRUE);
$composite_entity_first->save();
$node = $this->drupalCreateNode([
'type' => 'revisionable',
'title' => 'First composite',
'field_composite_entity' => $composite_entity_first,
]);
$node->save();
// Scenario 2: A composite with an old revision that is used and a default
// revision that is not. Result: Nothing should be deleted.
$composite_entity_second = EntityTestCompositeRelationship::create([
'name' => 'first used, second not used',
]);
$composite_entity_second->save();
$node = $this->drupalCreateNode([
'type' => 'revisionable',
'title' => 'Second composite',
'field_composite_entity' => $composite_entity_second,
]);
$node->save();
$node = $this->getNodeByTitle('Second composite');
$node = $node_storage->createRevision($node);
$node->set('field_composite_entity', NULL);
$node->save();
$composite_entity_second = EntityTestCompositeRelationship::load($composite_entity_second->id());
$composite_entity_second->setNewRevision(TRUE);
$composite_entity_second->save();
// Scenario 3: A composite with an old revision and a default revision both
// that are not used with empty parent fields. Result: Nothing should be
// deleted since we do not know if it is still used.
$composite_entity_third = EntityTestCompositeRelationship::create([
'name' => 'first not used, second not used',
]);
$composite_entity_third->save();
$composite_entity_third = EntityTestCompositeRelationship::load($composite_entity_third->id());
$composite_entity_third->setNewRevision(TRUE);
$composite_entity_third->save();
// Scenario 4: A composite with an old revision and a default revision both
// that are not used with filled parent fields. Result: Should first delete
// the old revision and then the default revision. Delete the entity too.
$composite_entity_fourth = EntityTestCompositeRelationship::create([
'name' => '1st filled not, 2nd filled not',
'parent_id' => 1001,
'parent_type' => 'node',
'parent_field_name' => 'field_composite_entity',
]);
$composite_entity_fourth->save();
$composite_entity_fourth = EntityTestCompositeRelationship::load($composite_entity_fourth->id());
$composite_entity_fourth->setNewRevision(TRUE);
$composite_entity_fourth->set('parent_id', 1001);
$composite_entity_fourth->save();
// Scenario 5: A composite with many revisions and 2 at least used. Result:
// Delete all unused revisions.
$composite_entity_fifth = EntityTestCompositeRelationship::create([
'name' => '1st not, 2nd used, 3rd not, 4th',
'parent_id' => 1001,
'parent_type' => 'node',
'parent_field_name' => 'field_composite_entity',
]);
$composite_entity_fifth->save();
$composite_entity_fifth = EntityTestCompositeRelationship::load($composite_entity_fifth->id());
$composite_entity_fifth->setNewRevision(TRUE);
$composite_entity_fifth->save();
$node = $this->drupalCreateNode([
'type' => 'revisionable',
'title' => 'Third composite',
'field_composite_entity' => $composite_entity_fifth,
]);
$node->save();
$node = $this->getNodeByTitle('Third composite');
$node = $node_storage->createRevision($node);
$node->set('field_composite_entity', NULL);
$node->save();
$composite_entity_fifth = EntityTestCompositeRelationship::load($composite_entity_fifth->id());
$composite_entity_fifth->setNewRevision(TRUE);
$composite_entity_fifth->save();
$node = $this->getNodeByTitle('Third composite');
$node = $node_storage->createRevision($node);
$node->set('field_composite_entity', $composite_entity_fifth);
$node->save();
// Scenario 6: A composite with wrong parent fields filled pointing to a non
// existent parent (Parent 1). However, Parent 2 references it. Result: Must
// not be deleted.
$node = $this->drupalCreateNode([
'type' => 'revisionable',
'title' => 'DELETED composite',
]);
$node->save();
$composite_entity_sixth = EntityTestCompositeRelationship::create([
'name' => 'wrong parent fields',
'parent_id' => $node->id(),
'parent_type' => 'node',
'parent_field_name' => 'field_composite_entity',
]);
$composite_entity_sixth->save();
$node->delete();
$node = $this->drupalCreateNode([
'type' => 'revisionable',
'title' => 'Fourth composite',
'field_composite_entity' => $composite_entity_sixth,
]);
$node->save();
}
/**
* Inserts non revisionable entities needed for testing.
*/
public function insertNonRevisionableData() {
/** @var \Drupal\node\NodeStorageInterface $node_storage */
NodeType::create(['type' => 'non_revisionable', 'new_revision' => FALSE])->save();
// Add a translatable field and a not translatable field to both content
// types.
$field_storage = FieldStorageConfig::loadByName('node', 'field_composite_entity');
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'non_revisionable',
'translatable' => FALSE,
]);
$field->save();
// Scenario 1: A composite with a default revision that is referenced and an
// old revision that is not. Result: Only the old revision is deleted.
$composite_entity_first = EntityTestCompositeRelationship::create([
'name' => 'NR first not used, second used',
'parent_id' => 1001,
'parent_type' => 'node',
'parent_field_name' => 'field_composite_entity',
]);
$composite_entity_first->save();
$composite_entity_first = EntityTestCompositeRelationship::load($composite_entity_first->id());
$composite_entity_first->setNewRevision(TRUE);
$composite_entity_first->save();
$node = $this->drupalCreateNode([
'type' => 'non_revisionable',
'title' => 'First composite',
'field_composite_entity' => $composite_entity_first,
]);
$node->save();
// Scenario 2: A composite with an old revision that is used and a default
// revision that is not. Result: Nothing should be deleted.
$composite_entity_second = EntityTestCompositeRelationship::create([
'name' => 'NR first used, second not used',
]);
$composite_entity_second->save();
$node = $this->drupalCreateNode([
'type' => 'non_revisionable',
'title' => 'Second composite',
'field_composite_entity' => $composite_entity_second,
]);
$node->save();
$composite_entity_second = EntityTestCompositeRelationship::load($composite_entity_second->id());
$composite_entity_second->setNewRevision(TRUE);
$composite_entity_second->save();
// Scenario 3: A composite with many revisions and 2 at least used. Result:
// Delete all unused revisions.
$composite_entity_third = EntityTestCompositeRelationship::create([
'name' => 'NR 1st not, 2nd, 3rd not, 4th',
'parent_id' => 1001,
'parent_type' => 'node',
'parent_field_name' => 'field_composite_entity',
]);
$composite_entity_third->save();
$composite_entity_third = EntityTestCompositeRelationship::load($composite_entity_third->id());
$composite_entity_third->setNewRevision(TRUE);
$composite_entity_third->save();
$node = $this->drupalCreateNode([
'type' => 'non_revisionable',
'title' => 'Third composite',
'field_composite_entity' => $composite_entity_third,
]);
$node->save();
$node = $this->getNodeByTitle('Third composite');
$node->set('field_composite_entity', NULL);
$node->save();
$composite_entity_third = EntityTestCompositeRelationship::load($composite_entity_third->id());
$composite_entity_third->setNewRevision(TRUE);
$composite_entity_third->save();
$node = $this->getNodeByTitle('Third composite');
$node->set('field_composite_entity', $composite_entity_third);
$node->save();
}
}

View File

@@ -0,0 +1,744 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Kernel;
use Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\node\Traits\NodeCreationTrait;
/**
* Tests the entity_reference_revisions composite relationship.
*
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsCompositeTest extends EntityKernelTestBase {
use ContentTypeCreationTrait;
use NodeCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = array(
'node',
'field',
'entity_reference_revisions',
'entity_composite_relationship_test',
'language'
);
/**
* The current database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*
*/
protected $entityTypeManager;
/**
* The cron service.
*
* @var \Drupal\Core\Cron
*/
protected $cron;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test_composite');
$this->installSchema('node', ['node_access']);
// Create article content type.
NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
// Create the reference to the composite entity test.
$field_storage = FieldStorageConfig::create(array(
'field_name' => 'composite_reference',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => array(
'target_type' => 'entity_test_composite'
),
));
$field_storage->save();
$field = FieldConfig::create(array(
'field_storage' => $field_storage,
'bundle' => 'article',
'translatable' => FALSE,
));
$field->save();
// Inject database connection, entity type manager and cron for the tests.
$this->database = \Drupal::database();
$this->entityTypeManager = \Drupal::entityTypeManager();
$this->cron = \Drupal::service('cron');
}
/**
* Test for maintaining composite relationship.
*
* Tests that the referenced entity saves the parent type and id when saving.
*/
public function testEntityReferenceRevisionsCompositeRelationship() {
// Create the test composite entity.
$composite = EntityTestCompositeRelationship::create(array(
'uuid' => $this->randomMachineName(),
'name' => $this->randomMachineName(),
));
$composite->save();
// Assert that there is only 1 revision of the composite entity.
$composite_revisions_count = \Drupal::entityQuery('entity_test_composite')
->condition('uuid', $composite->uuid())
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals(1, $composite_revisions_count);
// Create a node with a reference to the test composite entity.
/** @var \Drupal\node\NodeInterface $node */
$node = Node::create(array(
'title' => $this->randomMachineName(),
'type' => 'article',
));
$node->save();
$node->set('composite_reference', $composite);
$this->assertTrue($node->hasTranslationChanges());
$node->save();
// Assert that there is only 1 revision when creating a node.
$node_revisions_count = \Drupal::entityQuery('node')
->condition('nid', $node->id())
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals(1, $node_revisions_count);
// Assert there is no new composite revision after creating a host entity.
$composite_revisions_count = \Drupal::entityQuery('entity_test_composite')
->condition('uuid', $composite->uuid())
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals(1, $composite_revisions_count);
// Verify the value of parent type and id after create a node.
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertEquals($node->getEntityTypeId(), $composite->parent_type->value);
$this->assertEquals($node->id(), $composite->parent_id->value);
$this->assertEquals('composite_reference', $composite->parent_field_name->value);
// Create second revision of the node.
$original_composite_revision = $node->composite_reference[0]->target_revision_id;
$original_node_revision = $node->getRevisionId();
$node->setTitle('2nd revision');
$node->setNewRevision();
$node->save();
$node = Node::load($node->id());
// Check the revision of the node.
$this->assertEquals('2nd revision', $node->getTitle(), 'New node revision has changed data.');
$this->assertNotEquals($original_composite_revision, $node->composite_reference[0]->target_revision_id, 'Composite entity got new revision when its host did.');
// Make sure that there are only 2 revisions.
$node_revisions_count = \Drupal::entityQuery('node')
->condition('nid', $node->id())
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals(2,$node_revisions_count);
// Revert to first revision of the node.
$node = $this->entityTypeManager->getStorage('node')->loadRevision($original_node_revision);
$node->setNewRevision();
$node->isDefaultRevision(TRUE);
$node->save();
$node = Node::load($node->id());
// Check the revision of the node.
$this->assertNotEquals('2nd revision', $node->getTitle(), 'Node did not keep changed title after reversion.');
$this->assertNotEquals($original_composite_revision, $node->composite_reference[0]->target_revision_id, 'Composite entity got new revision when its host reverted to an old revision.');
$node_storage = $this->entityTypeManager->getStorage('node');
// Test that removing composite references results in translation changes.
$node->set('composite_reference', []);
$this->assertTrue($node->hasTranslationChanges());
// Test that changing composite reference results in translation changes.
$changed_composite_reference = $composite;
$changed_composite_reference->set('name', 'Changing composite reference');
$this->assertTrue((bool) $changed_composite_reference->isRevisionTranslationAffected());
$node->set('composite_reference', $changed_composite_reference);
$node->setNewRevision();
$this->assertTrue($node->hasTranslationChanges());
$node->save();
$nid = $node->id();
$node_storage->resetCache([$nid]);
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->load($nid);
// Check the composite has changed.
$this->assertEquals('Changing composite reference', $node->get('composite_reference')->entity->getName());
// Make sure the node has 4 revisions.
$node_revisions_count = $node_storage->getQuery()
->condition('nid', $nid)
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals(4, $node_revisions_count);
// Make sure the node has no revision with revision translation affected
// flag set to NULL.
$node_revisions_count = $node_storage->getQuery()
->condition('nid', $nid)
->allRevisions()
->condition('revision_translation_affected', NULL, 'IS NULL')
->accessCheck(TRUE)
->count()
->execute();
$this->assertEquals(0, $node_revisions_count, 'Node has a revision with revision translation affected set to NULL');
// Revert the changes to avoid interfering with the delete test.
$node->set('composite_reference', $composite);
// Test that the composite entity is deleted when its parent is deleted.
$node->delete();
$this->assertNotNull(EntityTestCompositeRelationship::load($composite->id()));
$this->cron->run();
$this->assertNull(EntityTestCompositeRelationship::load($composite->id()));
// Test that the deleting composite entity does not break the parent entity
// when creating a new revision.
$composite = EntityTestCompositeRelationship::create([
'name' => $this->randomMachineName(),
]);
$composite->save();
// Create a node with a reference to the test composite entity.
/** @var \Drupal\node\NodeInterface $node */
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $composite,
]);
$node->save();
// Delete the composite entity.
$composite->delete();
// Re-apply the field item values to unset the computed "entity" property.
$field_item = $node->get('composite_reference')->get(0);
$field_item->setValue($field_item->getValue(), FALSE);
$new_revision = $this->entityTypeManager->getStorage('node')->createRevision($node);
$this->assertTrue($new_revision->get('composite_reference')->isEmpty());
}
/**
* Tests composite relationship with translations and an untranslatable field.
*/
function testCompositeRelationshipWithTranslationNonTranslatableField() {
ConfigurableLanguage::createFromLangcode('de')->save();
// Create the test composite entity with a translation.
$composite = EntityTestCompositeRelationship::create(array(
'uuid' => $this->randomMachineName(),
'name' => $this->randomMachineName(),
));
$composite->addTranslation('de', $composite->toArray());
$composite->save();
// Create a node with a reference to the test composite entity.
$node = Node::create(array(
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $composite,
));
$node->addTranslation('de', $node->toArray());
$node->save();
// Verify the value of parent type and id after create a node.
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertEquals($node->getEntityTypeId(), $composite->parent_type->value);
$this->assertEquals($node->id(), $composite->parent_id->value);
$this->assertEquals('composite_reference', $composite->parent_field_name->value);
$this->assertTrue($composite->hasTranslation('de'));
// Test that the composite entity is not deleted when the german translation
// of the parent is deleted.
$node->removeTranslation('de');
$node->save();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
$this->assertFalse($composite->hasTranslation('de'));
// Change the language of the entity, ensure that doesn't try to delete
// the default translation.
$node->set('langcode', 'de');
$node->save();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
// Test that the composite entity is deleted when its parent is deleted.
$node->delete();
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNull($composite);
}
/**
* Tests composite relationship with translations and a translatable field.
*/
function testCompositeRelationshipWithTranslationTranslatableField() {
$field_config = FieldConfig::loadByName('node', 'article', 'composite_reference');
$field_config->setTranslatable(TRUE);
$field_config->save();
ConfigurableLanguage::createFromLangcode('de')->save();
// Create the test composite entity with a translation.
$composite = EntityTestCompositeRelationship::create(array(
'uuid' => $this->randomMachineName(),
'name' => $this->randomMachineName(),
));
$composite->addTranslation('de', $composite->toArray());
$composite->save();
// Create a node with a reference to the test composite entity.
$node = Node::create(array(
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $composite,
));
$node->addTranslation('de', $node->toArray());
$node->save();
// Verify the value of parent type and id after create a node.
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertEquals($node->getEntityTypeId(), $composite->parent_type->value);
$this->assertEquals($node->id(), $composite->parent_id->value);
$this->assertEquals('composite_reference', $composite->parent_field_name->value);
// Test that the composite entity is not deleted when the German parent
// translation is removed.
$node->removeTranslation('de');
$node->save();
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
// Test that the composite entity is deleted when its parent is deleted.
$node->delete();
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNull($composite);
}
/**
* Tests composite relationship with revisions.
*/
function testCompositeRelationshipWithRevisions() {
// Create the test composite entity with a translation.
$composite = EntityTestCompositeRelationship::create(array(
'uuid' => $this->randomMachineName(),
'name' => $this->randomMachineName(),
));
$composite->save();
// Create a node with a reference to the test composite entity.
$node = Node::create(array(
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $composite,
));
$node->save();
// Verify the value of parent type and id after create a node.
$composite = EntityTestCompositeRelationship::load($composite->id());
$composite_original_revision_id = $composite->getRevisionId();
$node_original_revision_id = $node->getRevisionId();
$this->assertEquals($node->getEntityTypeId(), $composite->parent_type->value);
$this->assertEquals($node->id(), $composite->parent_id->value);
$this->assertEquals('composite_reference', $composite->parent_field_name->value);
$node->setNewRevision(TRUE);
$node->save();
// Ensure that we saved a new revision ID.
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotEquals($composite_original_revision_id, $composite->getRevisionId());
// Test that deleting the first revision does not delete the composite.
$this->entityTypeManager->getStorage('node')->deleteRevision($node_original_revision_id);
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
// Ensure that the composite revision was deleted as well.
$composite_revision = $this->entityTypeManager->getStorage('entity_test_composite')->loadRevision($composite_original_revision_id);
$this->assertNull($composite_revision);
// Test that the composite entity is deleted when its parent is deleted.
$node->delete();
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNull($composite);
}
/**
* Tests that the composite revision is not deleted if it is the default one.
*/
function testCompositeRelationshipDefaultRevision() {
// Create a node with a reference to a test composite entity.
$composite = EntityTestCompositeRelationship::create([
'uuid' => $this->randomMachineName(),
'name' => $this->randomMachineName(),
]);
$composite->save();
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $composite,
]);
$node->save();
$composite = EntityTestCompositeRelationship::load($composite->id());
$composite_original_revision_id = $composite->getRevisionId();
$node_original_revision_id = $node->getRevisionId();
// Set a new revision, composite entity should have a new revision as well.
$node->setNewRevision(TRUE);
$node->save();
// Ensure that we saved a new revision ID.
$composite2 = EntityTestCompositeRelationship::load($composite->id());
$composite2_rev_id = $composite2->getRevisionId();
$this->assertNotEquals($composite2_rev_id, $composite_original_revision_id);
// Revert default composite entity revision to the original revision.
$composite_original = $this->entityTypeManager->getStorage('entity_test_composite')->loadRevision($composite_original_revision_id);
$composite_original->isDefaultRevision(TRUE);
$composite_original->save();
// Check the default composite revision is the original composite revision.
$this->assertEquals($composite_original_revision_id, $composite_original->getrevisionId());
// Test deleting the first node revision, referencing to the default
// composite revision, does not delete the default composite revision.
$this->entityTypeManager->getStorage('node')->deleteRevision($node_original_revision_id);
$composite_default = EntityTestCompositeRelationship::load($composite_original->id());
$this->assertNotNull($composite_default);
$composite_default_revision = $this->entityTypeManager->getStorage('entity_test_composite')->loadRevision($composite_original->getrevisionId());
$this->assertNotNull($composite_default_revision);
// Ensure the second revision still exists.
$composite2_revision = $this->entityTypeManager->getStorage('entity_test_composite')->loadRevision($composite2_rev_id);
$this->assertNotNull($composite2_revision);
}
/**
* Tests that the composite revision is not deleted if it is still in use.
*/
function testCompositeRelationshipDuplicatedRevisions() {
// Create a node with a reference to a test composite entity.
$composite = EntityTestCompositeRelationship::create([
'uuid' => $this->randomMachineName(),
'name' => $this->randomMachineName(),
]);
$composite->save();
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $composite,
]);
$node->save();
$composite = EntityTestCompositeRelationship::load($composite->id());
$composite_original_revision_id = $composite->getRevisionId();
$node_original_revision_id = $node->getRevisionId();
// Set a new revision, composite entity should have a new revision as well.
$node->setNewRevision(TRUE);
$node->save();
// Ensure that we saved a new revision ID.
$composite2 = EntityTestCompositeRelationship::load($composite->id());
$composite2_rev_id = $composite2->getRevisionId();
$this->assertNotEquals($composite2_rev_id, $composite_original_revision_id);
// Set the new node revision to reference to the original composite
// revision as well to test this composite revision will not be deleted.
$this->database->update('node__composite_reference')
->fields(['composite_reference_target_revision_id' => $composite_original_revision_id])
->condition('revision_id', $node->getRevisionId())
->execute();
$this->database->update('node_revision__composite_reference')
->fields(['composite_reference_target_revision_id' => $composite_original_revision_id])
->condition('revision_id', $node->getRevisionId())
->execute();
// Test deleting the first revision does not delete the composite.
$this->entityTypeManager->getStorage('node')->deleteRevision($node_original_revision_id);
$composite2 = EntityTestCompositeRelationship::load($composite2->id());
$this->assertNotNull($composite2);
// Ensure the original composite revision is not deleted because it is
// still referenced by the second node revision.
$composite_original_revision = $this->entityTypeManager->getStorage('entity_test_composite')->loadRevision($composite_original_revision_id);
$this->assertNotNull($composite_original_revision);
// Ensure the second revision still exists.
$composite2_revision = $this->entityTypeManager->getStorage('entity_test_composite')->loadRevision($composite2_rev_id);
$this->assertNotNull($composite2_revision);
// Test that the composite entity is deleted when its parent is deleted.
$node->delete();
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite2->id());
$this->assertNull($composite);
}
/**
* Tests the composite entity is deleted after removing its reference.
*/
public function testCompositeDeleteAfterRemovingReference() {
list($composite, $node) = $this->assignCompositeToNode();
// Remove reference to the composite entity from the node.
$node->set('composite_reference', NULL);
$node->save();
// Verify that the composite entity is not yet removed after deleting the
// parent.
$node->delete();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
// Verify that the composite entity is removed after running cron.
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNull($composite);
}
/**
* Tests the composite entity is deleted after removing its reference.
*
* Includes revisions on the host entity.
*/
public function testCompositeDeleteAfterRemovingReferenceWithRevisions() {
list($composite, $node) = $this->assignCompositeToNode();
// Remove reference to the composite entity from the node in a new revision.
$node->set('composite_reference', NULL);
$node->setNewRevision();
$node->save();
$composite = EntityTestCompositeRelationship::load($composite->id());
// Verify the composite entity is not removed on nodes with revisions.
$this->assertNotNull($composite);
// Verify that the composite entity is not yet removed after deleting the
// parent.
$node->delete();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
// Verify that the composite entity is removed after running cron.
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNull($composite);
}
/**
* Tests the composite entity is not deleted when changing parents.
*
* Includes revisions on the host entity.
*/
public function testCompositeDeleteAfterChangingParent() {
list($composite, $node) = $this->assignCompositeToNode();
// Remove reference to the composite entity from the node.
$node->set('composite_reference', NULL);
$node->setNewRevision();
$node->save();
// Setting a new revision of the composite entity in the second node.
$composite = EntityTestCompositeRelationship::load($composite->id());
$composite->setNewRevision(TRUE);
$composite->save();
$second_node = Node::create([
'title' => 'Second node',
'type' => 'article',
'composite_reference' => $composite,
]);
$second_node->save();
// Remove reference to the composite entity from the node.
$second_node->set('composite_reference', NULL);
$second_node->setNewRevision(TRUE);
$second_node->save();
// Verify the composite entity is not removed on nodes with revisions.
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
// Verify the amount of revisions of each entity.
$this->assertRevisionCount(2, 'entity_test_composite', $composite->id());
$this->assertRevisionCount(2, 'node', $node->id());
$this->assertRevisionCount(2, 'node', $second_node->id());
// Test that the composite entity is not deleted when its new parent is
// deleted, since it is still being used in a previous revision with a
// different parent.
$second_node->delete();
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
// Delete the parent of the previous revision.
$node->delete();
// Verify that the composite entity is removed after running cron.
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNull($composite);
}
/**
* Composite entity with revisions isn't deleted when changing parents.
*
* Includes revisions on the host entity.
*/
public function testCompositeDeleteRevisionAfterChangingParent() {
list($composite, $node) = $this->assignCompositeToNode();
// Remove reference to the composite entity from the node.
$node->set('composite_reference', NULL);
$node->setNewRevision();
$node->save();
// Setting a new revision of the composite entity in the second node.
$composite = EntityTestCompositeRelationship::load($composite->id());
$composite->setNewRevision(TRUE);
$composite->save();
$composite = EntityTestCompositeRelationship::load($composite->id());
$second_node = Node::create([
'title' => 'Second node',
'type' => 'article',
'composite_reference' => $composite,
]);
$second_node->save();
// Remove reference to the composite entity from the node.
$second_node->set('composite_reference', NULL);
$second_node->setNewRevision(TRUE);
$second_node->save();
// Verify the composite entity is not removed on nodes with revisions.
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
// Verify the amount of revisions of each entity.
$this->assertRevisionCount(2, 'entity_test_composite', $composite->id());
$this->assertRevisionCount(2, 'node', $node->id());
$this->assertRevisionCount(2, 'node', $second_node->id());
// Test that the composite entity is not deleted when its old parent is
// deleted.
$node->delete();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
// Verify that the composite entity is not removed after running cron but
// the previous unused revision is deleted.
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
$this->assertRevisionCount(1, 'entity_test_composite', $composite->id());
}
/**
* Tests the composite entity is not deleted when duplicating host entity.
*
* Includes revisions on the host entity.
*/
public function testCompositeDeleteAfterDuplicatingParent() {
list($composite, $node) = $this->assignCompositeToNode();
$node->setNewRevision(TRUE);
$node->save();
// Create a duplicate of the node.
$duplicate_node = $node->createDuplicate();
$duplicate_node->save();
$duplicate_node->setNewRevision(TRUE);
$duplicate_node->save();
// Verify the amount of revisions of each entity.
$this->assertRevisionCount(3, 'entity_test_composite', $composite->id());
$this->assertRevisionCount(2, 'node', $node->id());
$this->assertRevisionCount(2, 'node', $duplicate_node->id());
// Test that the composite entity is not deleted when the duplicate is
// deleted.
$duplicate_node->delete();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
$this->cron->run();
$composite = EntityTestCompositeRelationship::load($composite->id());
$this->assertNotNull($composite);
}
/**
* Asserts the revision count of a certain entity.
*
* @param int $expected
* The expected count.
* @param string $entity_type_id
* The entity type ID, e.g. node.
* @param int $entity_id
* The entity ID.
*/
protected function assertRevisionCount($expected, $entity_type_id, $entity_id) {
$id_field = \Drupal::entityTypeManager()
->getDefinition($entity_type_id)
->getKey('id');
$revision_count = \Drupal::entityQuery($entity_type_id)
->condition($id_field, $entity_id)
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals($expected, $revision_count);
}
/**
* Creates and assigns the composite entity to a node.
*
* @param string $node_type
* The node type.
*
* @return array
* An array containing a composite and a node entity.
*/
protected function assignCompositeToNode($node_type = 'article') {
$composite = EntityTestCompositeRelationship::create([
'uuid' => $this->randomMachineName(),
'name' => $this->randomMachineName(),
]);
$composite->save();
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => $node_type,
'composite_reference' => $composite,
]);
$node->save();
return [$composite, $node];
}
}

View File

@@ -0,0 +1,352 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Kernel;
use Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\node\Traits\NodeCreationTrait;
/**
* Tests entity_reference_revisions composites with a translatable field.
*
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsCompositeTranslatableFieldTest extends EntityKernelTestBase {
use ContentTypeCreationTrait;
use NodeCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = array(
'node',
'field',
'entity_reference_revisions',
'entity_composite_relationship_test',
'language',
'content_translation'
);
/**
* The current database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
ConfigurableLanguage::createFromLangcode('de')->save();
ConfigurableLanguage::createFromLangcode('fr')->save();
$this->installEntitySchema('entity_test_composite');
$this->installSchema('node', ['node_access']);
// Create article content type.
NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
// Create the reference to the composite entity test.
$field_storage = FieldStorageConfig::create(array(
'field_name' => 'composite_reference',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => array(
'target_type' => 'entity_test_composite'
),
));
$field_storage->save();
$field = FieldConfig::create(array(
'field_storage' => $field_storage,
'bundle' => 'article',
'translatable' => TRUE,
));
$field->save();
// Inject database connection and entity type manager for the tests.
$this->database = \Drupal::database();
$this->entityTypeManager = \Drupal::entityTypeManager();
// @todo content_translation should not be needed for a storage test, but
// \Drupal\Core\Entity\ContentEntityBase::isTranslatable() only returns
// TRUE if the bundle is explicitly translatable.
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
\Drupal::service('content_translation.manager')->setEnabled('entity_test_composite', 'entity_test_composite', TRUE);
\Drupal::service('content_translation.manager')->setBundleTranslationSettings('node', 'article', [
'untranslatable_fields_hide' => TRUE,
]);
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
}
/**
* Test the storage for handling pending revisions with translations.
*/
public function testCompositePendingRevisionTranslation() {
/** @var \Drupal\node\NodeStorageInterface $node_storage */
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
// Create the test composite entity.
$composite = EntityTestCompositeRelationship::create([
'langcode' => 'en',
'name' => 'Initial Source Composite',
]);
$composite->save();
// Create a node with a reference to the test composite entity.
$node = Node::create([
'langcode' => 'en',
'title' => 'Initial Source Node',
'type' => 'article',
'composite_reference' => $composite,
]);
$node->save();
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->load($node->id());
// Assert the revision count.
$this->assertRevisionCount(1, 'node', $node->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite->id());
// Create a translation as a pending revision for both the composite and the
// node. While technically, the referenced composite could be the same
// entity, for translatable fields, it makes more sense if each translation
// points to a separate entity, each only with a single language.
$composite_de = $node->get('composite_reference')->entity->createDuplicate();
$composite_de->set('langcode', 'de');
$composite_de->set('name', 'Pending Revision Composite #1 DE');
/** @var \Drupal\node\NodeInterface $node_de */
$node_de = $node->addTranslation('de', ['title' => 'Pending Revision Node #1 DE', 'composite_reference' => $composite_de] + $node->toArray());
$node_de->setNewRevision(TRUE);
$node_de->isDefaultRevision(FALSE);
$node_de->save();
// Assert the revision count.
$this->assertRevisionCount(2, 'node', $node->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_de->id());
// The DE translation will now reference to a pending revision of the
// composite entity but the en translation will reference the existing,
// unchanged revision.
/** @var \Drupal\node\NodeInterface $node_revision */
$node_revision = $node_storage->loadRevision($node_de->getRevisionId());
$this->assertFalse($node_revision->isDefaultRevision());
$this->assertFalse((bool) $node_revision->isRevisionTranslationAffected());
$this->assertEquals('Initial Source Node', $node_revision->label());
$this->assertTrue($node_revision->get('composite_reference')->entity->isDefaultRevision());
$this->assertEquals('Initial Source Composite', $node_revision->get('composite_reference')->entity->label());
$this->assertFalse($node_revision->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals($node->get('composite_reference')->target_revision_id, $node_revision->get('composite_reference')->target_revision_id);
$node_de = $node_revision->getTranslation('de');
$this->assertTrue((bool) $node_de->isRevisionTranslationAffected());
$this->assertEquals('Pending Revision Node #1 DE', $node_de->label());
// The composite is the default revision because it is a new entity.
$this->assertTrue($node_de->get('composite_reference')->entity->isDefaultRevision());
$this->assertEquals('Pending Revision Composite #1 DE', $node_de->get('composite_reference')->entity->label());
$this->assertNotEquals($node->get('composite_reference')->target_revision_id, $node_de->get('composite_reference')->target_revision_id);
// Reload the default revision of the node, make sure that the composite
// there is unchanged.
$node = $node_storage->load($node->id());
$this->assertFalse($node->hasTranslation('de'));
$this->assertEquals('Initial Source Node', $node->label());
$this->assertFalse($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals('Initial Source Composite', $node->get('composite_reference')->entity->label());
// Create a second translation revision for FR.
$composite_fr = $node->get('composite_reference')->entity->createDuplicate();
$composite_fr->set('langcode', 'fr');
$composite_fr->set('name', 'Pending Revision Composite #1 FR');
$node_fr = $node->addTranslation('fr', ['title' => 'Pending Revision Node #1 FR', 'composite_reference' => $composite_fr] + $node->toArray());
$node_fr->setNewRevision(TRUE);
$node_fr->isDefaultRevision(FALSE);
$node_fr->save();
// Assert the revision count.
$this->assertRevisionCount(3, 'node', $node->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_de->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_fr->id());
// Now assert that all 3 revisions exist as expected. Two translation
// pending revisions, each has the original revision as parent without
// any existing translation.
/** @var \Drupal\node\NodeInterface $node_fr */
$node_revision = $node_storage->loadRevision($node_fr->getRevisionId());
$this->assertFalse($node_revision->isDefaultRevision());
$this->assertFalse((bool) $node_revision->isRevisionTranslationAffected());
$this->assertEquals('Initial Source Node', $node_revision->label());
$this->assertTrue($node_revision->get('composite_reference')->entity->isDefaultRevision());
$this->assertEquals('Initial Source Composite', $node_revision->get('composite_reference')->entity->label());
$this->assertFalse($node_revision->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals($node->get('composite_reference')->target_revision_id, $node_revision->get('composite_reference')->target_revision_id);
$node_fr = $node_revision->getTranslation('fr');
$this->assertTrue((bool) $node_fr->isRevisionTranslationAffected());
$this->assertEquals('Pending Revision Node #1 FR', $node_fr->label());
$this->assertTrue($node_fr->get('composite_reference')->entity->isDefaultRevision());
$this->assertEquals('Pending Revision Composite #1 FR', $node_fr->get('composite_reference')->entity->label());
$this->assertNotEquals($node->get('composite_reference')->target_revision_id, $node_fr->get('composite_reference')->target_revision_id);
$node_de = $node_storage->loadRevision($node_de->getRevisionId())->getTranslation('de');
$this->assertTrue((bool) $node_de->isRevisionTranslationAffected());
$this->assertEquals('Pending Revision Node #1 DE', $node_de->label());
$this->assertTrue($node_de->get('composite_reference')->entity->isDefaultRevision());
$this->assertEquals('Pending Revision Composite #1 DE', $node_de->get('composite_reference')->entity->label());
$this->assertNotEquals($node->get('composite_reference')->target_revision_id, $node_de->get('composite_reference')->target_revision_id);
// Reload the default revision of the node, make sure that the composite
// there is unchanged.
$node = $node_storage->load($node->id());
$this->assertFalse($node->hasTranslation('de'));
$this->assertEquals('Initial Source Node', $node->label());
$this->assertFalse($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals('Initial Source Composite', $node->get('composite_reference')->entity->label());
// Now make a change to the initial source revision, save as a new default
// revision.
$initial_revision_id = $node->getRevisionId();
$node->get('composite_reference')->entity->set('name', 'Updated Source Composite');
$node->setTitle('Updated Source Node');
$node->setNewRevision(TRUE);
$node->save();
// Assert the revision count.
$this->assertRevisionCount(4, 'node', $node->id());
$this->assertRevisionCount(2, 'entity_test_composite', $composite->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_de->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_fr->id());
// Assert the two english revisions.
// Reload the default revision of the node, make sure that the composite
// there is unchanged.
$node = $node_storage->load($node->id());
$this->assertTrue($node->isDefaultRevision());
$this->assertFalse($node->hasTranslation('de'));
$this->assertFalse($node->hasTranslation('fr'));
$this->assertTrue((bool) $node->isRevisionTranslationAffected());
$this->assertEquals('Updated Source Node', $node->label());
$this->assertTrue($node->get('composite_reference')->entity->isDefaultRevision());
$this->assertFalse($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals('Updated Source Composite', $node->get('composite_reference')->entity->label());
$node_initial = $node_storage->loadRevision($initial_revision_id);
$this->assertFalse($node_initial->isDefaultRevision());
$this->assertFalse($node_initial->hasTranslation('de'));
$this->assertFalse($node_initial->hasTranslation('fr'));
$this->assertEquals('Initial Source Node', $node_initial->label());
$this->assertFalse($node_initial->get('composite_reference')->entity->isDefaultRevision());
$this->assertFalse($node_initial->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals('Initial Source Composite', $node_initial->get('composite_reference')->entity->label());
// Now publish the FR pending revision.
$node_storage->createRevision($node_fr->getTranslation('fr'))->save();
// Assert the revision count.
$this->assertRevisionCount(5, 'node', $node->id());
$this->assertRevisionCount(2, 'entity_test_composite', $composite->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_de->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_fr->id());
// The new default revision should now have the updated english source and
// the french pending revision.
$node = $node_storage->load($node->id());
$this->assertTrue($node->isDefaultRevision());
$this->assertFalse($node->hasTranslation('de'));
$this->assertTrue($node->hasTranslation('fr'));
$node_fr = $node->getTranslation('fr');
$this->assertFalse((bool) $node->isRevisionTranslationAffected());
$this->assertTrue((bool) $node->getTranslation('fr')->isRevisionTranslationAffected());
$this->assertEquals('Updated Source Node', $node->label());
$this->assertTrue($node->get('composite_reference')->entity->isDefaultRevision());
$this->assertFalse($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertTrue($node_fr->get('composite_reference')->entity->hasTranslation('fr'));
$this->assertEquals('Pending Revision Node #1 FR', $node_fr->label());
$this->assertEquals('Pending Revision Composite #1 FR', $node_fr->get('composite_reference')->entity->getTranslation('fr')->label());
$this->assertEquals('Updated Source Composite', $node->get('composite_reference')->entity->label());
// Now publish the DE pending revision as well.
$node_storage->createRevision($node_de->getTranslation('de'))->save();
// Assert the revision count.
$this->assertRevisionCount(6, 'node', $node->id());
$this->assertRevisionCount(2, 'entity_test_composite', $composite->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_de->id());
$this->assertRevisionCount(1, 'entity_test_composite', $composite_fr->id());
// The new default revision should now have the updated source and both
// translations.
$node = $node_storage->load($node->id());
$this->assertTrue($node->isDefaultRevision());
$this->assertTrue($node->hasTranslation('de'));
$this->assertTrue($node->hasTranslation('fr'));
$node_fr = $node->getTranslation('fr');
$node_de = $node->getTranslation('de');
$this->assertFalse((bool) $node->isRevisionTranslationAffected());
$this->assertFalse((bool) $node->getTranslation('fr')->isRevisionTranslationAffected());
$this->assertTrue((bool) $node->getTranslation('de')->isRevisionTranslationAffected());
$this->assertEquals('Updated Source Node', $node->label());
// Each translation only has the composite in its translation.
$this->assertTrue($node->get('composite_reference')->entity->hasTranslation('en'));
$this->assertFalse($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertFalse($node->get('composite_reference')->entity->hasTranslation('fr'));
$this->assertFalse($node_fr->get('composite_reference')->entity->hasTranslation('en'));
$this->assertTrue($node_fr->get('composite_reference')->entity->hasTranslation('fr'));
$this->assertFalse($node_fr->get('composite_reference')->entity->hasTranslation('de'));
$this->assertFalse($node_de->get('composite_reference')->entity->hasTranslation('en'));
$this->assertTrue($node_de->get('composite_reference')->entity->hasTranslation('de'));
$this->assertFalse($node_de->get('composite_reference')->entity->hasTranslation('fr'));
$this->assertEquals('Pending Revision Node #1 FR', $node_fr->label());
$this->assertEquals('Pending Revision Composite #1 FR', $node_fr->get('composite_reference')->entity->getTranslation('fr')->label());
$this->assertEquals('Pending Revision Node #1 DE', $node_de->label());
$this->assertEquals('Pending Revision Composite #1 DE', $node_de->get('composite_reference')->entity->getTranslation('de')->label());
$this->assertEquals('Updated Source Composite', $node->get('composite_reference')->entity->label());
}
/**
* Asserts the revision count of a certain entity.
*
* @param int $expected
* The expected count.
* @param string $entity_type_id
* The entity type ID, e.g. node.
* @param int $entity_id
* The entity ID.
*/
protected function assertRevisionCount($expected, $entity_type_id, $entity_id) {
$id_field = \Drupal::entityTypeManager()->getDefinition($entity_type_id)->getKey('id');
$revision_count = \Drupal::entityQuery($entity_type_id)
->condition($id_field, $entity_id)
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals($expected, $revision_count);
}
}

View File

@@ -0,0 +1,643 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Kernel;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\node\Traits\NodeCreationTrait;
/**
* Tests the entity_reference_revisions composite relationship.
*
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsCompositeTranslationTest extends EntityKernelTestBase {
use ContentTypeCreationTrait;
use NodeCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'node',
'field',
'entity_reference_revisions',
'entity_composite_relationship_test',
'language',
'content_translation'
];
/**
* The current database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
ConfigurableLanguage::createFromLangcode('de')->save();
ConfigurableLanguage::createFromLangcode('fr')->save();
$this->installEntitySchema('entity_test_composite');
$this->installSchema('node', ['node_access']);
// Create article content type.
NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
// Create the reference to the composite entity test.
$field_storage = FieldStorageConfig::create([
'field_name' => 'composite_reference',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'target_type' => 'entity_test_composite'
],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'article',
'translatable' => FALSE,
]);
$field->save();
// Create an untranslatable field on the composite entity.
$text_field_storage = FieldStorageConfig::create([
'field_name' => 'field_untranslatable',
'entity_type' => 'entity_test_composite',
'type' => 'string',
]);
$text_field_storage->save();
$text_field = FieldConfig::create([
'field_storage' => $text_field_storage,
'bundle' => 'entity_test_composite',
'translatable' => FALSE,
]);
$text_field->save();
// Add a nested composite field.
$field_storage = FieldStorageConfig::create([
'field_name' => 'composite_reference',
'entity_type' => 'entity_test_composite',
'type' => 'entity_reference_revisions',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'target_type' => 'entity_test_composite'
],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'entity_test_composite',
'translatable' => FALSE,
]);
$field->save();
// Inject database connection and entity type manager for the tests.
$this->database = \Drupal::database();
$this->entityTypeManager = \Drupal::entityTypeManager();
// @todo content_translation should not be needed for a storage test, but
// \Drupal\Core\Entity\ContentEntityBase::isTranslatable() only returns
// TRUE if the bundle is explicitly translatable.
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
\Drupal::service('content_translation.manager')->setEnabled('entity_test_composite', 'entity_test_composite', TRUE);
\Drupal::service('content_translation.manager')->setBundleTranslationSettings('node', 'article', [
'untranslatable_fields_hide' => TRUE,
]);
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
}
/**
* Test the storage for handling pending revisions with translations.
*/
public function testCompositePendingRevisionTranslation() {
/** @var \Drupal\node\NodeStorageInterface $node_storage */
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
// Create a nested composite entity.
$nested_composite = EntityTestCompositeRelationship::create([
'langcode' => 'en',
'name' => 'Initial Nested Source Composite',
]);
$nested_composite->save();
// Create a composite entity.
$composite = EntityTestCompositeRelationship::create([
'langcode' => 'en',
'name' => 'Initial Source Composite',
'field_untranslatable' => 'Initial untranslatable field',
'composite_reference' => $nested_composite,
]);
$composite->save();
// Create a node with a reference to the test composite entity.
$node = Node::create([
'langcode' => 'en',
'title' => 'Initial Source Node',
'type' => 'article',
'composite_reference' => $composite,
]);
$node->save();
$initial_revision_id = $node->getRevisionId();
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->load($node->id());
// Assert that there is only 1 revision when creating a node.
$this->assertRevisionCount(1, $node);
// Assert there is no new composite revision after creating a host entity.
$this->assertRevisionCount(1, $composite);
// Assert there is no new composite revision after creating a host entity.
$this->assertRevisionCount(1, $nested_composite);
// Create a second nested composite entity.
$second_nested_composite = EntityTestCompositeRelationship::create([
'langcode' => 'en',
'name' => 'Initial Nested Composite #2',
]);
// Add a pending revision.
$node = $node_storage->createRevision($node, FALSE);
$node->get('composite_reference')->entity->get('composite_reference')->appendItem($second_nested_composite);
$node->save();
$pending_en_revision_id = $node->getRevisionId();
$this->assertRevisionCount(2, $node);
$this->assertRevisionCount(2, $composite);
$this->assertRevisionCount(2, $nested_composite);
$this->assertRevisionCount(1, $second_nested_composite);
// Create a DE translation, start as a draft to replicate the behavior of
// the UI.
$node_de = $node->addTranslation('de', ['title' => 'New Node #1 DE'] + $node->toArray());
$node_de = $node_storage->createRevision($node_de, FALSE);
// Despite starting of the draft revision, creating draft of the translation
// uses the paragraphs of the default revision.
$this->assertCount(1, $node_de->get('composite_reference')->entity->get('composite_reference'));
$node_de->get('composite_reference')->entity->getTranslation('de')->set('name', 'New Composite #1 DE');
$node_de->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('de')->set('name', 'New Nested Composite #1 DE');
$node_de->isDefaultRevision(TRUE);
$violations = $node_de->validate();
foreach ($violations as $violation) {
$this->fail($violation->getPropertyPath() . ': ' . $violation->getMessage());
}
$this->assertEquals(0, count($violations));
$node_de->save();
$this->assertRevisionCount(3, $node);
$this->assertRevisionCount(3, $composite);
$this->assertRevisionCount(3, $nested_composite);
$this->assertRevisionCount(1, $second_nested_composite);
// Update the translation as a pending revision for both the composite and
// the node.
$node_de->get('composite_reference')->entity->getTranslation('de')->set('name', 'Pending Revision Composite #1 DE');
$node_de->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('de')->set('name', 'Pending Nested Composite #1 DE');
$node_de->set('title', 'Pending Revision Node #1 DE');
$node_de->setNewRevision(TRUE);
$node_de->isDefaultRevision(FALSE);
$violations = $node_de->validate();
foreach ($violations as $violation) {
$this->fail($violation->getMessage());
}
$this->assertEquals(0, count($violations));
$node_de->save();
$this->assertRevisionCount(4, $node);
$this->assertRevisionCount(4, $composite);
$this->assertRevisionCount(4, $nested_composite);
$this->assertRevisionCount(1, $second_nested_composite);
/** @var \Drupal\node\NodeInterface $node_de */
$node_de = $node_storage->loadRevision($node_de->getRevisionId());
$this->assertFalse($node_de->isDefaultRevision());
$this->assertFalse((bool) $node_de->isRevisionTranslationAffected());
$this->assertTrue((bool) $node_de->getTranslation('de')->isRevisionTranslationAffected());
$this->assertEquals('Pending Revision Node #1 DE', $node_de->getTranslation('de')->label());
$this->assertEquals('Initial Source Node', $node_de->label());
$this->assertFalse($node_de->get('composite_reference')->entity->isDefaultRevision());
$this->assertEquals('Pending Revision Composite #1 DE', $node_de->get('composite_reference')->entity->getTranslation('de')->label());
$this->assertEquals('Pending Nested Composite #1 DE', $node_de->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('de')->label());
$this->assertEquals('Initial untranslatable field', $node_de->get('composite_reference')->entity->getTranslation('de')->get('field_untranslatable')->value);
$this->assertEquals('Initial Source Composite', $node_de->get('composite_reference')->entity->label());
// Reload the default revision of the node, make sure that the composite
// there is unchanged.
$node = $node_storage->load($node->id());
$this->assertTrue($node->hasTranslation('de'));
$this->assertEquals('Initial Source Node', $node->label());
$this->assertTrue($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals('Initial Source Composite', $node->get('composite_reference')->entity->label());
// Create a FR translation, start as a draft to replicate the behavior of
// the UI.
$node_fr = $node->addTranslation('fr', ['title' => 'Pending Revision Node #1 FR'] + $node->toArray());
$node_fr = $node_storage->createRevision($node_fr, FALSE);
$node_fr->get('composite_reference')->entity->getTranslation('fr')->set('name', 'Pending Revision Composite #1 FR');
$node_fr->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('fr')->set('name', 'Pending Nested Composite #1 FR');
$violations = $node_fr->validate();
$this->assertEquals(0, count($violations));
$node_fr->save();
// Now assert that all 3 revisions exist as expected. Two translation
// pending revisions, each composite has the original revision as parent
// without any existing translation.
/** @var \Drupal\node\NodeInterface $node_fr */
$node_fr = $node_storage->loadRevision($node_fr->getRevisionId());
$this->assertFalse($node_fr->isDefaultRevision());
$this->assertTrue($node_fr->hasTranslation('de'));
$this->assertFalse((bool) $node_fr->isRevisionTranslationAffected());
$this->assertTrue((bool) $node_fr->getTranslation('fr')->isRevisionTranslationAffected());
$this->assertEquals('Pending Revision Node #1 FR', $node_fr->getTranslation('fr')->label());
$this->assertEquals('Initial Source Node', $node_fr->label());
$this->assertFalse($node_fr->get('composite_reference')->entity->isDefaultRevision());
$this->assertTrue($node_fr->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals('Pending Revision Composite #1 FR', $node_fr->get('composite_reference')->entity->getTranslation('fr')->label());
$this->assertEquals('Pending Nested Composite #1 FR', $node_fr->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('fr')->label());
$this->assertEquals('Initial untranslatable field', $node_fr->get('composite_reference')->entity->getTranslation('fr')->get('field_untranslatable')->value);
$this->assertEquals('Initial Source Composite', $node_fr->get('composite_reference')->entity->label());
$node_de = $node_storage->loadRevision($node_de->getRevisionId());
$this->assertFalse($node_de->isDefaultRevision());
$this->assertFalse($node_de->hasTranslation('fr'));
$this->assertEquals('Pending Revision Node #1 DE', $node_de->getTranslation('de')->label());
$this->assertEquals('Initial Source Node', $node_de->label());
$this->assertFalse($node_de->get('composite_reference')->entity->isDefaultRevision());
$this->assertFalse($node_de->get('composite_reference')->entity->hasTranslation('fr'));
$this->assertEquals('Pending Revision Composite #1 DE', $node_de->get('composite_reference')->entity->getTranslation('de')->label());
$this->assertEquals('Pending Nested Composite #1 DE', $node_de->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('de')->label());
$this->assertEquals('Initial untranslatable field', $node_de->get('composite_reference')->entity->getTranslation('de')->get('field_untranslatable')->value);
$this->assertEquals('Initial Source Composite', $node_de->get('composite_reference')->entity->label());
// Reload the default revision of the node, make sure that the composite
// there is unchanged.
$node = $node_storage->load($node->id());
$this->assertTrue($node->hasTranslation('de'));
$this->assertEquals('Initial Source Node', $node->label());
$this->assertTrue($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals('Initial Source Composite', $node->get('composite_reference')->entity->label());
// Create another pending EN revision and make that the default.
$node = $node_storage->loadRevision($pending_en_revision_id);
$new_revision = $node_storage->createRevision($node);
$new_revision->get('composite_reference')->entity->set('name', 'Updated Source Composite');
$new_revision->get('composite_reference')->entity->set('field_untranslatable', 'Updated untranslatable field');
$new_revision->setTitle('Updated Source Node');
$new_revision->get('composite_reference')->entity->get('composite_reference')[1]->entity->set('name', 'Draft Nested Source Composite #2');
$violations = $new_revision->validate();
$this->assertEquals(0, count($violations));
$new_revision->save();
// Assert the two english revisions.
// Reload the default revision of the node, make sure that the composite
// there is unchanged.
$node = $node_storage->load($node->id());
$this->assertTrue($node->isDefaultRevision());
$this->assertTrue($node->hasTranslation('de'));
$this->assertFalse($node->hasTranslation('fr'));
$this->assertTrue((bool) $node->isRevisionTranslationAffected());
$this->assertEquals('Updated Source Node', $node->label());
$this->assertTrue($node->get('composite_reference')->entity->isDefaultRevision());
$this->assertTrue($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertFalse($node->get('composite_reference')->entity->hasTranslation('fr'));
$this->assertEquals('Updated Source Composite', $node->get('composite_reference')->entity->label());
$this->assertEquals('Initial Nested Source Composite', $node->get('composite_reference')->entity->get('composite_reference')->entity->label());
$this->assertEquals('Draft Nested Source Composite #2', $node->get('composite_reference')->entity->get('composite_reference')[1]->entity->label());
$this->assertEquals('Updated untranslatable field', $node->get('composite_reference')->entity->get('field_untranslatable')->value);
$node_initial = $node_storage->loadRevision($initial_revision_id);
$this->assertFalse($node_initial->isDefaultRevision());
$this->assertFalse($node_initial->hasTranslation('de'));
$this->assertFalse($node_initial->hasTranslation('fr'));
$this->assertEquals('Initial Source Node', $node_initial->label());
$this->assertFalse($node_initial->get('composite_reference')->entity->isDefaultRevision());
$this->assertFalse($node_initial->get('composite_reference')->entity->hasTranslation('de'));
$this->assertEquals('Initial Source Composite', $node_initial->get('composite_reference')->entity->label());
$this->assertEquals('Initial Nested Source Composite', $node_initial->get('composite_reference')->entity->get('composite_reference')->entity->label());
$this->assertEquals('Initial untranslatable field', $node_initial->get('composite_reference')->entity->get('field_untranslatable')->value);
$this->assertCount(1, $node_initial->get('composite_reference')->entity->get('composite_reference'));
// The current node_fr pending revision still has the initial value before
// "merging" it, but it will get the new value for the untranslatable field
// in the new revision.
$node_fr = $node_storage->loadRevision($node_fr->getRevisionId());
$this->assertEquals('Initial untranslatable field', $node_fr->get('composite_reference')->entity->get('field_untranslatable')->value);
$this->assertCount(1, $node_fr->get('composite_reference')->entity->get('composite_reference'));
// Now publish the FR pending revision and also add a translation for
// the second composite that it now has.
$new_revision = $node_storage->createRevision($node_fr->getTranslation('fr'));
$this->assertCount(2, $new_revision->get('composite_reference')->entity->get('composite_reference'));
$new_revision->get('composite_reference')->entity->get('composite_reference')[1]->entity->getTranslation('fr')->set('name', 'FR Nested Composite #2');
$violations = $new_revision->validate();
$this->assertEquals(0, count($violations));
$new_revision->save();
$this->assertRevisionCount(7, $node);
$this->assertRevisionCount(7, $composite);
$this->assertRevisionCount(7, $nested_composite);
$this->assertRevisionCount(3, $second_nested_composite);
// The new default revision should now have the updated english source,
// original german translation and the french pending revision.
$node = $node_storage->load($node->id());
$this->assertTrue($node->isDefaultRevision());
$this->assertTrue($node->hasTranslation('de'));
$this->assertTrue($node->hasTranslation('fr'));
$this->assertFalse((bool) $node->isRevisionTranslationAffected());
$this->assertTrue((bool) $node->getTranslation('fr')->isRevisionTranslationAffected());
$this->assertEquals('Updated Source Node', $node->label());
$this->assertTrue($node->get('composite_reference')->entity->isDefaultRevision());
$this->assertTrue($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertTrue($node->get('composite_reference')->entity->hasTranslation('fr'));
$this->assertEquals('Pending Revision Node #1 FR', $node->getTranslation('fr')->label());
$this->assertEquals('Pending Revision Composite #1 FR', $node->get('composite_reference')->entity->getTranslation('fr')->label());
$this->assertEquals('Pending Nested Composite #1 FR', $node->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('fr')->label());
$this->assertEquals('New Node #1 DE', $node->getTranslation('de')->label());
$this->assertEquals('New Composite #1 DE', $node->get('composite_reference')->entity->getTranslation('de')->label());
$this->assertEquals('New Nested Composite #1 DE', $node->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('de')->label());
$this->assertEquals('Updated Source Composite', $node->get('composite_reference')->entity->label());
$this->assertEquals('Updated untranslatable field', $node->get('composite_reference')->entity->get('field_untranslatable')->value);
$this->assertEquals('Draft Nested Source Composite #2', $node->get('composite_reference')->entity->get('composite_reference')[1]->entity->label());
$this->assertEquals('FR Nested Composite #2', $node->get('composite_reference')->entity->get('composite_reference')[1]->entity->getTranslation('fr')->label());
// Now publish the DE pending revision as well.
$new_revision = $node_storage->createRevision($node_de->getTranslation('de'));
$violations = $new_revision->validate();
$this->assertCount(2, $new_revision->get('composite_reference')->entity->get('composite_reference'));
$this->assertEquals(0, count($violations));
$new_revision->save();
$this->assertRevisionCount(8, $node);
$this->assertRevisionCount(8, $composite);
$this->assertRevisionCount(8, $nested_composite);
$this->assertRevisionCount(4, $second_nested_composite);
// The new default revision should now have the updated source and both
// translations.
$node = $node_storage->load($node->id());
$this->assertTrue($node->isDefaultRevision());
$this->assertTrue($node->hasTranslation('de'));
$this->assertTrue($node->hasTranslation('fr'));
$this->assertFalse((bool) $node->isRevisionTranslationAffected());
$this->assertFalse((bool) $node->getTranslation('fr')->isRevisionTranslationAffected());
$this->assertTrue((bool) $node->getTranslation('de')->isRevisionTranslationAffected());
$this->assertEquals('Updated Source Node', $node->label());
$this->assertTrue($node->get('composite_reference')->entity->isDefaultRevision());
$this->assertTrue($node->get('composite_reference')->entity->hasTranslation('de'));
$this->assertTrue($node->get('composite_reference')->entity->hasTranslation('fr'));
$this->assertEquals('Pending Revision Node #1 FR', $node->getTranslation('fr')->label());
$this->assertEquals('Pending Revision Composite #1 FR', $node->get('composite_reference')->entity->getTranslation('fr')->label());
$this->assertEquals('Pending Revision Node #1 DE', $node->getTranslation('de')->label());
$this->assertEquals('Pending Revision Composite #1 DE', $node->get('composite_reference')->entity->getTranslation('de')->label());
$this->assertEquals('Pending Nested Composite #1 DE', $node->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('de')->label());
$this->assertEquals('Updated Source Composite', $node->get('composite_reference')->entity->label());
$this->assertEquals('Updated untranslatable field', $node->get('composite_reference')->entity->get('field_untranslatable')->value);
$this->assertEquals('Draft Nested Source Composite #2', $node->get('composite_reference')->entity->get('composite_reference')[1]->entity->label());
$this->assertEquals('FR Nested Composite #2', $node->get('composite_reference')->entity->get('composite_reference')[1]->entity->getTranslation('fr')->label());
// The second nested composite of DE inherited the default values for its
// translation.
$this->assertEquals('Draft Nested Source Composite #2', $node->get('composite_reference')->entity->get('composite_reference')[1]->entity->getTranslation('de')->label());
// Simulate creating a new pending revision like
// \Drupal\content_moderation\EntityTypeInfo::entityPrepareForm().
$new_revision = $node_storage->createRevision($node);
$revision_key = $new_revision->getEntityType()->getKey('revision');
$new_revision->set($revision_key, $new_revision->getLoadedRevisionId());
$new_revision->save();
$this->assertEquals('Pending Nested Composite #1 DE', $new_revision->get('composite_reference')->entity->get('composite_reference')->entity->getTranslation('de')->label());
}
/**
* Tests that composite translations affects the host entity's translations.
*/
public function testCompositeTranslation() {
/** @var \Drupal\node\NodeStorageInterface $node_storage */
$node_storage = $this->entityTypeManager->getStorage('node');
// Create a composite entity.
$composite = EntityTestCompositeRelationship::create([
'langcode' => 'en',
'name' => 'Initial Source Composite',
]);
$composite->save();
// Create a node with a reference to the test composite entity.
$node = Node::create([
'langcode' => 'en',
'title' => 'Initial Source Node',
'type' => 'article',
'composite_reference' => $composite,
]);
$node->save();
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->load($node->id());
// Assert that there is only 1 revision when creating a node.
$this->assertRevisionCount(1, $node);
// Assert that there is only 1 affected revision when creating a node.
$this->assertAffectedRevisionCount(1, $node);
// Assert there is no new composite revision after creating a host entity.
$this->assertRevisionCount(1, $composite);
$node_de = $node->addTranslation('de', ['title' => 'New Node #1 DE'] + $node->toArray());
$node_de = $node_storage->createRevision($node_de, FALSE);
$node_de->get('composite_reference')->entity->getTranslation('de')->set('name', 'New Composite #1 DE');
$node_de->isDefaultRevision(TRUE);
$violations = $node_de->validate();
foreach ($violations as $violation) {
$this->fail($violation->getPropertyPath() . ': ' . $violation->getMessage());
}
$this->assertEquals(0, count($violations));
$node_de->save();
$this->assertAffectedRevisionCount(1, $node_de);
$this->assertAffectedRevisionCount(1, $node);
// Test that changing composite non default language (DE) reference results
// in translation changes for this language but not for the default
// language.
$node_de->get('composite_reference')->entity->getTranslation('de')->set('name', 'Change Composite #1 DE');
$node_de->setNewRevision();
$node_de->save();
$this->assertEquals('Change Composite #1 DE', $node_de->get('composite_reference')->entity->getTranslation('de')->getName());
// Make sure the node DE has one more affected translation revision.
$this->assertAffectedRevisionCount(2, $node_de);
// Make sure the node EN has only one 1 affected translation revision.
$this->assertAffectedRevisionCount(1, $node);
// Test that changing composite in default language (EN) results in
// translation changes for this language but not for the DE language.
$node = $node_storage->load($node->id());
$node->get('composite_reference')->entity->set('name', 'Update Source #1');
$node->setNewRevision();
$node->save();
$this->assertEquals('Update Source #1', $node->get('composite_reference')->entity->getTranslation('en')->getName());
// The node EN now has 2 affected translation revision.
$this->assertAffectedRevisionCount(2, $node);
// The node DE still has 2 affected translation revisions.
$this->assertAffectedRevisionCount(2, $node_de);
}
/**
* Tests that nested composite translations affects the host translations.
*/
public function testNestedCompositeTranslation() {
/** @var \Drupal\node\NodeStorageInterface $node_storage */
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
// Create a nested composite entity.
$nested_composite = EntityTestCompositeRelationship::create([
'langcode' => 'en',
'name' => 'Initial Nested Source Composite',
]);
$nested_composite->addTranslation('de', ['name' => 'Nested Source Composite DE'] + $nested_composite->toArray());
$nested_composite->save();
// Create a composite entity.
$composite = EntityTestCompositeRelationship::create([
'langcode' => 'en',
'name' => 'Initial Source Composite',
'field_untranslatable' => 'Initial untranslatable field',
'composite_reference' => $nested_composite,
]);
$composite->addTranslation('de', ['name' => 'Source Composite DE'] + $composite->toArray());
$composite->save();
// Create a node with a reference to the test composite entity.
$node = Node::create([
'langcode' => 'en',
'title' => 'Initial Source Node',
'type' => 'article',
'composite_reference' => $composite,
]);
$node->save();
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->load($node->id());
// Assert that there is only 1 revision when creating a node.
$this->assertRevisionCount(1, $node);
// Assert that there is only 1 affected revision when creating a node.
$this->assertAffectedRevisionCount(1, $node);
// Assert there is no new composite revision after creating a host entity.
$this->assertRevisionCount(1, $composite);
// Assert there is no new nested composite revision after creating a host
// entity.
$this->assertRevisionCount(1, $nested_composite);
$node_de = $node->addTranslation('de', ['title' => 'New Node #1 DE'] + $node->toArray());
$node_de = $node_storage->createRevision($node_de, FALSE);
$node_de->get('composite_reference')->entity->getTranslation('de')->get('composite_reference')->entity->getTranslation('de')->set('name', 'New Nested Composite #1 DE');
$node_de->isDefaultRevision(TRUE);
$node_de->save();
$this->assertAffectedRevisionCount(1, $node_de);
$this->assertAffectedRevisionCount(1, $node);
// Test that changing nested composite non default language (DE) reference
// results in translation changes for this language but not for the default
// language.
$node_de->get('composite_reference')->entity->getTranslation('de')->get('composite_reference')->entity->getTranslation('de')->set('name', 'Change Nested Composite #1 DE');
$node_de->setNewRevision();
$node_de->save();
$this->assertEquals('Change Nested Composite #1 DE', $node_de->get('composite_reference')->entity->getTranslation('de')->get('composite_reference')->entity->getTranslation('de')->getName());
// Make sure the node DE has one more affected translation revision.
$this->assertAffectedRevisionCount(2, $node_de);
// Make sure the node EN has only one 1 affected translation revision.
$this->assertAffectedRevisionCount(1, $node);
// Test that changing nested composite in default language (EN) results in
// translation changes for this language but not for the DE language.
$node = $node_storage->load($node->id());
$node->get('composite_reference')->entity->get('composite_reference')->entity->set('name', 'Update Nested Source #1');
$node->setNewRevision();
$node->save();
$this->assertEquals('Update Nested Source #1', $node->get('composite_reference')->entity->getTranslation('en')->get('composite_reference')->entity->getTranslation('en')->getName());
// The node EN now has 2 affected translation revision.
$this->assertAffectedRevisionCount(2, $node);
// The node DE still has 2 affected translation revisions.
$this->assertAffectedRevisionCount(2, $node_de);
}
/**
* Asserts the affected revision count of a certain entity.
*
* @param int $expected
* The expected count.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*/
protected function assertAffectedRevisionCount($expected, EntityInterface $entity) {
$entity_type = $entity->getEntityType();
$affected_revisions_count = $this->entityTypeManager->getStorage($entity_type->id())
->getQuery()
->condition($entity_type->getKey('id'), $entity->id())
->condition($entity_type->getKey('langcode'), $entity->language()->getId())
->condition($entity_type->getKey('revision_translation_affected'), 1)
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals($expected, $affected_revisions_count);
}
/**
* Asserts the revision count of an entity.
*
* @param int $expected
* The expected amount of revisions.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*/
protected function assertRevisionCount($expected, EntityInterface $entity) {
$node_revisions_count = \Drupal::entityQuery($entity->getEntityTypeId())
->condition($entity->getEntityType()->getKey('id'), $entity->id())
->allRevisions()
->count()
->accessCheck(TRUE)
->execute();
$this->assertEquals($expected, $node_revisions_count);
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Kernel;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* @coversDefaultClass \Drupal\entity_reference_revisions\Plugin\Field\FieldFormatter\EntityReferenceRevisionsEntityFormatter
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsFormatterTest extends KernelTestBase {
use UserCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'node',
'user',
'system',
'field',
'entity_reference_revisions',
'entity_composite_relationship_test',
'entity_test',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create article content type.
$values = ['type' => 'article', 'name' => 'Article'];
$node_type = NodeType::create($values);
$node_type->save();
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installEntitySchema('entity_test');
$this->installEntitySchema('entity_test_composite');
$this->installSchema('system', ['sequences']);
$this->installSchema('node', ['node_access']);
// Add the entity_reference_revisions field to article.
$field_storage = FieldStorageConfig::create([
'field_name' => 'composite_reference',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => [
'target_type' => 'entity_test_composite'
],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'article',
]);
$field->save();
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_test',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => [
'target_type' => 'entity_test',
],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'article',
]);
$field->save();
$user = $this->createUser(['administer entity_test composite relationship']);
\Drupal::currentUser()->setAccount($user);
}
public function testFormatterWithDeletedReference() {
// Create the test composite entity.
$text = 'Dummy text';
$entity_test = EntityTestCompositeRelationship::create([
'uuid' => $text,
'name' => $text,
]);
$entity_test->save();
$text = 'Clever text';
// Set the name to a new text.
/** @var \Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship $entity_test */
$entity_test->name = $text;
$entity_test->setNeedsSave(TRUE);
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $entity_test,
]);
$node->save();
// entity_reference_revisions_entity_view
$result = $node->composite_reference->view(['type' => 'entity_reference_revisions_entity_view']);
$this->setRawContent($this->render($result));
$this->assertText('Clever text');
// Remove the referenced entity.
$entity_test->delete();
$node = Node::load($node->id());
$result = $node->composite_reference->view(['type' => 'entity_reference_revisions_entity_view']);
$this->render($result);
$this->assertNoText('Clever text');
}
/**
* Tests the label formatter.
*/
public function testLabelFormatter() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$formatter = 'entity_reference_revisions_label';
// Create the test composite entity.
$entity_test_composite = EntityTestCompositeRelationship::create([
'name' => $this->randomMachineName(),
]);
$entity_test_composite->save();
// Create the test entity.
$entity_test = EntityTest::create([
'name' => $this->randomMachineName(),
]);
$entity_test->save();
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $entity_test_composite,
'field_test' => $entity_test,
]);
$node->save();
// The 'link' settings is TRUE by default.
$build = $node->get('field_test')->view([
'type' => $formatter,
'settings' => [],
]);
$expected_field_cacheability = [
'contexts' => [],
'tags' => [],
'max-age' => Cache::PERMANENT,
];
$this->assertEquals($build['#cache'], $expected_field_cacheability, 'The field render array contains the entity access cacheability metadata');
$expected_item = [
'#type' => 'link',
'#title' => $entity_test->label(),
'#url' => $entity_test->toUrl(),
'#options' => $entity_test->toUrl()->getOptions(),
'#cache' => [
'contexts' => [
'user.permissions',
],
'tags' => $entity_test->getCacheTags(),
],
];
$this->assertEquals($renderer->renderRoot($build[0]), $renderer->renderRoot($expected_item), sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
$this->assertEquals(CacheableMetadata::createFromRenderArray($build[0]), CacheableMetadata::createFromRenderArray($expected_item));
// Test with the 'link' setting set to FALSE.
$build = $node->get('field_test')->view([
'type' => $formatter,
'settings' => ['link' => FALSE],
]);
$this->assertEquals($build[0]['#plain_text'], $entity_test->label(), sprintf('The markup returned by the %s formatter is correct.', $formatter));
// Test an entity type that doesn't have any link templates, which means
// \Drupal\Core\Entity\EntityInterface::toUrl() will throw an exception
// and the label formatter will output only the label instead of a link.
$build = $node->get('composite_reference')->view([
'type' => $formatter,
'settings' => ['link' => TRUE],
]);
$this->assertEquals($build[0]['#plain_text'], $entity_test_composite->label(), sprintf('The markup returned by the %s formatter is correct for an entity type with no valid link template.', $formatter));
}
}

View File

@@ -0,0 +1,321 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Kernel;
use Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* Tests the entity_reference_revisions NeedsSaveInterface.
*
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsSaveTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = array(
'node',
'user',
'system',
'field',
'entity_reference_revisions',
'entity_composite_relationship_test',
);
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create article content type.
$values = ['type' => 'article', 'name' => 'Article'];
$node_type = NodeType::create($values);
$node_type->save();
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installEntitySchema('entity_test_composite');
$this->installSchema('system', ['sequences']);
$this->installSchema('node', ['node_access']);
}
/**
* Test for NeedsSaveInterface implementation.
*
* Tests that the referenced entity is saved when needsSave() is TRUE.
*/
public function testNeedsSave() {
// Add the entity_reference_revisions field to article.
$field_storage = FieldStorageConfig::create(array(
'field_name' => 'composite_reference',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => array(
'target_type' => 'entity_test_composite'
),
));
$field_storage->save();
$field = FieldConfig::create(array(
'field_storage' => $field_storage,
'bundle' => 'article',
));
$field->save();
$text = 'Dummy text';
// Create the test composite entity.
$entity_test = EntityTestCompositeRelationship::create(array(
'uuid' => $text,
'name' => $text,
));
$entity_test->save();
$text = 'Clever text';
// Set the name to a new text.
/** @var \Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship $entity_test */
$entity_test->name = $text;
$entity_test->setNeedsSave(TRUE);
// Create a node with a reference to the test entity and save.
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $entity_test,
]);
// Check the name is properly set and that getValue() returns the entity
// when it is marked as needs save."
$values = $node->composite_reference->getValue();
$this->assertTrue(isset($values[0]['entity']));
static::assertEquals($values[0]['entity']->name->value, $text);
$node->composite_reference->setValue($values);
static::assertEquals($node->composite_reference->entity->name->value, $text);
$node->save();
// Check that the name has been updated when the parent has been saved.
/** @var \Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship $entity_test_after */
$entity_test_after = EntityTestCompositeRelationship::load($entity_test->id());
static::assertEquals($entity_test_after->name->value, $text);
$new_text = 'Dummy text again';
// Set another name and save the node without marking it as needs saving.
$entity_test_after->name = $new_text;
$entity_test_after->setNeedsSave(FALSE);
// Load the Node and check the composite reference entity is not returned
// from getValue() if it is not marked as needs saving.
$node = Node::load($node->id());
$values = $node->composite_reference->getValue();
$this->assertFalse(isset($values[0]['entity']));
$node->composite_reference = $entity_test_after;
$node->save();
// Check the name is not updated.
\Drupal::entityTypeManager()->getStorage('entity_test_composite')->resetCache();
$entity_test_after = EntityTestCompositeRelationship::load($entity_test->id());
static::assertEquals($text, $entity_test_after->name->value);
// Test if after delete the referenced entity there are no problems setting
// the referencing values to the parent.
$entity_test->delete();
$node = Node::load($node->id());
$node->save();
// Test if the needs save variable is set as false after saving.
$entity_needs_save = EntityTestCompositeRelationship::create([
'uuid' => $text,
'name' => $text,
]);
$entity_needs_save->setNeedsSave(TRUE);
$entity_needs_save->save();
$this->assertFalse($entity_needs_save->needsSave());
}
/**
* Test for NeedsSaveInterface implementation.
*
* Tests that the fields in the parent are properly updated.
*/
public function testSaveNewEntity() {
// Add the entity_reference_revisions field to article.
$field_storage = FieldStorageConfig::create(array(
'field_name' => 'composite_reference',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => array(
'target_type' => 'entity_test_composite'
),
));
$field_storage->save();
$field = FieldConfig::create(array(
'field_storage' => $field_storage,
'bundle' => 'article',
));
$field->save();
$text = 'Dummy text';
// Create the test entity.
$entity_test = EntityTestCompositeRelationship::create(array(
'uuid' => $text,
'name' => $text,
));
// Create a node with a reference to the test entity and save.
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => 'article',
'composite_reference' => $entity_test,
]);
$validate = $node->validate();
$this->assertEmpty($validate);
$node->save();
// Test that the fields on node are properly set.
$node_after = Node::load($node->id());
static::assertEquals($node_after->composite_reference[0]->target_id, $entity_test->id());
static::assertEquals($node_after->composite_reference[0]->target_revision_id, $entity_test->getRevisionId());
// Check that the entity is not new after save parent.
$this->assertFalse($entity_test->isNew());
// Create a new test entity.
$text = 'Smart text';
$second_entity_test = EntityTestCompositeRelationship::create(array(
'uuid' => $text,
'name' => $text,
));
$second_entity_test->save();
// Set the new test entity to the node field.
$node_after->composite_reference = $second_entity_test;
// Check the fields have been updated.
static::assertEquals($node_after->composite_reference[0]->target_id, $second_entity_test->id());
static::assertEquals($node_after->composite_reference[0]->target_revision_id, $second_entity_test->getRevisionId());
}
/**
* Tests entity_reference_revisions default value and config dependencies.
*/
public function testEntityReferenceRevisionsDefaultValue() {
// Create a test target node used as entity reference by another test node.
$node_target = Node::create([
'title' => 'Target node',
'type' => 'article',
'body' => 'Target body text',
'uuid' => '2d04c2b4-9c3d-4fa6-869e-ecb6fa5c9410',
]);
$node_target->save();
// Create an entity reference field to reference to the test target node.
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
$field_storage = FieldStorageConfig::create([
'field_name' => 'target_node_reference',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => ['target_type' => 'node'],
]);
$field_storage->save();
/** @var \Drupal\field\Entity\FieldConfig $field */
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'article',
'required' => FALSE,
'settings' => ['handler_settings' => ['target_bundles' => ['article' => 'article']]],
]);
// Add reference values to field config that will be used as default value.
$default_value = [
[
'target_id' => $node_target->id(),
'target_revision_id' => $node_target->getRevisionId(),
'target_uuid' => $node_target->uuid(),
],
];
$field->setDefaultValue($default_value)->save();
// Resave the target node, so that the default revision is not the one we
// want to use.
$revision_id = $node_target->getRevisionId();
$node_target_after = Node::load($node_target->id());
$node_target_after->setNewRevision();
$node_target_after->save();
$this->assertTrue($node_target_after->getRevisionId() != $revision_id);
// Create another node.
$node_host = Node::create([
'title' => 'Host node',
'type' => 'article',
'body' => 'Host body text',
'target_node_reference' => $node_target,
]);
$node_host->save();
// Check if the ERR default values are properly created.
$node_host_after = Node::load($node_host->id());
$this->assertEquals($node_host_after->target_node_reference->target_id, $node_target->id());
$this->assertEquals($node_host_after->target_node_reference->target_revision_id, $revision_id);
// Check if the configuration dependencies are properly created.
$dependencies = $field->calculateDependencies()->getDependencies();
$this->assertEquals($dependencies['content'][0], 'node:article:2d04c2b4-9c3d-4fa6-869e-ecb6fa5c9410');
$this->assertEquals($dependencies['config'][0], 'field.storage.node.target_node_reference');
$this->assertEquals($dependencies['config'][1], 'node.type.article');
$this->assertEquals($dependencies['module'][0], 'entity_reference_revisions');
}
/**
* Tests FieldType\EntityReferenceRevisionsItem::deleteRevision
*/
public function testEntityReferenceRevisionsDeleteHandleDeletedChild() {
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_reference',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => [
'target_type' => 'node',
],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'article',
]);
$field->save();
$child = Node::create([
'type' => 'article',
'title' => 'Child node',
]);
$child->save();
$node = Node::create([
'type' => 'article',
'title' => 'Parent node',
'field_reference' => [
[
'target_id' => $child->id(),
'target_revision_id' => $child->getRevisionId(),
]
],
]);
// Create two revisions.
$node->save();
$revisionId = $node->getRevisionId();
$node->setNewRevision(TRUE);
$node->save();
// Force delete the child Paragraph.
// Core APIs allow this although it is an inconsistent storage situation
// for Paragraphs.
$child->delete();
// Previously deleting a revision with a lost child failed fatal.
\Drupal::entityTypeManager()->getStorage('node')->deleteRevision($revisionId);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Kernel\Plugin\Derivative;
use Drupal\entity_reference_revisions\Plugin\migrate\destination\EntityReferenceRevisions;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\Plugin\MigrateDestinationPluginManager;
/**
* Tests the migration deriver.
*
* @coversDefaultClass \Drupal\entity_reference_revisions\Plugin\Derivative\MigrateEntityReferenceRevisions
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsDeriverTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['migrate', 'entity_reference_revisions', 'entity_composite_relationship_test'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(static::$modules);
}
/**
* Tests deriver.
*
* @covers ::getDerivativeDefinitions
*/
public function testDestinationDeriver() {
/** @var MigrateDestinationPluginManager $migrationDestinationManager */
$migrationDestinationManager = \Drupal::service('plugin.manager.migrate.destination');
$destination = $migrationDestinationManager->getDefinition('entity_reference_revisions:entity_test_composite');
$this->assertEquals(EntityReferenceRevisions::class, $destination['class']);
}
}

View File

@@ -0,0 +1,660 @@
<?php
namespace Drupal\Tests\entity_reference_revisions\Kernel\Plugin\migrate\destination;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessageInterface;
use Drupal\migrate\Plugin\Migration;
use Drupal\node\Entity\NodeType;
/**
* Tests the migration destination plugin.
*
* @coversDefaultClass \Drupal\entity_reference_revisions\Plugin\migrate\destination\EntityReferenceRevisions
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsDestinationTest extends KernelTestBase implements MigrateMessageInterface {
/**
* The migration plugin manager.
*
* @var \Drupal\migrate\Plugin\MigrationPluginManager
*/
protected $migrationPluginManager;
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate',
'entity_reference_revisions',
'entity_composite_relationship_test',
'user',
'system',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test_composite');
$this->installSchema('system', ['sequences']);
$this->installConfig(static::$modules);
$this->migrationPluginManager = \Drupal::service('plugin.manager.migration');
}
/**
* Tests get entity type id.
*
* @dataProvider getEntityTypeIdDataProvider
*
* @covers ::getEntityTypeId
*/
public function testGetEntityTypeId(array $definition, $expected) {
/** @var \Drupal\migrate\Plugin\Migration $migration */
$migration = $this->migrationPluginManager->createStubMigration($definition);
/** @var \Drupal\entity_reference_revisions\Plugin\migrate\destination\EntityReferenceRevisions $destination */
$destination = $migration->getDestinationPlugin();
/** @var \Drupal\Core\Entity\EntityStorageBase $storage */
$reflected_storage = new \ReflectionProperty($destination, 'storage');
$reflected_storage->setAccessible(TRUE);
$storage = $reflected_storage->getValue($destination);
$reflected_entity_type_id = new \ReflectionProperty($storage, 'entityTypeId');
$reflected_entity_type_id->setAccessible(TRUE);
$actual = $reflected_entity_type_id->getValue($storage);
$this->assertEquals($expected, $actual);
}
/**
* Provides multiple migration definitions for "getEntityTypeId" test.
*/
public static function getEntityTypeIdDataProvider() {
$data = self::getEntityDataProvider();
foreach ($data as &$datum) {
$datum['expected'] = 'entity_test_composite';
}
return $data;
}
/**
* Tests get entity.
*
* @dataProvider getEntityDataProvider
*
* @covers ::getEntity
* @covers ::rollback
* @covers ::rollbackNonTranslation
*/
public function testGetEntity(array $definition, array $expected) {
/** @var \Drupal\migrate\Plugin\Migration $migration */
$migration = $this->migrationPluginManager->createStubMigration($definition);
$migrationExecutable = (new MigrateExecutable($migration, $this));
/** @var \Drupal\Core\Entity\EntityStorageBase $storage */
$reflected_storage = new \ReflectionProperty($migration->getDestinationPlugin(), 'storage');
$reflected_storage->setAccessible(TRUE);
$storage = $reflected_storage->getValue($migration->getDestinationPlugin());
// Test inserting and updating by looping twice.
for ($i = 0; $i < 2; $i++) {
$migrationExecutable->import();
$migration->getIdMap()->prepareUpdate();
foreach ($expected as $data) {
$entity = $storage->loadRevision($data['revision_id']);
$this->assertEquals($data['label'], $entity->label());
$this->assertEquals($data['id'], $entity->id());
}
}
$migrationExecutable->rollback();
foreach ($expected as $data) {
$entity = $storage->loadRevision($data['id']);
$this->assertEmpty($entity);
}
}
/**
* Provides multiple migration definitions for "getEntity" test.
*/
public static function getEntityDataProvider() {
return [
'without keys' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['id' => 1, 'name' => 'content item 1a'],
['id' => 1, 'name' => 'content item 1b'],
['id' => 2, 'name' => 'content item 2'],
],
'ids' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'text'],
],
],
'process' => [
'name' => 'name',
],
'destination' => [
'plugin' => 'entity_reference_revisions:entity_test_composite',
],
],
'expected' => [
['id' => 1, 'revision_id' => 1, 'label' => 'content item 1a'],
['id' => 2, 'revision_id' => 2, 'label' => 'content item 1b'],
['id' => 3, 'revision_id' => 3, 'label' => 'content item 2'],
],
],
'with ids' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['id' => 1, 'name' => 'content item 1a'],
['id' => 1, 'name' => 'content item 1b'],
['id' => 2, 'name' => 'content item 2'],
['id' => 3, 'name' => 'content item 3'],
],
'ids' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'text'],
],
],
'process' => [
'name' => 'name',
'id' => 'id',
],
'destination' => [
'plugin' => 'entity_reference_revisions:entity_test_composite',
],
],
'expected' => [
['id' => 1, 'revision_id' => 1, 'label' => 'content item 1b'],
['id' => 2, 'revision_id' => 2, 'label' => 'content item 2'],
['id' => 3, 'revision_id' => 3, 'label' => 'content item 3'],
],
],
'with ids and new revisions' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['id' => 1, 'name' => 'content item 1a'],
['id' => 1, 'name' => 'content item 1b'],
['id' => 2, 'name' => 'content item 2'],
],
'ids' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'text'],
],
],
'process' => [
'name' => 'name',
'id' => 'id',
],
'destination' => [
'plugin' => 'entity_reference_revisions:entity_test_composite',
'new_revisions' => TRUE,
],
],
'expected' => [
['id' => 1, 'revision_id' => 1, 'label' => 'content item 1a'],
['id' => 1, 'revision_id' => 2, 'label' => 'content item 1b'],
['id' => 2, 'revision_id' => 3, 'label' => 'content item 2'],
],
],
'with ids and revisions' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['id' => 1, 'revision_id' => 1, 'name' => 'content item 1'],
['id' => 2, 'revision_id' => 2, 'name' => 'content item 2'],
['id' => 3, 'revision_id' => 3, 'name' => 'content item 3'],
],
'ids' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'text'],
],
],
'process' => [
'id' => 'id',
'revision_id' => 'revision_id',
'name' => 'name',
],
'destination' => [
'plugin' => 'entity_reference_revisions:entity_test_composite',
],
],
'expected' => [
['id' => 1, 'revision_id' => 1, 'label' => 'content item 1'],
['id' => 2, 'revision_id' => 2, 'label' => 'content item 2'],
['id' => 3, 'revision_id' => 3, 'label' => 'content item 3'],
],
],
];
}
/**
* Tests get entity.
*
* @dataProvider getEntityDataProviderForceRevision
*
* @covers ::getEntity
* @covers ::rollback
* @covers ::rollbackNonTranslation
*/
public function testGetEntityForceRevision(array $definition, array $expected) {
/** @var \Drupal\migrate\Plugin\Migration $migration */
$migration = $this->migrationPluginManager->createStubMigration($definition);
$migrationExecutable = (new MigrateExecutable($migration, $this));
/** @var \Drupal\Core\Entity\EntityStorageBase $storage */
$reflected_storage = new \ReflectionProperty($migration->getDestinationPlugin(), 'storage');
$reflected_storage->setAccessible(TRUE);
$storage = $reflected_storage->getValue($migration->getDestinationPlugin());
// Test inserting and updating by looping twice.
for ($i = 0; $i < 2; $i++) {
$migrationExecutable->import();
$migration->getIdMap()->prepareUpdate();
foreach ($expected[$i] as $data) {
$entity = $storage->loadRevision($data['revision_id']);
$this->assertEquals($data['label'], $entity->label());
$this->assertEquals($data['id'], $entity->id());
}
}
$migrationExecutable->rollback();
for ($i = 0; $i < 2; $i++) {
foreach ($expected[$i] as $data) {
$entity = $storage->loadRevision($data['id']);
$this->assertEmpty($entity);
}
}
}
/**
* Provides multiple migration definitions for "getEntity" test.
*/
public static function getEntityDataProviderForceRevision() {
return [
'with ids, new revisions and no force revision' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['id' => 1, 'name' => 'content item 1a'],
['id' => 2, 'name' => 'content item 2'],
],
'ids' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'text'],
],
],
'process' => [
'name' => 'name',
'id' => 'id',
],
'destination' => [
'plugin' => 'entity_reference_revisions:entity_test_composite',
'new_revisions' => TRUE,
'force_revision' => FALSE,
],
],
'expected' => [
[
['id' => 1, 'revision_id' => 1, 'label' => 'content item 1a'],
['id' => 2, 'revision_id' => 2, 'label' => 'content item 2'],
],
[
['id' => 1, 'revision_id' => 1, 'label' => 'content item 1a'],
['id' => 2, 'revision_id' => 2, 'label' => 'content item 2'],
]
],
],
'with ids, new revisions and force revision' => [
'definition' => [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['id' => 1, 'name' => 'content item 1a'],
['id' => 2, 'name' => 'content item 2'],
],
'ids' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'text'],
],
],
'process' => [
'name' => 'name',
'id' => 'id',
],
'destination' => [
'plugin' => 'entity_reference_revisions:entity_test_composite',
'new_revisions' => TRUE,
'force_revision' => TRUE,
],
],
'expected' => [
[
['id' => 1, 'revision_id' => 1, 'label' => 'content item 1a'],
['id' => 2, 'revision_id' => 2, 'label' => 'content item 2'],
],
[
['id' => 1, 'revision_id' => 3, 'label' => 'content item 1a'],
['id' => 2, 'revision_id' => 4, 'label' => 'content item 2'],
]
],
],
];
}
/**
* Tests multi-value and single-value destination field linkage.
*
* @dataProvider destinationFieldMappingDataProvider
*/
public function testDestinationFieldMapping(array $data) {
$this->enableModules(['node', 'field']);
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installSchema('node', ['node_access']);
// Create new content type.
$values = ['type' => 'article', 'name' => 'Article'];
$node_type = NodeType::create($values);
$node_type->save();
// Add the field_err_single field to the node type.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_err_single',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => [
'target_type' => 'entity_test_composite',
],
'cardinality' => 1,
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'article',
]);
$field->save();
// Add the field_err_multiple field to the node type.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_err_multiple',
'entity_type' => 'node',
'type' => 'entity_reference_revisions',
'settings' => [
'target_type' => 'entity_test_composite',
],
'cardinality' => -1,
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'article',
]);
$field->save();
$definitions = [];
$instances = [];
foreach ($data as $datum) {
$definitions[$datum['definition']['id']] = $datum['definition'];
$instances[$datum['definition']['id']] = $this->migrationPluginManager->createStubMigration($datum['definition']);
}
// Reflection is easier than mocking. We need to use createInstance for
// purposes of registering the migration for the migration process plugin.
$reflector = new \ReflectionObject($this->migrationPluginManager);
$property = $reflector->getProperty('definitions');
$property->setAccessible(TRUE);
$property->setValue($this->migrationPluginManager, $definitions);
$this->container->set('plugin.manager.migration', $this->migrationPluginManager);
foreach ($data as $datum) {
$migration = $this->migrationPluginManager->createInstance($datum['definition']['id']);
$migrationExecutable = (new MigrateExecutable($migration, $this));
/** @var \Drupal\Core\Entity\EntityStorageBase $storage */
$reflected_storage = new \ReflectionProperty($migration->getDestinationPlugin(), 'storage');
$reflected_storage->setAccessible(TRUE);
$storage = $reflected_storage->getValue($migration->getDestinationPlugin());
$migrationExecutable->import();
foreach ($datum['expected'] as $expected) {
$entity = $storage->loadRevision($expected['id']);
$properties = array_diff_key($expected, array_flip(['id']));
foreach ($properties as $property => $value) {
if (is_array($value)) {
foreach ($value as $delta => $text) {
$this->assertNotEmpty($entity->{$property}[$delta]->entity, "Entity property $property with $delta is empty");
$this->assertEquals($text, $entity->{$property}[$delta]->entity->label());
}
}
else {
$this->assertNotEmpty($entity, 'Entity with label ' . $expected[$property] . ' is empty');
$this->assertEquals($expected[$property], $entity->label());
}
}
}
}
}
/**
* Provides multiple migration definitions for "getEntity" test.
*/
public static function destinationFieldMappingDataProvider() {
return [
'scenario 1' => [
[
'single err' => [
'definition' => [
'id' => 'single_err',
'class' => Migration::class,
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'photo' => 'Photo1 here',
],
[
'id' => 2,
'photo' => 'Photo2 here',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'name' => 'photo',
],
'destination' => [
'plugin' => 'entity_reference_revisions:entity_test_composite',
],
],
'expected' => [
['id' => 1, 'name' => 'Photo1 here'],
['id' => 2, 'name' => 'Photo2 here'],
],
],
'multiple err author1' => [
'definition' => [
'id' => 'multiple_err_author1',
'class' => Migration::class,
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'author' => 'Author 1',
],
[
'id' => 2,
'author' => 'Author 2',
],
],
'ids' => [
'author' => ['type' => 'text'],
],
],
'process' => [
'name' => 'author',
],
'destination' => [
'plugin' => 'entity_reference_revisions:entity_test_composite',
],
],
'expected' => [
['id' => 3, 'name' => 'Author 1'],
['id' => 4, 'name' => 'Author 2'],
],
],
'multiple err author 2' => [
'definition' => [
'id' => 'multiple_err_author2',
'class' => Migration::class,
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'author' => 'Author 3',
],
[
'id' => 2,
'author' => 'Author 4',
],
],
'ids' => [
'author' => ['type' => 'text'],
],
],
'process' => [
'name' => 'author',
],
'destination' => [
'plugin' => 'entity_reference_revisions:entity_test_composite',
],
],
'expected' => [
['id' => 5, 'name' => 'Author 3'],
['id' => 6, 'name' => 'Author 4'],
],
],
'destination entity' => [
'definition' => [
'id' => 'node_migration',
'class' => Migration::class,
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'Article 1',
'photo' => 'Photo1 here',
'author' => ['Author 1', 'Author 3'],
],
[
'id' => 2,
'title' => 'Article 2',
'photo' => 'Photo2 here',
'author' => ['Author 2', 'Author 4'],
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'title' => 'title',
'type' => [
'plugin' => 'default_value',
'default_value' => 'article',
],
'field_err_single/target_id' => [
[
'plugin' => 'migration_lookup',
'migration' => ['single_err'],
'no_stub' => TRUE,
'source' => 'id',
],
[
'plugin' => 'extract',
'index' => [
'0',
],
],
],
'field_err_single/target_revision_id' => [
[
'plugin' => 'migration_lookup',
'migration' => ['single_err'],
'no_stub' => TRUE,
'source' => 'id',
],
[
'plugin' => 'extract',
'index' => [
1,
],
],
],
'field_err_multiple' => [
[
'plugin' => 'migration_lookup',
'migration' => [
'multiple_err_author1',
'multiple_err_author2',
],
'no_stub' => TRUE,
'source' => 'author',
],
[
'plugin' => 'sub_process',
'process' => [
'target_id' => '0',
'target_revision_id' => '1',
],
],
],
],
'destination' => [
'plugin' => 'entity:node',
],
],
'expected' => [
[
'id' => 1,
'title' => 'Article 1',
'field_err_single' => ['Photo1 here'],
'field_err_multiple' => ['Author 1', 'Author 3'],
],
[
'id' => 2,
'title' => 'Article 2',
'field_err_single' => ['Photo2 here'],
'field_err_multiple' => ['Author 2', 'Author 4'],
],
],
],
],
],
];
}
/**
* {@inheritdoc}
*/
public function display($message, $type = 'status') {
$this->assertTrue($type == 'status', $message);
}
}