Index: modules/simpletest/tests/actions.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/actions.test,v
retrieving revision 1.5
diff -u -p -r1.5 actions.test
--- modules/simpletest/tests/actions.test	13 Jul 2009 21:51:41 -0000	1.5
+++ modules/simpletest/tests/actions.test	16 Aug 2009 00:31:13 -0000
@@ -6,7 +6,7 @@ class ActionsConfigurationTestCase exten
     return array(
       'name' => 'Actions configuration',
       'description' => 'Tests complex actions configuration by adding, editing, and deleting a complex action.',
-      'group' => 'System',
+      'group' => 'Actions',
     );
   }
 
@@ -61,3 +61,68 @@ class ActionsConfigurationTestCase exten
     $this->assertFalse($exists, t('Make sure the action is gone from the database after being deleted.'));
   }
 }
+
+class ActionLoopTestCase extends DrupalWebTestCase {
+  protected $limit = 35;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Actions firing in a loop',
+      'description' => 'Tests actions firing in a loop, and makes sure they abort properly.',
+      'group' => 'Actions',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('dblog', 'trigger', 'actions_loop_test');
+  }
+
+  function testActionLoop() {
+    $user = $this->drupalCreateUser(array('administer actions'));
+    $this->drupalLogin($user);
+
+    $hash = md5('actions_loop_test_log');
+    $edit = array('aid' => $hash);
+    $this->drupalPost('admin/structure/trigger/actions_loop_test', $edit, t('Assign'));
+    $result = db_query('SELECT * FROM {watchdog}');
+    $this->triggerActions();
+    $this->eraseWatchdogEntries();
+    $this->setMaxStack(mt_rand(10, 50));
+    $this->triggerActions();
+  }
+
+  protected function setMaxStack($limit) {
+    $this->limit = $limit;
+    variable_set('actions_max_stack', $limit);
+    $this->refreshVariables();
+  }
+
+  protected function triggerActions() {
+    $this->drupalGet('<front>', array('query' => array('trigger_actions_on_watchdog' => TRUE)));
+    $expected = array();
+    $expected[] = 'Triggering action loop';
+    for ($i = 1; $i <= $this->limit; $i++) {
+      $expected[] = "Test log #$i";
+    }
+    $expected[] = 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.';
+
+    $result = db_query("SELECT * FROM {watchdog} WHERE type='actions_loop_test' OR type='actions' ORDER BY timestamp");
+    $loop_started = FALSE;
+    while ($row = db_fetch_object($result)) {
+      // Ignore everything until the first message from actions_loop_test.
+      if ($row->type != 'actions_loop_test' && !$loop_started) {
+        continue;
+      }
+      elseif (!$loop_started) {
+        $loop_started = TRUE;
+      }
+      $expected_message = array_shift($expected);
+      $this->assertEqual($row->message, $expected_message, t('Expected message %expected, got %message.', array('%expected' => $expected_message, '%message' => $row->message)));
+    }
+    $this->assertTrue(empty($expected), t('All expected messages found.'));
+  }
+
+  protected function eraseWatchdogEntries() {
+    db_delete('watchdog')->execute();
+  }
+}
Index: modules/simpletest/tests/actions_loop_test.info
===================================================================
RCS file: modules/simpletest/tests/actions_loop_test.info
diff -N modules/simpletest/tests/actions_loop_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/actions_loop_test.info	16 Aug 2009 00:31:13 -0000
@@ -0,0 +1,8 @@
+; $Id$
+name = Actions loop test
+description = Support module for action loop testing.
+package = Testing
+version = VERSION
+core = 7.x
+files[] = actions_loop_test.module
+;hidden = TRUE
Index: modules/simpletest/tests/actions_loop_test.install
===================================================================
RCS file: modules/simpletest/tests/actions_loop_test.install
diff -N modules/simpletest/tests/actions_loop_test.install
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/actions_loop_test.install	16 Aug 2009 00:31:13 -0000
@@ -0,0 +1,12 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_install().
+ */
+function actions_loop_test_install() {
+   db_update('system')
+    ->fields(array('weight' => 1))
+    ->condition('name', 'actions_loop_test')
+    ->execute();
+}
Index: modules/simpletest/tests/actions_loop_test.module
===================================================================
RCS file: modules/simpletest/tests/actions_loop_test.module
diff -N modules/simpletest/tests/actions_loop_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/actions_loop_test.module	16 Aug 2009 00:31:13 -0000
@@ -0,0 +1,94 @@
+<?php
+// $Id$
+
+/**
+ * Implement hook_hook_info().
+ */
+function actions_loop_test_hook_info() {
+  return array(
+    'actions_loop_test' => array(
+      'watchdog' => array(
+        'run' => array(
+          'runs when' => t('When a message is logged'),
+        ),
+      ),
+    ),
+  );
+}
+
+/**
+ * Implement hook_watchdog().
+ */
+function actions_loop_test_watchdog(array $log_entry) {
+  // If the triggering actions is not explicitly enabled, abort.
+  if (empty($_GET['trigger_actions_on_watchdog'])) {
+    return;
+  }
+  $aids = _trigger_get_hook_aids('watchdog', 'run');
+  $context = array(
+    'hook' => 'watchdog',
+    'op' => 'run',
+  );
+  actions_do(array_keys($aids), $log_entry, $context);
+}
+
+/**
+ * Implement hook_help().
+ */
+function actions_loop_test_help() {
+  if (!empty($_GET['trigger_actions_on_watchdog'])) {
+    watchdog_skip_semaphore('actions_loop_test', 'Triggering action loop');
+  }
+}
+
+/**
+ * Implement hook_action_info().
+ */
+function actions_loop_test_action_info() {
+  return array(
+    'actions_loop_test_log' => array(
+      'description' => t('Write a message to the log.'),
+      'type' => 'system',
+      'configurable' => FALSE,
+      'hooks' => array(
+        'any' => TRUE,
+      )
+    ),
+  );
+}
+
+/**
+ * Write a message to the log.
+ */
+function actions_loop_test_log() {
+  $count = &drupal_static(__FUNCTION__, 0);
+  $count++;
+  watchdog_skip_semaphore('actions_loop_test', "Test log #$count");
+}
+
+/**
+ * Replacement of the watchdog() function that eliminates the use of semaphores
+ * so that we can test the abortion of an action loop.
+ */
+function watchdog_skip_semaphore($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
+  global $user, $base_root;
+
+  // Prepare the fields to be logged
+  $log_entry = array(
+    'type'        => $type,
+    'message'     => $message,
+    'variables'   => $variables,
+    'severity'    => $severity,
+    'link'        => $link,
+    'user'        => $user,
+    'request_uri' => $base_root . request_uri(),
+    'referer'     => $_SERVER['HTTP_REFERER'],
+    'ip'          => ip_address(),
+    'timestamp'   => REQUEST_TIME,
+  );
+
+  // Call the logging hooks to log/process the message
+  foreach (module_implements('watchdog') as $module) {
+    module_invoke($module, 'watchdog', $log_entry);
+  }
+}
Index: modules/trigger/trigger.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/trigger/trigger.admin.inc,v
retrieving revision 1.14
diff -u -p -r1.14 trigger.admin.inc
--- modules/trigger/trigger.admin.inc	20 Jul 2009 18:51:35 -0000	1.14
+++ modules/trigger/trigger.admin.inc	16 Aug 2009 00:31:13 -0000
@@ -23,11 +23,13 @@ function trigger_assign($type = NULL) {
 
   $build = array();
   $hooks = module_invoke_all('hook_info');
-  foreach ($hooks as $module => $hook) {
-    if (isset($hook[$type])) {
-      foreach ($hook[$type] as $op => $description) {
-        $form_id = 'trigger_' . $type . '_' . $op . '_assign_form';
-        $build[$form_id] = drupal_get_form($form_id, $type, $op, $description['runs when']);
+  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']);
+        }
       }
     }
   }
