Index: modules/trigger/trigger.test =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.test,v retrieving revision 1.15 diff -u -r1.15 trigger.test --- modules/trigger/trigger.test 28 Jul 2009 19:18:08 -0000 1.15 +++ modules/trigger/trigger.test 19 Aug 2009 23:17:22 -0000 @@ -78,32 +78,32 @@ '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]; @@ -140,7 +140,7 @@ // 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'); @@ -151,7 +151,7 @@ ); $this->drupalPost('admin/settings/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(); @@ -161,7 +161,7 @@ ); $this->drupalPost('admin/settings/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(); @@ -175,3 +175,132 @@ $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); + $ourtrig = 'trigger_test_another_action'; + $hash = md5($ourtrig); + $edit = array('aid' => $hash); + $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign')); + + // reset the action variable, just in case + variable_set( $ourtrig, 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($ourtrig, FALSE), t('Check that creating a user triggered the test action.')); + + // reset the action variable + variable_set( $ourtrig, 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); + $ourtrig = 'trigger_test_another_action'; + $hash = md5($ourtrig); + $edit = array('aid' => $hash); + $this->drupalPost('admin/structure/trigger/comment', $edit, t('Assign')); + + // reset the action variable, just in case + variable_set( $ourtrig, 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($ourtrig, 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); + $ourtrig = 'trigger_test_another_action'; + $hash = md5($ourtrig); + $edit = array('aid' => $hash); + $this->drupalPost('admin/structure/trigger/taxonomy', $edit, t('Assign')); + + // reset the action variable, just in case + variable_set( $ourtrig, FALSE ); + + // Create a taxonomy vocab 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($ourtrig, FALSE), t('Check that creating a taxonomy term triggered the action.')); + + } + + +} + Index: modules/trigger/trigger.install =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.install,v retrieving revision 1.11 diff -u -r1.11 trigger.install --- modules/trigger/trigger.install 1 Jun 2009 22:07:10 -0000 1.11 +++ modules/trigger/trigger.install 19 Aug 2009 23:17:22 -0000 @@ -37,14 +37,14 @@ '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.', + 'description' => 'Primary Key: The name of the internal Drupal hook or group of triggers; 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 specific operation of the hook or gorup: for example, presave.', ), 'aid' => array( 'type' => 'varchar', Index: modules/trigger/trigger.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.api.php,v retrieving revision 1.4 diff -u -r1.4 trigger.api.php --- modules/trigger/trigger.api.php 29 May 2009 19:15:08 -0000 1.4 +++ modules/trigger/trigger.api.php 19 Aug 2009 23:17:22 -0000 @@ -12,69 +12,66 @@ */ /** - * Declare information about one or more Drupal actions. + * Declare information about actions. * - * 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.) + * 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 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 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. + * - 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'. + * - 'description': (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 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'). + * 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 associative array of the events that + * can trigger this action. The keys of this array are the trigger + * groups from hook_trigger_info(), and the values are the allowed + * individual trigger operations within that group. You can also declare + * support for any trigger by returning array('any' => TRUE) 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 a trigger is + * assigned to an action with this behavior, any node save actions + * also assigned to this trigger are moved later in the list. */ function hook_action_info() { return array( 'comment_unpublish_action' => array( - 'description' => t('Unpublish comment'), 'type' => 'comment', + 'description' => t('Unpublish comment'), 'configurable' => FALSE, - 'hooks' => array( + 'triggers' => array( 'comment' => array('insert', 'update'), ) ), 'comment_unpublish_by_keyword_action' => array( - 'description' => t('Unpublish comment containing keyword(s)'), 'type' => 'comment', + 'description' => t('Unpublish comment containing keyword(s)'), 'configurable' => TRUE, - 'hooks' => array( + 'triggers' => array( 'comment' => array('insert', 'update'), ) ) @@ -106,44 +103,52 @@ } /** - * Expose a list of triggers (events) that your module is allowing users to - * assign actions to. + * Declare triggers (events) for 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. + * 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. * - * 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. + * - The next key is the name of a group of triggers for this module, + * which can be the same as the name of the module if there is only one + * group of triggers for this module. It must be unique across all modules, + * and is never displayed to the user. + * - Within each group, each individual trigger is keyed by an operation + * 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 'runs when', 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, 'node' as the group, and defines triggers for operations + * 'insert', 'update', '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'), + 'runs when' => t('When either saving new content or updating existing content'), ), 'insert' => array( - 'runs when' => t('After saving a new post'), + 'runs when' => t('After saving new content'), ), 'update' => array( - 'runs when' => t('After saving an updated post'), + 'runs when' => t('After saving updated content'), ), 'delete' => array( - 'runs when' => t('After deleting a post') + 'runs when' => t('After deleting content') ), 'view' => array( 'runs when' => t('When content is viewed by an authenticated user') Index: modules/trigger/trigger.module =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.module,v retrieving revision 1.42 diff -u -r1.42 trigger.module --- modules/trigger/trigger.module 12 Aug 2009 12:36:05 -0000 1.42 +++ modules/trigger/trigger.module 19 Aug 2009 23:17:22 -0000 @@ -8,15 +8,15 @@ */ /** - * 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/settings/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/settings/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.') . '

