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 01:34:24 -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 01:34:24 -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 01:34:25 -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 01:34:25 -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 01:34:25 -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 01:34:25 -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 01:34:25 -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('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 01:34:26 -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 01:34:27 -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 01:34:27 -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(), ), ); }