Index: modules/trigger/trigger.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/trigger/trigger.module,v
retrieving revision 1.42
diff -u -p -r1.42 trigger.module
--- modules/trigger/trigger.module	12 Aug 2009 12:36:05 -0000	1.42
+++ modules/trigger/trigger.module	16 Aug 2009 00:31:13 -0000
@@ -39,47 +39,15 @@ function trigger_menu() {
     'title' => 'Triggers',
     'description' => 'Tell Drupal when to execute actions.',
     'page callback' => 'trigger_assign',
-    'access callback' => 'trigger_access_check',
-    'access arguments' => array('node'),
-  );
-  // We don't use a menu wildcard here because these are tabs,
-  // not invisible items.
-  $items['admin/structure/trigger/node'] = array(
-    'title' => 'Content',
-    'page callback' => 'trigger_assign',
-    'page arguments' => array('node'),
-    'access callback' => 'trigger_access_check',
-    'access arguments' => array('node'),
-    'type' => MENU_LOCAL_TASK,
-  );
-  $items['admin/structure/trigger/user'] = array(
-    'title' => 'Users',
-    'page callback' => 'trigger_assign',
-    'page arguments' => array('user'),
-    'access callback' => 'trigger_access_check',
-    'access arguments' => array('user'),
-    'type' => MENU_LOCAL_TASK,
-  );
-  $items['admin/structure/trigger/comment'] = array(
-    'title' => 'Comments',
-    'page callback' => 'trigger_assign',
-    'page arguments' => array('comment'),
-    'access callback' => 'trigger_access_check',
-    'access arguments' => array('comment'),
-    'type' => MENU_LOCAL_TASK,
-  );
-  $items['admin/structure/trigger/taxonomy'] = array(
-    'title' => 'Taxonomy',
-    'page callback' => 'trigger_assign',
-    'page arguments' => array('taxonomy'),
-    'access callback' => 'trigger_access_check',
-    'access arguments' => array('taxonomy'),
-    'type' => MENU_LOCAL_TASK,
+    'access arguments' => array('administer actions'),
   );
+
+  // 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('cron'),
+    'page arguments' => array('system'),
     'access arguments' => array('administer actions'),
     'type' => MENU_LOCAL_TASK,
   );
@@ -88,11 +56,12 @@ function trigger_menu() {
   // their hooks and have actions assignable to them.
   $hooks = module_invoke_all('hook_info');
   foreach ($hooks as $module => $hook) {
-    // We've already done these.
-    if (in_array($module, array('node', 'comment', 'user', 'system', 'taxonomy'))) {
+    // We've already done system.module..
+    if ($module == 'system') {
       continue;
     }
     $info = db_select('system')
+      ->fields('system', array('info'))
       ->condition('name', $module)
       ->execute()
       ->fetchField();
@@ -102,7 +71,7 @@ function trigger_menu() {
       'title' => $nice_name,
       'page callback' => 'trigger_assign',
       'page arguments' => array($module),
-      'access arguments' => array($module),
+      'access arguments' => array('administer actions'),
       'type' => MENU_LOCAL_TASK,
     );
   }
@@ -119,13 +88,6 @@ function trigger_menu() {
 }
 
 /**
- * Access callback for menu system.
- */
-function trigger_access_check($module) {
-  return (module_exists($module) && user_access('administer actions'));
-}
-
-/**
  * Get the aids of actions to be executed for a hook-op combination.
  *
  * @param $hook
