Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.796
diff -u -p -r1.796 comment.module
--- modules/comment/comment.module	23 Oct 2009 22:24:12 -0000	1.796
+++ modules/comment/comment.module	27 Oct 2009 20:51:24 -0000
@@ -2334,18 +2334,33 @@ function comment_action_info() {
       'label' => t('Publish comment'),
       'type' => 'comment',
       'configurable' => FALSE,
-      'triggers' => array('comment_insert', 'comment_update'),
+      'behavior' => array(
+        'before' => 'comment_save_action',
+      ),
+      'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
     ),
     'comment_unpublish_action' => array(
       'label' => t('Unpublish comment'),
       'type' => 'comment',
       'configurable' => FALSE,
-      'triggers' => array('comment_insert', 'comment_update'),
+      'behavior' => array(
+        'before' => 'comment_save_action',
+      ),
+      'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
     ),
     'comment_unpublish_by_keyword_action' => array(
       'label' => t('Unpublish comment containing keyword(s)'),
       'type' => 'comment',
       'configurable' => TRUE,
+      'behavior' => array(
+        'before' => 'comment_save_action',
+      ),
+      'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
+    ),
+    'comment_save_action' => array(
+      'label' => t('Save comment'),
+      'type' => 'comment',
+      'configurable' => FALSE,
       'triggers' => array('comment_insert', 'comment_update'),
     ),
   );
