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