diff --git a/src/Core/RulesDefaultEventHandler.php b/src/Core/RulesDefaultEventHandler.php index f2297bae..1af70b63 100644 --- a/src/Core/RulesDefaultEventHandler.php +++ b/src/Core/RulesDefaultEventHandler.php @@ -22,6 +22,7 @@ public function getContextDefinitions() { $definition = $this->getPluginDefinition(); if ($this instanceof RulesConfigurableEventHandlerInterface) { $this->refineContextDefinitions(); + $definition = $this->getPluginDefinition(); } return !empty($definition['context']) ? $definition['context'] : []; } diff --git a/src/Entity/ReactionRuleConfig.php b/src/Entity/ReactionRuleConfig.php index 6c10f3ae..9efed6cf 100644 --- a/src/Entity/ReactionRuleConfig.php +++ b/src/Entity/ReactionRuleConfig.php @@ -164,7 +164,7 @@ public function getExpression() { */ public function getComponent() { $component = RulesComponent::create($this->getExpression()); - $component->addContextDefinitionsForEvents($this->getEventNames()); + $component->addContextDefinitionsForEvents($this->getEventBaseNames()); return $component; } @@ -253,6 +253,31 @@ public function getEventNames() { return $names; } + /** + * Gets the base names of all events the rule is reacting on. + * + * For a configured event name like {EVENT_NAME}--{SUFFIX}, the base event + * name {EVENT_NAME} is returned. + * + * @return string[] + * The array of base event names of the rule. + * + * @see \Drupal\rules\Core\RulesConfigurableEventHandlerInterface::getEventNameSuffix() + */ + public function getEventBaseNames() { + $names = []; + foreach ($this->events as $event) { + $event_name = $event['event_name']; + if (strpos($event_name, '--') !== FALSE) { + // Cut off any suffix from a configured event name. + $parts = explode('--', $event_name, 2); + $event_name = $parts[0]; + } + $names[] = $event_name; + } + return $names; + } + /** * {@inheritdoc} */ diff --git a/src/Entity/ReactionRuleStorage.php b/src/Entity/ReactionRuleStorage.php index 1fabfbbf..50baa766 100644 --- a/src/Entity/ReactionRuleStorage.php +++ b/src/Entity/ReactionRuleStorage.php @@ -95,8 +95,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI protected function getRegisteredEvents() { $events = []; foreach ($this->loadMultiple() as $rules_config) { - foreach ($rules_config->getEventNames() as $event_name) { - $event_name = $this->eventManager->getEventBaseName($event_name); + foreach ($rules_config->getEventBaseNames() as $event_name) { if (!isset($events[$event_name])) { $events[$event_name] = $event_name; } @@ -122,7 +121,7 @@ public function save(EntityInterface $entity) { // otherwise the reaction rule will not fire. However, we can do an // optimization: if every event was already registered before, we do not // have to rebuild the container. - foreach ($entity->getEventNames() as $event_name) { + foreach ($entity->getEventBaseNames() as $event_name) { if (empty($events_before[$event_name])) { $this->drupalKernel->rebuildContainer(); break; diff --git a/src/EventHandler/ConfigurableEventHandlerEntityBundle.php b/src/EventHandler/ConfigurableEventHandlerEntityBundle.php index 86baa041..58362796 100644 --- a/src/EventHandler/ConfigurableEventHandlerEntityBundle.php +++ b/src/EventHandler/ConfigurableEventHandlerEntityBundle.php @@ -15,6 +15,42 @@ */ class ConfigurableEventHandlerEntityBundle extends ConfigurableEventHandlerBase { + /** + * The bundles information for the entity. + * + * @var array + */ + protected $bundlesInfo; + + /** + * The entity info plugin definition. + * + * @var mixed + */ + protected $entityInfo; + + /** + * The entity type. + * + * @var string + */ + protected $entityTypeId; + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->entityTypeId = $plugin_definition['entity_type_id']; + // @todo: This needs to use dependency injection. + $this->entityInfo = \Drupal::entityTypeManager()->getDefinition($this->entityTypeId); + // @tdo: use EntityTypeBundleInfo service. + $this->bundlesInfo = \Drupal::entityManager()->getBundleInfo($this->entityTypeId); + if (!$this->bundlesInfo) { + throw new \InvalidArgumentException('Unsupported event name passed.'); + } + } + /** * {@inheritdoc} */ @@ -30,42 +66,64 @@ public static function determineQualifiedEvents(Event $event, $event_name, array * {@inheritdoc} */ public function summary() { - // Nothing to do by default. + $bundle = $this->configuration['bundle']; + $bundle_label = isset($this->bundlesInfo[$bundle]['label']) ? $this->bundlesInfo[$bundle]['label'] : $bundle; + $suffix = isset($bundle) ? ' ' . t('of @bundle-key %name', array('@bundle-key' => $this->entityInfo->getBundleLabel(), '%name' => $bundle_label)) : ''; + return $this->pluginDefinition['label']->render() . $suffix; } /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - // Nothing to do by default. + $form['bundle'] = array( + '#type' => 'select', + '#title' => t('Restrict by @bundle', array('@bundle' => $this->entityInfo->getBundleLabel())), + '#description' => t('If you need to filter for multiple values, either add multiple events or use the "Entity is of bundle" condition instead.'), + '#default_value' => $this->configuration['bundle'], + '#empty_value' => '', + ); + foreach ($this->bundlesInfo as $name => $bundle_info) { + $form['bundle']['#options'][$name] = $bundle_info['label']; + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function extractConfigurationFormValues(array &$form, FormStateInterface $form_state) { + $this->configuration['bundle'] = $form_state->getValue('bundle'); } /** * {@inheritdoc} */ public function validate() { - // Nothing to check by default. + // Nothing to validate. } /** * {@inheritdoc} */ public function getEventNameSuffix() { - // Nothing to do by default. + return isset($this->configuration['bundle']) ? $this->configuration['bundle'] : FALSE; } /** * {@inheritdoc} */ public function refineContextDefinitions() { - // Nothing to refine by default. + if ($bundle = $this->getEventNameSuffix()) { + $this->pluginDefinition['context']['entity']->setBundles([$bundle]); + } } /** * {@inheritdoc} */ public function calculateDependencies() { - // Nothing to calculate by default. + // @todo: Implement. } } diff --git a/src/Form/ReactionRuleAddForm.php b/src/Form/ReactionRuleAddForm.php index ab26b69b..7b1869e7 100644 --- a/src/Form/ReactionRuleAddForm.php +++ b/src/Form/ReactionRuleAddForm.php @@ -8,6 +8,7 @@ namespace Drupal\rules\Form; use Drupal\Core\Form\FormStateInterface; +use Drupal\rules\Core\RulesConfigurableEventHandlerInterface; use Drupal\rules\Core\RulesEventManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -71,12 +72,37 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('React on event'), '#options' => $options, '#required' => TRUE, + '#ajax' => $this->getDefaultAjax(), '#description' => $this->t('Whenever the event occurs, rule evaluation is triggered.'), + '#executes_submit_callback' => array('::submitForm'), ]; + $form['event_configuration'] = array(); + if ($values = $form_state->getValue('events')) { + $event_name = $values[0]['event_name']; + if ($handler = $this->getEventHandler($event_name)) { + $form['event_configuration'] = $handler->buildConfigurationForm(array(), $form_state); + } + } + return $form; } + /** + * {@inheritdoc} + */ + public function buildEntity(array $form, FormStateInterface $form_state) { + $entity = parent::buildEntity($form, $form_state); + foreach ($entity->getEventBaseNames() as $event_name) { + if ($handler = $this->getEventHandler($event_name)) { + $handler->extractConfigurationFormValues($form['event_configuration'], $form_state); + $entity->set('configuration', $handler->getConfiguration()); + $entity->set('events', [['event_name' => $event_name . '--' . $handler->getConfiguration()['bundle']]]); + } + } + return $entity; + } + /** * {@inheritdoc} */ @@ -87,4 +113,26 @@ public function save(array $form, FormStateInterface $form_state) { $form_state->setRedirect('entity.rules_reaction_rule.edit_form', ['rules_reaction_rule' => $this->entity->id()]); } + /** + * Gets event handler class. + * + * Currently event handler is available only when the event is configurable. + * + * @param $event_name + * The event base name. + * @param array $configuration + * The event configuration. + * + * @return \Drupal\rules\Core\RulesConfigurableEventHandlerInterface|null + * The event handler, null if there is no proper handler. + */ + protected function getEventHandler($event_name, $configuration = []) { + $event_definition = $this->eventManager->getDefinition($event_name); + $handler_class = $event_definition['class']; + if (is_subclass_of($handler_class, RulesConfigurableEventHandlerInterface::class)) { + $handler = new $handler_class($configuration, $this->eventManager->getEventBaseName($event_name), $event_definition); + return $handler; + } + } + } diff --git a/src/Form/ReactionRuleEditForm.php b/src/Form/ReactionRuleEditForm.php index b1f3f7fc..4cf4b8c7 100644 --- a/src/Form/ReactionRuleEditForm.php +++ b/src/Form/ReactionRuleEditForm.php @@ -72,8 +72,9 @@ protected function prepareEntity() { */ public function form(array $form, FormStateInterface $form_state) { foreach ($this->entity->getEventNames() as $key => $event_name) { - $event_definition = $this->eventManager->getDefinition($event_name); - $form['event'][$key] = [ + $event_base_name = $this->eventManager->getEventBaseName($event_name); + $event_definition = $this->eventManager->getDefinition($event_base_name); + $form['events'][$key] = [ '#type' => 'item', '#title' => $this->t('Events:'), '#markup' => $this->t('@label (@name)', [ diff --git a/src/Form/RulesComponentFormBase.php b/src/Form/RulesComponentFormBase.php index ccb8c6dd..a29c7b90 100644 --- a/src/Form/RulesComponentFormBase.php +++ b/src/Form/RulesComponentFormBase.php @@ -19,6 +19,10 @@ abstract class RulesComponentFormBase extends EntityForm { * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { + // Specify the wrapper div used by #ajax. + $form['#prefix'] = '
'; + $form['#suffix'] = '
'; + $form['settings'] = [ '#type' => 'details', '#title' => $this->t('Settings'), @@ -78,4 +82,32 @@ public function exists($id) { return (bool) $this->entityTypeManager->getStorage($type)->load($id); } + /** + * Get default form #ajax properties. + * + * @param string $effect + * (optional) The jQuery effect to use when placing the new HTML (used with + * 'wrapper'). Valid options are 'none' (default), 'slide', or 'fade'. + * + * @return array + */ + public function getDefaultAjax($effect = 'none') { + return array( + 'callback' => '::reloadForm', + 'wrapper' => 'rules-form-wrapper', + 'effect' => $effect, + 'speed' => 'fast', + ); + } + + /** + * Ajax callback to reload the form. + * + * @return array + * The reloaded form. + */ + public function reloadForm(array $form, FormStateInterface $form_state) { + return $form; + } + }