diff --git a/config/schema/rules.schema.yml b/config/schema/rules.schema.yml index 7b1b7225..9230169d 100644 --- a/config/schema/rules.schema.yml +++ b/config/schema/rules.schema.yml @@ -35,9 +35,21 @@ rules.reaction.*: label: type: label label: 'Label' - event: - type: string - label: 'Event' + events: + type: sequence + label: 'Events' + sequence: + type: mapping + label: 'Event' + mapping: + event_name: + type: string + label: 'Name' + configuration: + type: sequence + label: 'Configuration' + sequence: + type: mapping module: type: string label: 'Module' diff --git a/src/Entity/ReactionRuleConfig.php b/src/Entity/ReactionRuleConfig.php index 8620d9ec..6c10f3ae 100644 --- a/src/Entity/ReactionRuleConfig.php +++ b/src/Entity/ReactionRuleConfig.php @@ -37,7 +37,7 @@ * config_export = { * "id", * "label", - * "event", + * "events", * "module", * "description", * "tag", @@ -116,11 +116,16 @@ class ReactionRuleConfig extends ConfigEntityBase implements RulesUiComponentPro protected $module = 'rules'; /** - * The event name this reaction rule is reacting on. + * The events this reaction rule is reacting on. * - * @var string + * Events array. The array is numerically indexed and contains arrays with the + * following structure: + * - event_name: String with the event machine name. + * - configuration: An array containing the event configuration. + * + * @var array */ - protected $event; + protected $events = []; /** * Sets a Rules expression instance for this Reaction rule. @@ -159,7 +164,7 @@ public function getExpression() { */ public function getComponent() { $component = RulesComponent::create($this->getExpression()); - $component->addContextDefinitionsForEvents([$this->getEvent()]); + $component->addContextDefinitionsForEvents($this->getEventNames()); return $component; } @@ -222,10 +227,30 @@ public function getTag() { } /** - * Returns the event on which this rule will trigger. + * Gets configuration of all events the rule is reacting on. + * + * @return array + * The events array. The array is numerically indexed and contains arrays + * with the following structure: + * - event_name: String with the event machine name. + * - configuration: An array containing the event configuration. + */ + public function getEvents() { + return $this->events; + } + + /** + * Gets fully qualified names of all events the rule is reacting on. + * + * @return string[] + * The array of fully qualified event names of the rule. */ - public function getEvent() { - return $this->event; + public function getEventNames() { + $names = []; + foreach ($this->events as $event) { + $names[] = $event['event_name']; + } + return $names; } /** diff --git a/src/Entity/ReactionRuleStorage.php b/src/Entity/ReactionRuleStorage.php index e03cab6d..1fabfbbf 100644 --- a/src/Entity/ReactionRuleStorage.php +++ b/src/Entity/ReactionRuleStorage.php @@ -95,10 +95,11 @@ public static function createInstance(ContainerInterface $container, EntityTypeI protected function getRegisteredEvents() { $events = []; foreach ($this->loadMultiple() as $rules_config) { - $event = $rules_config->getEvent(); - $event = $this->eventManager->getEventBaseName($event); - if ($event && !isset($events[$event])) { - $events[$event] = $event; + foreach ($rules_config->getEventNames() as $event_name) { + $event_name = $this->eventManager->getEventBaseName($event_name); + if (!isset($events[$event_name])) { + $events[$event_name] = $event_name; + } } } return $events; @@ -119,10 +120,13 @@ public function save(EntityInterface $entity) { // After the reaction rule is saved, we need to rebuild the container, // otherwise the reaction rule will not fire. However, we can do an - // optimization: if the event was already registered before, we do not have - // to rebuild the container. - if (empty($events_before[$entity->getEvent()])) { - $this->drupalKernel->rebuildContainer(); + // optimization: if every event was already registered before, we do not + // have to rebuild the container. + foreach ($entity->getEventNames() as $event_name) { + if (empty($events_before[$event_name])) { + $this->drupalKernel->rebuildContainer(); + break; + } } return $return; diff --git a/src/EventSubscriber/GenericEventSubscriber.php b/src/EventSubscriber/GenericEventSubscriber.php index ce3edd81..4fa13f64 100644 --- a/src/EventSubscriber/GenericEventSubscriber.php +++ b/src/EventSubscriber/GenericEventSubscriber.php @@ -125,7 +125,7 @@ public function onRulesEvent(Event $event, $event_name) { // another rule. foreach ($triggered_events as $triggered_event) { // @todo Only load active reaction rules here. - $configs = $storage->loadByProperties(['event' => $triggered_event]); + $configs = $storage->loadByProperties(['events.*.event_name' => $triggered_event]); // Loop over all rules and execute them. foreach ($configs as $config) { diff --git a/src/Form/ReactionRuleAddForm.php b/src/Form/ReactionRuleAddForm.php index 2fb521bd..ab26b69b 100644 --- a/src/Form/ReactionRuleAddForm.php +++ b/src/Form/ReactionRuleAddForm.php @@ -65,7 +65,8 @@ public function form(array $form, FormStateInterface $form_state) { } } - $form['event'] = [ + $form['events']['#tree'] = TRUE; + $form['events'][]['event_name'] = [ '#type' => 'select', '#title' => $this->t('React on event'), '#options' => $options, diff --git a/src/Form/ReactionRuleEditForm.php b/src/Form/ReactionRuleEditForm.php index 1a876c44..b1f3f7fc 100644 --- a/src/Form/ReactionRuleEditForm.php +++ b/src/Form/ReactionRuleEditForm.php @@ -71,12 +71,17 @@ protected function prepareEntity() { * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { - $event_name = $this->entity->getEvent(); - $event_definition = $this->eventManager->getDefinition($event_name); - $form['event']['#markup'] = $this->t('Event: @label (@name)', [ - '@label' => $event_definition['label'], - '@name' => $event_name, - ]); + foreach ($this->entity->getEventNames() as $key => $event_name) { + $event_definition = $this->eventManager->getDefinition($event_name); + $form['event'][$key] = [ + '#type' => 'item', + '#title' => $this->t('Events:'), + '#markup' => $this->t('@label (@name)', [ + '@label' => $event_definition['label'], + '@name' => $event_name, + ]), + ]; + } $form = $this->rulesUiHandler->getForm()->buildForm($form, $form_state); return parent::form($form, $form_state); } diff --git a/tests/src/Kernel/ConfigurableEventHandlerTest.php b/tests/src/Kernel/ConfigurableEventHandlerTest.php index 28c2bfa3..57eb1534 100644 --- a/tests/src/Kernel/ConfigurableEventHandlerTest.php +++ b/tests/src/Kernel/ConfigurableEventHandlerTest.php @@ -93,9 +93,11 @@ public function testConfigurableEventHandler() { ); $config_entity1 = $this->storage->create([ 'id' => 'test_rule1', - 'expression_id' => 'rules_rule', - 'event' => 'rules_entity_presave:node--page', - ])->setExpression($rule1); + ]); + $config_entity1->set('events', [ + ['event_name' => 'rules_entity_presave:node--page'], + ]); + $config_entity1->set('expression', $rule1->getConfiguration()); $config_entity1->save(); // Create rule2 with the 'rules_entity_presave:node' event. @@ -106,9 +108,11 @@ public function testConfigurableEventHandler() { ); $config_entity2 = $this->storage->create([ 'id' => 'test_rule2', - 'expression_id' => 'rules_rule', - 'event' => 'rules_entity_presave:node', - ])->setExpression($rule2); + ]); + $config_entity2->set('events', [ + ['event_name' => 'rules_entity_presave:node'], + ]); + $config_entity2->set('expression', $rule2->getConfiguration()); $config_entity2->save(); // The logger instance has changed, refresh it. diff --git a/tests/src/Kernel/EventIntegrationTest.php b/tests/src/Kernel/EventIntegrationTest.php index d1dff4f4..7bc7e5d3 100644 --- a/tests/src/Kernel/EventIntegrationTest.php +++ b/tests/src/Kernel/EventIntegrationTest.php @@ -53,7 +53,7 @@ public function testUserLoginEvent() { $config_entity = $this->storage->create([ 'id' => 'test_rule', - 'event' => 'rules_user_login', + 'events' => [['event_name' => 'rules_user_login']], 'expression' => $rule->getConfiguration(), ]); $config_entity->save(); @@ -79,7 +79,7 @@ public function testUserLogoutEvent() { $config_entity = $this->storage->create([ 'id' => 'test_rule', - 'event' => 'rules_user_logout', + 'events' => [['event_name' => 'rules_user_logout']], 'expression' => $rule->getConfiguration(), ]); $config_entity->save(); @@ -105,7 +105,7 @@ public function testCronEvent() { $config_entity = $this->storage->create([ 'id' => 'test_rule', - 'event' => 'rules_system_cron', + 'events' => [['event_name' => 'rules_system_cron']], 'expression' => $rule->getConfiguration(), ]); $config_entity->save(); @@ -130,7 +130,7 @@ public function testSystemLoggerEvent() { $config_entity = $this->storage->create([ 'id' => 'test_rule', - 'event' => 'rules_system_logger_event', + 'events' => [['event_name' => 'rules_system_logger_event']], 'expression' => $rule->getConfiguration(), ]); $config_entity->save(); @@ -156,7 +156,7 @@ public function testInitEvent() { $config_entity = $this->storage->create([ 'id' => 'test_rule', - 'event' => KernelEvents::REQUEST, + 'events' => [['event_name' => KernelEvents::REQUEST]], 'expression' => $rule->getConfiguration(), ]); $config_entity->save(); @@ -180,4 +180,37 @@ public function testInitEvent() { $this->assertRulesLogEntryExists('action called'); } + /** + * Test that rules config supports multiple events. + */ + public function testMultipleEvents() { + $rule = $this->expressionManager->createRule(); + $rule->addCondition('rules_test_true'); + $rule->addAction('rules_test_log'); + + $config_entity = $this->storage->create([ + 'id' => 'test_rule', + ]); + $config_entity->set('events', [ + ['event_name' => 'rules_user_login'], + ['event_name' => 'rules_user_logout'], + ]); + $config_entity->set('expression', $rule->getConfiguration()); + $config_entity->save(); + + // The logger instance has changed, refresh it. + $this->logger = $this->container->get('logger.channel.rules'); + + $account = User::create(['name' => 'test_user']); + // Invoke the hook manually which should trigger the rules_user_login event. + rules_user_login($account); + // Invoke the hook manually which should trigger the rules_user_logout + // event. + rules_user_logout($account); + + // Test that the action in the rule logged something. + $this->assertRulesLogEntryExists('action called'); + $this->assertRulesLogEntryExists('action called', 1); + } + }