Index: includes/actions.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/actions.inc,v retrieving revision 1.30 diff -u -p -r1.30 actions.inc --- includes/actions.inc 1 Sep 2009 16:50:11 -0000 1.30 +++ includes/actions.inc 2 Sep 2009 02:12:16 -0000 @@ -7,35 +7,31 @@ */ /** - * Perform a given list of actions by executing their callback functions. + * Performs a given list of actions by executing their callback functions. * - * Given the IDs of actions to perform, find out what the callbacks - * for the actions are by querying the database. Then call each callback - * using the function call $function($object, $context, $a1, $a2) - * where $function is the name of a function written in compliance with - * the action specification; that is, foo($object, $context). + * Given the IDs of actions to perform, this function finds out what the + * callback functions for the actions are by querying the database. Then + * it calls each callback using the function call $function($object, $context, + * $a1, $a2), passing the input arguments of this function (see below) to the + * action function. * * @param $action_ids - * The ID of the action to perform. Can be a single action ID or an array - * of IDs. IDs of instances will be numeric; IDs of singletons will be - * function names. + * The IDs of the actions to perform. Can be a single action ID or an array + * of IDs. IDs of configurable actions must be given as numeric action IDs; + * IDs of non-configurable actions may be given as action function names. * @param $object - * Parameter that will be passed along to the callback. Typically the - * object that the action will act on; a node, user or comment object. + * The object that the action will act on: a node, user, or comment object. * @param $context - * Parameter that will be passed along to the callback. $context is a - * keyed array containing extra information about what is currently - * happening at the time of the call. Typically $context['hook'] and - * $context['op'] will tell which hook-op combination resulted in this - * call to actions_do(). + * Associative array containing extra information about what triggered + * the action call, with $context['hook'] giving the name of the hook + * that resulted in this call to actions_do(). * @param $a1 - * Parameter that will be passed along to the callback. + * Passed along to the callback. * @param $a2 - * Parameter that will be passed along to the callback. - * + * Passed along to the callback. * @return - * An associative array containing the result of the function that - * performs the action, keyed on action ID. + * An associative array containing the results of the functions that + * perform the actions, keyed on action ID. */ function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 = NULL) { // $stack tracks the number of recursive calls. @@ -109,49 +105,20 @@ function actions_do($action_ids, $object } /** - * Discover all action functions by invoking hook_action_info(). + * Discovers all available actions by invoking hook_action_info(). * - * @code - * mymodule_action_info() { - * return array( - * 'mymodule_functiondescription_action' => array( - * 'type' => 'node', - * 'description' => t('Save node'), - * 'configurable' => FALSE, - * 'hooks' => array( - * 'node' => array('delete', 'insert', 'update', 'view'), - * 'comment' => array('delete', 'insert', 'update', 'view'), - * ) - * ) - * ); - * } - * @endcode - * - * The description is used in presenting possible actions to the user for - * configuration. The type is used to present these actions in a logical - * grouping and to denote context. Some types are 'node', 'user', 'comment', - * and 'system'. If an action is configurable it will provide form, - * validation and submission functions. The hooks the action supports - * are declared in the 'hooks' array. + * This function contrasts with actions_get_all_actions(); see the + * documentation of actions_get_all_actions() for an explanation. * * @param $reset * Reset the action info static cache. - * * @return - * An associative array keyed on function name. The value of each key is - * an array containing information about the action, such as type of - * action and description of the action, e.g.: - * @code - * $actions['node_publish_action'] = array( - * 'type' => 'node', - * 'description' => t('Publish post'), - * 'configurable' => FALSE, - * 'hooks' => array( - * 'node' => array('presave', 'insert', 'update', 'view'), - * 'comment' => array('delete', 'insert', 'update', 'view'), - * ), - * ); - * @endcode + * An associative array keyed on action function name, with the same format + * as the return value of hook_action_info(), containing all + * modules' hook_action_info() return values as modified by any + * hook_action_info_alter() implementations. + * + * @see hook_action_info() */ function actions_list($reset = FALSE) { static $actions; @@ -165,19 +132,24 @@ function actions_list($reset = FALSE) { } /** - * Retrieve all action instances from the database. + * Retrieves all action instances from the database. * - * Compare with actions_list() which gathers actions by invoking - * hook_action_info(). The two are synchronized by visiting - * /admin/structure/actions (when actions.module is enabled) which runs - * actions_synchronize(). + * This function differs from the actions_list() function, which gathers + * actions by invoking hook_action_info(). The actions returned by this + * function and the actions returned by actions_list() are partially + * synchronized. Non-configurable actions from hook_action_info() + * implementations are put into the database when actions_synchronize() is + * called, which happens when admin/config/system/actions is visited. Configurable + * actions are not added to the database until they are configured in the + * user interface, in which case a database row is created for each + * configuration of each action. * * @return - * Associative array keyed by action ID. Each value is an associative array - * with keys 'callback', 'description', 'type' and 'configurable'. + * Associative array keyed by numeric action ID. Each value is an associative + * array with keys 'callback', 'label', 'type' and 'configurable'. */ function actions_get_all_actions() { - $actions = db_query("SELECT aid, type, callback, parameters, description FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC); + $actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC); foreach ($actions as &$action) { $action['configurable'] = (bool) $action['parameters']; unset($action['parameters']); @@ -187,28 +159,26 @@ function actions_get_all_actions() { } /** - * Create an associative array keyed by md5 hashes of function names. + * Creates an associative array keyed by md5 hashes of function names or IDs. * * Hashes are used to prevent actual function names from going out into HTML * forms and coming back. * * @param $actions - * An associative array with function names as keys and associative arrays - * with keys 'description', 'type', etc. as values. Generally the output of - * actions_list() or actions_get_all_actions() is given as input to this - * function. - * + * An associative array with function names or action IDs as keys + * and associative arrays with keys 'label', 'type', etc. as values. + * This is usually the output of actions_list() or actions_get_all_actions(). * @return - * An associative array keyed on md5 hash of function names. The value of - * each key is an associative array of function, description, and type for - * the action. + * An associative array whose keys are md5 hashes of the input array keys, and + * whose corresponding values are associative arrays with components + * 'callback', 'label', 'type', and 'configurable' from the input array. */ function actions_actions_map($actions) { $actions_map = array(); foreach ($actions as $callback => $array) { $key = md5($callback); $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback; - $actions_map[$key]['description'] = $array['description']; + $actions_map[$key]['label'] = $array['label']; $actions_map[$key]['type'] = $array['type']; $actions_map[$key]['configurable'] = $array['configurable']; } @@ -216,17 +186,19 @@ function actions_actions_map($actions) { } /** - * Given an md5 hash of a function name, return the function name. + * Given an md5 hash of an action array key, returns the key (function or ID). * - * Faster than actions_actions_map() when you only need the function name. + * Faster than actions_actions_map() when you only need the function name or ID. * * @param $hash - * MD5 hash of a function name. - * + * MD5 hash of a function name or action ID array key. The array key + * is a key into the return value of actions_list() (array key is the action + * function name) or actions_get_all_actions() (array key is the action ID). * @return - * The corresponding function name or FALSE if none is found. + * The corresponding array key, or FALSE if no match is found. */ function actions_function_lookup($hash) { + // Check for a function name match. $actions_list = actions_list(); foreach ($actions_list as $function => $array) { if (md5($function) == $hash) { @@ -234,26 +206,26 @@ function actions_function_lookup($hash) } } - // Must be an instance; must check database. + // Must be a configurable action; check database. return db_query("SELECT aid FROM {actions} WHERE MD5(aid) = :hash AND parameters <> ''", array(':hash' => $hash))->fetchField(); } /** - * Synchronize actions that are provided by modules. + * Synchronizes actions that are provided by modules in hook_action_info(). * - * Actions provided by modules are synchronized with actions that are stored in - * the actions table. This is necessary so that actions that do not require - * configuration can receive action IDs. This is not necessarily the best - * approach, but it is the most straightforward. + * Actions provided by modules in hook_action_info() implementations + * are synchronized with actions that are stored in the actions + * database table. This is necessary so that actions that do not + * require configuration can receive action IDs. * * @param $delete_orphans - * Boolean if TRUE, any actions that exist in the database but are no longer + * If TRUE, any actions that exist in the database but are no longer * found in the code (for example, because the module that provides them has * been disabled) will be deleted. */ function actions_synchronize($delete_orphans = FALSE) { $actions_in_code = actions_list(TRUE); - $actions_in_db = db_query("SELECT aid, callback, description FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC); + $actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC); // Go through all the actions provided by modules. foreach ($actions_in_code as $callback => $array) { @@ -272,10 +244,10 @@ function actions_synchronize($delete_orp 'type' => $array['type'], 'callback' => $callback, 'parameters' => '', - 'description' => $array['description'], + 'label' => $array['label'], )) ->execute(); - watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['description']))); + watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['label']))); } } } @@ -285,10 +257,10 @@ function actions_synchronize($delete_orp $orphaned = array_keys($actions_in_db); if ($delete_orphans) { - $actions = db_query('SELECT aid, description FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll(); + $actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll(); foreach ($actions as $action) { actions_delete($action->aid); - watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->description))); + watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->label))); } } else { @@ -301,7 +273,7 @@ function actions_synchronize($delete_orp } /** - * Save an action and its associated user-supplied parameter values to the database. + * Saves an action and its user-supplied parameter values to the database. * * @param $function * The name of the function to be called when this action is performed. @@ -311,16 +283,15 @@ function actions_synchronize($delete_orp * @param $params * An associative array with parameter names as keys and parameter values as * values. - * @param $desc - * A user-supplied description of this particular action, e.g., 'Send e-mail + * @param $label + * A user-supplied label of this particular action, e.g., 'Send e-mail * to Jim'. * @param $aid * The ID of this action. If omitted, a new action is created. - * * @return * The ID of the action. */ -function actions_save($function, $type, $params, $desc, $aid = NULL) { +function actions_save($function, $type, $params, $label, $aid = NULL) { // aid is the callback for singleton actions so we need to keep a separate // table for numeric aids. if (!$aid) { @@ -333,29 +304,28 @@ function actions_save($function, $type, 'callback' => $function, 'type' => $type, 'parameters' => serialize($params), - 'description' => $desc, + 'label' => $label, )) ->execute(); - watchdog('actions', 'Action %action saved.', array('%action' => $desc)); + watchdog('actions', 'Action %action saved.', array('%action' => $label)); return $aid; } /** - * Retrieve a single action from the database. + * Retrieves a single action from the database. * * @param $aid * The ID of the action to retrieve. - * * @return * The appropriate action row from the database as an object. */ function actions_load($aid) { - return db_query("SELECT aid, type, callback, parameters, description FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject(); + return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject(); } /** - * Delete a single action from the database. + * Deletes a single action from the database. * * @param $aid * The ID of the action to delete. Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.341 diff -u -p -r1.341 menu.inc --- includes/menu.inc 24 Aug 2009 01:49:41 -0000 1.341 +++ includes/menu.inc 2 Sep 2009 02:12:16 -0000 @@ -2784,6 +2784,9 @@ function _menu_router_save($menu, $masks foreach ($menu as $path => $item) { // Fill in insert object values. + if (!isset($item['title'])) { + $foo = 'bar'; + } $insert->values(array( 'path' => $item['path'], 'load_functions' => $item['load_functions'], Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.765 diff -u -p -r1.765 comment.module --- modules/comment/comment.module 29 Aug 2009 10:46:40 -0000 1.765 +++ modules/comment/comment.module 2 Sep 2009 02:12:17 -0000 @@ -2282,49 +2282,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'), ) ); } Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1118 diff -u -p -r1.1118 node.module --- modules/node/node.module 1 Sep 2009 16:50:11 -0000 1.1118 +++ modules/node/node.module 2 Sep 2009 02:12:17 -0000 @@ -2790,30 +2790,16 @@ function node_forms() { } /** - * Implement hook_hook_info(). + * Format the "Submitted by username on date/time" for each node + * + * @ingroup themeable */ -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') - ), - ), - ), - ); +function theme_node_submitted($node) { + return t('Submitted by !username on @datetime', + array( + '!username' => theme('username', $node), + '@datetime' => format_date($node->created), + )); } /** @@ -2823,90 +2809,84 @@ 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'), ), ); } Index: modules/simpletest/tests/actions.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/actions.test,v retrieving revision 1.7 diff -u -p -r1.7 actions.test --- modules/simpletest/tests/actions.test 1 Sep 2009 16:50:12 -0000 1.7 +++ modules/simpletest/tests/actions.test 2 Sep 2009 02:12:17 -0000 @@ -26,27 +26,27 @@ class ActionsConfigurationTestCase exten // 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 exten $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.')); } Index: modules/simpletest/tests/actions_loop_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/actions_loop_test.module,v retrieving revision 1.1 diff -u -p -r1.1 actions_loop_test.module --- modules/simpletest/tests/actions_loop_test.module 22 Aug 2009 16:16:19 -0000 1.1 +++ modules/simpletest/tests/actions_loop_test.module 2 Sep 2009 02:12:17 -0000 @@ -2,15 +2,13 @@ // $Id: actions_loop_test.module,v 1.1 2009/08/22 16:16:19 webchick Exp $ /** - * 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(arra } // 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'), ), ); } Index: modules/system/system.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v retrieving revision 1.197 diff -u -p -r1.197 system.admin.inc --- modules/system/system.admin.inc 26 Aug 2009 10:53:45 -0000 1.197 +++ modules/system/system.admin.inc 2 Sep 2009 02:12:17 -0000 @@ -2294,3 +2294,272 @@ function theme_system_themes_form($form) $output .= drupal_render_children($form); return $output; } + + +/** + * 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['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' => '

' . t('Actions available to Drupal:') . '

'); + $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_state, $options = array()) { + $form['parent'] = array( + '#type' => 'fieldset', + '#title' => t('Make a new advanced action available'), + '#prefix' => '
', + '#suffix' => '
', + ); + $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_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_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; + } + } + } + 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. + * + * @ingroup forms + * @see system_actions_delete_form_submit() + */ +function system_actions_delete_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'); +} Index: modules/system/system.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v retrieving revision 1.71 diff -u -p -r1.71 system.api.php --- modules/system/system.api.php 1 Sep 2009 17:40:27 -0000 1.71 +++ modules/system/system.api.php 2 Sep 2009 02:12:18 -0000 @@ -669,7 +669,7 @@ function hook_image_toolkits() { * An array containing the message data. Keys in this array include: * - 'id': * The drupal_mail() id of the message. Look at module source code or - * drupal_mail() for possible id values. + * drupal_mail() for possible id values. * - 'to': * The address or addresses the message will be sent to. The * formatting of this string must comply with RFC 2822. @@ -681,7 +681,7 @@ function hook_image_toolkits() { * characters, or the email may not be sent properly. * - 'body': * An array of strings containing the message text. The message body is - * created by concatenating the individual array strings into a single text + * created by concatenating the individual array strings into a single text * string using "\n\n" as a separator. * - 'headers': * Associative array containing mail headers, such as From, Sender, @@ -731,7 +731,7 @@ function hook_system_info_alter(&$info, * Permissions are checked using user_access(). * * For a detailed usage example, see page_example.module. - * + * * @return * An array of which permission names are the keys and their corresponding * values are descriptions of each permission. @@ -2189,7 +2189,7 @@ function hook_profile_tasks() { 'myprofile_final_site_setup' => array( ), ); - return $tasks; + return $tasks; } /** @@ -2215,5 +2215,91 @@ function hook_file_mimetype_mapping_alte } /** + * 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: (1) an action definition + * (returned by this hook), (2) a function which performs the action + * (which by convention is named module + '_' + description of what + * the function does + '_action'), and (3) 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': (required) The type of object this action acts upon, + * Core actions have types 'node', 'user', 'comment', and 'system'. + * - 'label': (required) The human-readable name of the action, + * which should be passed through the t() function for translation. + * - 'configurable': (required) 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': (required) An array of the events (that is, hook) that + * can trigger this action. 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, the only behavior that is recognized by + * the trigger module is '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". */ Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.383 diff -u -p -r1.383 system.install --- modules/system/system.install 29 Aug 2009 10:38:58 -0000 1.383 +++ modules/system/system.install 2 Sep 2009 02:12:18 -0000 @@ -550,8 +550,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, @@ -2459,6 +2459,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. */ Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.784 diff -u -p -r1.784 system.module --- modules/system/system.module 1 Sep 2009 16:50:12 -0000 1.784 +++ modules/system/system.module 2 Sep 2009 02:12:18 -0000 @@ -2440,336 +2440,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'), + 'label' => t('Send e-mail'), 'type' => 'system', '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'), + 'label' => t('Ban IP address of current user'), 'type' => 'user', 'configurable' => FALSE, - 'hooks' => array(), + 'triggers' => array(), ), 'system_goto_action' => array( - 'description' => t('Redirect to URL'), + 'label' => t('Redirect to URL'), 'type' => 'system', 'configurable' => TRUE, - 'hooks' => array( - 'node' => array('view', 'insert', 'update', 'delete'), - 'comment' => array('view', 'insert', 'update', 'delete'), - 'user' => array('view', 'insert', 'update', 'delete', 'login'), - ) + 'triggers' => array('any'), ) ); } /** - * 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' => '

' . t('Actions available to Drupal:') . '

'); - $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_state, $options = array()) { - $form['parent'] = array( - '#type' => 'fieldset', - '#title' => t('Make a new advanced action available'), - '#prefix' => '
', - '#suffix' => '
', - ); - $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_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_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') - ); -} - -/** - * 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() @@ -3022,7 +2724,6 @@ function theme_system_compact_link() { return $output; } - /** * Send Drupal and the major version number in the META GENERATOR HTML. * Index: modules/taxonomy/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v retrieving revision 1.507 diff -u -p -r1.507 taxonomy.module --- modules/taxonomy/taxonomy.module 27 Aug 2009 20:48:31 -0000 1.507 +++ modules/taxonomy/taxonomy.module 2 Sep 2009 02:12:18 -0000 @@ -1710,27 +1710,6 @@ function taxonomy_implode_tags($tags, $v } /** - * 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: Index: modules/trigger/trigger.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.admin.inc,v retrieving revision 1.16 diff -u -p -r1.16 trigger.admin.inc --- modules/trigger/trigger.admin.inc 1 Sep 2009 16:50:12 -0000 1.16 +++ modules/trigger/trigger.admin.inc 2 Sep 2009 02:12:18 -0000 @@ -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']); } } } @@ -40,24 +40,26 @@ function trigger_assign($type = NULL) { * Confirm removal of an assigned action. * * @param $hook - * @param $op + * @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 $aid * The action ID. * @ingroup forms * @see trigger_unassign_submit() */ -function trigger_unassign($form_state, $hook = NULL, $op = NULL, $aid = NULL) { - if (!($hook && $op && $aid)) { - drupal_goto('admin/structure/trigger/assign'); +function trigger_unassign($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_state, $ $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,29 @@ function trigger_unassign_submit($form, } /** - * 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_state, $hook, $op, $description) { - $form['hook'] = array( +function trigger_assign_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 +131,53 @@ function trigger_assign_form($form_state $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' => "
", '#suffix' => '
', ); // 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. Add action.', array('@link' => url('admin/config/system/actions/manage'))) ); } @@ -191,13 +193,12 @@ function trigger_assign_form_validate($f $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 +211,53 @@ function trigger_assign_form_submit($for 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. + + // TODO: need to support the new comment_presave hook here too? + $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 +269,12 @@ function trigger_assign_form_submit($for 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 +289,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(); - } -} Index: modules/trigger/trigger.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.api.php,v retrieving revision 1.4 diff -u -p -r1.4 trigger.api.php --- modules/trigger/trigger.api.php 29 May 2009 19:15:08 -0000 1.4 +++ modules/trigger/trigger.api.php 2 Sep 2009 02:12:18 -0000 @@ -12,142 +12,51 @@ */ /** - * 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') ), ), ); Index: modules/trigger/trigger.install =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.install,v retrieving revision 1.11 diff -u -p -r1.11 trigger.install --- modules/trigger/trigger.install 1 Jun 2009 22:07:10 -0000 1.11 +++ modules/trigger/trigger.install 2 Sep 2009 02:12:18 -0000 @@ -37,14 +37,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', @@ -60,7 +53,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'), ), @@ -68,4 +61,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; +} Index: modules/trigger/trigger.module =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.module,v retrieving revision 1.47 diff -u -p -r1.47 trigger.module --- modules/trigger/trigger.module 1 Sep 2009 16:50:12 -0000 1.47 +++ modules/trigger/trigger.module 2 Sep 2009 02:12:19 -0000 @@ -8,16 +8,16 @@ */ /** - * Implement hook_help(). + * Implements hook_help(). */ function trigger_help($path, $arg) { - $explanation = '

' . 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 Actions settings page contains a list of existing actions and provides the ability to create and configure additional actions.', array('@url' => url('admin/config/system/actions'))) . '

'; + $explanation = '

' . 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 Actions settings page contains a list of existing actions and provides the ability to create and configure additional actions.', array('@url' => url('admin/config/system/actions'))) . '

'; switch ($path) { case 'admin/structure/trigger/comment': return $explanation . '

' . 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.') . '

'; case 'admin/structure/trigger/node': - return $explanation . '

' . 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.') . '

