summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2009-09-19 11:07:37 +0000
committerDries Buytaert <dries@buytaert.net>2009-09-19 11:07:37 +0000
commitfd1c63b5c0ce91849d6088b7aad40b27ea7bb96f (patch)
tree7742fc7fdc55f819c066be08a3419995b591439b /modules
parenta557b0de2ac5d0b2048a456f94f9b8047afa71b9 (diff)
downloadbrdo-fd1c63b5c0ce91849d6088b7aad40b27ea7bb96f.tar.gz
brdo-fd1c63b5c0ce91849d6088b7aad40b27ea7bb96f.tar.bz2
- Patch ##525540 by jvandyk, sun, jhodgdon, fago | webchick, TheRec, Dave Reid, brianV, sun.core, cweagans, Dries: gave trigger.module and includes/actions.inc an API overhaul. Simplified definitions of actions and triggers and removed dependency on the combination of hooks and operations. Triggers now directly map to module hooks.
Diffstat (limited to 'modules')
-rw-r--r--modules/comment/comment.module36
-rw-r--r--modules/node/node.module101
-rw-r--r--modules/simpletest/tests/actions.test18
-rw-r--r--modules/simpletest/tests/actions_loop_test.module20
-rw-r--r--modules/system/system.admin.inc273
-rw-r--r--modules/system/system.api.php84
-rw-r--r--modules/system/system.install13
-rw-r--r--modules/system/system.module316
-rw-r--r--modules/taxonomy/taxonomy.module21
-rw-r--r--modules/trigger/tests/trigger_test.module74
-rw-r--r--modules/trigger/trigger.admin.inc185
-rw-r--r--modules/trigger/trigger.api.php166
-rw-r--r--modules/trigger/trigger.install24
-rw-r--r--modules/trigger/trigger.module384
-rw-r--r--modules/trigger/trigger.test164
-rw-r--r--modules/user/user.module34
16 files changed, 1006 insertions, 907 deletions
diff --git a/modules/comment/comment.module b/modules/comment/comment.module
index 690c5db03..9aaba268f 100644
--- a/modules/comment/comment.module
+++ b/modules/comment/comment.module
@@ -2296,49 +2296,21 @@ function vancode2int($c = '00') {
}
/**
- * Implement hook_hook_info().
- */
-function comment_hook_info() {
- return array(
- 'comment' => array(
- 'comment' => array(
- 'insert' => array(
- 'runs when' => t('After saving a new comment'),
- ),
- 'update' => array(
- 'runs when' => t('After saving an updated comment'),
- ),
- 'delete' => array(
- 'runs when' => t('After deleting a comment')
- ),
- 'view' => array(
- 'runs when' => t('When a comment is being viewed by an authenticated user')
- ),
- ),
- ),
- );
-}
-
-/**
* Implement hook_action_info().
*/
function comment_action_info() {
return array(
'comment_unpublish_action' => array(
- 'description' => t('Unpublish comment'),
+ 'label' => t('Unpublish comment'),
'type' => 'comment',
'configurable' => FALSE,
- 'hooks' => array(
- 'comment' => array('insert', 'update'),
- )
+ 'triggers' => array('comment_insert', 'comment_update'),
),
'comment_unpublish_by_keyword_action' => array(
- 'description' => t('Unpublish comment containing keyword(s)'),
+ 'label' => t('Unpublish comment containing keyword(s)'),
'type' => 'comment',
'configurable' => TRUE,
- 'hooks' => array(
- 'comment' => array('insert', 'update'),
- )
+ 'triggers' => array('comment_insert', 'comment_update'),
)
);
}
diff --git a/modules/node/node.module b/modules/node/node.module
index 46cf0a91c..719e392bc 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -2754,123 +2754,90 @@ function node_forms() {
}
/**
- * Implement hook_hook_info().
- */
-function node_hook_info() {
- return array(
- 'node' => array(
- 'node' => array(
- 'presave' => array(
- 'runs when' => t('When either saving a new post or updating an existing post'),
- ),
- 'insert' => array(
- 'runs when' => t('After saving a new post'),
- ),
- 'update' => array(
- 'runs when' => t('After saving an updated post'),
- ),
- 'delete' => array(
- 'runs when' => t('After deleting a post')
- ),
- 'view' => array(
- 'runs when' => t('When content is viewed by an authenticated user')
- ),
- ),
- ),
- );
-}
-
-/**
* Implement hook_action_info().
*/
function node_action_info() {
return array(
'node_publish_action' => array(
'type' => 'node',
- 'description' => t('Publish post'),
+ 'label' => t('Publish content'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('insert', 'update'),
- ),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update'),
),
'node_unpublish_action' => array(
'type' => 'node',
- 'description' => t('Unpublish post'),
+ 'label' => t('Unpublish content'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('delete', 'insert', 'update'),
+ 'triggers' => array(
+ 'node_presave',
+ 'comment_insert',
+ 'comment_update',
+ 'comment_delete'
),
),
'node_make_sticky_action' => array(
'type' => 'node',
- 'description' => t('Make post sticky'),
+ 'label' => t('Make content sticky'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('insert', 'update'),
- ),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update'),
),
'node_make_unsticky_action' => array(
'type' => 'node',
- 'description' => t('Make post unsticky'),
+ 'label' => t('Make content unsticky'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('delete', 'insert', 'update'),
+ 'triggers' => array(
+ 'node_presave',
+ 'comment_insert',
+ 'comment_update',
+ 'comment_delete'
),
),
'node_promote_action' => array(
'type' => 'node',
- 'description' => t('Promote post to front page'),
+ 'label' => t('Promote content to front page'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('insert', 'update'),
- ),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update'),
),
'node_unpromote_action' => array(
'type' => 'node',
- 'description' => t('Remove post from front page'),
+ 'label' => t('Remove content from front page'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('delete', 'insert', 'update'),
+ 'triggers' => array(
+ 'node_presave',
+ 'comment_insert',
+ 'comment_update',
+ 'comment_delete'
),
),
'node_assign_owner_action' => array(
'type' => 'node',
- 'description' => t('Change the author of a post'),
+ 'label' => t('Change the author of content'),
'configurable' => TRUE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'any' => TRUE,
- 'node' => array('presave'),
- 'comment' => array('delete', 'insert', 'update'),
+ 'triggers' => array(
+ 'node_presave',
+ 'comment_insert',
+ 'comment_update',
+ 'comment_delete',
),
),
'node_save_action' => array(
'type' => 'node',
- 'description' => t('Save post'),
+ 'label' => t('Save content'),
'configurable' => FALSE,
- 'hooks' => array(
- 'comment' => array('delete', 'insert', 'update'),
- ),
+ 'triggers' => array('comment_delete', 'comment_insert', 'comment_update'),
),
'node_unpublish_by_keyword_action' => array(
'type' => 'node',
- 'description' => t('Unpublish post containing keyword(s)'),
+ 'label' => t('Unpublish content containing keyword(s)'),
'configurable' => TRUE,
- 'hooks' => array(
- 'node' => array('presave', 'insert', 'update'),
- ),
+ 'triggers' => array('node_presave', 'node_insert', 'node_update'),
),
);
}
diff --git a/modules/simpletest/tests/actions.test b/modules/simpletest/tests/actions.test
index a36684047..12a97cc5f 100644
--- a/modules/simpletest/tests/actions.test
+++ b/modules/simpletest/tests/actions.test
@@ -26,27 +26,27 @@ class ActionsConfigurationTestCase extends DrupalWebTestCase {
// Make a POST request to the individual action configuration page.
$edit = array();
- $action_description = $this->randomName();
- $edit['actions_description'] = $action_description;
+ $action_label = $this->randomName();
+ $edit['actions_label'] = $action_label;
$edit['url'] = 'admin';
$this->drupalPost('admin/config/system/actions/configure/' . md5('system_goto_action'), $edit, t('Save'));
// Make sure that the new complex action was saved properly.
$this->assertText(t('The action has been successfully saved.'), t("Make sure we get a confirmation that we've successfully saved the complex action."));
- $this->assertText($action_description, t("Make sure the action description appears on the configuration page after we've saved the complex action."));
+ $this->assertText($action_label, t("Make sure the action label appears on the configuration page after we've saved the complex action."));
// Make another POST request to the action edit page.
$this->clickLink(t('configure'));
$edit = array();
- $new_action_description = $this->randomName();
- $edit['actions_description'] = $new_action_description;
+ $new_action_label = $this->randomName();
+ $edit['actions_label'] = $new_action_label;
$edit['url'] = 'admin';
$this->drupalPost('admin/config/system/actions/configure/1', $edit, t('Save'));
// Make sure that the action updated properly.
$this->assertText(t('The action has been successfully saved.'), t("Make sure we get a confirmation that we've successfully updated the complex action."));
- $this->assertNoText($action_description, t("Make sure the old action description does NOT appear on the configuration page after we've updated the complex action."));
- $this->assertText($new_action_description, t("Make sure the action description appears on the configuration page after we've updated the complex action."));
+ $this->assertNoText($action_label, t("Make sure the old action label does NOT appear on the configuration page after we've updated the complex action."));
+ $this->assertText($new_action_label, t("Make sure the action label appears on the configuration page after we've updated the complex action."));
// Make sure that deletions work properly.
$this->clickLink(t('delete'));
@@ -54,9 +54,9 @@ class ActionsConfigurationTestCase extends DrupalWebTestCase {
$this->drupalPost('admin/config/system/actions/delete/1', $edit, t('Delete'));
// Make sure that the action was actually deleted.
- $this->assertRaw(t('Action %action was deleted', array('%action' => $new_action_description)), t('Make sure that we get a delete confirmation message.'));
+ $this->assertRaw(t('Action %action was deleted', array('%action' => $new_action_label)), t('Make sure that we get a delete confirmation message.'));
$this->drupalGet('admin/config/system/actions/manage');
- $this->assertNoText($new_action_description, t("Make sure the action description does not appear on the overview page after we've deleted the action."));
+ $this->assertNoText($new_action_label, t("Make sure the action label does not appear on the overview page after we've deleted the action."));
$exists = db_query('SELECT aid FROM {actions} WHERE callback = :callback', array(':callback' => 'drupal_goto_action'))->fetchField();
$this->assertFalse($exists, t('Make sure the action is gone from the database after being deleted.'));
}
diff --git a/modules/simpletest/tests/actions_loop_test.module b/modules/simpletest/tests/actions_loop_test.module
index 58b47fd8b..a31415891 100644
--- a/modules/simpletest/tests/actions_loop_test.module
+++ b/modules/simpletest/tests/actions_loop_test.module
@@ -2,15 +2,13 @@
// $Id$
/**
- * Implement hook_hook_info().
+ * Implement hook_trigger_info().
*/
-function actions_loop_test_hook_info() {
+function actions_loop_test_trigger_info() {
return array(
'actions_loop_test' => array(
'watchdog' => array(
- 'run' => array(
- 'runs when' => t('When a message is logged'),
- ),
+ 'label' => t('When a message is logged'),
),
),
);
@@ -26,13 +24,11 @@ function actions_loop_test_watchdog(array $log_entry) {
}
// Get all the action ids assigned to the trigger on the watchdog hook's
// "run" event.
- $aids = _trigger_get_hook_aids('watchdog', 'run');
+ $aids = trigger_get_assigned_actions('watchdog');
// We can pass in any applicable information in $context. There isn't much in
- // this case, but we'll pass in the hook name and the operation name as the
- // bare minimum.
+ // this case, but we'll pass in the hook name as the bare minimum.
$context = array(
'hook' => 'watchdog',
- 'op' => 'run',
);
// Fire the actions on the associated object ($log_entry) and the context
// variable.
@@ -54,12 +50,10 @@ function actions_loop_test_init() {
function actions_loop_test_action_info() {
return array(
'actions_loop_test_log' => array(
- 'description' => t('Write a message to the log.'),
+ 'label' => t('Write a message to the log.'),
'type' => 'system',
'configurable' => FALSE,
- 'hooks' => array(
- 'any' => TRUE,
- )
+ 'triggers' => array('any'),
),
);
}
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index d7c380f9d..48806cdb4 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -2296,3 +2296,276 @@ function theme_system_themes_form($form) {
$output .= drupal_render_children($form);
return $output;
}
+
+/**
+ * Menu callback; Displays an overview of available and configured actions.
+ */
+function system_actions_manage() {
+ actions_synchronize();
+ $actions = actions_list();
+ $actions_map = actions_actions_map($actions);
+ $options = array(t('Choose an advanced action'));
+ $unconfigurable = array();
+
+ foreach ($actions_map as $key => $array) {
+ if ($array['configurable']) {
+ $options[$key] = $array['label'] . '...';
+ }
+ else {
+ $unconfigurable[] = $array;
+ }
+ }
+
+ $row = array();
+ $instances_present = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchField();
+ $header = array(
+ array('data' => t('Action type'), 'field' => 'type'),
+ array('data' => t('Label'), 'field' => 'label'),
+ array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')
+ );
+ $query = db_select('actions')->extend('PagerDefault')->extend('TableSort');
+ $result = $query
+ ->fields('actions')
+ ->limit(50)
+ ->orderByHeader($header)
+ ->execute();
+
+ foreach ($result as $action) {
+ $row[] = array(
+ array('data' => $action->type),
+ array('data' => $action->label),
+ array('data' => $action->parameters ? l(t('configure'), "admin/config/system/actions/configure/$action->aid") : ''),
+ array('data' => $action->parameters ? l(t('delete'), "admin/config/system/actions/delete/$action->aid") : '')
+ );
+ }
+
+ if ($row) {
+ $pager = theme('pager', NULL);
+ if (!empty($pager)) {
+ $row[] = array(array('data' => $pager, 'colspan' => '3'));
+ }
+ $build['system_actions_header'] = array('#markup' => '<h3>' . t('Actions available to Drupal:') . '</h3>');
+ $build['system_actions_table'] = array('#markup' => theme('table', $header, $row));
+ }
+
+ if ($actions_map) {
+ $build['system_actions_manage_form'] = drupal_get_form('system_actions_manage_form', $options);
+ }
+
+ return $build;
+}
+
+/**
+ * Define the form for the actions overview page.
+ *
+ * @param $form_state
+ * An associative array containing the current state of the form; not used.
+ * @param $options
+ * An array of configurable actions.
+ * @return
+ * Form definition.
+ *
+ * @ingroup forms
+ * @see system_actions_manage_form_submit()
+ */
+function system_actions_manage_form($form, &$form_state, $options = array()) {
+ $form['parent'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Make a new advanced action available'),
+ '#prefix' => '<div class="container-inline">',
+ '#suffix' => '</div>',
+ );
+ $form['parent']['action'] = array(
+ '#type' => 'select',
+ '#default_value' => '',
+ '#options' => $options,
+ '#description' => '',
+ );
+ $form['parent']['buttons']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Create'),
+ );
+ return $form;
+}
+
+/**
+ * Process system_actions_manage form submissions.
+ *
+ * @see system_actions_manage_form()
+ */
+function system_actions_manage_form_submit($form, &$form_state) {
+ if ($form_state['values']['action']) {
+ $form_state['redirect'] = 'admin/config/system/actions/configure/' . $form_state['values']['action'];
+ }
+}
+
+/**
+ * Menu callback; Creates the form for configuration of a single action.
+ *
+ * We provide the "Description" field. The rest of the form is provided by the
+ * action. We then provide the Save button. Because we are combining unknown
+ * form elements with the action configuration form, we use an 'actions_' prefix
+ * on our elements.
+ *
+ * @param $action
+ * md5 hash of an action ID or an integer. If it is an md5 hash, we are
+ * creating a new instance. If it is an integer, we are editing an existing
+ * instance.
+ * @return
+ * A form definition.
+ *
+ * @see system_actions_configure_validate()
+ * @see system_actions_configure_submit()
+ */
+function system_actions_configure($form, &$form_state, $action = NULL) {
+ if ($action === NULL) {
+ drupal_goto('admin/config/system/actions');
+ }
+
+ $actions_map = actions_actions_map(actions_list());
+ $edit = array();
+
+ // Numeric action denotes saved instance of a configurable action.
+ if (is_numeric($action)) {
+ $aid = $action;
+ // Load stored parameter values from database.
+ $data = db_query("SELECT * FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetch();
+ $edit['actions_label'] = $data->label;
+ $edit['actions_type'] = $data->type;
+ $function = $data->callback;
+ $action = md5($data->callback);
+ $params = unserialize($data->parameters);
+ if ($params) {
+ foreach ($params as $name => $val) {
+ $edit[$name] = $val;
+ }
+ }
+ }
+ // Otherwise, we are creating a new action instance.
+ else {
+ $function = $actions_map[$action]['callback'];
+ $edit['actions_label'] = $actions_map[$action]['label'];
+ $edit['actions_type'] = $actions_map[$action]['type'];
+ }
+
+ $form['actions_label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#default_value' => $edit['actions_label'],
+ '#maxlength' => '255',
+ '#description' => t('A unique label for this advanced action. This label will be displayed in the interface of modules that integrate with actions, such as Trigger module.'),
+ '#weight' => -10
+ );
+ $action_form = $function . '_form';
+ $form = array_merge($form, $action_form($edit));
+ $form['actions_type'] = array(
+ '#type' => 'value',
+ '#value' => $edit['actions_type'],
+ );
+ $form['actions_action'] = array(
+ '#type' => 'hidden',
+ '#value' => $action,
+ );
+ // $aid is set when configuring an existing action instance.
+ if (isset($aid)) {
+ $form['actions_aid'] = array(
+ '#type' => 'hidden',
+ '#value' => $aid,
+ );
+ }
+ $form['actions_configured'] = array(
+ '#type' => 'hidden',
+ '#value' => '1',
+ );
+ $form['buttons']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#weight' => 13
+ );
+
+ return $form;
+}
+
+/**
+ * Validate system_actions_configure() form submissions.
+ */
+function system_actions_configure_validate($form, &$form_state) {
+ $function = actions_function_lookup($form_state['values']['actions_action']) . '_validate';
+ // Hand off validation to the action.
+ if (function_exists($function)) {
+ $function($form, $form_state);
+ }
+}
+
+/**
+ * Process system_actions_configure() form submissions.
+ */
+function system_actions_configure_submit($form, &$form_state) {
+ $function = actions_function_lookup($form_state['values']['actions_action']);
+ $submit_function = $function . '_submit';
+
+ // Action will return keyed array of values to store.
+ $params = $submit_function($form, $form_state);
+ $aid = isset($form_state['values']['actions_aid']) ? $form_state['values']['actions_aid'] : NULL;
+
+ actions_save($function, $form_state['values']['actions_type'], $params, $form_state['values']['actions_label'], $aid);
+ drupal_set_message(t('The action has been successfully saved.'));
+
+ $form_state['redirect'] = 'admin/config/system/actions/manage';
+}
+
+/**
+ * Create the form for confirmation of deleting an action.
+ *
+ * @see system_actions_delete_form_submit()
+ * @ingroup forms
+ */
+function system_actions_delete_form($form, &$form_state, $action) {
+ $form['aid'] = array(
+ '#type' => 'hidden',
+ '#value' => $action->aid,
+ );
+ return confirm_form($form,
+ t('Are you sure you want to delete the action %action?', array('%action' => $action->label)),
+ 'admin/config/system/actions/manage',
+ t('This cannot be undone.'),
+ t('Delete'),
+ t('Cancel')
+ );
+}
+
+/**
+ * Process system_actions_delete form submissions.
+ *
+ * Post-deletion operations for action deletion.
+ */
+function system_actions_delete_form_submit($form, &$form_state) {
+ $aid = $form_state['values']['aid'];
+ $action = actions_load($aid);
+ actions_delete($aid);
+ $label = check_plain($action->label);
+ watchdog('user', 'Deleted action %aid (%action)', array('%aid' => $aid, '%action' => $label));
+ drupal_set_message(t('Action %action was deleted', array('%action' => $label)));
+ $form_state['redirect'] = 'admin/config/system/actions/manage';
+}
+
+/**
+ * Post-deletion operations for deleting action orphans.
+ *
+ * @param $orphaned
+ * An array of orphaned actions.
+ */
+function system_action_delete_orphans_post($orphaned) {
+ foreach ($orphaned as $callback) {
+ drupal_set_message(t("Deleted orphaned action (%action).", array('%action' => $callback)));
+ }
+}
+
+/**
+ * Remove actions that are in the database but not supported by any enabled module.
+ */
+function system_actions_remove_orphans() {
+ actions_synchronize(TRUE);
+ drupal_goto('admin/config/system/actions/manage');
+}
+
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 1251187ab..6a79445ac 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -2211,5 +2211,89 @@ function hook_file_mimetype_mapping_alter(&$mapping) {
}
/**
+ * Declares information about actions.
+ *
+ * Any module can define actions, and then call actions_do() to make those
+ * actions happen in response to events. The trigger module provides a user
+ * interface for associating actions with module-defined triggers, and it makes
+ * sure the core triggers fire off actions when their events happen.
+ *
+ * An action consists of two or three parts:
+ * - an action definition (returned by this hook)
+ * - a function which performs the action (which by convention is named
+ * MODULE_description-of-function_action)
+ * - an optional form definition function that defines a configuration form
+ * (which has the name of the action function with '_form' appended to it.)
+ *
+ * The action function takes two to four arguments, which come from the input
+ * arguments to actions_do().
+ *
+ * @return
+ * An associative array of action descriptions. The keys of the array
+ * are the names of the action functions, and each corresponding value
+ * is an associative array with the following key-value pairs:
+ * - 'type': The type of object this action acts upon. Core actions have types
+ * 'node', 'user', 'comment', and 'system'.
+ * - 'label': The human-readable name of the action, which should be passed
+ * through the t() function for translation.
+ * - 'configurable': If FALSE, then the action doesn't require any extra
+ * configuration. If TRUE, then your module must define a form function with
+ * the same name as the action function with '_form' appended (e.g., the
+ * form for 'node_assign_owner_action' is 'node_assign_owner_action_form'.)
+ * This function takes $context as its only parameter, and is paired with
+ * the usual _submit function, and possibly a _validate function.
+ * - 'triggers': An array of the events (that is, hooks) that can trigger this
+ * action. For example: array('node_insert', 'user_update'). You can also
+ * declare support for any trigger by returning array('any') for this value.
+ * - 'behavior': (optional) machine-readable array of behaviors of this
+ * action, used to signal additional actions that may need to be triggered.
+ * Currently recognized behaviors by Trigger module:
+ * - 'changes_node_property': If an action with this behavior is assigned to
+ * a trigger other than 'node_presave', any node save actions also
+ * assigned to this trigger are moved later in the list. If a node save
+ * action is not present, one will be added.
+ */
+function hook_action_info() {
+ return array(
+ 'comment_unpublish_action' => array(
+ 'type' => 'comment',
+ 'label' => t('Unpublish comment'),
+ 'configurable' => FALSE,
+ 'triggers' => array('comment_insert', 'comment_update'),
+ ),
+ 'comment_unpublish_by_keyword_action' => array(
+ 'type' => 'comment',
+ 'label' => t('Unpublish comment containing keyword(s)'),
+ 'configurable' => TRUE,
+ 'triggers' => array('comment_insert', 'comment_update'),
+ ),
+ );
+}
+
+/**
+ * Executes code after an action is deleted.
+ *
+ * @param $aid
+ * The action ID.
+ */
+function hook_actions_delete($aid) {
+ db_delete('actions_assignments')
+ ->condition('aid', $aid)
+ ->execute();
+}
+
+/**
+ * Alters the actions declared by another module.
+ *
+ * Called by actions_list() to allow modules to alter the return values from
+ * implementations of hook_action_info().
+ *
+ * @see trigger_example_action_info_alter().
+ */
+function hook_action_info_alter(&$actions) {
+ $actions['node_unpublish_action']['label'] = t('Unpublish and remove from public view.');
+}
+
+/**
* @} End of "addtogroup hooks".
*/
diff --git a/modules/system/system.install b/modules/system/system.install
index 85423a0a4..87c4de592 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -574,8 +574,8 @@ function system_schema() {
'not null' => TRUE,
'size' => 'big',
),
- 'description' => array(
- 'description' => 'Description of the action.',
+ 'label' => array(
+ 'description' => 'Label of the action.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
@@ -2493,6 +2493,15 @@ function system_update_7037() {
}
/**
+ * Rename action description to label.
+ */
+function system_update_7038() {
+ $ret = array();
+ db_change_field($ret, 'actions', 'description', 'label', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '0'));
+ return $ret;
+}
+
+/**
* @} End of "defgroup updates-6.x-to-7.x"
* The next series of updates should start at 8000.
*/
diff --git a/modules/system/system.module b/modules/system/system.module
index 867ec7213..ab3c966b5 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -2403,336 +2403,38 @@ function system_cron() {
}
/**
- * Implement hook_hook_info().
- */
-function system_hook_info() {
- return array(
- 'system' => array(
- 'cron' => array(
- 'run' => array(
- 'runs when' => t('When cron runs'),
- ),
- ),
- ),
- );
-}
-
-/**
* Implement hook_action_info().
*/
function system_action_info() {
return array(
'system_message_action' => array(
'type' => 'system',
- 'description' => t('Display a message to the user'),
+ 'label' => t('Display a message to the user'),
'configurable' => TRUE,
- 'hooks' => array(
- 'node' => array('view', 'insert', 'update', 'delete'),
- 'comment' => array('view', 'insert', 'update', 'delete'),
- 'user' => array('view', 'insert', 'update', 'delete', 'login'),
- 'taxonomy' => array('insert', 'update', 'delete'),
- ),
+ 'triggers' => array('any'),
),
'system_send_email_action' => array(
- 'description' => t('Send e-mail'),
'type' => 'system',
+ 'label' => t('Send e-mail'),
'configurable' => TRUE,
- 'hooks' => array(
- 'node' => array('view', 'insert', 'update', 'delete'),
- 'comment' => array('view', 'insert', 'update', 'delete'),
- 'user' => array('view', 'insert', 'update', 'delete', 'login'),
- 'taxonomy' => array('insert', 'update', 'delete'),
- 'cron' => array('run'),
- )
+ 'triggers' => array('any'),
),
'system_block_ip_action' => array(
- 'description' => t('Ban IP address of current user'),
'type' => 'user',
+ 'label' => t('Ban IP address of current user'),
'configurable' => FALSE,
- 'hooks' => array(),
+ 'triggers' => array(),
),
'system_goto_action' => array(
- 'description' => t('Redirect to URL'),
'type' => 'system',
+ 'label' => t('Redirect to URL'),
'configurable' => TRUE,
- 'hooks' => array(
- 'node' => array('view', 'insert', 'update', 'delete'),
- 'comment' => array('view', 'insert', 'update', 'delete'),
- 'user' => array('view', 'insert', 'update', 'delete', 'login'),
- )
- )
- );
-}
-
-/**
- * Menu callback. Display an overview of available and configured actions.
- */
-function system_actions_manage() {
- actions_synchronize();
- $actions = actions_list();
- $actions_map = actions_actions_map($actions);
- $options = array(t('Choose an advanced action'));
- $unconfigurable = array();
-
- foreach ($actions_map as $key => $array) {
- if ($array['configurable']) {
- $options[$key] = $array['description'] . '...';
- }
- else {
- $unconfigurable[] = $array;
- }
- }
-
- $row = array();
- $instances_present = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchField();
- $header = array(
- array('data' => t('Action type'), 'field' => 'type'),
- array('data' => t('Description'), 'field' => 'description'),
- array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')
- );
- $query = db_select('actions')->extend('PagerDefault')->extend('TableSort');
- $result = $query
- ->fields('actions')
- ->limit(50)
- ->orderByHeader($header)
- ->execute();
-
- foreach ($result as $action) {
- $row[] = array(
- array('data' => $action->type),
- array('data' => $action->description),
- array('data' => $action->parameters ? l(t('configure'), "admin/config/system/actions/configure/$action->aid") : ''),
- array('data' => $action->parameters ? l(t('delete'), "admin/config/system/actions/delete/$action->aid") : '')
- );
- }
-
- if ($row) {
- $pager = theme('pager', NULL);
- if (!empty($pager)) {
- $row[] = array(array('data' => $pager, 'colspan' => '3'));
- }
- $build['system_actions_header'] = array('#markup' => '<h3>' . t('Actions available to Drupal:') . '</h3>');
- $build['system_actions_table'] = array('#markup' => theme('table', $header, $row));
- }
-
- if ($actions_map) {
- $build['system_actions_manage_form'] = drupal_get_form('system_actions_manage_form', $options);
- }
-
- return $build;
-}
-
-/**
- * Define the form for the actions overview page.
- *
- * @see system_actions_manage_form_submit()
- * @ingroup forms
- * @param $form_state
- * An associative array containing the current state of the form; not used.
- * @param $options
- * An array of configurable actions.
- * @return
- * Form definition.
- */
-function system_actions_manage_form($form, $form_state, $options = array()) {
- $form['parent'] = array(
- '#type' => 'fieldset',
- '#title' => t('Make a new advanced action available'),
- '#prefix' => '<div class="container-inline">',
- '#suffix' => '</div>',
- );
- $form['parent']['action'] = array(
- '#type' => 'select',
- '#default_value' => '',
- '#options' => $options,
- '#description' => '',
- );
- $form['parent']['buttons']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Create'),
- );
- return $form;
-}
-
-/**
- * Process system_actions_manage form submissions.
- */
-function system_actions_manage_form_submit($form, &$form_state) {
- if ($form_state['values']['action']) {
- $form_state['redirect'] = 'admin/config/system/actions/configure/' . $form_state['values']['action'];
- }
-}
-
-/**
- * Menu callback. Create the form for configuration of a single action.
- *
- * We provide the "Description" field. The rest of the form
- * is provided by the action. We then provide the Save button.
- * Because we are combining unknown form elements with the action
- * configuration form, we use actions_ prefix on our elements.
- *
- * @see system_actions_configure_validate()
- * @see system_actions_configure_submit()
- * @param $action
- * md5 hash of action ID or an integer. If it's an md5 hash, we
- * are creating a new instance. If it's an integer, we're editing
- * an existing instance.
- * @return
- * Form definition.
- */
-function system_actions_configure($form, &$form_state, $action = NULL) {
- if ($action === NULL) {
- drupal_goto('admin/config/system/actions');
- }
-
- $actions_map = actions_actions_map(actions_list());
- $edit = array();
-
- // Numeric action denotes saved instance of a configurable action;
- // else we are creating a new action instance.
- if (is_numeric($action)) {
- $aid = $action;
- // Load stored parameter values from database.
- $data = db_query("SELECT * FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetch();
- $edit['actions_description'] = $data->description;
- $edit['actions_type'] = $data->type;
- $function = $data->callback;
- $action = md5($data->callback);
- $params = unserialize($data->parameters);
- if ($params) {
- foreach ($params as $name => $val) {
- $edit[$name] = $val;
- }
- }
- }
- else {
- $function = $actions_map[$action]['callback'];
- $edit['actions_description'] = $actions_map[$action]['description'];
- $edit['actions_type'] = $actions_map[$action]['type'];
- }
-
- $form['actions_description'] = array(
- '#type' => 'textfield',
- '#title' => t('Description'),
- '#default_value' => $edit['actions_description'],
- '#maxlength' => '255',
- '#description' => t('A unique description for this advanced action. This description will be displayed in the interface of modules that integrate with actions, such as Trigger module.'),
- '#weight' => -10
- );
- $action_form = $function . '_form';
- $form = array_merge($form, $action_form($edit));
- $form['actions_type'] = array(
- '#type' => 'value',
- '#value' => $edit['actions_type'],
- );
- $form['actions_action'] = array(
- '#type' => 'hidden',
- '#value' => $action,
- );
- // $aid is set when configuring an existing action instance.
- if (isset($aid)) {
- $form['actions_aid'] = array(
- '#type' => 'hidden',
- '#value' => $aid,
- );
- }
- $form['actions_configured'] = array(
- '#type' => 'hidden',
- '#value' => '1',
- );
- $form['buttons']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Save'),
- '#weight' => 13
- );
-
- return $form;
-}
-
-/**
- * Validate system_actions_configure form submissions.
- */
-function system_actions_configure_validate($form, $form_state) {
- $function = actions_function_lookup($form_state['values']['actions_action']) . '_validate';
- // Hand off validation to the action.
- if (function_exists($function)) {
- $function($form, $form_state);
- }
-}
-
-/**
- * Process system_actions_configure form submissions.
- */
-function system_actions_configure_submit($form, &$form_state) {
- $function = actions_function_lookup($form_state['values']['actions_action']);
- $submit_function = $function . '_submit';
-
- // Action will return keyed array of values to store.
- $params = $submit_function($form, $form_state);
- $aid = isset($form_state['values']['actions_aid']) ? $form_state['values']['actions_aid'] : NULL;
-
- actions_save($function, $form_state['values']['actions_type'], $params, $form_state['values']['actions_description'], $aid);
- drupal_set_message(t('The action has been successfully saved.'));
-
- $form_state['redirect'] = 'admin/config/system/actions/manage';
-}
-
-/**
- * Create the form for confirmation of deleting an action.
- *
- * @ingroup forms
- * @see system_actions_delete_form_submit()
- */
-function system_actions_delete_form($form, $form_state, $action) {
-
- $form['aid'] = array(
- '#type' => 'hidden',
- '#value' => $action->aid,
- );
- return confirm_form($form,
- t('Are you sure you want to delete the action %action?', array('%action' => $action->description)),
- 'admin/config/system/actions/manage',
- t('This cannot be undone.'),
- t('Delete'), t('Cancel')
+ 'triggers' => array('any'),
+ ),
);
}
/**
- * Process system_actions_delete form submissions.
- *
- * Post-deletion operations for action deletion.
- */
-function system_actions_delete_form_submit($form, &$form_state) {
- $aid = $form_state['values']['aid'];
- $action = actions_load($aid);
- actions_delete($aid);
- $description = check_plain($action->description);
- watchdog('user', 'Deleted action %aid (%action)', array('%aid' => $aid, '%action' => $description));
- drupal_set_message(t('Action %action was deleted', array('%action' => $description)));
- $form_state['redirect'] = 'admin/config/system/actions/manage';
-}
-
-/**
- * Post-deletion operations for deleting action orphans.
- *
- * @param $orphaned
- * An array of orphaned actions.
- */
-function system_action_delete_orphans_post($orphaned) {
- foreach ($orphaned as $callback) {
- drupal_set_message(t("Deleted orphaned action (%action).", array('%action' => $callback)));
- }
-}
-
-/**
- * Remove actions that are in the database but not supported by any enabled module.
- */
-function system_actions_remove_orphans() {
- actions_synchronize(TRUE);
- drupal_goto('admin/config/system/actions/manage');
-}
-
-/**
* Return a form definition so the Send email action can be configured.
*
* @see system_send_email_action_validate()
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index 7bf15935a..ed8aa2c98 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -1703,27 +1703,6 @@ function taxonomy_implode_tags($tags, $vid = NULL) {
}
/**
- * Implement hook_hook_info().
- */
-function taxonomy_hook_info() {
- return array(
- 'taxonomy' => array(
- 'taxonomy' => array(
- 'insert' => array(
- 'runs when' => t('After saving a new term to the database'),
- ),
- 'update' => array(
- 'runs when' => t('After saving an updated term to the database'),
- ),
- 'delete' => array(
- 'runs when' => t('After deleting a term')
- ),
- ),
- ),
- );
-}
-
-/**
* Implement hook_field_info().
*
* Field settings:
diff --git a/modules/trigger/tests/trigger_test.module b/modules/trigger/tests/trigger_test.module
index bb973ba11..4793ad05f 100644
--- a/modules/trigger/tests/trigger_test.module
+++ b/modules/trigger/tests/trigger_test.module
@@ -10,22 +10,64 @@
* Implementation of hook_action_info().
*/
function trigger_test_action_info() {
- // Register an action that can be assigned to the trigger "cron run".
+ // Register an action that can be assigned to the trigger "cron".
return array(
'trigger_test_system_cron_action' => array(
'type' => 'system',
- 'description' => t('Cron test action'),
+ 'label' => t('Cron test action'),
'configurable' => FALSE,
- 'hooks' => array(
- 'cron' => array('run'),
- ),
+ 'triggers' => array('cron'),
),
'trigger_test_system_cron_conf_action' => array(
'type' => 'system',
- 'description' => t('Cron test configurable action'),
+ 'label' => t('Cron test configurable action'),
'configurable' => TRUE,
- 'hooks' => array(
- 'cron' => array('run'),
+ 'triggers' => array('cron'),
+ ),
+ 'trigger_test_generic_action' => array(
+ 'type' => 'system',
+ 'label' => t('Generic test action'),
+ 'configurable' => FALSE,
+ 'triggers' => array(
+ 'taxonomy_term_insert',
+ 'taxonomy_term_update',
+ 'taxonomy_delete',
+ 'comment_insert',
+ 'comment_update',
+ 'comment_delete',
+ 'user_insert',
+ 'user_update',
+ 'user_delete',
+ 'user_login',
+ 'user_logout',
+ 'user_view',
+ ),
+ ),
+ 'trigger_test_generic_any_action' => array(
+ 'type' => 'system',
+ 'label' => t('Generic test action for any trigger'),
+ 'configurable' => FALSE,
+ 'triggers' => array('any'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_trigger_info().
+ */
+function trigger_test_trigger_info() {
+ // Register triggers that this module provides. The first is an additional
+ // node trigger and the second is our own, which should create a new tab
+ // on the trigger assignment page.
+ return array(
+ 'node' => array(
+ 'node_triggertest' => array(
+ 'runs when' => t('A test trigger is fired'),
+ ),
+ ),
+ 'trigger_test' => array(
+ 'trigger_test_triggertest' => array(
+ 'runs when' => t('Another test trigger is fired'),
),
),
);
@@ -74,3 +116,19 @@ function trigger_test_system_cron_conf_action_submit($form, $form_state) {
);
return $params;
}
+
+/**
+ * Action fired during the "taxonomy", "comment", and "user" trigger tests.
+ */
+function trigger_test_generic_action($context) {
+ // Indicate successful execution by setting a persistent variable.
+ variable_set('trigger_test_generic_action', TRUE);
+}
+
+/**
+ * Action fired during the additional trigger tests.
+ */
+function trigger_test_generic_any_action($context) {
+ // Indicate successful execution by setting a persistent variable.
+ variable_set('trigger_test_generic_any_action', TRUE);
+}
diff --git a/modules/trigger/trigger.admin.inc b/modules/trigger/trigger.admin.inc
index 5e4c3f408..9db7a928f 100644
--- a/modules/trigger/trigger.admin.inc
+++ b/modules/trigger/trigger.admin.inc
@@ -7,29 +7,29 @@
*/
/**
- * Build the form that allows users to assign actions to hooks.
+ * Builds the form that allows users to assign actions to triggers.
*
- * @param $type
- * Name of hook.
+ * @param $module_to_display
+ * Which tab of triggers to display. E.g., 'node' for all
+ * node-related triggers.
* @return
* HTML form.
*/
-function trigger_assign($type = NULL) {
+function trigger_assign($module_to_display = NULL) {
// If no type is specified we default to node actions, since they
// are the most common.
- if (!isset($type)) {
+ if (!isset($module_to_display)) {
drupal_goto('admin/structure/trigger/node');
}
$build = array();
- $hooks = module_invoke_all('hook_info');
- foreach ($hooks as $module => $module_hooks) {
- if ($module == $type) {
- foreach ($module_hooks as $hook => $data) {
- foreach ($data as $op => $description) {
- $form_id = 'trigger_' . $hook . '_' . $op . '_assign_form';
- $build[$form_id] = drupal_get_form($form_id, $hook, $op, $description['runs when']);
- }
+ $trigger_info = module_invoke_all('trigger_info');
+ drupal_alter('trigger_info', $trigger_info);
+ foreach ($trigger_info as $module => $hooks) {
+ if ($module == $module_to_display) {
+ foreach ($hooks as $hook => $description) {
+ $form_id = 'trigger_' . $hook . '_assign_form';
+ $build[$form_id] = drupal_get_form($form_id, $module, $hook, $description['label']);
}
}
}
@@ -39,25 +39,27 @@ function trigger_assign($type = NULL) {
/**
* Confirm removal of an assigned action.
*
+ * @param $module
+ * The tab of triggers the user will be directed to after successful
+ * removal of the action, or if the confirmation form is cancelled.
* @param $hook
- * @param $op
* @param $aid
* The action ID.
* @ingroup forms
* @see trigger_unassign_submit()
*/
-function trigger_unassign($form, $form_state, $hook = NULL, $op = NULL, $aid = NULL) {
- if (!($hook && $op && $aid)) {
- drupal_goto('admin/structure/trigger/assign');
+function trigger_unassign($form, $form_state, $module, $hook = NULL, $aid = NULL) {
+ if (!($hook && $aid)) {
+ drupal_goto('admin/structure/trigger');
}
$form['hook'] = array(
'#type' => 'value',
'#value' => $hook,
);
- $form['operation'] = array(
+ $form['module'] = array(
'#type' => 'value',
- '#value' => $op,
+ '#value' => $module,
);
$form['aid'] = array(
'#type' => 'value',
@@ -67,30 +69,31 @@ function trigger_unassign($form, $form_state, $hook = NULL, $op = NULL, $aid = N
$action = actions_function_lookup($aid);
$actions = actions_get_all_actions();
- $destination = 'admin/structure/trigger/' . ($hook == 'node' ? 'node' : $hook);
+ $destination = 'admin/structure/trigger/' . $module;
return confirm_form($form,
- t('Are you sure you want to unassign the action %title?', array('%title' => $actions[$action]['description'])),
+ t('Are you sure you want to unassign the action %title?', array('%title' => $actions[$action]['label'])),
$destination,
t('You can assign it again later if you wish.'),
t('Unassign'), t('Cancel')
);
}
+/**
+ * Submit callback for trigger_unassign() form.
+ */
function trigger_unassign_submit($form, &$form_state) {
$form_values = $form_state['values'];
if ($form_values['confirm'] == 1) {
$aid = actions_function_lookup($form_values['aid']);
db_delete('trigger_assignments')
->condition('hook', $form_values['hook'])
- ->condition('op', $form_values['operation'])
->condition('aid', $aid)
->execute();
$actions = actions_get_all_actions();
- watchdog('actions', 'Action %action has been unassigned.', array('%action' => check_plain($actions[$aid]['description'])));
- drupal_set_message(t('Action %action has been unassigned.', array('%action' => $actions[$aid]['description'])));
- $hook = $form_values['hook'] == 'node' ? 'node' : $form_values['hook'];
- $form_state['redirect'] = 'admin/structure/trigger/' . $hook;
+ watchdog('actions', 'Action %action has been unassigned.', array('%action' => check_plain($actions[$aid]['label'])));
+ drupal_set_message(t('Action %action has been unassigned.', array('%action' => $actions[$aid]['label'])));
+ $form_state['redirect'] = 'admin/structure/trigger/' . $form_values['module'];
}
else {
drupal_goto('admin/structure/trigger');
@@ -98,30 +101,27 @@ function trigger_unassign_submit($form, &$form_state) {
}
/**
- * Create the form definition for assigning an action to a hook-op combination.
+ * Returns the form for assigning an action to a trigger.
*
- * @param $form_state
- * Information about the current form.
+ * @param $module
+ * The name of the trigger group, e.g., 'node'.
* @param $hook
- * The name of the hook, e.g., 'node'.
- * @param $op
- * The name of the hook operation, e.g., 'insert'.
- * @param $description
- * A plain English description of what this hook operation does.
- * @return
+ * The name of the trigger hook, e.g., 'node_insert'.
+ * @param $label
+ * A plain English description of what this trigger does.
*
* @ingoup forms
* @see trigger_assign_form_validate()
* @see trigger_assign_form_submit()
*/
-function trigger_assign_form($form, $form_state, $hook, $op, $description) {
- $form['hook'] = array(
+function trigger_assign_form($form, $form_state, $module, $hook, $label) {
+ $form['module'] = array(
'#type' => 'hidden',
- '#value' => $hook,
+ '#value' => $module,
);
- $form['operation'] = array(
+ $form['hook'] = array(
'#type' => 'hidden',
- '#value' => $op,
+ '#value' => $hook,
);
// All of these forms use the same validate and submit functions.
$form['#validate'][] = 'trigger_assign_form_validate';
@@ -129,53 +129,53 @@ function trigger_assign_form($form, $form_state, $hook, $op, $description) {
$options = array();
$functions = array();
- // Restrict the options list to actions that declare support for this hook-op
- // combination.
+ // Restrict the options list to actions that declare support for this hook.
foreach (actions_list() as $func => $metadata) {
- if (isset($metadata['hooks']['any']) || (isset($metadata['hooks'][$hook]) && is_array($metadata['hooks'][$hook]) && (in_array($op, $metadata['hooks'][$hook])))) {
+ if (in_array('any', $metadata['triggers']) || in_array($hook, $metadata['triggers'])) {
$functions[] = $func;
}
}
foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {
if (in_array($action['callback'], $functions)) {
- $options[$action['type']][$aid] = $action['description'];
+ $options[$action['type']][$aid] = $action['label'];
}
}
- $form[$op] = array(
+ $form[$hook] = array(
'#type' => 'fieldset',
- '#title' => t('Trigger: ') . $description,
+ '#title' => t('Trigger: ') . $label,
'#theme' => 'trigger_display'
- );
- // Retrieve actions that are already assigned to this hook-op combination.
- $actions = _trigger_get_hook_actions($hook, $op);
- $form[$op]['assigned']['#type'] = 'value';
- $form[$op]['assigned']['#value'] = array();
- foreach ($actions as $aid => $description) {
- $form[$op]['assigned']['#value'][$aid] = array(
- 'description' => $description,
- 'link' => l(t('unassign'), "admin/structure/trigger/unassign/$hook/$op/" . md5($aid))
+ );
+
+ // Retrieve actions that are already assigned to this hook combination.
+ $actions = trigger_get_assigned_actions($hook);
+ $form[$hook]['assigned']['#type'] = 'value';
+ $form[$hook]['assigned']['#value'] = array();
+ foreach ($actions as $aid => $info) {
+ $form[$hook]['assigned']['#value'][$aid] = array(
+ 'label' => $info['label'],
+ 'link' => l(t('unassign'), "admin/structure/trigger/unassign/$module/$hook/" . md5($aid)),
);
}
- $form[$op]['parent'] = array(
+ $form[$hook]['parent'] = array(
'#prefix' => "<div class='container-inline'>",
'#suffix' => '</div>',
);
// List possible actions that may be assigned.
if (count($options) != 0) {
array_unshift($options, t('Choose an action'));
- $form[$op]['parent']['aid'] = array(
+ $form[$hook]['parent']['aid'] = array(
'#type' => 'select',
'#options' => $options,
);
- $form[$op]['parent']['submit'] = array(
+ $form[$hook]['parent']['submit'] = array(
'#type' => 'submit',
'#value' => t('Assign')
);
}
else {
- $form[$op]['none'] = array(
+ $form[$hook]['none'] = array(
'#markup' => t('No actions available for this trigger. <a href="@link">Add action</a>.', array('@link' => url('admin/config/system/actions/manage')))
);
}
@@ -191,13 +191,12 @@ function trigger_assign_form_validate($form, $form_state) {
$form_values = $form_state['values'];
if (!empty($form_values['aid'])) {
$aid = actions_function_lookup($form_values['aid']);
- $aid_exists = db_query("SELECT aid FROM {trigger_assignments} WHERE hook = :hook AND op = :op AND aid = :aid", array(
+ $aid_exists = db_query("SELECT aid FROM {trigger_assignments} WHERE hook = :hook AND aid = :aid", array(
':hook' => $form_values['hook'],
- ':op' => $form_values['operation'],
':aid' => $aid,
))->fetchField();
if ($aid_exists) {
- form_set_error($form_values['operation'], t('The action you chose is already assigned to that trigger.'));
+ form_set_error($form_values['hook'], t('The action you chose is already assigned to that trigger.'));
}
}
}
@@ -210,54 +209,51 @@ function trigger_assign_form_submit($form, $form_state) {
if (!empty($form_values['aid'])) {
$aid = actions_function_lookup($form_values['aid']);
- $weight = db_query("SELECT MAX(weight) FROM {trigger_assignments} WHERE hook = :hook AND op = :op", array(
+ $weight = db_query("SELECT MAX(weight) FROM {trigger_assignments} WHERE hook = :hook", array(
':hook' => $form_values['hook'],
- ':op' => $form_values['operation'],
))->fetchField();
db_insert('trigger_assignments')
->fields(array(
- 'hook' => $form_values['hook'],
- 'op' => $form_values['operation'],
- 'aid' => $aid,
+ 'hook' => $form_values['hook'],
+ 'aid' => $aid,
'weight' => $weight + 1,
))
->execute();
// If this action changes a node property, we need to save the node
// so the change will persist.
+
$actions = actions_list();
- if (isset($actions[$aid]['behavior']) && in_array('changes_node_property', $actions[$aid]['behavior']) && ($form_values['operation'] != 'presave')) {
- // Delete previous node_save_action if it exists, and re-add a new one at a higher weight.
- $save_post_action_assigned = db_query("SELECT aid FROM {trigger_assignments} WHERE hook = :hook AND op = :op AND aid = :aid", array(
+ if (isset($actions[$aid]['behavior']) && in_array('changes_node_property', $actions[$aid]['behavior']) && ($form_values['hook'] != 'node_presave') && ($form_values['hook'] != 'comment_presave')) {
+ // Delete previous node_save_action if it exists, and re-add a new one at
+ // a higher weight.
+ $save_post_action_assigned = db_query("SELECT aid FROM {trigger_assignments} WHERE hook = :hook AND aid = :aid", array(
':hook' => $form_values['hook'],
- ':op' => $form_values['operation'],
':aid' => 'node_save_action',
))->fetchField();
if ($save_post_action_assigned) {
db_delete('trigger_assignments')
->condition('hook', $form_values['hook'])
- ->condition('op', $form_values['operation'])
->condition('aid', 'node_save_action')
->execute();
}
db_insert('trigger_assignments')
->fields(array(
- 'hook' => $form_values['hook'],
- 'op' => $form_values['operation'],
- 'aid' => 'node_save_action',
+ 'hook' => $form_values['hook'],
+ 'aid' => 'node_save_action',
'weight' => $weight + 2,
))
->execute();
if (!$save_post_action_assigned) {
- drupal_set_message(t('You have added an action that changes a the property of a post. A Save post action has been added so that the property change will be saved.'));
+ drupal_set_message(t("You have added an action that changes a the property of some content. Your 'Save content' action has been moved later in the list so that the property change will be saved."));
}
}
}
}
/**
- * Display actions assigned to this hook-op combination in a table.
+ * Displays actions assigned to this hook in a table.
*
* @param array $element
* The fieldset including all assigned actions.
@@ -269,12 +265,12 @@ function trigger_assign_form_submit($form, $form_state) {
function theme_trigger_display($element) {
$header = array();
$rows = array();
- if (count($element['assigned']['#value'])) {
+ if (isset($element['assigned']) && count($element['assigned']['#value'])) {
$header = array(array('data' => t('Name')), array('data' => t('Operation')));
$rows = array();
foreach ($element['assigned']['#value'] as $aid => $info) {
$rows[] = array(
- $info['description'],
+ $info['label'],
$info['link']
);
}
@@ -289,34 +285,3 @@ function theme_trigger_display($element) {
return $output;
}
-
-/**
- * Get the actions that have already been defined for this
- * type-hook-op combination.
- *
- * @param $type
- * One of 'node', 'user', 'comment'.
- * @param $hook
- * The name of the hook for which actions have been assigned,
- * e.g. 'node'.
- * @param $op
- * The hook operation for which the actions have been assigned,
- * e.g., 'view'.
- * @return
- * An array of action descriptions keyed by action IDs.
- */
-function _trigger_get_hook_actions($hook, $op, $type = NULL) {
- if ($type) {
- return db_query("SELECT h.aid, a.description FROM {trigger_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE a.type = :type AND h.hook = :hook AND h.op = :op ORDER BY h.weight", array(
- ':type' => $type,
- ':hook' => $hook,
- ':op' => $op,
- ))->fetchAllKeyed();
- }
- else {
- return db_query("SELECT h.aid, a.description FROM {trigger_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE h.hook = :hook AND h.op = :op ORDER BY h.weight", array(
- ':hook' => $hook,
- ':op' => $op,
- ))->fetchAllKeyed();
- }
-}
diff --git a/modules/trigger/trigger.api.php b/modules/trigger/trigger.api.php
index cc63d5c1a..0f7834e80 100644
--- a/modules/trigger/trigger.api.php
+++ b/modules/trigger/trigger.api.php
@@ -12,142 +12,50 @@
*/
/**
- * Declare information about one or more Drupal actions.
+ * Declares triggers (events) for users to assign actions to.
*
- * Any module can define any number of Drupal actions. The trigger module is an
- * example of a module that uses actions. An action consists of two or three
- * parts: (1) an action definition (returned by this hook), (2) a function which
- * does the action (which by convention is named module + '_' + description of
- * what the function does + '_action'), and an optional form definition
- * function that defines a configuration form (which has the name of the action
- * with '_form' appended to it.)
+ * This hook is used by the trigger module to create a list of triggers (events)
+ * that users can assign actions to. Your module is responsible for detecting
+ * that the events have occurred, calling trigger_get_assigned_actions() to find
+ * out which actions the user has associated with your trigger, and then calling
+ * actions_do() to fire off the actions.
*
- * @return
- * - An array of action descriptions. Each action description is an associative
- * array, where the key of the item is the action's function, and the
- * following key-value pairs:
- * - 'type': (required) the type is determined by what object the action
- * acts on. Possible choices are node, user, comment, and system. Or
- * whatever your own custom type is. So, for the nodequeue module, the
- * type might be set to 'nodequeue' if the action would be performed on a
- * nodequeue.
- * - 'description': (required) The human-readable name of the action.
- * - 'configurable': (required) If FALSE, then the action doesn't require
- * any extra configuration. If TRUE, then you should define a form
- * function with the same name as the key, but with '_form' appended to
- * it (i.e., the form for 'node_assign_owner_action' is
- * 'node_assign_owner_action_form'.)
- * This function will take the $context as the only parameter, and is
- * paired with the usual _submit function, and possibly a _validate
- * function.
- * - 'hooks': (required) An array of all of the operations this action is
- * appropriate for, keyed by hook name. The trigger module uses this to
- * filter out inappropriate actions when presenting the interface for
- * assigning actions to events. If you are writing actions in your own
- * modules and you simply want to declare support for all possible hooks,
- * you can set 'hooks' => array('any' => TRUE). Common hooks are 'user',
- * 'node', 'comment', or 'taxonomy'. Any hook that has been described
- * to Drupal in hook_hook_info() will work is a possiblity.
- * - 'behavior': (optional) Human-readable array of behavior descriptions.
- * The only one we have now is 'changes node property'. You will almost
- * certainly never have to return this in your own implementations of this
- * hook.
- *
- * The function that is called when the action is triggered is passed two
- * parameters - an object of the same type as the 'type' value of the
- * hook_action_info array, and a context variable that contains the context
- * under which the action is currently running, sent as an array. For example,
- * the actions module sets the 'hook' and 'op' keys of the context array (so,
- * 'hook' may be 'node' and 'op' may be 'insert').
- */
-function hook_action_info() {
- return array(
- 'comment_unpublish_action' => array(
- 'description' => t('Unpublish comment'),
- 'type' => 'comment',
- 'configurable' => FALSE,
- 'hooks' => array(
- 'comment' => array('insert', 'update'),
- )
- ),
- 'comment_unpublish_by_keyword_action' => array(
- 'description' => t('Unpublish comment containing keyword(s)'),
- 'type' => 'comment',
- 'configurable' => TRUE,
- 'hooks' => array(
- 'comment' => array('insert', 'update'),
- )
- )
- );
-}
-
-/**
- * Execute code after an action is deleted.
- *
- * @param $aid
- * The action ID.
- */
-function hook_actions_delete($aid) {
- db_delete('actions_assignments')
- ->condition('aid', $aid)
- ->execute();
-}
-
-/**
- * Alter the actions declared by another module.
- *
- * Called by actions_list() to allow modules to alter the return
- * values from implementations of hook_action_info().
- *
- * @see trigger_example_action_info_alter().
- */
-function hook_action_info_alter(&$actions) {
- $actions['node_unpublish_action']['description'] = t('Unpublish and remove from public view.');
-}
-
-/**
- * Expose a list of triggers (events) that your module is allowing users to
- * assign actions to.
- *
- * This hook is used by the Triggers API to present information about triggers
- * (or events) that your module allows users to assign actions to.
- *
- * See also hook_action_info().
+ * @see hook_action_info().
*
* @return
- * - A nested array. The outermost key defines the module that the triggers
- * are from. The menu system will use the key to look at the .info file of
- * the module and make a local task (a tab) in the trigger UI.
- * - The next key defines the hook being described.
- * - Inside of that array are a list of arrays keyed by hook operation.
- * - Each of those arrays have a key of 'runs when' and a value which is
- * an English description of the hook.
- *
- * For example, the node_hook_info implementation has 'node' as the outermost
- * key, as that's the module it's in. Next it has 'node' as the next key,
- * as hook_node() is what applies to changes in nodes. Finally the keys
- * after that are the various operations for hook_node() that the node module
- * is exposing as triggers.
+ * A nested associative array.
+ * - The outermost key is the name of the module that is defining the triggers.
+ * This will be used to create a local task (tab) in the trigger module's
+ * user interface. A contrib module may supply a trigger for a core module by
+ * giving the core module's name as the key. For example, you could use the
+ * 'node' key to add a node-related trigger.
+ * - Within each module, each individual trigger is keyed by a hook name
+ * describing the particular trigger (this is not visible to the user, but
+ * can be used by your module for identification).
+ * - Each trigger is described by an associative array. Currently, the only
+ * key-value pair is 'label', which contains a translated human-readable
+ * description of the triggering event.
+ * For example, the trigger set for the 'node' module has 'node' as the
+ * outermost key and defines triggers for 'node_insert', 'node_update',
+ * 'node_delete' etc. that fire when a node is saved, updated, etc.
*/
-function hook_hook_info() {
+function hook_trigger_info() {
return array(
'node' => array(
- 'node' => array(
- 'presave' => array(
- 'runs when' => t('When either saving a new post or updating an existing post'),
- ),
- 'insert' => array(
- 'runs when' => t('After saving a new post'),
- ),
- 'update' => array(
- 'runs when' => t('After saving an updated post'),
- ),
- 'delete' => array(
- 'runs when' => t('After deleting a post')
- ),
- 'view' => array(
- 'runs when' => t('When content is viewed by an authenticated user')
- ),
+ 'node_presave' => array(
+ 'label' => t('When either saving new content or updating existing content'),
+ ),
+ 'node_insert' => array(
+ 'label' => t('After saving new content'),
+ ),
+ 'node_update' => array(
+ 'label' => t('After saving updated content'),
+ ),
+ 'node_delete' => array(
+ 'label' => t('After deleting content'),
+ ),
+ 'node_view' => array(
+ 'label' => t('When content is viewed by an authenticated user'),
),
),
);
diff --git a/modules/trigger/trigger.install b/modules/trigger/trigger.install
index 2e014d071..b8553e48c 100644
--- a/modules/trigger/trigger.install
+++ b/modules/trigger/trigger.install
@@ -26,14 +26,7 @@ function trigger_schema() {
'length' => 32,
'not null' => TRUE,
'default' => '',
- 'description' => 'Primary Key: The name of the internal Drupal hook upon which an action is firing; for example, node.',
- ),
- 'op' => array(
- 'type' => 'varchar',
- 'length' => 32,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'Primary Key: The specific operation of the hook upon which an action is firing: for example, presave.',
+ 'description' => 'Primary Key: The name of the internal Drupal hook; for example, node_insert.',
),
'aid' => array(
'type' => 'varchar',
@@ -49,7 +42,7 @@ function trigger_schema() {
'description' => 'The weight of the trigger assignment in relation to other triggers.',
),
),
- 'primary key' => array('hook', 'op', 'aid'),
+ 'primary key' => array('hook', 'aid'),
'foreign keys' => array(
'aid' => array('actions' => 'aid'),
),
@@ -57,4 +50,17 @@ function trigger_schema() {
return $schema;
}
+/**
+ * Adds operation names to the hook names and drops the "op" field.
+ */
+function trigger_update_7000() {
+ $ret = array();
+ $result = db_query("SELECT hook, op, aid FROM {trigger_assignments} WHERE op <> ''");
+ while ($row = db_fetch_object($result)) {
+ $ret[] = update_sql("UPDATE {trigger_assignments} SET hook = '%s' WHERE hook = '%s' AND op = '%s' AND aid = '%s'", $row->hook . '_' . $row->op, $row->hook, $row->op, $row->aid);
+ }
+ $ret[] = update_sql("ALTER TABLE {trigger_assignments} DROP op");
+
+ return $ret;
+}
diff --git a/modules/trigger/trigger.module b/modules/trigger/trigger.module
index 281400fe5..d889007e4 100644
--- a/modules/trigger/trigger.module
+++ b/modules/trigger/trigger.module
@@ -11,13 +11,13 @@
* Implement hook_help().
*/
function trigger_help($path, $arg) {
- $explanation = '<p>' . t('Triggers are system events, such as when new content is added or when a user logs in. Trigger module combines these triggers with actions (functional tasks), such as unpublishing content or e-mailing an administrator. The <a href="@url">Actions settings page</a> contains a list of existing actions and provides the ability to create and configure additional actions.', array('@url' => url('admin/config/system/actions'))) . '</p>';
+ $explanation = '<p>' . t('Triggers are system events, such as when new content is added or when a user logs in. The trigger module associates these triggers with actions (functional tasks), such as unpublishing content or e-mailing an administrator. The <a href="@url">Actions settings page</a> contains a list of existing actions and provides the ability to create and configure additional actions.', array('@url' => url('admin/config/system/actions'))) . '</p>';
switch ($path) {
case 'admin/structure/trigger/comment':
return $explanation . '<p>' . t('Below you can assign actions to run when certain comment-related triggers happen. For example, you could promote a post to the front page when a comment is added.') . '</p>';
case 'admin/structure/trigger/node':
- return $explanation . '<p>' . t('Below you can assign actions to run when certain content-related triggers happen. For example, you could send an e-mail to an administrator when a post is created or updated.') . '</p>';
- case 'admin/structure/trigger/cron':
+ return $explanation . '<p>' . t('Below you can assign actions to run when certain content-related triggers happen. For example, you could send an e-mail to an administrator when content is created or updated.') . '</p>';
+ case 'admin/structure/trigger/system':
return $explanation . '<p>' . t('Below you can assign actions to run during each pass of a <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))) . '</p>';
case 'admin/structure/trigger/taxonomy':
return $explanation . '<p>' . t('Below you can assign actions to run when certain taxonomy-related triggers happen. For example, you could send an e-mail to an administrator when a term is deleted.') . '</p>';
@@ -37,46 +37,36 @@ function trigger_help($path, $arg) {
function trigger_menu() {
$items['admin/structure/trigger'] = array(
'title' => 'Triggers',
- 'description' => 'Tell Drupal when to execute actions.',
+ 'description' => 'Configure when to execute actions.',
'page callback' => 'trigger_assign',
'access arguments' => array('administer actions'),
'file' => 'trigger.admin.inc',
);
- // Explicitly define the system menu item so we can label it "cron" rather
- // than "system".
- $items['admin/structure/trigger/cron'] = array(
- 'title' => 'Cron',
- 'page callback' => 'trigger_assign',
- 'page arguments' => array('system'),
- 'access arguments' => array('administer actions'),
- 'type' => MENU_LOCAL_TASK,
- 'file' => 'trigger.admin.inc',
- );
-
// We want contributed modules to be able to describe
// their hooks and have actions assignable to them.
- $hooks = module_invoke_all('hook_info');
- foreach ($hooks as $module => $hook) {
- // We've already done system.module.
- if ($module == 'system') {
- continue;
- }
+ $trigger_info = module_invoke_all('trigger_info');
+ drupal_alter('trigger_info', $trigger_info);
+
+ foreach ($trigger_info as $module => $hooks) {
$info = db_select('system')
->fields('system', array('info'))
->condition('name', $module)
+ ->condition('status', 1)
->execute()
->fetchField();
- $info = unserialize($info);
- $nice_name = $info['name'];
- $items["admin/structure/trigger/$module"] = array(
- 'title' => $nice_name,
- 'page callback' => 'trigger_assign',
- 'page arguments' => array($module),
- 'access arguments' => array('administer actions'),
- 'type' => MENU_LOCAL_TASK,
- 'file' => 'trigger.admin.inc',
- );
+ if ($info) {
+ $info = unserialize($info);
+ $nice_name = $info['name'];
+ $items["admin/structure/trigger/$module"] = array(
+ 'title' => $nice_name,
+ 'page callback' => 'trigger_assign',
+ 'page arguments' => array($module),
+ 'access arguments' => array('administer actions'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'trigger.admin.inc',
+ );
+ }
}
$items['admin/structure/trigger/unassign'] = array(
'title' => 'Unassign',
@@ -92,21 +82,96 @@ function trigger_menu() {
}
/**
- * Get the aids of actions to be executed for a hook-op combination.
+ * Implement hook_trigger_info().
+ *
+ * Defines all the triggers that this module implements triggers for.
+ */
+function trigger_trigger_info() {
+ return array(
+ 'node' => array(
+ 'node_presave' => array(
+ 'label' => t('When either saving new content or updating existing content'),
+ ),
+ 'node_insert' => array(
+ 'label' => t('After saving new content'),
+ ),
+ 'node_update' => array(
+ 'label' => t('After saving updated content'),
+ ),
+ 'node_delete' => array(
+ 'label' => t('After deleting content'),
+ ),
+ 'node_view' => array(
+ 'label' => t('When content is viewed by an authenticated user'),
+ ),
+ ),
+ 'comment' => array(
+ 'comment_insert' => array(
+ 'label' => t('After saving a new comment'),
+ ),
+ 'comment_update' => array(
+ 'label' => t('After saving an updated comment'),
+ ),
+ 'comment_delete' => array(
+ 'label' => t('After deleting a comment'),
+ ),
+ 'comment_view' => array(
+ 'label' => t('When a comment is being viewed by an authenticated user'),
+ ),
+ ),
+ 'taxonomy' => array(
+ 'taxonomy_term_insert' => array(
+ 'label' => t('After saving a new term to the database'),
+ ),
+ 'taxonomy_term_update' => array(
+ 'label' => t('After saving an updated term to the database'),
+ ),
+ 'taxonomy_term_delete' => array(
+ 'label' => t('After deleting a term'),
+ ),
+ ),
+ 'system' => array(
+ 'cron' => array(
+ 'label' => t('When cron runs'),
+ ),
+ ),
+ 'user' => array(
+ 'user_insert' => array(
+ 'label' => t('After a user account has been created'),
+ ),
+ 'user_update' => array(
+ 'label' => t("After a user's profile has been updated"),
+ ),
+ 'user_delete' => array(
+ 'label' => t('After a user has been deleted'),
+ ),
+ 'user_login' => array(
+ 'label' => t('After a user has logged in'),
+ ),
+ 'user_logout' => array(
+ 'label' => t('After a user has logged out'),
+ ),
+ 'user_view' => array(
+ 'label' => t("When a user's profile is being viewed"),
+ ),
+ ),
+ );
+ }
+
+/**
+ * Gets the action IDs of actions to be executed for a hook.
*
* @param $hook
* The name of the hook being fired.
- * @param $op
- * The name of the operation being executed. Defaults to an empty string
- * because some hooks (e.g., hook_cron()) do not have operations.
* @return
- * An array of action IDs.
+ * An array whose keys are action IDs that the user has associated with
+ * this trigger, and whose values are arrays containing the action type and
+ * label.
*/
-function _trigger_get_hook_aids($hook, $op = '') {
- return db_query("SELECT ta.aid, a.type FROM {trigger_assignments} ta LEFT JOIN {actions} a ON ta.aid = a.aid WHERE ta.hook = :hook AND ta.op = :op ORDER BY ta.weight", array(
+function trigger_get_assigned_actions($hook) {
+ return db_query("SELECT ta.aid, a.type, a.label FROM {trigger_assignments} ta LEFT JOIN {actions} a ON ta.aid = a.aid WHERE ta.hook = :hook ORDER BY ta.weight", array(
':hook' => $hook,
- ':op' => $op,
- ))->fetchAllKeyed();
+ ))->fetchAllAssoc( 'aid', PDO::FETCH_ASSOC);
}
/**
@@ -122,16 +187,16 @@ function trigger_theme() {
}
/**
- * Implement hook_forms(). We reuse code by using the
- * same assignment form definition for each node-op combination.
+ * Implement hook_forms().
+ *
+ * We re-use code by using the same assignment form definition for each hook.
*/
function trigger_forms() {
- $hooks = module_invoke_all('hook_info');
- foreach ($hooks as $module => $info) {
- foreach ($hooks[$module] as $hook => $ops) {
- foreach ($ops as $op => $description) {
- $forms['trigger_' . $hook . '_' . $op . '_assign_form'] = array('callback' => 'trigger_assign_form');
- }
+ $trigger_info = _trigger_get_all_info();
+ $forms = array();
+ foreach ($trigger_info as $module => $hooks) {
+ foreach ($hooks as $hook => $description) {
+ $forms['trigger_' . $hook . '_assign_form'] = array('callback' => 'trigger_assign_form');
}
}
@@ -139,26 +204,26 @@ function trigger_forms() {
}
/**
- * When an action is called in a context that does not match its type,
- * the object that the action expects must be retrieved. For example, when
- * an action that works on users is called during the node hook, the
- * user object is not available since the node hook doesn't pass it.
- * So here we load the object the action expects.
+ * Loads associated objects for node triggers.
+ *
+ * When an action is called in a context that does not match its type, the
+ * object that the action expects must be retrieved. For example, when an action
+ * that works on users is called during a node hook implementation, the user
+ * object is not available since the node hook call doesn't pass it. So here we
+ * load the object the action expects.
*
* @param $type
* The type of action that is about to be called.
* @param $node
* The node that was passed via the node hook.
+ *
* @return
* The object expected by the action that is about to be called.
*/
function _trigger_normalize_node_context($type, $node) {
+ // Note that comment-type actions are not supported in node contexts,
+ // because we wouldn't know which comment to choose.
switch ($type) {
- // If an action that works on comments is being called in a node context,
- // the action is expecting a comment object. But we do not know which comment
- // to give it. The first? The most recent? All of them? So comment actions
- // in a node context are not supported.
-
// An action that works on users is being called in a node context.
// Load the user object of the node's author.
case 'user':
@@ -167,33 +232,41 @@ function _trigger_normalize_node_context($type, $node) {
}
/**
- * Simple wrapper function to make user hooks work with new entry points.
+ * Calls action functions for node triggers.
*
- * @TODO: Take advantage of the new API and reorganise/remove this function.
+ * @param $node
+ * Node object.
+ * @param $op
+ * Operation to trigger.
+ * @param $a3
+ * Additional argument to action function.
+ * @param $a4
+ * Additional argument to action function.
*/
-function _trigger_node($node, $op, $a3 = NULL, $a4 = NULL) {
+function _trigger_node($node, $hook, $a3 = NULL, $a4 = NULL) {
// Keep objects for reuse so that changes actions make to objects can persist.
static $objects;
// Prevent recursion by tracking which operations have already been called.
static $recursion;
- if (isset($recursion[$op])) {
+ if (isset($recursion[$hook])) {
return;
}
- $recursion[$op] = TRUE;
+ $recursion[$hook] = TRUE;
- $aids = _trigger_get_hook_aids('node', $op);
+ $aids = trigger_get_assigned_actions($hook);
if (!$aids) {
return;
}
$context = array(
- 'hook' => 'node',
- 'op' => $op,
+ 'group' => 'node',
+ 'hook' => $hook,
);
// We need to get the expected object if the action's type is not 'node'.
// We keep the object in $objects so we can reuse it if we have multiple actions
// that make changes to an object.
- foreach ($aids as $aid => $type) {
+ foreach ($aids as $aid => $info) {
+ $type = $info['type'];
if ($type != 'node') {
if (!isset($objects[$type])) {
$objects[$type] = _trigger_normalize_node_context($type, $node);
@@ -212,48 +285,51 @@ function _trigger_node($node, $op, $a3 = NULL, $a4 = NULL) {
* Implement hook_node_view().
*/
function trigger_node_view($node, $build_mode) {
- _trigger_node($node, 'view', $build_mode);
+ _trigger_node($node, 'node_view', $build_mode);
}
/**
* Implement hook_node_update().
*/
function trigger_node_update($node) {
- _trigger_node($node, 'update');
+ _trigger_node($node, 'node_update');
}
/**
* Implement hook_node_presave().
*/
function trigger_node_presave($node) {
- _trigger_node($node, 'presave');
+ _trigger_node($node, 'node_presave');
}
/**
* Implement hook_node_insert().
*/
function trigger_node_insert($node) {
- _trigger_node($node, 'insert');
+ _trigger_node($node, 'node_insert');
}
/**
* Implement hook_node_delete().
*/
function trigger_node_delete($node) {
- _trigger_node($node, 'delete');
+ _trigger_node($node, 'node_delete');
}
/**
- * When an action is called in a context that does not match its type,
- * the object that the action expects must be retrieved. For example, when
- * an action that works on nodes is called during the comment hook, the
- * node object is not available since the comment hook doesn't pass it.
- * So here we load the object the action expects.
+ * Loads associated objects for comment triggers.
+ *
+ * When an action is called in a context that does not match its type, the
+ * object that the action expects must be retrieved. For example, when an action
+ * that works on nodes is called during the comment hook, the node object is not
+ * available since the comment hook doesn't pass it. So here we load the object
+ * the action expects.
*
* @param $type
* The type of action that is about to be called.
* @param $comment
* The comment that was passed via the comment hook.
+ *
* @return
* The object expected by the action that is about to be called.
*/
@@ -273,51 +349,51 @@ function _trigger_normalize_comment_context($type, $comment) {
* Implement hook_comment_insert().
*/
function trigger_comment_insert($comment) {
- _trigger_comment($comment, 'insert');
+ _trigger_comment($comment, 'comment_insert');
}
/**
* Implement hook_comment_update().
*/
function trigger_comment_update($comment) {
- _trigger_comment($comment, 'update');
+ _trigger_comment($comment, 'comment_update');
}
/**
* Implement hook_comment_delete().
*/
function trigger_comment_delete($comment) {
- _trigger_comment($comment, 'delete');
+ _trigger_comment($comment, 'comment_delete');
}
/**
* Implement hook_comment_view().
*/
function trigger_comment_view($comment) {
- _trigger_comment($comment, 'view');
+ _trigger_comment($comment, 'comment_view');
}
/**
- * Helper function for implementations of hook_comment_op().
+ * Calls action functions for comment triggers.
*
* @param $a1
- * Argument, which will be passed to the action.
- * It can be a array of form values or a comment.
+ * Comment object or array of form values.
* @param $op
- * What kind of action is being performed.
+ * Operation to trigger.
*/
-function _trigger_comment($a1, $op) {
+function _trigger_comment($a1, $hook) {
// Keep objects for reuse so that changes actions make to objects can persist.
static $objects;
- $aids = _trigger_get_hook_aids('comment', $op);
+ $aids = trigger_get_assigned_actions($hook);
$context = array(
- 'hook' => 'comment',
- 'op' => $op,
+ 'group' => 'comment',
+ 'hook' => $hook,
);
// We need to get the expected object if the action's type is not 'comment'.
- // We keep the object in $objects so we can reuse it if we have multiple actions
- // that make changes to an object.
- foreach ($aids as $aid => $type) {
+ // We keep the object in $objects so we can reuse it if we have multiple
+ // actions that make changes to an object.
+ foreach ($aids as $aid => $info) {
+ $type = $info['type'];
if ($type != 'comment') {
if (!isset($objects[$type])) {
$objects[$type] = _trigger_normalize_comment_context($type, $a1);
@@ -338,10 +414,10 @@ function _trigger_comment($a1, $op) {
* Implement hook_cron().
*/
function trigger_cron() {
- $aids = _trigger_get_hook_aids('cron', 'run');
+ $aids = trigger_get_assigned_actions('cron');
$context = array(
+ 'group' => 'cron',
'hook' => 'cron',
- 'op' => 'run',
);
// Cron does not act on any specific object.
$object = NULL;
@@ -349,11 +425,13 @@ function trigger_cron() {
}
/**
- * When an action is called in a context that does not match its type,
- * the object that the action expects must be retrieved. For example, when
- * an action that works on nodes is called during the user hook, the
- * node object is not available since the user hook doesn't pass it.
- * So here we load the object the action expects.
+ * Loads associated objects for user triggers.
+ *
+ * When an action is called in a context that does not match its type, the
+ * object that the action expects must be retrieved. For example, when an action
+ * that works on nodes is called during the user hook, the node object is not
+ * available since the user hook doesn't pass it. So here we load the object the
+ * action expects.
*
* @param $type
* The type of action that is about to be called.
@@ -363,12 +441,9 @@ function trigger_cron() {
* The object expected by the action that is about to be called.
*/
function _trigger_normalize_user_context($type, $account) {
+ // Note that comment-type actions are not supported in user contexts,
+ // because we wouldn't know which comment to choose.
switch ($type) {
- // If an action that works on comments is being called in a user context,
- // the action is expecting a comment object. But we have no way of
- // determining the appropriate comment object to pass. So comment
- // actions in a user context are not supported.
-
// An action that works with nodes is being called in a user context.
// If a single node is being viewed, return the node.
case 'node':
@@ -376,35 +451,36 @@ function _trigger_normalize_user_context($type, $account) {
if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
return node_load(array('nid' => arg(1)));
}
+ break;
}
}
/**
- * trigger_user_login
+ * Implement hook_user_login().
*/
function trigger_user_login(&$edit, $account, $category) {
- _trigger_user('login', $edit, $account, $category);
+ _trigger_user('user_login', $edit, $account, $category);
}
/**
* Implement hook_user_logout().
*/
function trigger_user_logout($account) {
- _trigger_user('logout', $edit = NULL, $account);
+ _trigger_user('user_logout', $edit = NULL, $account);
}
/**
* Implement hook_user_insert().
*/
function trigger_user_insert(&$edit, $account, $category) {
- _trigger_user('insert', $edit, $account, $category);
+ _trigger_user('user_insert', $edit, $account, $category);
}
/**
* Implement hook_user_update().
*/
function trigger_user_update(&$edit, $account, $category) {
- _trigger_user('update', $edit, $account, $category);
+ _trigger_user('user_update', $edit, $account, $category);
}
/**
@@ -414,7 +490,7 @@ function trigger_user_cancel($edit, $account, $method) {
switch ($method) {
case 'user_cancel_reassign':
case 'user_cancel_delete':
- _trigger_user('delete', $edit, $account, $method);
+ _trigger_user('user_delete', $edit, $account, $method);
break;
}
}
@@ -423,24 +499,23 @@ function trigger_user_cancel($edit, $account, $method) {
* Implement hook_user_view().
*/
function trigger_user_view($account) {
- _trigger_user('view', $edit = NULL, $account, NULL);
+ _trigger_user('user_view', $edit = NULL, $account, NULL);
}
/**
- * Simple wrapper function to make user hooks work with new entry points.
- *
- * @TODO: Take advantage of the new API and reorganise/remove this function.
+ * Calls action functions for user triggers.
*/
-function _trigger_user($op, &$edit, $account, $category = NULL) {
+function _trigger_user($hook, &$edit, $account, $category = NULL) {
// Keep objects for reuse so that changes actions make to objects can persist.
static $objects;
- $aids = _trigger_get_hook_aids('user', $op);
+ $aids = trigger_get_assigned_actions($hook);
$context = array(
- 'hook' => 'user',
- 'op' => $op,
+ 'group' => 'user',
+ 'hook' => $hook,
'form_values' => &$edit,
);
- foreach ($aids as $aid => $type) {
+ foreach ($aids as $aid => $info) {
+ $type = $info['type'];
if ($type != 'user') {
if (!isset($objects[$type])) {
$objects[$type] = _trigger_normalize_user_context($type, $account);
@@ -455,50 +530,67 @@ function _trigger_user($op, &$edit, $account, $category = NULL) {
}
/**
- * Implement hook_taxonomy().
+ * Calls action functions for taxonomy triggers.
+ *
+ * @param $hook
+ * Hook to trigger actions for taxonomy_term_insert(),
+ * taxonomy_term_update(), and taxonomy_term_delete().
+ * @param $array
+ * Item on which operation is being performed, either a term or
+ * form values.
*/
-function trigger_taxonomy($op, $type, $array) {
- if ($type != 'term') {
- return;
- }
- $aids = _trigger_get_hook_aids('taxonomy', $op);
+function _trigger_taxonomy($hook, $array) {
+ $aids = trigger_get_assigned_actions($hook);
$context = array(
- 'hook' => 'taxonomy',
- 'op' => $op
+ 'group' => 'taxonomy',
+ 'hook' => $hook
);
actions_do(array_keys($aids), (object) $array, $context);
}
/**
- * Often we generate a select field of all actions. This function
- * generates the options for that select.
- *
- * @param $type
- * One of 'node', 'user', 'comment'.
- * @return
- * Array keyed by action ID.
+ * Implement hook_taxonomy_term_insert().
*/
-function trigger_options($type = 'all') {
- $options = array(t('Choose an action'));
- foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {
- $options[$action['type']][$aid] = $action['description'];
- }
+function trigger_taxonomy_term_insert($term) {
+ _trigger_taxonomy('taxonomy_term_insert', (array) $term);
+}
- if ($type == 'all') {
- return $options;
- }
- else {
- return $options[$type];
- }
+/**
+ * Implement hook_taxonomy_term_update().
+ */
+function trigger_taxonomy_term_update($term) {
+ _trigger_taxonomy('taxonomy_term_update', (array) $term);
+}
+
+/**
+ * Implement hook_taxonomy_term_delete().
+ */
+function trigger_taxonomy_term_delete($term) {
+ _trigger_taxonomy('taxonomy_term_delete', (array) $term);
}
/**
* Implement hook_actions_delete().
*
- * Remove all trigger entries for the given action, when deleted.
+ * Removes all trigger entries for the given action, when an action is deleted.
*/
function trigger_actions_delete($aid) {
db_delete('trigger_assignments')
->condition('aid', $aid)
->execute();
}
+
+/**
+ * Retrieves and caches information from hook_trigger_info() implementations.
+ */
+function _trigger_get_all_info() {
+ static $triggers = NULL;
+ if( $triggers ) {
+ return $triggers;
+ }
+
+ $triggers = module_invoke_all('trigger_info');
+ drupal_alter('trigger_info', $triggers);
+ return $triggers;
+}
+
diff --git a/modules/trigger/trigger.test b/modules/trigger/trigger.test
index 884982f83..4889bc59e 100644
--- a/modules/trigger/trigger.test
+++ b/modules/trigger/trigger.test
@@ -59,7 +59,7 @@ class TriggerContentTestCase extends DrupalWebTestCase {
$this->assertRaw(t('The action you chose is already assigned to that trigger.'), t('Check to make sure an error occurs when assigning an action to a trigger twice.'));
// Test 3: The action should be able to be unassigned from a trigger.
- $this->drupalPost('admin/structure/trigger/unassign/node/presave/' . $hash, array(), t('Unassign'));
+ $this->drupalPost('admin/structure/trigger/unassign/node/node_presave/' . $hash, array(), t('Unassign'));
$this->assertRaw(t('Action %action has been unassigned.', array('%action' => ucfirst($info['name']))), t('Check to make sure the @action action can be unassigned from the trigger.', array('@action' => $info['name'])));
$assigned = db_query("SELECT COUNT(*) FROM {trigger_assignments} WHERE aid IN (:keys)", array(':keys' => $content_actions))->fetchField();
$this->assertFalse($assigned, t('Check to make sure unassign worked properly at the database level.'));
@@ -79,32 +79,32 @@ class TriggerContentTestCase extends DrupalWebTestCase {
'node_publish_action' => array(
'property' => 'status',
'expected' => 1,
- 'name' => t('publish post'),
+ 'name' => t('publish content'),
),
'node_unpublish_action' => array(
'property' => 'status',
'expected' => 0,
- 'name' => t('unpublish post'),
+ 'name' => t('unpublish content'),
),
'node_make_sticky_action' => array(
'property' => 'sticky',
'expected' => 1,
- 'name' => t('make post sticky'),
+ 'name' => t('make content sticky'),
),
'node_make_unsticky_action' => array(
'property' => 'sticky',
'expected' => 0,
- 'name' => t('make post unsticky'),
+ 'name' => t('make content unsticky'),
),
'node_promote_action' => array(
'property' => 'promote',
'expected' => 1,
- 'name' => t('promote post to front page'),
+ 'name' => t('promote content to front page'),
),
'node_unpromote_action' => array(
'property' => 'promote',
'expected' => 0,
- 'name' => t('remove post from front page'),
+ 'name' => t('remove content from front page'),
),
);
return $info[$action];
@@ -138,41 +138,161 @@ class TriggerCronTestCase extends DrupalWebTestCase {
// Create an administrative user.
$test_user = $this->drupalCreateUser(array('administer actions'));
$this->drupalLogin($test_user);
-
+
// Assign a non-configurable action to the cron run trigger.
$edit = array('aid' => md5('trigger_test_system_cron_action'));
- $this->drupalPost('admin/structure/trigger/cron', $edit, t('Assign'));
-
+ $this->drupalPost('admin/structure/trigger/system', $edit, t('Assign'));
+
// Assign a configurable action to the cron trigger.
$hash = md5('trigger_test_system_cron_conf_action');
- $action_description = $this->randomName();
+ $action_label = $this->randomName();
$edit = array(
- 'actions_description' => $action_description,
- 'subject' => $action_description,
+ 'actions_label' => $action_label,
+ 'subject' => $action_label,
);
$this->drupalPost('admin/config/system/actions/configure/' . $hash, $edit, t('Save'));
$edit = array('aid' => md5('1'));
- $this->drupalPost('admin/structure/trigger/cron', $edit, t('Assign'));
-
+ $this->drupalPost('admin/structure/trigger/system', $edit, t('Assign'));
+
// Add a second configurable action to the cron trigger.
- $action_description = $this->randomName();
+ $action_label = $this->randomName();
$edit = array(
- 'actions_description' => $action_description,
- 'subject' => $action_description,
+ 'actions_label' => $action_label,
+ 'subject' => $action_label,
);
$this->drupalPost('admin/config/system/actions/configure/' . $hash, $edit, t('Save'));
$edit = array('aid' => md5('2'));
- $this->drupalPost('admin/structure/trigger/cron', $edit, t('Assign'));
-
+ $this->drupalPost('admin/structure/trigger/system', $edit, t('Assign'));
+
// Force a cron run.
drupal_cron_run();
-
+
// Make sure the non-configurable action has fired.
$action_run = variable_get('trigger_test_system_cron_action', FALSE);
$this->assertTrue($action_run, t('Check that the cron run triggered the test action.'));
-
+
// Make sure that both configurable actions have fired.
$action_run = variable_get('trigger_test_system_cron_conf_action', 0) == 2;
$this->assertTrue($action_run, t('Check that the cron run triggered both complex actions.'));
}
}
+
+/**
+ * Test other triggers.
+ */
+class TriggerOtherTestCase extends DrupalWebTestCase {
+ var $_cleanup_roles = array();
+ var $_cleanup_users = array();
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Trigger other actions',
+ 'description' => 'Test triggering of user, comment, taxonomy actions.' ,
+ 'group' => 'Trigger',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('trigger', 'trigger_test');
+ }
+
+ /**
+ * Test triggering on user create.
+ */
+ function testActionsUser() {
+ // Assign an action to the create user trigger.
+ $test_user = $this->drupalCreateUser(array('administer actions'));
+ $this->drupalLogin($test_user);
+ $action_id = 'trigger_test_generic_action';
+ $hash = md5($action_id);
+ $edit = array('aid' => $hash);
+ $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'));
+
+ // Set action variable to FALSE.
+ variable_set( $action_id, FALSE );
+
+ // Create an unblocked user
+ $web_user = $this->drupalCreateUser(array('administer users'));
+ $this->drupalLogin($web_user);
+ $name = $this->randomName();
+ $pass = user_password();
+ $edit = array();
+ $edit['name'] = $name;
+ $edit['mail'] = $name . '@example.com';
+ $edit['pass[pass1]'] = $pass;
+ $edit['pass[pass2]'] = $pass;
+ $edit['status'] = 1;
+ $this->drupalPost('admin/people/create', $edit, t('Create new account'));
+
+ // Verify that the action variable has been set.
+ $this->assertTrue(variable_get($action_id, FALSE), t('Check that creating a user triggered the test action.'));
+
+ // Reset the action variable.
+ variable_set( $action_id, FALSE );
+ }
+
+ /**
+ * Test triggering on comment save.
+ */
+ function testActionsComment() {
+ // Assign an action to the comment save trigger.
+ $test_user = $this->drupalCreateUser(array('administer actions'));
+ $this->drupalLogin($test_user);
+ $action_id = 'trigger_test_generic_action';
+ $hash = md5($action_id);
+ $edit = array('aid' => $hash);
+ $this->drupalPost('admin/structure/trigger/comment', $edit, t('Assign'));
+
+ // Set action variable to FALSE.
+ variable_set( $action_id, FALSE );
+
+ // Create a node and add a comment to it.
+ $web_user = $this->drupalCreateUser(array('create article content', 'access content', 'post comments without approval', 'post comments'));
+ $this->drupalLogin($web_user);
+ $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
+ $edit = array();
+ $edit['subject'] = $this->randomName(10);
+ $edit['comment'] = $this->randomName(10) . ' ' . $this->randomName(10);
+ $this->drupalGet('comment/reply/' . $node->nid);
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Verify that the action variable has been set.
+ $this->assertTrue(variable_get($action_id, FALSE), t('Check that creating a comment triggered the action.'));
+ }
+
+ /**
+ * Test triggering on taxonomy new term.
+ */
+ function testActionsTaxonomy() {
+ // Assign an action to the taxonomy term save trigger.
+ $test_user = $this->drupalCreateUser(array('administer actions'));
+ $this->drupalLogin($test_user);
+ $action_id = 'trigger_test_generic_action';
+ $hash = md5($action_id);
+ $edit = array('aid' => $hash);
+ $this->drupalPost('admin/structure/trigger/taxonomy', $edit, t('Assign'));
+
+ // Set action variable to FALSE.
+ variable_set( $action_id, FALSE );
+
+ // Create a taxonomy vocabulary and add a term to it.
+
+ // Create a vocabulary.
+ $vocabulary = new stdClass();
+ $vocabulary->name = $this->randomName();
+ $vocabulary->description = $this->randomName();
+ $vocabulary->machine_name = drupal_strtolower($this->randomName());
+ $vocabulary->help = '';
+ $vocabulary->nodes = array('article' => 'article');
+ $vocabulary->weight = mt_rand(0, 10);
+ taxonomy_vocabulary_save($vocabulary);
+
+ $term = new stdClass();
+ $term->name = $this->randomName();
+ $term->vid = $vocabulary->vid;
+ taxonomy_term_save($term);
+
+ // Verify that the action variable has been set.
+ $this->assertTrue(variable_get($action_id, FALSE), t('Check that creating a taxonomy term triggered the action.'));
+ }
+}
diff --git a/modules/user/user.module b/modules/user/user.module
index 2c2c8896d..9cb594c73 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -2925,45 +2925,15 @@ function user_image_style_save($style) {
}
/**
- * Implement hook_hook_info().
- */
-function user_hook_info() {
- return array(
- 'user' => array(
- 'user' => array(
- 'insert' => array(
- 'runs when' => t('After a user account has been created'),
- ),
- 'update' => array(
- 'runs when' => t("After a user's profile has been updated"),
- ),
- 'delete' => array(
- 'runs when' => t('After a user has been deleted')
- ),
- 'login' => array(
- 'runs when' => t('After a user has logged in')
- ),
- 'logout' => array(
- 'runs when' => t('After a user has logged out')
- ),
- 'view' => array(
- 'runs when' => t("When a user's profile is being viewed")
- ),
- ),
- ),
- );
-}
-
-/**
* Implement hook_action_info().
*/
function user_action_info() {
return array(
'user_block_user_action' => array(
- 'description' => t('Block current user'),
+ 'label' => t('Block current user'),
'type' => 'user',
'configurable' => FALSE,
- 'hooks' => array(),
+ 'triggers' => array(),
),
);
}