'; + 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/cron': 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': @@ -24,7 +24,7 @@ case 'admin/structure/trigger/user': return $explanation . '

' . t("Below you can assign actions to run when certain user-related triggers happen. For example, you could send an e-mail to an administrator when a user account is deleted.") . '

'; case 'admin/help#trigger': - $output = '

' . t('The Trigger module provides the ability to trigger actions upon system events, such as when new content is added or when a user logs in.', array('@actions' => url('admin/settings/actions'))) . '

'; + $output = '

' . t('The trigger module provides the ability to trigger actions upon system events, such as when new content is added or when a user logs in.', array('@actions' => url('admin/settings/actions'))) . '

'; $output .= '

' . t('The combination of actions and triggers can perform many useful tasks, such as e-mailing an administrator if a user account is deleted, or automatically unpublishing comments that contain certain words. By default, there are five "contexts" of events (Comments, Content, Cron, Taxonomy, and Users), but more may be added by additional modules.') . '

'; $output .= '

' . t('For more information, see the online handbook entry for Trigger module.', array('@trigger' => 'http://drupal.org/handbook/modules/trigger/')) . '

'; return $output; @@ -32,12 +32,12 @@ } /** - * 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 callback' => 'trigger_access_check', 'access arguments' => array('node'), @@ -76,18 +76,18 @@ 'access arguments' => array('taxonomy'), 'type' => MENU_LOCAL_TASK, ); - $items['admin/structure/trigger/cron'] = array( - 'title' => 'Cron', + $items['admin/structure/trigger/system'] = array( + 'title' => 'System', 'page callback' => 'trigger_assign', - 'page arguments' => array('cron'), + 'page arguments' => array('system'), 'access arguments' => array('administer actions'), 'type' => MENU_LOCAL_TASK, ); // 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) { + $triggers = _trigger_get_all_info(); + foreach ($triggers as $module => $info) { // We've already done these. if (in_array($module, array('node', 'comment', 'user', 'system', 'taxonomy'))) { continue; @@ -102,6 +102,7 @@ 'title' => $nice_name, 'page callback' => 'trigger_assign', 'page arguments' => array($module), + 'access callback' => 'trigger_access_check', 'access arguments' => array($module), 'type' => MENU_LOCAL_TASK, ); @@ -119,32 +120,120 @@ } /** - * Access callback for menu system. + * Checks access permissions for menu system. */ function trigger_access_check($module) { return (module_exists($module) && user_access('administer actions')); } /** - * Get the aids of actions to be executed for a hook-op combination. + * Implements hook_trigger_info(). * - * @param $hook - * The name of the hook being fired. + * Defines all the triggers that this module causes triggers for. + */ +function trigger_trigger_info() { + return array( + 'node' => array( + 'node' => array( + 'presave' => array( + 'runs when' => t('When either saving new content or updating existing content'), + ), + 'insert' => array( + 'runs when' => t('After saving new content'), + ), + 'update' => array( + 'runs when' => t('After saving updated content'), + ), + 'delete' => array( + 'runs when' => t('After deleting content') + ), + 'view' => array( + 'runs when' => t('When content is viewed by an authenticated user') + ), + ), + ), + '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') + ), + ), + ), + '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') + ), + ), + ), + 'system' => array( + 'cron' => array( + 'run' => array( + 'runs when' => t('When cron runs'), + ), + ), + ), + '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") + ), + ), + ), + ); +} + +/** + * Returns the IDs of actions to be executed for a group-operation combination. + * + * @param $group + * The group of the trigger being executed. * @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. + * The trigger operation being executed. * @return - * An array of action IDs. - */ -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( - ':hook' => $hook, + * 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 + * description. + */ +function trigger_get_assigned_actions($group, $op = '') { + return db_query("SELECT ta.aid, a.type, a.description 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( + ':hook' => $group, ':op' => $op, - ))->fetchAllKeyed(); + ))->fetchAllAssoc( 'aid', PDO::FETCH_ASSOC); } /** - * Implement hook_theme(). + * Implements hook_theme(). */ function trigger_theme() { return array( @@ -156,15 +245,24 @@ } /** - * 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 + * node-op combination. */ 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'); + // cache this -- it is called several times + static $forms = NULL; + if($forms ) { + return $forms; + } + + $triggers = _trigger_get_all_info(); + $forms = array(); + foreach ($triggers as $module => $info) { + foreach ($info as $group => $stuff) { + foreach ($stuff as $op => $description) { + $forms['trigger_' . $group . '_' . $op . '_assign_form'] = array('callback' => 'trigger_assign_form'); } } } @@ -173,10 +271,12 @@ } /** + * 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 @@ -188,10 +288,8 @@ */ 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. @@ -201,9 +299,16 @@ } /** - * 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) { // Keep objects for reuse so that changes actions make to objects can persist. @@ -215,19 +320,20 @@ } $recursion[$op] = TRUE; - $aids = _trigger_get_hook_aids('node', $op); + $aids = trigger_get_assigned_actions('node', $op); if (!$aids) { return; } $context = array( - 'hook' => 'node', + 'group' => 'node', 'op' => $op, ); // 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); @@ -243,41 +349,43 @@ } /** - * 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); } /** - * Implement hook_node_update(). + * Implements hook_node_update() to call triggered actions. */ function trigger_node_update($node) { _trigger_node($node, 'update'); } /** - * Implement hook_node_presave(). + * Implements hook_node_presave() to call triggered actions. */ function trigger_node_presave($node) { _trigger_node($node, 'presave'); } /** - * Implement hook_node_insert(). + * Implements hook_node_insert() to call triggered actions. */ function trigger_node_insert($node) { _trigger_node($node, 'insert'); } /** - * Implement hook_node_delete(). + * Implements hook_node_delete() to call triggered actions. */ function trigger_node_delete($node) { _trigger_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 @@ -304,54 +412,54 @@ } /** - * Implement hook_comment_insert(). + * Implements hook_comment_insert() to call triggered actions. */ function trigger_comment_insert($comment) { _trigger_comment($comment, 'insert'); } /** - * Implement hook_comment_update(). + * Implements hook_comment_update() to call triggered actions. */ function trigger_comment_update($comment) { _trigger_comment($comment, 'update'); } /** - * Implement hook_comment_delete(). + * Implements hook_comment_delete() to call triggered actions. */ function trigger_comment_delete($comment) { _trigger_comment($comment, 'delete'); } /** - * Implement hook_comment_view(). + * Implements hook_comment_view() to call triggered actions. */ function trigger_comment_view($comment) { _trigger_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) { // 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('comment', $op); $context = array( - 'hook' => 'comment', + 'group' => 'comment', 'op' => $op, ); // 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); @@ -369,12 +477,12 @@ } /** - * 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', 'run'); $context = array( - 'hook' => 'cron', + 'group' => 'cron', 'op' => 'run', ); // Cron does not act on any specific object. @@ -383,6 +491,8 @@ } /** + * 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 @@ -398,10 +508,8 @@ */ 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. @@ -414,35 +522,35 @@ } /** - * trigger_user_login + * Implements hook_user_login() to call triggered actions. */ function trigger_user_login(&$edit, $account, $category) { _trigger_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); } /** - * 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); } /** - * 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); } /** - * Implement hook_user_cancel(). + * Implements hook_user_cancel() to call triggered actions. */ function trigger_user_cancel($edit, $account, $method) { switch ($method) { @@ -454,27 +562,26 @@ } /** - * Implement hook_user_view(). + * Implements hook_user_view() to call triggered actions. */ function trigger_user_view($account) { _trigger_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) { // 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('user', $op); $context = array( - 'hook' => 'user', + 'group' => 'user', 'op' => $op, '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); @@ -489,50 +596,64 @@ } /** - * Implement hook_taxonomy(). + * Calls action functions for taxonomy triggers. + * + * @param $op + * Operation to trigger actions for (delete, insert, update). + * @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($op, $array) { + $aids = trigger_get_assigned_actions('taxonomy', $op); $context = array( - 'hook' => 'taxonomy', + 'group' => 'taxonomy', 'op' => $op ); 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('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('update', (array) $term); +} + +/** + * Implements hook_taxonomy_term_delete to call triggered actions. + */ +function trigger_taxonomy_term_delete($term) { + _trigger_taxonomy('delete', (array) $term); } /** - * Implement hook_actions_delete(). + * 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(); } + +/** + * Retreives 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'); + return $triggers; +} Index: modules/trigger/trigger.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.admin.inc,v retrieving revision 1.14 diff -u -r1.14 trigger.admin.inc --- modules/trigger/trigger.admin.inc 20 Jul 2009 18:51:35 -0000 1.14 +++ modules/trigger/trigger.admin.inc 19 Aug 2009 23:17:22 -0000 @@ -7,51 +7,54 @@ */ /** - * Build the form that allows users to assign actions to hooks. + * Builds a form that allows users to assign actions to triggers. * - * @param $type - * Name of hook. + * @param $module + * Module to build the form for. * @return - * HTML form. + * Form API array. */ -function trigger_assign($type = NULL) { - // If no type is specified we default to node actions, since they +function trigger_assign($module = NULL) { + // If no module is specified we default to node actions, since they // are the most common. - if (!isset($type)) { + if (!isset($module)) { drupal_goto('admin/structure/trigger/node'); } $build = array(); - $hooks = module_invoke_all('hook_info'); - foreach ($hooks as $module => $hook) { - if (isset($hook[$type])) { - foreach ($hook[$type] as $op => $description) { - $form_id = 'trigger_' . $type . '_' . $op . '_assign_form'; - $build[$form_id] = drupal_get_form($form_id, $type, $op, $description['runs when']); - } + $triggers = _trigger_get_all_info(); + $info = $triggers[$module]; + foreach ($info as $group => $stuff) { + foreach ($stuff as $op => $description ) { + $form_id = 'trigger_' . $group . '_' . $op . '_assign_form'; + $build[$form_id] = drupal_get_form($form_id, $group, $op, $description['runs when']); } } return $build; } /** - * Confirm removal of an assigned action. + * Returns a form to confirm removal of an assigned action. * - * @param $hook + * @param $group + * Group this trigger is in. * @param $op + * Particular trigger operation within group. * @param $aid * The action ID. + * @return + * Form array. * @ingroup forms * @see trigger_unassign_submit() */ -function trigger_unassign($form_state, $hook = NULL, $op = NULL, $aid = NULL) { - if (!($hook && $op && $aid)) { +function trigger_unassign($form_state, $group = NULL, $op = NULL, $aid = NULL) { + if (!($group && $op && $aid)) { drupal_goto('admin/structure/trigger/assign'); } - $form['hook'] = array( + $form['group'] = array( '#type' => 'value', - '#value' => $hook, + '#value' => $group, ); $form['operation'] = array( '#type' => 'value', @@ -65,7 +68,7 @@ $action = actions_function_lookup($aid); $actions = actions_get_all_actions(); - $destination = 'admin/structure/trigger/' . ($hook == 'node' ? 'node' : $hook); + $destination = 'admin/structure/trigger/' . $group; return confirm_form($form, t('Are you sure you want to unassign the action %title?', array('%title' => $actions[$action]['description'])), @@ -75,20 +78,22 @@ ); } +/** + * 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('hook', $form_values['group']) ->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; + $form_state['redirect'] = 'admin/structure/trigger/' . $form_values['group']; } else { drupal_goto('admin/structure/trigger'); @@ -96,26 +101,27 @@ } /** - * Create the form definition for assigning an action to a hook-op combination. + * Returns the form for assigning an action to a group-op combination. * * @param $form_state * Information about the current form. - * @param $hook - * The name of the hook, e.g., 'node'. + * @param $group + * The name of the trigger group, e.g., 'node'. * @param $op - * The name of the hook operation, e.g., 'insert'. + * The name of the trigger operation, e.g., 'insert'. * @param $description - * A plain English description of what this hook operation does. + * A plain English description of what this trigger does. * @return + * Form array. * * @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, $group, $op, $description) { + $form['group'] = array( '#type' => 'hidden', - '#value' => $hook, + '#value' => $group, ); $form['operation'] = array( '#type' => 'hidden', @@ -127,10 +133,13 @@ $options = array(); $functions = array(); - // Restrict the options list to actions that declare support for this hook-op + // Restrict the options list to actions that declare support for this group-op // combination. 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 (isset($metadata['triggers']['any']) || + (isset($metadata['triggers'][$group]) && + is_array($metadata['triggers'][$group]) && + (in_array($op, $metadata['triggers'][$group])))) { $functions[] = $func; } } @@ -144,15 +153,16 @@ '#type' => 'fieldset', '#title' => t('Trigger: ') . $description, '#theme' => 'trigger_display' - ); - // Retrieve actions that are already assigned to this hook-op combination. - $actions = _trigger_get_hook_actions($hook, $op); + ); + + // Retrieve actions that are already assigned to this group-op combination. + $actions = trigger_get_assigned_actions($group, $op); $form[$op]['assigned']['#type'] = 'value'; $form[$op]['assigned']['#value'] = array(); - foreach ($actions as $aid => $description) { + foreach ($actions as $aid => $info) { $form[$op]['assigned']['#value'][$aid] = array( - 'description' => $description, - 'link' => l(t('unassign'), "admin/structure/trigger/unassign/$hook/$op/" . md5($aid)) + 'description' => $info['description'], + 'link' => l(t('unassign'), "admin/structure/trigger/unassign/$group/$op/" . md5($aid)) ); } @@ -190,7 +200,7 @@ 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( - ':hook' => $form_values['hook'], + ':hook' => $form_values['group'], ':op' => $form_values['operation'], ':aid' => $aid, ))->fetchField(); @@ -209,13 +219,13 @@ 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( - ':hook' => $form_values['hook'], + ':hook' => $form_values['group'], ':op' => $form_values['operation'], ))->fetchField(); db_insert('trigger_assignments') ->fields(array( - 'hook' => $form_values['hook'], + 'hook' => $form_values['group'], 'op' => $form_values['operation'], 'aid' => $aid, 'weight' => $weight + 1, @@ -227,35 +237,35 @@ 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( - ':hook' => $form_values['hook'], + ':hook' => $form_values['group'], ':op' => $form_values['operation'], ':aid' => 'node_save_action', ))->fetchField(); if ($save_post_action_assigned) { db_delete('trigger_assignments') - ->condition('hook', $form_values['hook']) + ->condition('hook', $form_values['group']) ->condition('op', $form_values['operation']) ->condition('aid', 'node_save_action') ->execute(); } db_insert('trigger_assignments') ->fields(array( - 'hook' => $form_values['hook'], + 'hook' => $form_values['group'], 'op' => $form_values['operation'], '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. + * Display actions assigned to this group-op combination in a table. * * @param array $element * The fieldset including all assigned actions. @@ -287,34 +297,3 @@ 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/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.750 diff -u -r1.750 system.module --- modules/system/system.module 17 Aug 2009 19:14:41 -0000 1.750 +++ modules/system/system.module 19 Aug 2009 23:17:22 -0000 @@ -2285,21 +2285,6 @@ } /** - * 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() { @@ -2308,10 +2293,10 @@ 'type' => 'system', 'description' => t('Display a message to the user'), 'configurable' => TRUE, - 'hooks' => array( + 'triggers' => array( 'node' => array('view', 'insert', 'update', 'delete'), 'comment' => array('view', 'insert', 'update', 'delete'), - 'user' => array('view', 'insert', 'update', 'delete', 'login'), + 'user' => array('view', 'insert', 'update', 'delete', 'login', 'logout'), 'taxonomy' => array('insert', 'update', 'delete'), ), ), @@ -2319,28 +2304,28 @@ 'description' => t('Send e-mail'), 'type' => 'system', 'configurable' => TRUE, - 'hooks' => array( + 'triggers' => array( 'node' => array('view', 'insert', 'update', 'delete'), 'comment' => array('view', 'insert', 'update', 'delete'), - 'user' => array('view', 'insert', 'update', 'delete', 'login'), + 'user' => array('view', 'insert', 'update', 'delete', 'login', 'logout'), 'taxonomy' => array('insert', 'update', 'delete'), 'cron' => array('run'), - ) + ), ), 'system_block_ip_action' => array( 'description' => t('Ban IP address of current user'), 'type' => 'user', 'configurable' => FALSE, - 'hooks' => array(), + 'triggers' => array(), ), 'system_goto_action' => array( 'description' => t('Redirect to URL'), 'type' => 'system', 'configurable' => TRUE, - 'hooks' => array( + 'triggers' => array( 'node' => array('view', 'insert', 'update', 'delete'), 'comment' => array('view', 'insert', 'update', 'delete'), - 'user' => array('view', 'insert', 'update', 'delete', 'login'), + 'user' => array('view', 'insert', 'update', 'delete', 'login', 'logout'), ) ) ); @@ -2694,7 +2679,7 @@ function system_send_email_action($object, $context) { global $user; - switch ($context['hook']) { + switch ($context['group']) { case 'node': // Because this is not an action of type 'node' the node // will not be passed as $object, but it will still be available @@ -2764,7 +2749,7 @@ '%site_name' => variable_get('site_name', 'Drupal'), '%username' => $account->name, ); - if ($context['hook'] == 'taxonomy') { + if ($context['group'] == 'taxonomy') { $object = $params['object']; $vocabulary = taxonomy_vocabulary_load($object->vid); $variables += array( @@ -2824,7 +2809,7 @@ // This action can be called in any context, but if placeholders // are used a node object must be present to be the source // of substituted text. - switch ($context['hook']) { + switch ($context['group']) { case 'node': // Because this is not an action of type 'node' the node // will not be passed as $object, but it will still be available Index: modules/taxonomy/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v retrieving revision 1.497 diff -u -r1.497 taxonomy.module --- modules/taxonomy/taxonomy.module 19 Aug 2009 13:31:13 -0000 1.497 +++ modules/taxonomy/taxonomy.module 19 Aug 2009 23:17:22 -0000 @@ -1819,27 +1819,6 @@ } /** - * 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/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.1023 diff -u -r1.1023 user.module --- modules/user/user.module 19 Aug 2009 13:31:14 -0000 1.1023 +++ modules/user/user.module 19 Aug 2009 23:17:22 -0000 @@ -2768,36 +2768,6 @@ } /** - * 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() { @@ -2806,7 +2776,7 @@ 'description' => t('Block current user'), 'type' => 'user', 'configurable' => FALSE, - 'hooks' => array(), + 'triggers' => array(), ), ); } Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.753 diff -u -r1.753 comment.module --- modules/comment/comment.module 17 Aug 2009 13:10:45 -0000 1.753 +++ modules/comment/comment.module 19 Aug 2009 23:17:22 -0000 @@ -2301,30 +2301,6 @@ } /** - * 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() { @@ -2333,7 +2309,7 @@ 'description' => t('Unpublish comment'), 'type' => 'comment', 'configurable' => FALSE, - 'hooks' => array( + 'triggers' => array( 'comment' => array('insert', 'update'), ) ), @@ -2341,7 +2317,7 @@ 'description' => t('Unpublish comment containing keyword(s)'), 'type' => 'comment', 'configurable' => TRUE, - 'hooks' => array( + 'triggers' => array( 'comment' => array('insert', 'update'), ) ) 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 -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 19 Aug 2009 23:17:22 -0000 @@ -16,15 +16,25 @@ 'type' => 'system', 'description' => t('Cron test action'), 'configurable' => FALSE, - 'hooks' => array( + 'triggers' => array( 'cron' => array('run'), ), ), + 'trigger_test_another_action' => array( + 'type' => 'misc', + 'description' => t('Another test action'), + 'configurable' => FALSE, + 'triggers' => array( + 'comment' => array('insert', 'update', 'delete', 'view'), + 'taxonomy' => array('insert', 'update', 'delete'), + 'user' => array('insert', 'update', 'delete', 'login', 'logout', 'view'), + ), + ), 'trigger_test_system_cron_conf_action' => array( 'type' => 'system', 'description' => t('Cron test configurable action'), 'configurable' => TRUE, - 'hooks' => array( + 'triggers' => array( 'cron' => array('run'), ), ), @@ -74,3 +84,11 @@ ); return $params; } + +/** + * Action fired during the "another action" trigger test. + */ +function trigger_test_another_action() { + // Indicate successful execution by setting a persistent variable. + variable_set('trigger_test_another_action', TRUE); +} Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1101 diff -u -r1.1101 node.module --- modules/node/node.module 19 Aug 2009 13:31:13 -0000 1.1101 +++ modules/node/node.module 19 Aug 2009 23:17:22 -0000 @@ -2842,103 +2842,76 @@ } /** - * Implement hook_hook_info(). - */ -function node_hook_info() { - return array( - 'node' => array( - 'node' => array( - 'presave' => array( - 'runs when' => t('When either saving a new post or updating an existing post'), - ), - 'insert' => array( - 'runs when' => t('After saving a new post'), - ), - 'update' => array( - 'runs when' => t('After saving an updated post'), - ), - 'delete' => array( - 'runs when' => t('After deleting a post') - ), - 'view' => array( - 'runs when' => t('When content is viewed by an authenticated user') - ), - ), - ), - ); -} - -/** * Implement hook_action_info(). */ function node_action_info() { return array( 'node_publish_action' => array( 'type' => 'node', - 'description' => t('Publish post'), + 'description' => t('Publish content'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( + 'triggers' => array( 'node' => array('presave'), 'comment' => array('insert', 'update'), ), ), 'node_unpublish_action' => array( 'type' => 'node', - 'description' => t('Unpublish post'), + 'description' => t('Unpublish content'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( + 'triggers' => array( 'node' => array('presave'), 'comment' => array('delete', 'insert', 'update'), ), ), 'node_make_sticky_action' => array( 'type' => 'node', - 'description' => t('Make post sticky'), + 'description' => t('Make content sticky'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( + 'triggers' => array( 'node' => array('presave'), 'comment' => array('insert', 'update'), ), ), 'node_make_unsticky_action' => array( 'type' => 'node', - 'description' => t('Make post unsticky'), + 'description' => t('Make content unsticky'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( + 'triggers' => array( 'node' => array('presave'), 'comment' => array('delete', 'insert', 'update'), ), ), 'node_promote_action' => array( 'type' => 'node', - 'description' => t('Promote post to front page'), + 'description' => t('Promote content to front page'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( + 'triggers' => array( 'node' => array('presave'), 'comment' => array('insert', 'update'), ), ), 'node_unpromote_action' => array( 'type' => 'node', - 'description' => t('Remove post from front page'), + 'description' => t('Remove content from front page'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( + 'triggers' => array( 'node' => array('presave'), 'comment' => array('delete', 'insert', 'update'), ), ), 'node_assign_owner_action' => array( 'type' => 'node', - 'description' => t('Change the author of a post'), + 'description' => t('Change the author of content'), 'configurable' => TRUE, 'behavior' => array('changes_node_property'), - 'hooks' => array( + 'triggers' => array( 'any' => TRUE, 'node' => array('presave'), 'comment' => array('delete', 'insert', 'update'), @@ -2946,17 +2919,17 @@ ), 'node_save_action' => array( 'type' => 'node', - 'description' => t('Save post'), + 'description' => t('Save content'), 'configurable' => FALSE, - 'hooks' => array( + 'triggers' => array( 'comment' => array('delete', 'insert', 'update'), ), ), 'node_unpublish_by_keyword_action' => array( 'type' => 'node', - 'description' => t('Unpublish post containing keyword(s)'), + 'description' => t('Unpublish content containing keyword(s)'), 'configurable' => TRUE, - 'hooks' => array( + 'triggers' => array( 'node' => array('presave', 'insert', 'update'), ), ), Index: includes/actions.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/actions.inc,v retrieving revision 1.29 diff -u -r1.29 actions.inc --- includes/actions.inc 20 Jul 2009 18:51:31 -0000 1.29 +++ includes/actions.inc 19 Aug 2009 23:17:22 -0000 @@ -7,35 +7,33 @@ */ /** - * 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 this function's input arguments (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. + * Parameter that will be passed along to the callback function. Typically the + * object that the action will act on:y 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(). + * Parameter that will be passed along to the callback function. Typically, + * an associative array containing extra information about what triggered + * the action call, with $context['group'] and $context['op'] giving the + * group-trigger combination that resulted in this call to actions_do(). * @param $a1 * Parameter that will be passed along to the callback. * @param $a2 * Parameter that will be 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 + * performs 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 +107,18 @@ } /** - * 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 that + * function's documentation 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. */ function actions_list($reset = FALSE) { static $actions; @@ -165,16 +132,21 @@ } /** - * 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/settings/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', 'description', 'type' and 'configurable'. */ function actions_get_all_actions() { $actions = db_query("SELECT aid, type, callback, parameters, description FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC); @@ -187,21 +159,19 @@ } /** - * 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 'description', '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', 'description', 'type', and 'configurable' from the input array. */ function actions_actions_map($actions) { $actions_map = array(); @@ -216,17 +186,19 @@ } /** - * 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,20 +206,20 @@ } } - // Must be an instance; must check database. + // Must be an action ID; 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. */ @@ -301,7 +273,7 @@ } /** - * 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. @@ -316,7 +288,6 @@ * to Jim'. * @param $aid * The ID of this action. If omitted, a new action is created. - * * @return * The ID of the action. */ @@ -342,11 +313,10 @@ } /** - * 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. */ @@ -355,7 +325,7 @@ } /** - * Delete a single action from the database. + * Deletes a single action from the database. * * @param $aid * The ID of the action to delete.