diff --git a/includes/rules.core.inc b/includes/rules.core.inc index c3bbdca..53904f2 100644 --- a/includes/rules.core.inc +++ b/includes/rules.core.inc @@ -1777,8 +1777,8 @@ interface RulesTriggerableInterface { * @param $event_name * The (configured) event's name. * - * @return array - * The event settings. If the event has no settings, the array is empty. + * @return array|null + * The array of event settings, or NULL if there are no settings. */ public function getEventSettings($event_name); diff --git a/includes/rules.event.inc b/includes/rules.event.inc new file mode 100755 index 0000000..893018d --- /dev/null +++ b/includes/rules.event.inc @@ -0,0 +1,353 @@ +type, $node, $view_mode); + * @endcode + * If the event settings are optional, both events have to be invoked whereas + * usually the more general event is invoked last. E.g.: + * @code + * rules_invoke_event('node_view--' . $node->type, $node, $view_mode); + * rules_invoke_event('node_view', $node, $view_mode); + * @endcode + * + * @see RulesEventHandlerBase + * @see RulesEventDefaultHandler + */ +interface RulesEventHandlerInterface { + + /** + * Constructs the event handler. + * + * @param string $event_name + * The base event string. + * @param array $info + * The event info of the given event. + */ + public function __construct($event_name, $info); + + /** + * Sets the event settings. + * + * @param array $settings + * An array of settings to set. + * + * @return RulesEventHandlerInterface + * The handler itself for chaining. + */ + public function setSettings(array $settings); + + /** + * Gets the event settings. + * + * @return array + * The array of settings. + */ + public function getSettings(); + + /** + * Returns an array of default settings. + * + * @return array + */ + public function getDefaults(); + + /** + * Returns a user-facing summary of the settings. + * + * @return string + * The summary in HTML, i.e. properly escaped or filtered. + */ + public function summary(); + + /** + * Builds the event settings form. + * + * @param array $form_state + * An associative array containing the current state of the form. + * + * @return array + * The form structure. + */ + public function buildForm(array &$form_state); + + /** + * Validate the event settings independent from a form submission. + * + * @throws RulesIntegrityException + * In case of validation errors, RulesIntegrityExceptions are thrown. + */ + public function validate(); + + /** + * Extract the form values and update the event settings. + * + * @param array $form + * An associative array containing the structure of the form. + * @param array $form_state + * An associative array containing the current state of the form. + */ + public function extractFormValues(array &$form, array &$form_state); + + /** + * Returns the suffix to be added to the base event named based upon settings. + * + * If event settings are used, the event name Rules uses for the configured + * event is {EVENT_NAME}--{SUFFIX}. + * + * @return string + * The suffix string. Return an empty string for not appending a suffix. + */ + public function getEventNameSuffix(); + + /** + * Returns info about the variables provided by this event. + * + * @return array + * An array of provided variables, keyed by variable names and with the + * variable info array as value. + */ + public function availableVariables(); +} + +/** + * Base class for event handler. + */ +abstract class RulesEventHandlerBase implements RulesEventHandlerInterface { + + /** + * The event name. + * + * @var string + */ + protected $eventName; + + /** + * The event info. + * + * @var array + */ + protected $eventInfo; + + /** + * The event settings. + * + * @var array + */ + protected $settings = array(); + + /** + * Implements RulesEventHandlerInterface::__construct() + */ + public function __construct($event_name, $info) { + $this->eventName = $event_name; + $this->eventInfo = $info; + $this->settings = $this->getDefaults(); + } + + /** + * Implements RulesEventHandlerInterface::getSettings() + */ + public function getSettings() { + return $this->settings; + } + + /** + * Implements RulesEventHandlerInterface::setSettings() + */ + public function setSettings(array $settings) { + $this->settings = $settings + $this->getDefaults(); + return $this; + } + + /** + * Implements RulesEventHandlerInterface::validate() + */ + public function validate() { + // Nothing to check by default. + } + + /** + * Implements RulesEventHandlerInterface::extractFormValues() + */ + public function extractFormValues(array &$form, array &$form_state) { + foreach ($this->getDefaults() as $key => $setting) { + $this->settings[$key] = isset($form_state['values'][$key]) ? $form_state['values'][$key] : $setting; + } + } + + /** + * Implements RulesEventHandlerInterface::availableVariables() + */ + public function availableVariables() { + return isset($this->eventInfo['variables']) ? $this->eventInfo['variables'] : array(); + } +} + +/** + * A handler for events having no settings. This is the default handler. + */ +class RulesEventDefaultHandler extends RulesEventHandlerBase { + + /** + * Implements RulesEventHandlerInterface::buildForm() + */ + public function buildForm(array &$form_state) { + return array(); + } + + /** + * Implements RulesEventHandlerInterface::getConfiguredEventName() + */ + public function getEventNameSuffix() { + return ''; + } + + /** + * Implements RulesEventHandlerInterface::summary() + */ + public function summary() { + return check_plain($this->eventInfo['label']); + } + + /** + * Implements RulesEventHandlerInterface::getDefaults() + */ + public function getDefaults() { + return array(); + } + + /** + * Implements RulesEventHandlerInterface::getSettings() + */ + public function getSettings() { + return NULL; + } +} + +/** + * Exposes the bundle of an entity as event setting. + */ +class RulesEventHandlerEntityBundle extends RulesEventHandlerBase { + + protected $entityType, $entityInfo, $bundleKey; + + /** + * Implements RulesEventHandlerInterface::__construct() + */ + public function __construct($event_name, $info) { + parent::__construct($event_name, $info); + // Cut off the suffix, e.g. remove 'view' from node_view. + $this->entityType = implode('_', explode('_', $event_name, -1)); + $this->entityInfo = entity_get_info($this->entityType); + if (!$this->entityInfo) { + throw new InvalidArgumentException('Unsupported event name passed.'); + } + } + + /** + * Implements RulesEventHandlerInterface::summary() + */ + public function summary() { + $bundle = &$this->settings['bundle']; + $bundle_label = isset($this->entityInfo['bundles'][$bundle]['label']) ? $this->entityInfo['bundles'][$bundle]['label'] : $bundle; + $suffix = isset($bundle) ? ' ' . t('of @bundle-key %name', array('@bundle-key' => $this->getBundlePropertyLabel(), '%name' => $bundle_label)) : ''; + return check_plain($this->eventInfo['label']) . $suffix; + } + + /** + * Implements RulesEventHandlerInterface::buildForm() + */ + public function buildForm(array &$form_state) { + $form['bundle'] = array( + '#type' => 'select', + '#title' => t('Restrict by @bundle', array('@bundle' => $this->getBundlePropertyLabel())), + '#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->settings['bundle'], + '#empty_value' => '', + ); + foreach ($this->entityInfo['bundles'] as $name => $bundle_info) { + $form['bundle']['#options'][$name] = $bundle_info['label']; + } + return $form; + } + + /** + * Returns the label to use for the bundle property. + * + * @return string + */ + protected function getBundlePropertyLabel() { + return $this->entityInfo['entity keys']['bundle']; + } + + /** + * Implements RulesEventHandlerInterface::extractFormValues() + */ + public function extractFormValues(array &$form, array &$form_state) { + $this->settings['bundle'] = !empty($form_state['values']['bundle']) ? $form_state['values']['bundle'] : NULL; + } + + /** + * Implements RulesEventHandlerInterface::validate() + */ + public function validate() { + if ($this->settings['bundle'] && empty($this->entityInfo['bundles'][$this->settings['bundle']])) { + throw new RulesIntegrityException(t('The @bundle %bundle of %entity_type is not known.', + array( + '%bundle' => $this->settings['bundle'], + '%entity_type' => $this->entityInfo['label'], + '@bundle' => $this->getBundlePropertyLabel(), + )), array(NULL, 'bundle')); + } + } + + /** + * Implements RulesEventHandlerInterface::getConfiguredEventName() + */ + public function getEventNameSuffix() { + return $this->settings['bundle']; + } + + /** + * Implements RulesEventHandlerInterface::getDefaults() + */ + public function getDefaults() { + return array( + 'bundle' => NULL, + ); + } + + /** + * Implements RulesEventHandlerInterface::availableVariables() + */ + public function availableVariables() { + $variables = $this->eventInfo['variables']; + if ($this->settings['bundle']) { + // Add the bundle to all variables of the entity type. + foreach ($variables as $name => $variable_info) { + if ($variable_info['type'] == $this->entityType) { + $variables[$name]['bundle'] = $this->settings['bundle']; + } + } + } + return $variables; + } +} diff --git a/includes/rules.plugins.inc b/includes/rules.plugins.inc index 6d61d15..cac9c60 100644 --- a/includes/rules.plugins.inc +++ b/includes/rules.plugins.inc @@ -490,7 +490,7 @@ class RulesReactionRule extends Rule implements RulesTriggerableInterface { protected function exportChildren($key = 'ON') { foreach ($this->events as $event_name) { - $export[$key][$event_name] = $this->getEventSettings($event_name); + $export[$key][$event_name] = (array) $this->getEventSettings($event_name); } return $export + parent::exportChildren(); } diff --git a/modules/events.inc b/modules/events.inc index d32f573..4ac9760 100644 --- a/modules/events.inc +++ b/modules/events.inc @@ -38,12 +38,12 @@ function rules_events_entity_unchanged($arguments, $name, $info) { function rules_entity_view($entity, $type, $view_mode, $langcode) { switch ($type) { case 'comment': - rules_invoke_event('comment_view', $entity, $view_mode); rules_invoke_event('comment_view--' . $entity->node_type, $entity, $view_mode); + rules_invoke_event('comment_view', $entity, $view_mode); break; case 'node': - rules_invoke_event('node_view', $entity, $view_mode); rules_invoke_event('node_view--' . $entity->type, $entity, $view_mode); + rules_invoke_event('node_view', $entity, $view_mode); break; case 'user': rules_invoke_event('user_view', $entity, $view_mode); @@ -57,16 +57,16 @@ function rules_entity_view($entity, $type, $view_mode, $langcode) { function rules_entity_presave($entity, $type) { switch ($type) { case 'comment': - rules_invoke_event('comment_presave', $entity); rules_invoke_event('comment_presave--' . $entity->node_type, $entity); + rules_invoke_event('comment_presave', $entity); break; case 'node': - rules_invoke_event('node_presave', $entity); rules_invoke_event('node_presave--' . $entity->type, $entity); + rules_invoke_event('node_presave', $entity); break; case 'taxonomy_term': - rules_invoke_event('taxonomy_term_presave', $entity); rules_invoke_event('taxonomy_term_presave--' . $entity->vocabulary_machine_name, $entity); + rules_invoke_event('taxonomy_term_presave', $entity); break; case 'taxonomy_vocabulary': case 'user': @@ -81,16 +81,16 @@ function rules_entity_presave($entity, $type) { function rules_entity_update($entity, $type) { switch ($type) { case 'comment': - rules_invoke_event('comment_update', $entity); rules_invoke_event('comment_update--' . $entity->node_type, $entity); + rules_invoke_event('comment_update', $entity); break; case 'node': - rules_invoke_event('node_update', $entity); rules_invoke_event('node_update--' . $entity->type, $entity); + rules_invoke_event('node_update', $entity); break; case 'taxonomy_term': - rules_invoke_event('taxonomy_term_update', $entity); rules_invoke_event('taxonomy_term_update--' . $entity->vocabulary_machine_name, $entity); + rules_invoke_event('taxonomy_term_update', $entity); break; case 'taxonomy_vocabulary': case 'user': @@ -105,16 +105,16 @@ function rules_entity_update($entity, $type) { function rules_entity_insert($entity, $type) { switch ($type) { case 'comment': - rules_invoke_event('comment_insert', $entity); rules_invoke_event('comment_insert--' . $entity->node_type, $entity); + rules_invoke_event('comment_insert', $entity); break; case 'node': - rules_invoke_event('node_insert', $entity); rules_invoke_event('node_insert--' . $entity->type, $entity); + rules_invoke_event('node_insert', $entity); break; case 'taxonomy_term': - rules_invoke_event('taxonomy_term_insert', $entity); rules_invoke_event('taxonomy_term_insert--' . $entity->vocabulary_machine_name, $entity); + rules_invoke_event('taxonomy_term_insert', $entity); break; case 'taxonomy_vocabulary': case 'user': @@ -129,16 +129,16 @@ function rules_entity_insert($entity, $type) { function rules_entity_delete($entity, $type) { switch ($type) { case 'comment': - rules_invoke_event('comment_delete', $entity); rules_invoke_event('comment_delete--' . $entity->node_type, $entity); + rules_invoke_event('comment_delete', $entity); break; case 'node': - rules_invoke_event('node_delete', $entity); rules_invoke_event('node_delete--' . $entity->type, $entity); + rules_invoke_event('node_delete', $entity); break; case 'taxonomy_term': - rules_invoke_event('taxonomy_term_delete', $entity); rules_invoke_event('taxonomy_term_delete--' . $entity->vocabulary_machine_name, $entity); + rules_invoke_event('taxonomy_term_delete', $entity); break; case 'taxonomy_vocabulary': case 'user': diff --git a/rules.api.php b/rules.api.php index 86b670c..3905692 100644 --- a/rules.api.php +++ b/rules.api.php @@ -244,13 +244,18 @@ function hook_rules_condition_info() { * - group: A group for this element, used for grouping the events in the * interface. Should start with a capital letter and be translated. * Required. - * - 'access callback': An callback, which has to return whether the + * - class: (optional) An event handler class implementing the + * RulesEventHandlerInterface. If none is specified the + * RulesEventDefaultHandler class will be used. While the default event + * handler has no settings, custom event handlers may be implemented to + * to make an event configurable. See RulesEventHandlerInterface. + * - access callback: (optional) An callback, which has to return whether the * currently logged in user is allowed to configure rules for this event. * Access should be only granted, if the user at least may access all the - * variables provided by the event. Optional. - * - help: A help text for rules reaction on this event. - * - variables: An array describing all variables that are available for - * elements reaction on this event. Optional. Each variable has to be + * variables provided by the event. + * - help: (optional) A help text for rules reaction on this event. + * - variables: (optional) An array describing all variables that are + * available for elements reacting on this event. Each variable has to be * described by a sub-array with the possible attributes: * - label: The label of the variable. Start capitalized. Required. * - type: The rules data type of the variable. All types declared in @@ -262,12 +267,12 @@ function hook_rules_condition_info() { * - 'options list': (optional) A callback that returns an array of possible * values for this variable as specified for entity properties at * hook_entity_property_info(). - * - 'skip save': If the variable is saved after the event has occurred - * anyway, set this to TRUE. So rules won't save the variable a second - * time. Optional, defaults to FALSE. - * - handler: A handler to load the actual variable value. This is useful - * for lazy loading variables. The handler gets all so far available - * variables passed in the order as defined. Optional. Also see + * - 'skip save': (optional) If the variable is saved after the event has + * occurred anyway, set this to TRUE. So rules won't save the variable a + * second time. Defaults to FALSE. + * - handler: (optional) A handler to load the actual variable value. This + * is useful for lazy loading variables. The handler gets all so far + * available variables passed in the order as defined. Also see * http://drupal.org/node/884554. * Note that for lazy-loading entities just the entity id may be passed * as variable value, so a handler is not necessary in that case. diff --git a/rules_admin/rules_admin.inc b/rules_admin/rules_admin.inc index 1df92d6..e8e9e28 100644 --- a/rules_admin/rules_admin.inc +++ b/rules_admin/rules_admin.inc @@ -314,7 +314,7 @@ function rules_admin_settings_cache_rebuild_submit($form, &$form_state) { function rules_admin_add_reaction_rule($form, &$form_state, $base_path) { RulesPluginUI::formDefaults($form, $form_state); - $rules_config = rules_reaction_rule(); + $rules_config = isset($form_state['rules_config']) ? $form_state['rules_config'] : rules_reaction_rule(); $rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE)); $form['settings']['#collapsible'] = FALSE; @@ -331,18 +331,27 @@ function rules_admin_add_reaction_rule($form, &$form_state, $base_path) { // Incorporate the form to add the first event. $form['settings'] += rules_ui_add_event(array(), $form_state, $rules_config, $base_path); $form['settings']['event']['#tree'] = FALSE; + $form['settings']['event_settings']['#tree'] = FALSE; unset($form['settings']['help']); unset($form['settings']['submit']); $form['submit']['#value'] = t('Save'); $form_state += array('rules_config' => $rules_config); + $form['#validate'][] = 'rules_ui_add_reaction_rule_validate'; $form['#validate'][] = 'rules_ui_edit_element_validate'; $form['#submit'][] = 'rules_ui_add_reaction_rule_submit'; return $form; } /** + * Form validation callback. + */ +function rules_ui_add_reaction_rule_validate(&$form, &$form_state) { + rules_ui_add_event_validate($form['settings'], $form_state); +} + +/** * Form submit callback. */ function rules_ui_add_reaction_rule_submit(&$form, &$form_state) { diff --git a/tests/rules.test b/tests/rules.test index 7c1f97d..0cc10b6 100644 --- a/tests/rules.test +++ b/tests/rules.test @@ -1254,7 +1254,7 @@ class RulesTriggerTestCase extends DrupalWebTestCase { } /** - * Tests creating and triggering a basic reaction rule. + * Tests creating and triggering a reaction rule with event settings. */ function testEventSettings() { $rule = rules_reaction_rule(); @@ -1264,9 +1264,9 @@ class RulesTriggerTestCase extends DrupalWebTestCase { $rule->integrityCheck()->save(); $node = $this->drupalCreateNode(array('type' => 'page', 'status' => 0)); - $this->assertEqual($node->status, 0, 'Rules has not been triggered.'); + $this->assertEqual($node->status, 0, 'Rule has not been triggered.'); $node = $this->drupalCreateNode(array('type' => 'article', 'status' => 0)); - $this->assertEqual($node->status, 1, 'Rules has been triggered.'); + $this->assertEqual($node->status, 1, 'Rule has been triggered.'); RulesLog::logger()->checkLog(); // Make sure an invalid bundle raises integrity problems. diff --git a/ui/ui.core.inc b/ui/ui.core.inc index 4bb8597..d202e34 100644 --- a/ui/ui.core.inc +++ b/ui/ui.core.inc @@ -569,7 +569,7 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface { if ($form['settings']['name']['#default_value'] != $form_values['name']) { $module = isset($this->element->module) ? $this->element->module : 'rules'; $this->element->name = $module . '_' . $form_values['name']; - $form_state['redirect'] = RulesPluginUI::path($this->element->name); + $form_state['redirect'] = RulesPluginUI::path($this->element->name, 'edit', $this->element); } $this->element->tags = empty($form_values['tags']) ? array() : drupal_explode_tags($form_values['tags']); diff --git a/ui/ui.forms.inc b/ui/ui.forms.inc index 4876349..fce0705 100644 --- a/ui/ui.forms.inc +++ b/ui/ui.forms.inc @@ -400,7 +400,7 @@ function rules_ui_add_event_page($form, &$form_state, RulesTriggerableInterface RulesPluginUI::$basePath = $base_path; RulesPluginUI::formDefaults($form, $form_state); $form = rules_ui_add_event($form, $form_state, $rules_config, $base_path); - $form['#validate'][] = 'rules_ui_add_event_apply_validate'; + $form['#validate'][] = 'rules_ui_add_event_validate'; return $form; } @@ -445,6 +445,7 @@ function rules_ui_add_event($form, &$form_state, RulesReactionRule $rules_config '#options' => RulesPluginUI::getOptions('event', $events), '#description' => t('Whenever the event occurs, rule evaluation is triggered.'), '#ajax' => rules_ui_form_default_ajax(), + '#required' => TRUE, ); if (!empty($form_state['values']['event'])) { $handler = rules_get_event_handler($form_state['values']['event']); @@ -464,7 +465,7 @@ function rules_ui_add_event($form, &$form_state, RulesReactionRule $rules_config /** * Validation callback for adding an event. */ -function rules_ui_add_event_apply_validate($form, $form_state) { +function rules_ui_add_event_validate($form, $form_state) { $handler = rules_get_event_handler($form_state['values']['event']); $handler->extractFormValues($form['event_settings'], $form_state); try {