@@ -2362,18 +2377,18 @@ function comment_action_info() {
  * @ingroup actions
  */
 function comment_publish_action($comment, $context = array()) {
-  if (isset($comment->cid)) {
-    $cid = $comment->cid;
+  if (isset($comment->comment)) {
     $subject = $comment->subject;
+    $comment->status = COMMENT_PUBLISHED;
   }
   else {
     $cid = $context['cid'];
     $subject = db_query('SELECT subject FROM {comment} WHERE cid = :cid', array(':cid', $cid))->fetchField();
+    db_update('comment')
+      ->fields(array('status' => COMMENT_PUBLISHED))
+      ->condition('cid', $cid)
+      ->execute();
   }
-  db_update('comment')
-    ->fields(array('status' => COMMENT_PUBLISHED))
-    ->condition('cid', $cid)
-    ->execute();
   watchdog('action', 'Published comment %subject.', array('%subject' => $subject));
 }
 
@@ -2388,18 +2403,18 @@ function comment_publish_action($comment
  * @ingroup actions
  */
 function comment_unpublish_action($comment, $context = array()) {
-  if (isset($comment->cid)) {
-    $cid = $comment->cid;
+  if (isset($comment->comment)) {
     $subject = $comment->subject;
+    $comment->status = COMMENT_NOT_PUBLISHED;
   }
   else {
     $cid = $context['cid'];
     $subject = db_query('SELECT subject FROM {comment} WHERE cid = :cid', array(':cid', $cid))->fetchField();
+    db_update('comment')
+      ->fields(array('status' => COMMENT_NOT_PUBLISHED))
+      ->condition('cid', $cid)
+      ->execute();
   }
-  db_update('comment')
-    ->fields(array('status' => COMMENT_NOT_PUBLISHED))
-    ->condition('cid', $cid)
-    ->execute();
   watchdog('action', 'Unpublished comment %subject.', array('%subject' => $subject));
 }
 
@@ -2409,9 +2424,8 @@ function comment_unpublish_action($comme
  * @param $comment
  *   A comment object.
  * @param $context
- *   An array providing more information about the context of the call to this action.
- *   Unused here, since this action currently only supports the insert and update ops of
- *   the comment hook, both of which provide a complete $comment object.
+ *   An array providing more information about the context of the call to this
+ *   action.
  *
  * @ingroup actions
  * @see comment_unpublish_by_keyword_action_form()
@@ -2420,10 +2434,7 @@ function comment_unpublish_action($comme
 function comment_unpublish_by_keyword_action($comment, $context) {
   foreach ($context['keywords'] as $keyword) {
     if (strpos($comment->comment, $keyword) !== FALSE || strpos($comment->subject, $keyword) !== FALSE) {
-      db_update('comment')
-        ->fields(array('status' => COMMENT_NOT_PUBLISHED))
-        ->condition('cid', $comment->cid)
-        ->execute();
+      $comment->status = COMMENT_NOT_PUBLISHED;
       watchdog('action', 'Unpublished comment %subject.', array('%subject' => $comment->subject));
       break;
     }
@@ -2458,6 +2469,14 @@ function comment_unpublish_by_keyword_ac
 }
 
 /**
+ * Action to save a comment.
+ */
+function comment_save_action($comment) {
+  comment_save($comment);
+  watchdog('action', 'Saved comment %title', array('%title' => $comment->subject));
+}
+
+/**
  * Implement hook_ranking().
  */
 function comment_ranking() {
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1154
diff -u -p -r1.1154 node.module
--- modules/node/node.module	23 Oct 2009 22:24:16 -0000	1.1154
+++ modules/node/node.module	27 Oct 2009 20:51:24 -0000
@@ -2940,76 +2940,70 @@ function node_action_info() {
       'type' => 'node',
       'label' => t('Publish content'),
       'configurable' => FALSE,
-      'behavior' => array('changes_node_property'),
-      'triggers' => array('node_presave', 'comment_insert', 'comment_update'),
+      'behavior' => array(
+        'before' => 'node_save_action',
+      ),
+      'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
     ),
     'node_unpublish_action' => array(
       'type' => 'node',
       'label' => t('Unpublish content'),
       'configurable' => FALSE,
-      'behavior' => array('changes_node_property'),
-      'triggers' => array(
-        'node_presave',
-        'comment_insert',
-        'comment_update',
-        'comment_delete'
+      'behavior' => array(
+        'before' => 'node_save_action',
       ),
+      'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
     ),
     'node_make_sticky_action' => array(
       'type' => 'node',
       'label' => t('Make content sticky'),
       'configurable' => FALSE,
-      'behavior' => array('changes_node_property'),
-      'triggers' => array('node_presave', 'comment_insert', 'comment_update'),
+      'behavior' => array(
+        'before' => 'node_save_action',
+      ),
+      'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
     ),
     'node_make_unsticky_action' => array(
       'type' => 'node',
       'label' => t('Make content unsticky'),
       'configurable' => FALSE,
-      'behavior' => array('changes_node_property'),
-      'triggers' => array(
-        'node_presave',
-        'comment_insert',
-        'comment_update',
-        'comment_delete'
+      'behavior' => array(
+        'before' => 'node_save_action',
       ),
+      'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
     ),
     'node_promote_action' => array(
       'type' => 'node',
       'label' => t('Promote content to front page'),
       'configurable' => FALSE,
-      'behavior' => array('changes_node_property'),
-      'triggers' => array('node_presave', 'comment_insert', 'comment_update'),
+      'behavior' => array(
+        'before' => 'node_save_action',
+      ),
+      'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
     ),
     'node_unpromote_action' => array(
       'type' => 'node',
       'label' => t('Remove content from front page'),
       'configurable' => FALSE,
-      'behavior' => array('changes_node_property'),
-      'triggers' => array(
-        'node_presave',
-        'comment_insert',
-        'comment_update',
-        'comment_delete'
+      'behavior' => array(
+        'before' => 'node_save_action',
       ),
+      'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
     ),
     'node_assign_owner_action' => array(
       'type' => 'node',
       'label' => t('Change the author of content'),
       'configurable' => TRUE,
-      'behavior' => array('changes_node_property'),
-      'triggers' => array(
-        'node_presave',
-        'comment_insert',
-        'comment_update',
-        'comment_delete',
+      'behavior' => array(
+        'before' => 'node_save_action',
       ),
+      'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
     ),
     'node_save_action' => array(
       'type' => 'node',
       'label' => t('Save content'),
       'configurable' => FALSE,
-      'triggers' => array('comment_delete', 'comment_insert', 'comment_update'),
+      'triggers' => array('comment_insert', 'comment_update', 'comment_delete'),
     ),
     'node_unpublish_by_keyword_action' => array(
       'type' => 'node',
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.97
diff -u -p -r1.97 system.api.php
--- modules/system/system.api.php	24 Oct 2009 05:28:16 -0000	1.97
+++ modules/system/system.api.php	27 Oct 2009 21:06:12 -0000
@@ -2355,13 +2355,20 @@ function hook_file_mimetype_mapping_alte
  *   - '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.
+ *   - 'behavior': (optional) A machine-readable array of behaviors of this
+ *     action, used to signal additionally required actions that may need to be
+ *     triggered. Currently recognized behaviors by Trigger module:
+ *     - 'before': Indicates that the action has to be run before another action.
+ *       The value is the internal name of the other action that needs to be
+ *       invoked afterwards. This is usually required for actions that change a
+ *       property of an object, so the object needs to be saved afterwards. For
+ *       example, when the 'node_publish' action is assigned to a trigger other
+ *       than 'node_presave', then the 'node_save_action' has to be invoked
+ *       afterwards. If the 'node_save_action' action already exists, it is
+ *       moved later in the list. Otherwise, it will be added.
+ *       Modules that are processing actions (like Trigger module) should take
+ *       special care for the "presave" event, in which case a dependent "save"
+ *       action should NOT be invoked.
  */
 function hook_action_info() {
   return array(
@@ -2369,12 +2376,21 @@ function hook_action_info() {
       'type' => 'comment',
       'label' => t('Unpublish comment'),
       'configurable' => FALSE,
-      'triggers' => array('comment_insert', 'comment_update'),
+      'behavior' => array(
+        'before' => 'comment_save_action',
+      ),
+      'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
     ),
     'comment_unpublish_by_keyword_action' => array(
       'type' => 'comment',
       'label' => t('Unpublish comment containing keyword(s)'),
       'configurable' => TRUE,
+      'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
+    ),
+    'comment_save_action' => array(
+      'type' => 'comment',
+      'label' => t('Save comment'),
+      'configurable' => FALSE,
       'triggers' => array('comment_insert', 'comment_update'),
     ),
   );
Index: modules/trigger/trigger.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/trigger/trigger.admin.inc,v
retrieving revision 1.19
diff -u -p -r1.19 trigger.admin.inc
--- modules/trigger/trigger.admin.inc	9 Oct 2009 01:00:06 -0000	1.19
+++ modules/trigger/trigger.admin.inc	27 Oct 2009 20:51:24 -0000
@@ -204,49 +204,51 @@ function trigger_assign_form_validate($f
 /**
  * Submit function for trigger_assign_form().
  */
-function trigger_assign_form_submit($form, $form_state) {
-  $form_values = $form_state['values'];
-
-  if (!empty($form_values['aid'])) {
-    $aid = actions_function_lookup($form_values['aid']);
+function trigger_assign_form_submit($form, &$form_state) {
+  if (!empty($form_state['values']['aid'])) {
+    $aid = actions_function_lookup($form_state['values']['aid']);
     $weight = db_query("SELECT MAX(weight) FROM {trigger_assignments} WHERE hook = :hook", array(
-      ':hook' => $form_values['hook'],
+      ':hook' => $form_state['values']['hook'],
     ))->fetchField();
 
     db_insert('trigger_assignments')
       ->fields(array(
-        'hook' => $form_values['hook'],
+        'hook' => $form_state['values']['hook'],
         'aid' => $aid,
         'weight' => $weight + 1,
       ))
       ->execute();
-    // If this action changes a node property, we need to save the node
+    // If this action changes an object property, we need to save the object,
     // so the change will persist.
-
     $actions = actions_list();
-    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
+    if (isset($actions[$aid]['behavior']['before']) && strpos($form_state['values']['hook'], 'presave') === FALSE) {
+      // Delete the action that must run after if it exists, and re-add it with
       // 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'],
-        ':aid' => 'node_save_action',
+        ':hook' => $form_state['values']['hook'],
+        ':aid' => $actions[$aid]['behavior']['before'],
       ))->fetchField();
 
       if ($save_post_action_assigned) {
         db_delete('trigger_assignments')
-          ->condition('hook', $form_values['hook'])
-          ->condition('aid', 'node_save_action')
+          ->condition('hook', $form_state['values']['hook'])
+          ->condition('aid', $actions[$aid]['behavior']['before'])
           ->execute();
       }
       db_insert('trigger_assignments')
         ->fields(array(
-          'hook' => $form_values['hook'],
-          'aid' => 'node_save_action',
+          'hook' => $form_state['values']['hook'],
+          'aid' => $actions[$aid]['behavior']['before'],
           'weight' => $weight + 2,
         ))
         ->execute();
-      if (!$save_post_action_assigned) {
-        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."));
+      // If an existing action was moved, inform the user.
+      if ($save_post_action_assigned) {
+        drupal_set_message(t("The added action changes a property of the content. The %label action has been moved later in the list so that the property change will be saved.", array('%label' => $actions[$actions[$aid]['behavior']['before']]['label'])));
+      }
+      // Otherwise, inform the user about the added, required action.
+      else {
+        drupal_set_message(t("The %label action has been appended after the added action in the list so that the property change will be saved.", array('%label' => $actions[$actions[$aid]['behavior']['before']]['label'])));
       }
     }
   }
Index: modules/trigger/trigger.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/trigger/trigger.module,v
retrieving revision 1.51
diff -u -p -r1.51 trigger.module
--- modules/trigger/trigger.module	23 Oct 2009 22:24:19 -0000	1.51
+++ modules/trigger/trigger.module	27 Oct 2009 20:51:24 -0000
@@ -106,7 +106,10 @@ function trigger_trigger_info() {
        ),
      ),
      'comment' => array(
-         'comment_insert' => array(
+       'comment_presave' => array(
+         'label' => t('When either saving a new comment or updating an existing comment'),
+       ),
+       'comment_insert' => array(
          'label' => t('After saving a new comment'),
        ),
        'comment_update' => array(
@@ -349,6 +352,13 @@ function _trigger_normalize_comment_cont
 }
 
 /**
+ * Implement hook_comment_presave().
+ */
+function trigger_comment_presave($comment) {
+  _trigger_comment($comment, 'comment_presave');
+}
+
+/**
  * Implement hook_comment_insert().
  */
 function trigger_comment_insert($comment) {
@@ -407,7 +417,6 @@ function _trigger_comment($a1, $hook) {
       actions_do($aid, $objects[$type], $context);
     }
     else {
-      $a1 = (object) $a1;
       actions_do($aid, $a1, $context);
     }
   }
