diff --git a/src/Plugin/RulesAction/EntitySave.php b/src/Plugin/RulesAction/EntitySave.php index 3479a57f..e1bb0075 100644 --- a/src/Plugin/RulesAction/EntitySave.php +++ b/src/Plugin/RulesAction/EntitySave.php @@ -3,32 +3,36 @@ namespace Drupal\rules\Plugin\RulesAction; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\rules\Core\RulesActionBase; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Entity\EntityStorageInterface; /** * Provides a 'Save entity' action. * * @RulesAction( * id = "rules_entity_save", - * label = @Translation("Save entity"), - * category = @Translation("Entity"), - * context = { - * "entity" = @ContextDefinition("entity", - * label = @Translation("Entity"), - * description = @Translation("Specifies the entity, which should be saved permanently.") - * ), - * "immediate" = @ContextDefinition("boolean", - * label = @Translation("Force saving immediately"), - * description = @Translation("Usually saving is postponed till the end of the evaluation, so that multiple saves can be fold into one. If this set, saving is forced to happen immediately."), - * default_value = FALSE, - * required = FALSE - * ) - * } + * deriver = "Drupal\rules\Plugin\RulesAction\EntitySaveDeriver", * ) * * @todo: Add access callback information from Drupal 7. */ -class EntitySave extends RulesActionBase { +class EntitySave extends RulesActionBase implements ContainerFactoryPluginInterface { + + /** + * The entity storage service. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $storage; + + /** + * The entity type id. + * + * @var string + */ + protected $entityTypeId; /** * Flag that indicates if the entity should be auto-saved later. @@ -37,6 +41,36 @@ class EntitySave extends RulesActionBase { */ protected $saveLater = TRUE; + /** + * Constructs an EntitySave object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin ID for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Entity\EntityStorageInterface $storage + * The entity storage service. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $storage) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->storage = $storage; + $this->entityTypeId = $plugin_definition['entity_type_id']; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager')->getStorage($plugin_definition['entity_type_id']) + ); + } + /** * Saves the Entity. * diff --git a/src/Plugin/RulesAction/EntitySaveDeriver.php b/src/Plugin/RulesAction/EntitySaveDeriver.php new file mode 100644 index 00000000..668dafef --- /dev/null +++ b/src/Plugin/RulesAction/EntitySaveDeriver.php @@ -0,0 +1,95 @@ +entityTypeManager = $entity_type_manager; + $this->entityFieldManager = $entity_field_manager; + $this->stringTranslation = $string_translation; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, $base_plugin_id) { + return new static( + $container->get('entity_type.manager'), + $container->get('entity_field.manager'), + $container->get('string_translation') + ); + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions($base_plugin_definition) { + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { + // Only allow content entities and ignore configuration entities. + if (!$entity_type instanceof ContentEntityTypeInterface) { + continue; + } + + $this->derivatives[$entity_type_id] = [ + 'label' => $this->t('Save @entity_type', ['@entity_type' => $entity_type->getLowercaseLabel()]), + 'category' => $entity_type->getLabel(), + 'entity_type_id' => $entity_type_id, + 'context' => [ + 'entity' => ContextDefinition::create("entity:$entity_type_id") + ->setLabel($entity_type->getLabel()) + ->setDescription($this->t('Specifies the @entity_type_label that should be saved permanently.', ['@entity_type_label' => $entity_type->getLabel()])) + ->setRequired(TRUE), + 'immediate' => ContextDefinition::create('boolean') + ->setLabel($this->t('Force saving immediately')) + ->setDescription($this->t('Usually saving is postponed till the end of the evaluation, so that multiple saves can be fold into one. If this set, saving is forced to happen immediately.')) + ->setDefaultValue(FALSE) + ->setRequired(FALSE), + ], + ] + $base_plugin_definition; + } + + return $this->derivatives; + } + +} diff --git a/tests/src/Integration/Action/EntityCreateTest.php b/tests/src/Integration/Action/EntityCreateTest.php index ee65d6e5..0e331271 100644 --- a/tests/src/Integration/Action/EntityCreateTest.php +++ b/tests/src/Integration/Action/EntityCreateTest.php @@ -55,31 +55,31 @@ public function setUp() { $bundle_field_definition->getItemDefinition() ->willReturn($item_definition->reveal()); $bundle_field_definition->getCardinality()->willReturn(1) - ->shouldBeCalledTimes(1); + ->shouldBeCalled(); $bundle_field_definition->getType()->willReturn('string'); $bundle_field_definition->getLabel()->willReturn('Bundle') - ->shouldBeCalledTimes(1); + ->shouldBeCalled(); $bundle_field_definition->getDescription() ->willReturn('Bundle mock description') - ->shouldBeCalledTimes(1); + ->shouldBeCalled(); $bundle_field_definition_required->getItemDefinition() ->willReturn($item_definition->reveal()); $bundle_field_definition_required->getCardinality()->willReturn(1) - ->shouldBeCalledTimes(1); + ->shouldBeCalled(); $bundle_field_definition_required->getType()->willReturn('string'); $bundle_field_definition_required->getLabel()->willReturn('Required field') - ->shouldBeCalledTimes(1); + ->shouldBeCalled(); $bundle_field_definition_required->getDescription() ->willReturn('Required field mock description') - ->shouldBeCalledTimes(1); + ->shouldBeCalled(); $bundle_field_definition_required->isRequired() ->willReturn(TRUE) - ->shouldBeCalledTimes(1); + ->shouldBeCalled(); $bundle_field_definition_optional->isRequired() ->willReturn(FALSE) - ->shouldBeCalledTimes(1); + ->shouldBeCalled(); // Prepare mocked entity storage. $entity_type_storage = $this->prophesize(EntityStorageBase::class); diff --git a/tests/src/Integration/Action/EntitySaveTest.php b/tests/src/Integration/Action/EntitySaveTest.php index 50b56178..3e24464b 100644 --- a/tests/src/Integration/Action/EntitySaveTest.php +++ b/tests/src/Integration/Action/EntitySaveTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\rules\Integration\Action; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityStorageBase; use Drupal\Tests\rules\Integration\RulesEntityIntegrationTestBase; /** @@ -33,7 +34,15 @@ public function setUp() { $this->entity = $this->prophesizeEntity(EntityInterface::class); - $this->action = $this->actionManager->createInstance('rules_entity_save'); + // Prepare mocked entity storage. + $entity_type_storage = $this->prophesize(EntityStorageBase::class); + + // Return the mocked storage controller. + $this->entityTypeManager->getStorage('test') + ->willReturn($entity_type_storage->reveal()); + + // Instantiate the action we are testing. + $this->action = $this->actionManager->createInstance('rules_entity_save:test'); } /** @@ -42,7 +51,21 @@ public function setUp() { * @covers ::summary */ public function testSummary() { - $this->assertEquals('Save entity', $this->action->summary()); + $this->assertEquals('Save test', $this->action->summary()); + } + + /** + * Tests the action execution. + * + * @covers ::execute + */ + public function testActionExecution() { + $this->entity->save()->shouldBeCalledTimes(1); + + $this->action->setContextValue('entity', $this->entity->reveal()) + ->setContextValue('immediate', TRUE); + + $this->action->execute(); } /** diff --git a/tests/src/Integration/Action/RulesComponentActionTest.php b/tests/src/Integration/Action/RulesComponentActionTest.php index 1179f607..a0ae6210 100644 --- a/tests/src/Integration/Action/RulesComponentActionTest.php +++ b/tests/src/Integration/Action/RulesComponentActionTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Config\Entity\ConfigEntityStorageInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityStorageBase; use Drupal\rules\Context\ContextConfig; use Drupal\rules\Context\ContextDefinition; use Drupal\rules\Engine\RulesComponent; @@ -17,6 +18,19 @@ */ class RulesComponentActionTest extends RulesEntityIntegrationTestBase { + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + // Prepare mocked entity storage. + $entity_type_storage = $this->prophesize(EntityStorageBase::class); + + // Return the mocked storage controller. + $this->entityTypeManager->getStorage('test') + ->willReturn($entity_type_storage->reveal()); + } + /** * Tests that a rule can be used as action. */ @@ -42,7 +56,7 @@ public function testActionAvailable() { public function testExecute() { // Set up a rules component that will just save an entity. $nested_rule = $this->rulesExpressionManager->createRule(); - $nested_rule->addAction('rules_entity_save', ContextConfig::create() + $nested_rule->addAction('rules_entity_save:test', ContextConfig::create() ->map('entity', 'entity') ); @@ -51,7 +65,7 @@ public function testExecute() { 'label' => 'Test rule', ], 'rules_component'); $rules_config->setExpression($nested_rule); - $rules_config->setContextDefinitions(['entity' => ContextDefinition::create('entity')]); + $rules_config->setContextDefinitions(['entity' => ContextDefinition::create('entity:test')]); $this->prophesizeStorage([$rules_config]); @@ -66,7 +80,7 @@ public function testExecute() { $entity->save()->shouldBeCalledTimes(1); RulesComponent::create($rule) - ->addContextDefinition('entity', ContextDefinition::create('entity')) + ->addContextDefinition('entity', ContextDefinition::create('entity:test')) ->setContextValue('entity', $entity->reveal()) ->execute(); } @@ -139,7 +153,7 @@ public function testAutosaveOnlyOnce() { $entity = $this->prophesizeEntity(EntityInterface::class); $nested_rule = $this->rulesExpressionManager->createRule(); - $nested_rule->addAction('rules_entity_save', ContextConfig::create() + $nested_rule->addAction('rules_entity_save:test', ContextConfig::create() ->map('entity', 'entity') ); @@ -148,7 +162,7 @@ public function testAutosaveOnlyOnce() { 'label' => 'Test rule', ], 'rules_component'); $rules_config->setExpression($nested_rule); - $rules_config->setContextDefinitions(['entity' => ContextDefinition::create('entity')]); + $rules_config->setContextDefinitions(['entity' => ContextDefinition::create('entity:test')]); $this->prophesizeStorage([$rules_config]); @@ -158,7 +172,7 @@ public function testAutosaveOnlyOnce() { $rule->addAction('rules_component:test_rule', ContextConfig::create() ->map('entity', 'entity') ); - $rule->addAction('rules_entity_save', ContextConfig::create() + $rule->addAction('rules_entity_save:test', ContextConfig::create() ->map('entity', 'entity') ); @@ -166,7 +180,7 @@ public function testAutosaveOnlyOnce() { $entity->save()->shouldBeCalledTimes(1); RulesComponent::create($rule) - ->addContextDefinition('entity', ContextDefinition::create('entity')) + ->addContextDefinition('entity', ContextDefinition::create('entity:test')) ->setContextValue('entity', $entity->reveal()) ->execute(); } diff --git a/tests/src/Integration/Engine/AutoSaveTest.php b/tests/src/Integration/Engine/AutoSaveTest.php index a923d94b..a2af62d3 100644 --- a/tests/src/Integration/Engine/AutoSaveTest.php +++ b/tests/src/Integration/Engine/AutoSaveTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\rules\Integration\Engine; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityStorageBase; use Drupal\rules\Context\ContextConfig; use Drupal\rules\Context\ContextDefinition; use Drupal\rules\Engine\RulesComponent; @@ -15,13 +16,26 @@ */ class AutoSaveTest extends RulesEntityIntegrationTestBase { + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + // Prepare mocked entity storage. + $entity_type_storage = $this->prophesize(EntityStorageBase::class); + + // Return the mocked storage controller. + $this->entityTypeManager->getStorage('test') + ->willReturn($entity_type_storage->reveal()); + } + /** * Tests auto saving after an action execution. */ public function testActionAutoSave() { $rule = $this->rulesExpressionManager->createRule(); // Just leverage the entity save action, which by default uses auto-saving. - $rule->addAction('rules_entity_save', ContextConfig::create() + $rule->addAction('rules_entity_save:test', ContextConfig::create() ->map('entity', 'entity') ); @@ -29,7 +43,7 @@ public function testActionAutoSave() { $entity->save()->shouldBeCalledTimes(1); RulesComponent::create($rule) - ->addContextDefinition('entity', ContextDefinition::create('entity')) + ->addContextDefinition('entity', ContextDefinition::create('entity:test')) ->setContextValue('entity', $entity->reveal()) ->execute(); } diff --git a/tests/src/Integration/Engine/IntegrityCheckTest.php b/tests/src/Integration/Engine/IntegrityCheckTest.php index c16f2455..a609a6fc 100644 --- a/tests/src/Integration/Engine/IntegrityCheckTest.php +++ b/tests/src/Integration/Engine/IntegrityCheckTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\rules\Integration\Engine; use Drupal\rules\Context\ContextConfig; +use Drupal\Core\Entity\EntityStorageBase; use Drupal\rules\Context\ContextDefinition; use Drupal\rules\Engine\RulesComponent; use Drupal\Tests\rules\Integration\RulesEntityIntegrationTestBase; @@ -14,17 +15,30 @@ */ class IntegrityCheckTest extends RulesEntityIntegrationTestBase { + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + // Prepare mocked entity storage. + $entity_type_storage = $this->prophesize(EntityStorageBase::class); + + // Return the mocked storage controller. + $this->entityTypeManager->getStorage('test') + ->willReturn($entity_type_storage->reveal()); + } + /** * Tests that the integrity check can be invoked. */ public function testIntegrityCheck() { $rule = $this->rulesExpressionManager->createRule(); - $rule->addAction('rules_entity_save', ContextConfig::create() + $rule->addAction('rules_entity_save:test', ContextConfig::create() ->map('entity', 'entity') ); $violation_list = RulesComponent::create($rule) - ->addContextDefinition('entity', ContextDefinition::create('entity')) + ->addContextDefinition('entity', ContextDefinition::create('entity:test')) ->checkIntegrity(); $this->assertEquals(0, iterator_count($violation_list)); } @@ -34,7 +48,7 @@ public function testIntegrityCheck() { */ public function testUnknownVariable() { $rule = $this->rulesExpressionManager->createRule(); - $action = $this->rulesExpressionManager->createAction('rules_entity_save', ContextConfig::create() + $action = $this->rulesExpressionManager->createAction('rules_entity_save:test', ContextConfig::create() ->map('entity', 'unknown_variable') ->toArray() ); @@ -45,7 +59,7 @@ public function testUnknownVariable() { $this->assertEquals(1, iterator_count($violation_list)); $violation = $violation_list[0]; $this->assertEquals( - 'Data selector unknown_variable for context Entity is invalid. Unable to get variable unknown_variable, it is not defined.', + 'Data selector unknown_variable for context Test is invalid. Unable to get variable unknown_variable, it is not defined.', (string) $violation->getMessage() ); $this->assertEquals($action->getUuid(), $violation->getUuid()); @@ -59,14 +73,14 @@ public function testCheckUuid() { // Just use a rule with 2 dummy actions. $rule->addAction('rules_entity_save', ContextConfig::create() ->map('entity', 'unknown_variable_1')); - $second_action = $this->rulesExpressionManager->createAction('rules_entity_save', ContextConfig::create() + $second_action = $this->rulesExpressionManager->createAction('rules_entity_save:test', ContextConfig::create() ->map('entity', 'unknown_variable_2') ->toArray() ); $rule->addExpressionObject($second_action); $all_violations = RulesComponent::create($rule) - ->addContextDefinition('entity', ContextDefinition::create('entity')) + ->addContextDefinition('entity', ContextDefinition::create('entity:test')) ->checkIntegrity(); $this->assertEquals(2, iterator_count($all_violations)); @@ -75,7 +89,7 @@ public function testCheckUuid() { $this->assertEquals(1, count($uuid_violations)); $violation = $uuid_violations[0]; $this->assertEquals( - 'Data selector unknown_variable_2 for context Entity is invalid. Unable to get variable unknown_variable_2, it is not defined.', + 'Data selector unknown_variable_2 for context Test is invalid. Unable to get variable unknown_variable_2, it is not defined.', (string) $violation->getMessage() ); $this->assertEquals($second_action->getUuid(), $violation->getUuid());