'; - case 'admin/structure/trigger/cron': + return $explanation . '

' . 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.') . '

'; + case 'admin/structure/trigger/system': return $explanation . '

' . t('Below you can assign actions to run during each pass of a cron maintenance task.', array('@cron' => url('admin/reports/status'))) . '

'; case 'admin/structure/trigger/taxonomy': return $explanation . '

' . 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.') . '

'; @@ -32,51 +32,41 @@ function trigger_help($path, $arg) { } /** - * Implement hook_menu(). + * Implements hook_menu(). */ 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', @@ -91,26 +81,101 @@ function trigger_menu() { return $items; } + /** + * Implements 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") + ), + ), + ); + } + /** - * Get the aids of actions to be executed for a hook-op combination. + * 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); } /** - * Implement hook_theme(). + * Implements hook_theme(). */ function trigger_theme() { return array( @@ -122,16 +187,17 @@ function trigger_theme() { } /** - * Implement hook_forms(). We reuse code by using the - * same assignment form definition for each node-op combination. + * Implements hook_forms(). + * + * We reuse 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,10 +205,12 @@ function trigger_forms() { } /** + * 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 the node hook, the - * user object is not available since the node hook doesn't pass it. + * 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 @@ -154,10 +222,8 @@ function trigger_forms() { */ function _trigger_normalize_node_context($type, $node) { 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. + // Note that comment-type actions are not supported in node contexts, + // because we wouldn't know which comment to choose. // An action that works on users is being called in a node context. // Load the user object of the node's author. @@ -167,33 +233,41 @@ function _trigger_normalize_node_context } /** - * 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); @@ -209,41 +283,43 @@ function _trigger_node($node, $op, $a3 = } /** - * Implement hook_node_view(). + * Implements hook_node_view() to call triggered actions. */ function trigger_node_view($node, $build_mode) { - _trigger_node($node, 'view', $build_mode); + _trigger_node($node, 'node_view', $build_mode); } /** - * Implement hook_node_update(). + * Implements hook_node_update() to call triggered actions. */ function trigger_node_update($node) { - _trigger_node($node, 'update'); + _trigger_node($node, 'node_update'); } /** - * Implement hook_node_presave(). + * Implements hook_node_presave() to call triggered actions. */ function trigger_node_presave($node) { - _trigger_node($node, 'presave'); + _trigger_node($node, 'node_presave'); } /** - * Implement hook_node_insert(). + * Implements hook_node_insert() to call triggered actions. */ function trigger_node_insert($node) { - _trigger_node($node, 'insert'); + _trigger_node($node, 'node_insert'); } /** - * Implement hook_node_delete(). + * Implements hook_node_delete() to call triggered actions. */ function trigger_node_delete($node) { - _trigger_node($node, 'delete'); + _trigger_node($node, 'node_delete'); } /** + * 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 @@ -270,54 +346,54 @@ function _trigger_normalize_comment_cont } /** - * Implement hook_comment_insert(). + * Implements hook_comment_insert() to call triggered actions. */ function trigger_comment_insert($comment) { - _trigger_comment($comment, 'insert'); + _trigger_comment($comment, 'comment_insert'); } /** - * Implement hook_comment_update(). + * Implements hook_comment_update() to call triggered actions. */ function trigger_comment_update($comment) { - _trigger_comment($comment, 'update'); + _trigger_comment($comment, 'comment_update'); } /** - * Implement hook_comment_delete(). + * Implements hook_comment_delete() to call triggered actions. */ function trigger_comment_delete($comment) { - _trigger_comment($comment, 'delete'); + _trigger_comment($comment, 'comment_delete'); } /** - * Implement hook_comment_view(). + * Implements hook_comment_view() to call triggered actions. */ 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) { + foreach ($aids as $aid => $info) { + $type = $info['type']; if ($type != 'comment') { if (!isset($objects[$type])) { $objects[$type] = _trigger_normalize_comment_context($type, $a1); @@ -335,13 +411,13 @@ function _trigger_comment($a1, $op) { } /** - * Implement hook_cron(). + * Implements hook_cron() to call triggered actions. */ 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,6 +425,8 @@ function trigger_cron() { } /** + * 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 @@ -364,10 +442,8 @@ function trigger_cron() { */ function _trigger_normalize_user_context($type, $account) { 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. + // Note that comment-type actions are not supported in user contexts, + // because we wouldn't know which comment to choose. // An action that works with nodes is being called in a user context. // If a single node is being viewed, return the node. @@ -380,67 +456,66 @@ function _trigger_normalize_user_context } /** - * trigger_user_login + * Implements hook_user_login() to call triggered actions. */ function trigger_user_login(&$edit, $account, $category) { - _trigger_user('login', $edit, $account, $category); + _trigger_user('user_login', $edit, $account, $category); } /** - * Implement hook_user_logout(). + * Implements hook_user_logout() to call triggered actions. */ function trigger_user_logout($account) { - _trigger_user('logout', $edit = NULL, $account); + _trigger_user('user_logout', $edit = NULL, $account); } /** - * Implement hook_user_insert(). + * Implements hook_user_insert() to call triggered actions. */ function trigger_user_insert(&$edit, $account, $category) { - _trigger_user('insert', $edit, $account, $category); + _trigger_user('user_insert', $edit, $account, $category); } /** - * Implement hook_user_update(). + * Implements hook_user_update() to call triggered actions. */ function trigger_user_update(&$edit, $account, $category) { - _trigger_user('update', $edit, $account, $category); + _trigger_user('user_update', $edit, $account, $category); } /** - * Implement hook_user_cancel(). + * Implements hook_user_cancel() to call triggered actions. */ 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; } } /** - * Implement hook_user_view(). + * Implements hook_user_view() to call triggered actions. */ 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,66 @@ function _trigger_user($op, &$edit, $acc } /** - * 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. + * Implements hook_taxonomy_term_insert to call triggered actions. */ -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]; - } +/** + * Implements hook_taxonomy_term_update to call triggered actions. + */ +function trigger_taxonomy_term_update($term) { + _trigger_taxonomy('taxonomy_term_update', (array) $term); } /** - * Implement hook_actions_delete(). + * Implements hook_taxonomy_term_delete to call triggered actions. + */ +function trigger_taxonomy_term_delete($term) { + _trigger_taxonomy('taxonomy_term_delete', (array) $term); +} + +/** + * Implements hook_actions_delete(). * - * Remove all trigger entries for the given action, when deleted. + * Removes all trigger entries for the given action, when 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; +} Index: modules/trigger/trigger.test =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.test,v retrieving revision 1.17 diff -u -p -r1.17 trigger.test --- modules/trigger/trigger.test 1 Sep 2009 16:50:12 -0000 1.17 +++ modules/trigger/trigger.test 2 Sep 2009 02:12:19 -0000 @@ -59,7 +59,7 @@ class TriggerContentTestCase extends Dru $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 Dru '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 Drupal // 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.')); + } +} Index: modules/trigger/tests/trigger_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/tests/trigger_test.module,v retrieving revision 1.2 diff -u -p -r1.2 trigger_test.module --- modules/trigger/tests/trigger_test.module 31 May 2009 03:12:19 -0000 1.2 +++ modules/trigger/tests/trigger_test.module 2 Sep 2009 02:12:19 -0000 @@ -10,28 +10,70 @@ * 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'), + ) + ) + ); +} + +/** * Action fired during the "cron run" trigger test. */ function trigger_test_system_cron_action() { @@ -40,7 +82,7 @@ function trigger_test_system_cron_action } /** - * Implement a configurable Drupal action. + * Implements a configurable Drupal action. */ function trigger_test_system_cron_conf_action($object, $context) { // Indicate successful execution by incrementing a persistent variable. @@ -74,3 +116,19 @@ function trigger_test_system_cron_conf_a ); 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); +} Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.1038 diff -u -p -r1.1038 user.module --- modules/user/user.module 1 Sep 2009 17:40:28 -0000 1.1038 +++ modules/user/user.module 2 Sep 2009 02:12:19 -0000 @@ -2220,7 +2220,7 @@ function user_roles($membersonly = FALSE * @param $role * A string with the role name, or an integer with the role ID. * @return - * A fully-loaded role object if a role with the given name or ID + * A fully-loaded role object if a role with the given name or ID * exists, FALSE otherwise. */ function user_role_load($role) { @@ -2240,7 +2240,7 @@ function user_role_load($role) { * @return * Status constant indicating if role was created or updated. * Failure to write the user role record will return FALSE. Otherwise. - * SAVED_NEW or SAVED_UPDATED is returned depending on the operation + * SAVED_NEW or SAVED_UPDATED is returned depending on the operation * performed. */ function user_role_save($role) { @@ -2294,7 +2294,7 @@ function user_role_delete($role) { * @param $permissions * An array of permissions strings. * @param $merge - * A boolean indicating whether to add permissions or to merge + * A boolean indicating whether to add permissions or to merge * with all existing permissions. */ function user_role_set_permissions($role, array $permissions = array(), $merge = FALSE) { @@ -2851,45 +2851,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(), ), ); }