Issue #3481695 by phenaproxima, a.dmitriiev, thejimbirch, b_sharpe, alexpott: Entity displays cloning requires special config action

(cherry picked from commit a1b47eafdb)
merge-requests/9925/head
Alex Pott 2024-10-23 17:58:12 +01:00
parent 25ad53d3df
commit 9b7d24a6fa
No known key found for this signature in database
GPG Key ID: BDA67E7EE836E5CE
8 changed files with 92 additions and 3 deletions

View File

@ -138,12 +138,14 @@ final class EntityMethod implements ConfigActionPluginInterface, ContainerFactor
if ($this->numberOfRequiredParams !== 1 && $this->numberOfParams !== 1) {
throw new EntityMethodException(sprintf('Entity method config action \'%s\' requires an array value. The number of parameters or required parameters for %s::%s() is not 1', $this->pluginId, $entity->getEntityType()->getClass(), $this->method));
}
$entity->{$this->method}($value);
$result = $entity->{$this->method}($value);
}
else {
$entity->{$this->method}(...$value);
$result = $entity->{$this->method}(...$value);
}
return $entity;
// If an instance of the entity (either itself, or a clone) was returned
// by the method, return that.
return is_a($result, get_class($entity)) ? $result : $entity;
}
}

View File

@ -324,6 +324,7 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
/**
* {@inheritdoc}
*/
#[ActionMethod(adminLabel: new TranslatableMarkup('Copy to another mode'), pluralize: FALSE)]
public function createCopy($mode) {
$display = $this->createDuplicate();
$display->mode = $display->originalMode = $mode;
@ -588,4 +589,20 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
return \Drupal::logger('system');
}
/**
* {@inheritdoc}
*/
public function set($property_name, $value): static {
// If changing the entity ID, also update the target entity type, bundle,
// and view mode.
if ($this->isNew() && $property_name === $this->getEntityType()->getKey('id')) {
if (substr_count($value, '.') !== 2) {
throw new \InvalidArgumentException("'$value' is not a valid entity display ID.");
}
[$this->targetEntityType, $this->bundle, $this->mode] = explode('.', $value);
}
parent::set($property_name, $value);
return $this;
}
}

View File

@ -72,6 +72,7 @@ class LayoutBuilderEntityViewDisplayValidationTest extends ConfigEntityValidatio
*/
public function testImmutableProperties(array $valid_values = []): void {
parent::testImmutableProperties([
'id' => 'entity_test_with_bundle.two.full',
'targetEntityType' => 'entity_test_with_bundle',
'bundle' => 'two',
]);

View File

@ -6,6 +6,7 @@ namespace Drupal\KernelTests\Core\Entity;
use Drupal\comment\Entity\CommentType;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
@ -170,4 +171,22 @@ class EntityDisplayBaseTest extends KernelTestBase {
$this->assertSame($expected_dependencies, $entity_display->getDependencies());
}
/**
* Tests that changing the entity ID updates related properties.
*/
public function testChangeId(): void {
/** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */
$display = $this->container->get(EntityDisplayRepositoryInterface::class)
->getViewDisplay('entity_test', 'entity_test');
$this->assertSame('entity_test.entity_test.default', $display->id());
$display->set('id', 'node.page.rss');
$this->assertSame('node', $display->getTargetEntityTypeId());
$this->assertSame('page', $display->getTargetBundle());
$this->assertSame('rss', $display->getMode());
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage("'a.b' is not a valid entity display ID.");
$display->set('id', 'a.b');
}
}

View File

@ -113,6 +113,7 @@ class EntityFormDisplayValidationTest extends ConfigEntityValidationTestBase {
*/
public function testImmutableProperties(array $valid_values = []): void {
parent::testImmutableProperties([
'id' => 'entity_test_with_bundle.two.default',
'targetEntityType' => 'entity_test_with_bundle',
'bundle' => 'two',
]);

View File

@ -92,6 +92,7 @@ class EntityViewDisplayValidationTest extends ConfigEntityValidationTestBase {
*/
public function testImmutableProperties(array $valid_values = []): void {
parent::testImmutableProperties([
'id' => 'entity_test_with_bundle.two.full',
'targetEntityType' => 'entity_test_with_bundle',
'bundle' => 'two',
]);

View File

@ -5,7 +5,10 @@ declare(strict_types=1);
namespace Drupal\KernelTests\Core\Recipe;
use Drupal\Core\Config\Action\ConfigActionException;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\user\Entity\Role;
@ -15,6 +18,7 @@ use Drupal\user\Entity\Role;
*/
class EntityCloneConfigActionTest extends KernelTestBase {
use ContentTypeCreationTrait;
use UserCreationTrait;
/**
@ -69,4 +73,37 @@ class EntityCloneConfigActionTest extends KernelTestBase {
$this->assertFalse($clone->hasPermission('access user profiles'));
}
/**
* Tests cloning entity displays, which have specialized logic for that.
*/
public function testCloneEntityDisplay(): void {
$this->container->get(ModuleInstallerInterface::class)->install(['node']);
$this->createContentType(['type' => 'alpha']);
$this->createContentType(['type' => 'beta']);
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = $this->container->get(EntityDisplayRepositoryInterface::class);
// Create the default view displays for each node type.
$display_repository->getViewDisplay('node', 'alpha')->save();
$display_repository->getViewDisplay('node', 'beta')->save();
// Ensure the `rss` displays don't exist yet.
$this->assertTrue($display_repository->getViewDisplay('node', 'alpha', 'rss')->isNew());
$this->assertTrue($display_repository->getViewDisplay('node', 'beta', 'rss')->isNew());
// Use the action to clone the default view displays to the `rss` view mode.
/** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
$manager = $this->container->get('plugin.manager.config_action');
$manager->applyAction('cloneAs', 'core.entity_view_display.node.alpha.default', 'node.alpha.rss');
$manager->applyAction('entity_method:core.entity_view_display:createCopy', 'core.entity_view_display.node.beta.default', 'rss');
$this->assertFalse($display_repository->getViewDisplay('node', 'alpha', 'rss')->isNew());
$this->assertFalse($display_repository->getViewDisplay('node', 'beta', 'rss')->isNew());
// Ensure that this also works with wildcards.
$this->assertTrue($display_repository->getViewDisplay('node', 'alpha', 'search_result')->isNew());
$this->assertTrue($display_repository->getViewDisplay('node', 'beta', 'search_result')->isNew());
$manager->applyAction('entity_method:core.entity_view_display:createCopy', 'core.entity_view_display.node.*.default', 'search_result');
$this->assertFalse($display_repository->getViewDisplay('node', 'alpha', 'search_result')->isNew());
$this->assertFalse($display_repository->getViewDisplay('node', 'beta', 'search_result')->isNew());
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\Tests\Core\Config\Entity;
use Drupal\Core\Entity\EntityDisplayBase;
use Drupal\Core\Entity\EntityType;
use Drupal\Tests\UnitTestCase;
use PHPUnit\Framework\MockObject\MockObject;
@ -25,6 +26,7 @@ class EntityDisplayBaseTest extends UnitTestCase {
*/
protected function setUp(): void {
parent::setUp();
$this->entityDisplay = $this->getMockBuilder(EntityDisplayBaseMockableClass::class)
->disableOriginalConstructor()
->onlyMethods([])
@ -91,4 +93,13 @@ class EntityDisplayBaseMockableClass extends EntityDisplayBase {
return NULL;
}
public function getEntityType() {
return new EntityType([
'id' => 'entity_view_display',
'entity_keys' => [
'id' => 'id',
],
]);
}
}