/[drupal]/contributions/modules/actions/actions.module
ViewVC logotype

Diff of /contributions/modules/actions/actions.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph | View Patch Patch

revision 1.30, Thu May 24 20:30:56 2007 UTC revision 1.31, Mon Mar 17 04:21:38 2008 UTC
# Line 1  Line 1 
1  <?php  <?php
2  /* $Id: actions.module,v 1.28 2007/05/01 21:13:40 jvandyk Exp $ */  // $Id$
3    
4  /**  /**
5  * @file   * @file
6  * Enables functions to be stored as scripts to be triggered by other modules.   * Enables functions to be stored and executed at a later time when
7  */   * triggered by other modules or by one of Drupal's core API hooks.
8     */
9    
10    include_once(drupal_get_path('module', 'actions') .'/actions.inc');
11    
12  /**  /**
13  * Implementation of hook_help().   * Implementation of hook_help().
14  */   */
15  function actions_help($section) {  function actions_help($section) {
16    $output = '';    $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/settings/actions'))) .'</p>';
17    switch ($section) {    switch ($section) {
18      case 'admin/build/actions':      case 'admin/settings/actions':
19        $output .= t('This page lists all actions that are available. Simple actions that do not require any configuration are listed automatically. Actions that need to be configured are listed in the <em>Add new action</em> menu. To add a configurable action, select the action and click the <em>Add new action</em> button. After completing the configuration form, the action will be available for use by Drupal.');      case 'admin/settings/actions/manage':
20        break;        $output = '<p>'. t('Actions are individual tasks that the system can do, such as unpublishing a piece of content or banning a user. Modules, such as the trigger module, can fire these actions when certain system events happen; for example, when a new post is added or when a user logs in. Modules may also provide additional actions.') .'</p>';
21      case 'admin/build/actions/config':        $output .= '<p>'. t('There are two types of actions: simple and advanced. Simple actions do not require any additional configuration, and are listed here automatically. Advanced actions can do more than simple actions; for example, send an e-mail to a specified address, or check for certain words within a piece of content. These actions need to be created and configured first before they may be used. To create an advanced action, select the action from the drop-down below and click the <em>Create</em> button.') .'</p>';
22        $output .= t('This is where you configure a certain action that will be performed at some time in the future. For example, you might configure an action to send email to your friend Royce. Your entry in the description field, below, should be descriptive enough to remind you of that.');        $output .= '<p>'. t('You may proceed to the <a href="@url">Triggers</a> page to assign these actions to system events.', array('@url' => url('admin/build/trigger'))) .'</p>';
23        break;        return $output;
24        case 'admin/settings/actions/configure':
25          return t('An advanced action offers additional configuration options which may be filled out below. Changing the <em>Description</em> field is recommended, in order to better identify the precise action taking place. This description will be displayed in modules such as the trigger module when assigning actions to system events, so it is best if it is as descriptive as possible (for example, "Send e-mail to Moderation Team" rather than simply "Send e-mail").');
26        case 'admin/build/trigger/comment':
27          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>';
28        case 'admin/build/trigger/node':
29          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>';
30        case 'admin/build/trigger/cron':
31          return $explanation .'<p>'. t('Below you can assign actions to run during each pass of a cron maintenance task.</p>');
32        case 'admin/build/trigger/taxonomy':
33          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>';
34        case 'admin/build/trigger/user':
35          return $explanation .'<p>'. t("Below you can assign actions to run when certain user-related triggers happen. For example, you could send an e-mail to an administrator when a user account is deleted.") .'</p>';
36        case 'admin/help#trigger':
37          $output = '<p>'. t('The Trigger module provides the ability to trigger <a href="@actions">actions</a> upon system events, such as when new content is added or when a user logs in.', array('@actions' => url('admin/settings/actions'))) .'</p>';
38          $output .= '<p>'. t('The combination of actions and triggers can perform many useful tasks, such as e-mailing an administrator if a user account is deleted, or automatically unpublishing comments that contain certain words. By default, there are five "contexts" of events (Comments, Content, Cron, Taxonomy, and Users), but more may be added by additional modules.') .'</p>';
39          $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@trigger">Trigger module</a>.', array('@trigger' => 'http://drupal.org/handbook/modules/trigger/')) .'</p>';
40          return $output;
41    }    }
   
   return $output;  
42  }  }
43    
44  /**  /**
45  * Implementation of hook_menu().   * Implementation of hook_menu().
46  */   */
47  function actions_menu($may_cache) {  function actions_menu($may_cache) {
48    $items = array();    $items = array();
49    $access = user_access('administer actions');    $access = user_access('administer_actions');
50    
51    if ($may_cache) {    if ($may_cache) {
52      $items[] = array(      $items[] = array(
53        'path'     => 'admin/build/actions',        'path' => 'admin/settings/actions',
54        'title'    => t('Actions'),        'title' => t('Actions'),
55        'description'    => t('Manage the actions defined for your site.'),        'description' => t('Manage the actions defined for your site.'),
56        'access'   => $access,        'callback' => 'actions_manage',
57        'callback' => 'actions_overview',        'access' => $access,
58        );      );
59      $items[] = array(      $items[] = array(
60        'path'     => 'admin/build/actions/overview',        'path' => 'admin/settings/actions/manage',
61        'title'    => t('Actions'),        'title' => t('Manage actions'),
62        'description'    => t('Manage the actions defined for your site.'),        'description' => t('Manage the actions defined for your site.'),
63        'access'   => $access,        'callback' => 'actions_manage',
64        'callback' => 'actions_overview',        'type' => MENU_DEFAULT_LOCAL_TASK,
65        'type' => MENU_DEFAULT_LOCAL_TASK        'weight' => -2,
66        );      );
67      $items[] = array(      $items[] = array(
68        'path' => 'admin/build/actions/assign',        'path' => 'admin/settings/actions/configure',
69        'title' => t('Assign actions'),        'title' => t('Configure an advanced action'),
70        'description' => t('Tell Drupal when to execute actions.'),        'callback' => 'drupal_get_form',
71        'access' => $access,        'callback arguments' => array('actions_configure'),
72        'callback' => 'actions_assign',        'type' => MENU_CALLBACK,
73        'type' => MENU_LOCAL_TASK      );
74        $items[] = array(
75          'path' => 'admin/settings/actions/delete',
76          'title' => t('Delete action'),
77          'description' => t('Delete an action.'),
78          'callback' => 'drupal_get_form',
79          'callback arguments' => array('actions_delete_form'),
80          'type' => MENU_CALLBACK,
81        );
82        $items[] = array(
83          'path' => 'admin/settings/actions/orphan',
84          'title' => t('Remove orphans'),
85          'callback' => 'actions_remove_orphans',
86          'type' => MENU_CALLBACK,
87      );      );
88      $items[] = array(      $items[] = array(
89        'path' => 'admin/build/actions/assign/node',        'path' => 'admin/build/trigger',
90        'title' => t('Node'),        'title' => t('Triggers'),
91        'description' => t('Tell Drupal when to execute actions.'),        'description' => t('Tell Drupal when to execute actions.'),
92          'callback' => 'actions_assign',
93        'access' => $access,        'access' => $access,
94        );
95        // We don't use a menu wildcard here because these are tabs,
96        // not invisible items.
97        $items[] = array(
98          'path' => 'admin/build/trigger/node',
99          'title' => t('Content'),
100        'callback' => 'actions_assign',        'callback' => 'actions_assign',
101        'callback arguments' => array('node'),        'callback arguments' => array('node'),
102        'type' => MENU_LOCAL_TASK        'access' => $access,
103          'type' => MENU_LOCAL_TASK,
104      );      );
105      $items[] = array(      $items[] = array(
106        'path' => 'admin/build/actions/assign/user',        'path' => 'admin/build/trigger/user',
107        'title' => t('User'),        'title' => t('Users'),
       'description' => t('Tell Drupal when to execute actions.'),  
       'access' => $access,  
108        'callback' => 'actions_assign',        'callback' => 'actions_assign',
109        'callback arguments' => array('user'),        'callback arguments' => array('user'),
110        'type' => MENU_LOCAL_TASK        'access' => $access,
111          'type' => MENU_LOCAL_TASK,
112      );      );
113      $items[] = array(      $items[] = array(
114        'path' => 'admin/build/actions/assign/comment',        'path' => 'admin/build/trigger/comment',
115        'title' => t('Comment'),        'title' => t('Comments'),
       'description' => t('Tell Drupal when to execute actions.'),  
       'access' => $access,  
116        'callback' => 'actions_assign',        'callback' => 'actions_assign',
117        'callback arguments' => array('comment'),        'callback arguments' => array('comment'),
118        'type' => MENU_LOCAL_TASK        'access' => $access,
119          'type' => MENU_LOCAL_TASK,
120      );      );
121      $items[] = array(      $items[] = array(
122        'path' => 'admin/build/actions/assign/cron',        'path' => 'admin/build/trigger/taxonomy',
123        'title' => t('Cron'),        'title' => t('Taxonomy'),
124        'description' => t('Tell Drupal when to execute actions.'),        'callback' => 'actions_assign',
125          'callback arguments' => array('taxonomy'),
126        'access' => $access,        'access' => $access,
127          'type' => MENU_LOCAL_TASK,
128        );
129        $items[] = array(
130          'path' => 'admin/build/trigger/cron',
131          'title' => t('Cron'),
132        'callback' => 'actions_assign',        'callback' => 'actions_assign',
133        'callback arguments' => array('cron'),        'callback arguments' => array('cron'),
134        'type' => MENU_LOCAL_TASK        'type' => MENU_LOCAL_TASK,
135      );      );
136    
137      // We want contributed modules to be able to describe
138      // their hooks and have actions assignable to them.
139        $hooks = module_invoke_all('hook_info');
140        foreach ($hooks as $module => $hook) {
141          // We've already done these.
142          if (in_array($module, array('node', 'comment', 'user', 'system', 'taxonomy'))) {
143            continue;
144          }
145          $info = db_result(db_query("SELECT info FROM {system} WHERE name = '%s'", $module));
146          $info = unserialize($info);
147          $nice_name = $info['name'];
148          $items[] = array(
149            'path' => 'admin/build/trigger/'. $module,
150            'title' => $nice_name,
151            'callback' => 'actions_assign',
152            'callback arguments' => array($module),
153            'access' => $module,
154            'type' => MENU_LOCAL_TASK,
155          );
156        }
157      $items[] = array(      $items[] = array(
158        'path' => 'admin/build/actions/assign/remove',        'path' => 'admin/build/trigger/unassign',
159        'title' => t('Unassign'),        'title' => t('Unassign'),
160        'description' => t('Remove an action assignment.'),        'description' => t('Unassign an action from a trigger.'),
       'access' => $access,  
       'type' => MENU_CALLBACK,  
161        'callback' => 'drupal_get_form',        'callback' => 'drupal_get_form',
162        'callback arguments' => array('actions_assign_remove'),        'callback arguments' => array('actions_unassign'),
163          'type' => MENU_CALLBACK,
164      );      );
165      $items[] = array(  
166        'path'     => 'admin/build/actions/config',      return $items;
       'title'    => t('Configure action'),  
       'access'   => $access,  
       'weight'   => -9,  
       'callback' => 'drupal_get_form',  
       'callback arguments' => array('actions_configure'),  
       'type'     => MENU_CALLBACK  
       );  
   
     $items[] = array(  
       'path'     => 'admin/build/actions/delete',  
       'title'    => t('Delete action'),  
       'access'   => $access,  
       'callback' => 'drupal_get_form',  
       'callback arguments' => array('actions_delete_form'),  
       'weight'   => -8,  
       'type'     => MENU_CALLBACK  
       );  
   
     $items[] = array(  
       'path'     => 'admin/build/actions/orphan',  
       'title'    => t('Delete action'),  
       'access'   => $access,  
       'callback' => 'actions_remove_orphans',  
       'weight'   => -8,  
       'type'     => MENU_CALLBACK  
       );  
167    }    }
   
   return $items;  
168  }  }
169    
170  /**  /**
171  * Implementation of hook_perm().   * Implementation of hook_perm().
172  */   */
173  function actions_perm() {  function actions_perm() {
174    return array('administer actions');    return array('administer_actions');
175  }  }
176    
177  /**  /**
178   * Menu callback.   * Access callback for menu system.
179   * Create the main action module page giving an overview of configured actions.   */
180   *  function actions_access_check($module) {
181      return (module_exists($module) && user_access('administer actions'));
182    }
183    
184    /**
185     * Menu callback. Display an overview of available and configured actions.
186   */   */
187  function actions_overview() {  function actions_manage() {
188    $output = '';    $output = '';
189    $actions = actions_list();    $actions = actions_list();
190    actions_synchronize($actions);    actions_synchronize($actions);
191    $actions_map = actions_actions_map($actions);    $actions_map = actions_actions_map($actions);
192    $options = array();    $options = array(t('Choose an advanced action'));
193    $unconfigurable = array();    $unconfigurable = array();
194    
195    foreach ($actions_map as $key => $array) {    foreach ($actions_map as $key => $array) {
196      if ($array['configurable']) {      if ($array['configurable']) {
197        $options[$key] = $array['description'] . '...';        $options[$key] = $array['description'] .'...';
198      }      }
199      else {      else {
200        $unconfigurable[] = $array;        $unconfigurable[] = $array;
201      }      }
202    }    }
203    
   if ($actions_map) {  
     $output .= drupal_get_form('actions_overview_form', $options);  
   }  
   
204    $row = array();    $row = array();
205    $instances_present = db_fetch_object(db_query("SELECT aid FROM {actions} WHERE params != ''"));    $instances_present = db_fetch_object(db_query("SELECT aid FROM {actions} WHERE parameters != ''"));
206    $header = array(    $header = array(
207      array('data' => t('Action Type'), 'field' => 'type'),      array('data' => t('Action type'), 'field' => 'type'),
208      array('data' => t('Description'), 'field' => 'description'),      array('data' => t('Description'), 'field' => 'description'),
209      array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')      array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')
210      );    );
211    $sql = 'SELECT * FROM {actions}';    $sql = 'SELECT * FROM {actions}';
212    $result = pager_query($sql . tablesort_sql($header), 50);    $result = pager_query($sql . tablesort_sql($header), 50);
213    while ($data = db_fetch_object($result)) {    while ($action = db_fetch_object($result)) {
214      $row[] = array(      $row[] = array(
215        array('data' => $data->type),        array('data' => $action->type),
216        array('data' => $data->description),        array('data' => $action->description),
217        array('data' => $data->params ? l(t('configure'), "admin/build/actions/config/$data->aid") : ''),        array('data' => $action->parameters ? l(t('configure'), "admin/settings/actions/configure/$action->aid") : ''),
218        array('data' => $data->params ? l(t('delete'), "admin/build/actions/delete/$data->aid") : '')        array('data' => $action->parameters ? l(t('delete'), "admin/settings/actions/delete/$action->aid") : '')
219      );      );
220    }    }
221    
# Line 188  function actions_overview() { Line 224  function actions_overview() {
224      if (!empty($pager)) {      if (!empty($pager)) {
225        $row[] = array(array('data' => $pager, 'colspan' => '3'));        $row[] = array(array('data' => $pager, 'colspan' => '3'));
226      }      }
227      $output .= '<h3>' . t('Actions available to Drupal:') . '</h3>';      $output .= '<h3>'. t('Actions available to Drupal:') .'</h3>';
228      $output .= theme('table', $header, $row);      $output .= theme('table', $header, $row);
229    }    }
230    
231      if ($actions_map) {
232        $output .= drupal_get_form('actions_manage_form', $options);
233      }
234    
235    return $output;    return $output;
236  }  }
237    
238  /**  /**
239   * Define the form for the actions overview page.   * Define the form for the actions overview page.
240   *   *
241     * @see actions_manage_form_submit()
242     * @ingroup forms
243   * @param $options   * @param $options
244   *   An array of configurable actions.   *   An array of configurable actions.
245   * @return   * @return
246   *   Form definition.   *   Form definition.
247   */   */
248  function actions_overview_form($options = array()) {  function actions_manage_form($options = array()) {
249    $form['action'] = array(    $form['parent'] = array(
250        '#type' => 'fieldset',
251        '#title' => t('Make a new advanced action available'),
252        '#prefix' => '<div class="container-inline">',
253        '#suffix' => '</div>',
254      );
255      $form['parent']['action'] = array(
256      '#type' => 'select',      '#type' => 'select',
     '#title' => t('Add new action'),  
257      '#default_value' => '',      '#default_value' => '',
258      '#options' => $options,      '#options' => $options,
259      '#description' => '',      '#description' => '',
260    );    );
261    $form['buttons']['submit'] = array(    $form['parent']['buttons']['submit'] = array(
262      '#type' => 'submit',      '#type' => 'submit',
263      '#value' => t('Add new action'),      '#value' => t('Create'),
264    );    );
265    return $form;    return $form;
266  }  }
267    
268  function actions_overview_form_submit($form_id, $form_values) {  /**
269    if ($form_values['action'] != '') {   * Process actions_manage form submissions.
270      return 'admin/build/actions/config/' . $form_values['action'];   */
271    function actions_manage_form_submit($form_id, $form_values) {
272      if ($form_values['action']) {
273        return 'admin/settings/actions/configure/'. $form_values['action'];
274    }    }
275  }  }
276    
277  /**  /**
278   * Menu callback.   * Menu callback. Create the form for configuration of a single action.
279   * Create the form for configuration of a single action.   *
280   * We provide the "Description" field. The rest of the form   * We provide the "Description" field. The rest of the form
281   * is provided by the action. We then provide the Save button.   * is provided by the action. We then provide the Save button.
282   * Because we are combining unknown form elements with the action   * Because we are combining unknown form elements with the action
283   * configuration form, we use actions_ prefix on our elements.   * configuration form, we use actions_ prefix on our elements.
284   *   *
285     * @see actions_configure_validate()
286     * @see actions_configure_submit()
287   * @param $action   * @param $action
288   *   md5 hash of action ID   *   md5 hash of action ID or an integer. If it's an md5 hash, we
289     *   are creating a new instance. If it's an integer, we're editing
290     *   an existing instance.
291     * @return
292     *   Form definition.
293   */   */
294  function actions_configure($action = NULL) {  function actions_configure($action = NULL) {
295    if ($action === NULL) {    if ($action === NULL) {
296      drupal_goto('admin/build/actions');      drupal_goto('admin/settings/actions');
297    }    }
298    
299    $actions_map = actions_actions_map(actions_list());    $actions_map = actions_actions_map(actions_list());
300    $edit = array();    $edit = array();
301    
302    if (is_numeric($action)) { // Editing saved instance of a configurable action.    // Numeric action denotes saved instance of a configurable action;
303      // else we are creating a new action instance.
304      if (is_numeric($action)) {
305      $aid = $action;      $aid = $action;
306      // Load stored parameter values from database.      // Load stored parameter values from database.
307      $data = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", intval($aid)));      $data = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", intval($aid)));
308      $edit['actions_description'] = $data->description;      $edit['actions_description'] = $data->description;
309      $edit['actions_type'] = $data->type;      $edit['actions_type'] = $data->type;
310      $function = $data->func;      $function = $data->callback;
311      $action = md5($data->func);      $action = md5($data->callback);
312      $params = unserialize($data->params);      $params = unserialize($data->parameters);
313      if ($params) {      if ($params) {
314        foreach ($params as $name => $val) {        foreach ($params as $name => $val) {
315          $edit[$name] = $val;          $edit[$name] = $val;
316        }        }
317      }      }
318    }    }
319    else { // Creating a new action instance.    else {
320      $function = $actions_map[$action]['function'];      $function = $actions_map[$action]['callback'];
321      $edit['actions_description'] = $actions_map[$action]['description'];      $edit['actions_description'] = $actions_map[$action]['description'];
322      $edit['actions_type'] = $actions_map[$action]['type'];      $edit['actions_type'] = $actions_map[$action]['type'];
323    }    }
324    
325      $form = array();
326    $form['actions_description'] = array(    $form['actions_description'] = array(
327      '#type' => 'textfield',      '#type' => 'textfield',
328      '#title' => t('Description'),      '#title' => t('Description'),
329      '#default_value' => $edit['actions_description'],      '#default_value' => $edit['actions_description'],
     '#size' => '70',  
330      '#maxlength' => '255',      '#maxlength' => '255',
331      '#description' => t('A unique description for this configuration of this action.'),      '#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.'),
332      '#weight' => -10      '#weight' => -10
333    );    );
334    $action_form = $function . '_form';    $action_form = $function .'_form';
335    $form = array_merge($form, $action_form($edit));    $form = array_merge($form, $action_form($edit));
336    $form['actions_type'] = array(    $form['actions_type'] = array(
337      '#type' => 'value',      '#type' => 'value',
# Line 284  function actions_configure($action = NUL Line 341  function actions_configure($action = NUL
341      '#type' => 'hidden',      '#type' => 'hidden',
342      '#value' => $action,      '#value' => $action,
343    );    );
344    if ($aid) { // Configuring an existing .    // $aid is set when configuring an existing action instance.
345      if (isset($aid)) {
346      $form['actions_aid'] = array(      $form['actions_aid'] = array(
347        '#type' => 'hidden',        '#type' => 'hidden',
348        '#value' => $aid,        '#value' => $aid,
# Line 303  function actions_configure($action = NUL Line 361  function actions_configure($action = NUL
361    return $form;    return $form;
362  }  }
363    
364    /**
365     * Validate actions_configure form submissions.
366     */
367  function actions_configure_validate($form_id, $form_values) {  function actions_configure_validate($form_id, $form_values) {
368    $function = actions_function_lookup($form_values['actions_action']) . '_validate';    $function = actions_function_lookup($form_values['actions_action']) .'_validate';
369    // Hand off validation to the action.    // Hand off validation to the action.
370    $function($form_id, $form_values);    if (function_exists($function)) {
371        $function('validate', $form_values);
372      }
373  }  }
374    
375    /**
376     * Process actions_configure form submissions.
377     */
378  function actions_configure_submit($form_id, $form_values) {  function actions_configure_submit($form_id, $form_values) {
379    $function = actions_function_lookup($form_values['actions_action']);    $function = actions_function_lookup($form_values['actions_action']);
380    $submit_function = $function . '_submit';    $submit_function = $function .'_submit';
381    
382    // Action will return keyed array of values to store.    // Action will return keyed array of values to store.
383    $params = $submit_function($form_id, $form_values);    $params = $submit_function($form_id, $form_values);
384    if (isset($form_values['actions_aid'])) {    $aid = isset($form_values['actions_aid']) ? $form_values['actions_aid'] : NULL;
     $aid = $form_values['actions_aid'];  
   }  
385    
386    actions_save($function, $form_values['actions_type'], $params, $form_values['actions_description'], $aid);    actions_save($function, $form_values['actions_type'], $params, $form_values['actions_description'], $aid);
387    drupal_set_message(t('The action has been successfully saved.'));    drupal_set_message(t('The action has been successfully saved.'));
388    
389    return 'admin/build/actions';    return 'admin/settings/actions/manage';
390  }  }
391    
392  /**  /**
  * Menu callback.  
393   * Create the form for confirmation of deleting an action.   * Create the form for confirmation of deleting an action.
394   *   *
395   *   * @ingroup forms
396     * @see actions_delete_form_submit()
397   */   */
398  function actions_delete_form($aid) {  function actions_delete_form($aid) {;
   if (!$aid) drupal_goto('admin/build/actions');  
399    $action = actions_load($aid);    $action = actions_load($aid);
400    $form['aid'] = array(    $form['aid'] = array(
401      '#type' => 'hidden',      '#type' => 'hidden',
402      '#value' => $aid      '#value' => $action->aid,
403    );    );
404      return confirm_form($form,
405    return confirm_form(      t('Are you sure you want to delete the action %action?', array('%action' => $action->description)),
406      $form,      'admin/settings/actions/manage',
     t('Really delete action !action?', array('!action' => theme('placeholder', $action->description))),  
     'admin/build/actions',  
407      t('This cannot be undone.'),      t('This cannot be undone.'),
408      t('Delete'),      t('Delete'), t('Cancel')
     t('Cancel')  
409    );    );
410  }  }
411    
412    /**
413     * Process actions_delete form submissions.
414     *
415     * Post-deletion operations for action deletion.
416     */
417  function actions_delete_form_submit($form_id, $form_values) {  function actions_delete_form_submit($form_id, $form_values) {
   // note that in the future we need to check for dependencies here  
418    $aid = $form_values['aid'];    $aid = $form_values['aid'];
419    $action = actions_load($aid);    $action = actions_load($aid);
420    actions_delete($aid);    actions_delete($aid);
421    $description = check_plain($action->description);    $description = check_plain($action->description);
422    watchdog('user', t('Deleted action %aid (%action)', array('%aid' => $aid, '%action' => $description)));    watchdog('user', 'Deleted action %aid (%action)', array('%aid' => $aid, '%action' => $description));
423    drupal_set_message(t('Action !action was deleted', array('!action' => theme('placeholder', $description))));    drupal_set_message(t('Action %action was deleted', array('%action' => $description)));
424    return 'admin/build/actions';    return 'admin/settings/actions/manage';
425  }  }
426    
   
427  /**  /**
428   * Save an action and its associated user-supplied parameter values to   * Post-deletion operations for deleting action orphans.
  * the database.  
  *  
  * @param $function  
  *   The name of the function to be called when this action is performed.  
  * @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 to Jim'.  
  * @param $aid  
  *   The ID of this action. If omitted, a new action is created.  
429   *   *
430   * @return   * @param $orphaned
431   *   The ID of the action.   *   An array of orphaned actions.
432   */   */
433  function actions_save($function, $type, $params, $desc, $aid = NULL) {  function actions_action_delete_orphans_post($orphaned) {
434    $serialized = serialize($params);    foreach ($orphaned as $callback) {
435    if ($aid) {      drupal_set_message(t("Deleted orphaned action (%action).", array('%action' => $callback)));
     db_query("UPDATE {actions} SET func = '%s', type = '%s', params = '%s', description = '%s' WHERE aid = %d", $function, $type, $serialized, $desc, $aid);  
     watchdog('user', t("Action '@action' saved.", array('%action' => $desc)));  
436    }    }
   else {  
     $aid = db_next_id('{actions}_aid');  
     db_query("INSERT INTO {actions} (aid, func, type, params, description) VALUES (%d, '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);  
     watchdog('user', t("Action '@action' created.", array('@action' => $desc)));  
   }  
   
   return $aid;  
437  }  }
438    
439  /**  /**
440   * Retrieve a single action from the database.   * Remove actions that are in the database but not supported by any enabled module.
  *  
  * @param $aid  
  *   integer The ID of the action to retrieve.  
  *  
  * @return  
  *   The appropriate action row from the database as an object.  
441   */   */
442  function actions_load($aid) {  function actions_remove_orphans() {
443    return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $aid));    actions_synchronize(actions_list(), TRUE);
444      drupal_goto('admin/settings/actions/manage');
445  }  }
446    
447  /**  /**
448   * Delete a single action from the database.   * Admin page callbacks for the trigger module.
  *  
  * @param $aid  
  *   integer The ID of the action to delete.  
  *  
449   */   */
 function actions_delete($aid) {  
   db_query("DELETE FROM {actions} WHERE aid = %d", $aid);  
 }  
   
450    
451  /**  /**
452   * Synchronize actions that are provided by modules with actions   * Build the form that allows users to assign actions to hooks.
  * 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.  
453   *   *
454     * @param $type
455     *   Name of hook.
456     * @return
457     *   HTML form.
458   */   */
459  function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {  function actions_assign($type = NULL) {
460    if (!$actions_in_code) {  
461      $actions_in_code = actions_list();    // If no type is specified we default to node actions, since they
462    }    // are the most common.
463    $actions_in_db = array();    if (!isset($type)) {
464    $result = db_query("SELECT * FROM {actions} WHERE params = ''");      drupal_goto('admin/build/trigger/node');
   while ($data = db_fetch_object($result)) {  
     $actions_in_db[$data->func] = array('aid' => $data->aid, 'description' => $data->description);  
465    }    }
466      if ($type == 'node') {
467    // Go through all the actions provided by modules.      $type = 'nodeapi';
   foreach ($actions_in_code as $func => $array) {  
     // Ignore configurable actions since their instances get put in  
     // when the user adds the action.  
     if (!$array['configurable']) {  
       // If we already have an action ID for this action, no need to assign aid.  
       if (array_key_exists($func, $actions_in_db)) {  
         unset($actions_in_db[$func]);  
       }  
       else {  
         // This is a new singleton that we don't have an aid for; assign one.  
         db_query("INSERT INTO {actions} (aid, type, func, params, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $func, $array['type'], $func, '', $array['description']);  
         drupal_set_message(t("Action '%action' added.", array('%action' => filter_xss_admin($array['description']))));  
       }  
     }  
468    }    }
469    
470    // Any actions that we have left in $actions_in_db are orphaned.    $output = '';
471    if ($actions_in_db) {    $hooks = module_invoke_all('hook_info');
472      $orphaned = '';    foreach ($hooks as $module => $hook) {
473        if (isset($hook[$type])) {
474      foreach ($actions_in_db as $func => $array) {        foreach ($hook[$type] as $op => $description) {
475        if ($delete_orphans) {          $form_id = 'actions_'. $type .'_'. $op .'_assign_form';
476          db_query("DELETE FROM {actions} WHERE func = '%s'", $func);          $output .= drupal_get_form($form_id, $type, $op, $description['runs when']);
         drupal_set_message(t("Deleted orphaned action '%action'.", array('%action' => $func)));  
       }  
       else {  
         $orphaned .= $func . ', ';  
477        }        }
478      }      }
   
     if (!$delete_orphans) {  
       $orphaned = rtrim(rtrim($orphaned, ' '), ',');  
       $link = l(t('Remove orphaned actions'), 'admin/build/actions/orphan');  
       $count = count($actions_in_db);  
       $phrase = format_plural($count, 'One orphaned action exists', '@count orphaned actions exist', array('@count' => $count));  
       drupal_set_message(t("%phrase in the actions table", array('%phrase' => $phrase)) . ' (' . $orphaned . '). ' . $link, 'warning');  
     }  
479    }    }
480      return $output;
481  }  }
482    
483  /**  /**
484   * Menu callback. Remove any actions that are in the database but not supported   * Confirm removal of an assigned action.
  * by any currently enabled module.  
  */  
 function actions_remove_orphans() {  
   actions_synchronize(actions_list(), TRUE);  
   drupal_goto('admin/build/actions');  
 }  
   
 /**  
  * Get the actions that have already been defined for this  
  * type/hook/op combination.  
485   *   *
  * @param $type  
  *   One of 'node', 'user', 'comment'.  
486   * @param $hook   * @param $hook
  *   The name of the hook for which actions have been assigned,  
  *   e.g. 'nodeapi'.  
487   * @param $op   * @param $op
488   *   The hook operation for which the actions have been assigned,   * @param $aid
489   *   e.g., 'view'.   *   The action ID.
490   * @return   * @ingroup forms
491   *   An array of action descriptions keyed by action IDs.   * @see actions_unassign_submit()
492   */   */
493  function _actions_get_hook_actions($hook, $op, $type = NULL) {  function actions_unassign($hook = NULL, $op = NULL, $aid = NULL, $form_values = NULL) {
494    $actions = array();    if (!($hook && $op && $aid)) {
495    if ($type) {      drupal_goto('admin/build/trigger/assign');
     $result = db_query("SELECT h.aid, a.description FROM {actions_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE a.type = '%s' AND h.hook = '%s' AND h.op = '%s' ORDER BY h.weight", $type, $hook, $op);  
496    }    }
497    else {  
498      $result = db_query("SELECT h.aid, a.description FROM {actions_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE h.hook = '%s' AND h.op = '%s' ORDER BY h.weight", $hook, $op);    $form['hook'] = array(
499        '#type' => 'value',
500        '#value' => $hook,
501      );
502      $form['operation'] = array(
503        '#type' => 'value',
504        '#value' => $op,
505      );
506      $form['aid'] = array(
507        '#type' => 'value',
508        '#value' => $aid,
509      );
510    
511      $action = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s' AND parameters != ''", $aid));
512      $actions = actions_get_all_actions();
513    
514      $destination = 'admin/build/trigger/'. ($hook == 'nodeapi' ? 'node' : $hook);
515    
516      return confirm_form($form,
517        t('Are you sure you want to unassign the action %title?', array('%title' => $actions[$action]['description'])),
518        $destination,
519        t('You can assign it again later if you wish.'),
520        t('Unassign'), t('Cancel')
521      );
522    }
523    
524    function actions_unassign_submit($form_id, $form_values) {
525      if ($form_values['confirm'] == 1) {
526        $aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s'", $form_values['aid']));
527        db_query("DELETE FROM {actions_assignments} WHERE hook = '%s' AND op = '%s' AND aid = '%s'", $form_values['hook'], $form_values['operation'], $aid);
528        watchdog('actions', t('Action %action has been unassigned.',  array('%action' => check_plain($actions[$aid]['description']))));
529        drupal_set_message(t('Action %action has been unassigned.', array('%action' => $actions[$aid]['description'])));
530        $hook = $form_values['hook'] == 'nodeapi' ? 'node' : $form_values['hook'];
531        return 'admin/build/trigger/'. $hook;
532    }    }
533    while ($action = db_fetch_object($result)) {    else {
534      $actions[$action->aid] = $action->description;      drupal_goto('admin/build/trigger');
535    }    }
   return $actions;  
536  }  }
537    
538  /**  /**
539   * Get the aids of actions to be executed for a hook/op combination.   * Create the form definition for assigning an action to a hook-op combination.
540   *   *
541     * @param $form_values
542     *   Information about the current form.
543   * @param $hook   * @param $hook
544   *   The name of the hook being fired.   *   The name of the hook, e.g., 'nodeapi'.
545   * @param $op   * @param $op
546   *   The name of the operation being executed. Defaults to an empty string   *   The name of the hook operation, e.g., 'insert'.
547   *   because some hooks (e.g., hook_cron()) do not have operations.   * @param $description
548     *   A plain English description of what this hook operation does.
549   * @return   * @return
550   *   An array of action IDs.   *
551     * @ingoup forms
552     * @see actions_assign_form_validate()
553     * @see actions_assign_form_submit()
554   */   */
555  function _actions_get_hook_aids($hook, $op = '') {  function actions_assign_form($hook, $op, $description, $form_value = NULL) {
   $aids = array();  
   $result = db_query("SELECT aa.aid, a.type FROM {actions_assignments} aa LEFT JOIN {actions} a ON aa.aid = a.aid WHERE aa.hook = '%s' AND aa.op = '%s' ORDER BY weight", $hook, $op);  
   while ($action = db_fetch_object($result)) {  
     $aids[$action->aid]['type'] = $action->type;  
   }  
   return $aids;  
 }  
   
 function actions_assign_form($hook, $op) {  
   $form['#multistep'] = TRUE;  
556    $form['hook'] = array(    $form['hook'] = array(
557      '#type' => 'hidden',      '#type' => 'hidden',
558      '#value' => $hook      '#value' => $hook,
559    );    );
560    $form['operation'] = array(    $form['operation'] = array(
561      '#type' => 'hidden',      '#type' => 'hidden',
562      '#value' => $op      '#value' => $op,
563    );    );
564      // All of these forms use the same validate and submit functions.
565      $form['#validate'] = array('actions_assign_form_validate' => array($form_id, &$form));
566      $form['#submit'] = array('actions_assign_form_submit' => array($form_id, &$form));
567    
568    $options = array();    $options = array();
569    $functions = array();    $functions = array();
570    // 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-op
571      // combination.
572    foreach (actions_list() as $func => $metadata) {    foreach (actions_list() as $func => $metadata) {
573      if (is_array($metadata['hooks'][$hook]) && in_array($op, $metadata['hooks'][$hook])) {      if (isset($metadata['hooks']['any']) || (isset($metadata['hooks'][$hook]) && is_array($metadata['hooks'][$hook]) && (in_array($op, $metadata['hooks'][$hook])))) {
574        $functions[] = $func;        $functions[] = $func;
575      }      }
576    }    }
577    foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {    foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {
578      if (in_array($action['function'], $functions)) {      if (in_array($action['callback'], $functions)) {
579        $options[$action['type']][$aid] = $action['description'];        $options[$action['type']][$aid] = $action['description'];
580      }      }
581    }    }
582    
583    $form[$op] = array(    $form[$op] = array(
584      '#type' => 'fieldset',      '#type' => 'fieldset',
585      '#title' => t('Actions to execute during the @hook %view operation:', array('@hook' => $hook, '%view' => $op)),      '#title' => t('Trigger: ') . $description,
586      '#theme' => 'actions_display'      '#theme' => 'actions_display'
587      );      );
588      // Retrieve actions that are already assigned to this hook-op combination.
589    $actions = _actions_get_hook_actions($hook, $op);    $actions = _actions_get_hook_actions($hook, $op);
590    $form[$op]['assigned']['#type'] = 'value';    $form[$op]['assigned']['#type'] = 'value';
591    $form[$op]['assigned']['#value'] = array();    $form[$op]['assigned']['#value'] = array();
592    foreach ($actions as $aid => $description) {    foreach ($actions as $aid => $description) {
593      $form[$op]['assigned']['#value'][$aid] = array(      $form[$op]['assigned']['#value'][$aid] = array(
594        'description' => $description,        'description' => $description,
595        'link' => l(t('remove'), "admin/build/actions/assign/remove/$hook/$op/" . md5($aid))        'link' => l(t('unassign'), "admin/build/trigger/unassign/$hook/$op/". md5($aid))
596        );      );
597    }    }
598    
599      $form[$op]['parent'] = array(
600        '#prefix' => "<div class='container-inline'>",
601        '#suffix' => '</div>',
602      );
603    // List possible actions that may be assigned.    // List possible actions that may be assigned.
604    if (count($options) != 0) {    if (count($options) != 0) {
605      array_unshift($options, t('Choose an action'));      array_unshift($options, t('Choose an action'));
606      $form[$op]['aid'] = array(      $form[$op]['parent']['aid'] = array(
607        '#type' => 'select',        '#type' => 'select',
608        '#options' => $options,        '#options' => $options,
609      );      );
610      $form[$op]['submit'] = array(      $form[$op]['parent']['submit'] = array(
611        '#type' => 'submit',        '#type' => 'submit',
612        '#value' => t('Add')        '#value' => t('Assign')
613      );      );
614    }    }
615    else {    else {
616      $form[$op]['none'] = array(      $form[$op]['none'] = array(
617        '#value' => t('No available actions support this context.')        '#value' => t('No available actions for this trigger.')
618      );      );
619    }    }
620    return $form;    return $form;
621  }  }
622    
623  function actions_assign_form_submit($form, $form_values) {  /**
624    if ($form_values['aid'] != '0') {   * Validation function for actions_assign_form().
625     *
626     * Makes sure that the user is not re-assigning an action to an event.
627     */
628    function actions_assign_form_validate($form_id, $form_values) {
629      if (!empty($form_values['aid'])) {
630      $aid = actions_function_lookup($form_values['aid']);      $aid = actions_function_lookup($form_values['aid']);
631        if (db_result(db_query("SELECT aid FROM {actions_assignments} WHERE hook = '%s' AND op = '%s' AND aid = '%s'", $form_values['hook'], $form_values['operation'], $aid))) {
632          form_set_error($form_values['operation'], t('The action you chose is already assigned to that trigger.'));
633        }
634      }
635    }
636    
637    /**
638     * Submit function for actions_assign_form().
639     */
640    function actions_assign_form_submit($form_id, $form_values) {
641      if (!empty($form_values['aid'])) {
642        $callback = actions_function_lookup($form_values['aid']);
643        $aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s'", $form_values['aid']));
644      $weight = db_result(db_query("SELECT MAX(weight) FROM {actions_assignments} WHERE hook = '%s' AND op = '%s'", $form_values['hook'], $form_values['operation']));      $weight = db_result(db_query("SELECT MAX(weight) FROM {actions_assignments} WHERE hook = '%s' AND op = '%s'", $form_values['hook'], $form_values['operation']));
645      db_query("INSERT INTO {actions_assignments} values ('%s', '%s', '%s', %d)", $form_values['hook'], $form_values['operation'], $aid, $weight + 1);      db_query("INSERT INTO {actions_assignments} values ('%s', '%s', '%s', %d)", $form_values['hook'], $form_values['operation'], $aid, $weight + 1);
646        // If this action changes a node property, we need to save the node
647        // so the change will persist.
648        $actions = actions_list();
649        if (isset($actions[$callback]['behavior']) && in_array('changes_node_property', $actions[$callback]['behavior']) && ($form_values['operation'] != 'presave')) {
650          // Delete previous node_save_action if it exists, and re-add a new one at a higher weight.
651          $save_post_action_assigned = db_result(db_query("SELECT aid FROM {actions_assignments} WHERE hook = '%s' AND op = '%s' AND aid = 'node_save_action'", $form_values['hook'], $form_values['operation']));
652          if ($save_post_action_assigned) {
653            db_query("DELETE FROM {actions_assignments} WHERE hook = '%s' AND op = '%s' AND aid = 'node_save_action'", $form_values['hook'], $form_values['operation']);
654          }
655          db_query("INSERT INTO {actions_assignments} VALUES ('%s', '%s', '%s', %d)", $form_values['hook'], $form_values['operation'], 'node_save_action', $weight + 2);
656          if (!$save_post_action_assigned) {
657            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.'));
658          }
659        }
660    }    }
661  }  }
662    
663  /**  /**
664   * Display actions assigned to this hook/op combination in a table.   * Display actions assigned to this hook-op combination in a table.
665   *   *
666   * @param array $element   * @param array $element
667   *   The fieldset including all assigned actions.   *   The fieldset including all assigned actions.
668   * @return   * @return
669   *   The rendered form with the table prepended.   *   The rendered form with the table prepended.
670     *
671     * @ingroup themeable
672   */   */
673  function theme_actions_display($element) {  function theme_actions_display($element) {
674      $header = array();
675      $rows = array();
676    if (count($element['assigned']['#value'])) {    if (count($element['assigned']['#value'])) {
677      $header = array(t('Name'), t('Operation'));      $header = array(array('data' => t('Name')), array('data' => t('Operation')));
678      $rows = array();      $rows = array();
679      foreach ($element['assigned']['#value'] as $aid => $info) {      foreach ($element['assigned']['#value'] as $aid => $info) {
680        $rows[] = array(        $rows[] = array(
# Line 624  function theme_actions_display($element) Line 684  function theme_actions_display($element)
684      }      }
685    }    }
686    
687    return theme('table', $header, $rows) . drupal_render($element);    if (count($rows)) {
688        $output = theme('table', $header, $rows) . drupal_render($element);
689      }
690      else {
691        $output = drupal_render($element);
692      }
693      return $output;
694  }  }
695    
696    
697  /**  /**
698   * Implementation of hook_action_assign().   * Get the actions that have already been defined for this
699   *   * type-hook-op combination.
  * We need to describe to the actions module the hook that actions can be assigned to.  
  *  
  * Return an array keyed by action type that includes fields for hook and op.  
700   *   *
701     * @param $type
702     *   One of 'node', 'user', 'comment'.
703     * @param $hook
704     *   The name of the hook for which actions have been assigned,
705     *   e.g. 'nodeapi'.
706     * @param $op
707     *   The hook operation for which the actions have been assigned,
708     *   e.g., 'view'.
709     * @return
710     *   An array of action descriptions keyed by action IDs.
711   */   */
712  function actions_action_assign() {  function _actions_get_hook_actions($hook, $op, $type = NULL) {
713    $assignments = array(    $actions = array();
714      'node' => array(    if ($type) {
715        'hook' => 'nodeapi'),      $result = db_query("SELECT h.aid, a.description FROM {actions_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE a.type = '%s' AND h.hook = '%s' AND h.op = '%s' ORDER BY h.weight", $type, $hook, $op);
       'op' => 'insert'  
       );  
 }  
   
 function actions_assign($type = NULL) {  
   if (!isset($type)) {  
     drupal_goto('admin/build/actions/assign/comment');  
716    }    }
717    $output = t('Here is where you can assign actions to node/user/comment activities.');    else {
718        $result = db_query("SELECT h.aid, a.description FROM {actions_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE h.hook = '%s' AND h.op = '%s' ORDER BY h.weight", $hook, $op);
   switch($type) {  
     case 'node':  
       $output .= drupal_get_form('actions_assign_form', 'nodeapi', 'insert');  
       $output .= drupal_get_form('actions_assign_form', 'nodeapi', 'update');  
       $output .= drupal_get_form('actions_assign_form', 'nodeapi', 'delete');  
       $output .= drupal_get_form('actions_assign_form', 'nodeapi', 'view');  
       break;  
     case 'comment':  
       $output .= drupal_get_form('actions_assign_form', 'comment', 'insert');  
       $output .= drupal_get_form('actions_assign_form', 'comment', 'update');  
       $output .= drupal_get_form('actions_assign_form', 'comment', 'delete');  
       $output .= drupal_get_form('actions_assign_form', 'comment', 'view');  
       break;  
     case 'user':  
       $output .= drupal_get_form('actions_assign_form', 'user', 'login');  
       $output .= drupal_get_form('actions_assign_form', 'user', 'logout');  
       $output .= drupal_get_form('actions_assign_form', 'user', 'insert');  
       $output .= drupal_get_form('actions_assign_form', 'user', 'update');  
       $output .= drupal_get_form('actions_assign_form', 'user', 'delete');  
       $output .= drupal_get_form('actions_assign_form', 'user', 'view');  
       break;  
     case 'cron':  
       $output .= drupal_get_form('actions_assign_form', 'cron', 'run');  
       break;  
     /*  
     default:  
       // here we fire a hook and build other forms for other hooks?  
     */  
719    }    }
720      while ($action = db_fetch_object($result)) {
721    return $output;      $actions[$action->aid] = $action->description;
 }  
   
 function actions_assign_remove($hook = NULL, $op = NULL, $aid = NULL) {  
   if (!($hook && $op && $aid)) {  
     drupal_goto('admin/actions/assign');  
722    }    }
723    $form['hook'] = array(    return $actions;
724      '#type' => 'value',  }
725      '#value' => $hook  
726      );  /**
727    $form['operation'] = array(   * Get the aids of actions to be executed for a hook-op combination.
728      '#type' => 'value',   *
729      '#value' => $op   * @param $hook
730      );   *   The name of the hook being fired.
731    $form['aid'] = array(   * @param $op
732      '#type' => 'value',   *   The name of the operation being executed. Defaults to an empty string
733      '#value' => $aid   *   because some hooks (e.g., hook_cron()) do not have operations.
734      );   * @return
735    $action = actions_function_lookup($aid);   *   An array of action IDs.
736    $actions = actions_get_all_actions();   */
737    $description = $actions[$action]['description'];  function _actions_get_hook_aids($hook, $op = '') {
738    return confirm_form(    $aids = array();
739      $form,    $result = db_query("SELECT aa.aid, a.type FROM {actions_assignments} aa LEFT JOIN {actions} a ON aa.aid = a.aid WHERE aa.hook = '%s' AND aa.op = '%s' ORDER BY weight", $hook, $op);
740      t('Are you sure you want to delete the action %title?', array('%title' => $description)),    while ($action = db_fetch_object($result)) {
741      'admin/build/actions/assign/'. $type,      $aids[$action->aid]['type'] = $action->type;
742      t('You can add it again later if you wish.'),    }
743      t('Delete'),    return $aids;
744      t('Cancel')  }
745    
746    /**
747     * Implementation of hook_theme().
748     */
749    function actions_theme() {
750      return array(
751        'actions_display' => array(
752          'arguments' => array('element'),
753        ),
754    );    );
755  }  }
756    
757  function actions_assign_remove_submit($form_id, $form_values) {  /**
758    if ($form_values['confirm'] == 1) {   * Implementation of hook_forms(). We reuse code by using the
759      $aid = actions_function_lookup($form_values['aid']);   * same assignment form definition for each node-op combination.
760      db_query("DELETE FROM {actions_assignments} WHERE hook = '%s' AND op = '%s' AND aid = '%s'", $form_values['hook'], $form_values['operation'], $aid);   */
761      watchdog('actions', t('Action %action deleted',  array('%action' => check_plain($actions[$aid]))));  function actions_forms() {
762      drupal_set_message(t('Action %action deleted.', array('%action' => $actions[$aid])));    $hooks = module_invoke_all('hook_info');
763      return('admin/build/actions/assign/'. $form_values['hook']);    foreach ($hooks as $module => $info) {
764        foreach ($hooks[$module] as $hook => $ops) {
765          foreach ($ops as $op => $description) {
766            $forms['actions_'. $hook .'_'. $op .'_assign_form'] = array('callback' => 'actions_assign_form');
767          }
768        }
769    }    }
770    
771      return $forms;
772  }  }
773    
774    /**
775     * When an action is called in a context that does not match its type,
776     * the object that the action expects must be retrieved. For example, when
777     * an action that works on users is called during the node hook, the
778     * user object is not available since the node hook doesn't pass it.
779     * So here we load the object the action expects.
780     *
781     * @param $type
782     *   The type of action that is about to be called.
783     * @param $node
784     *   The node that was passed via the nodeapi hook.
785     * @return
786     *   The object expected by the action that is about to be called.
787     */
788  function _actions_normalize_node_context($type, $node) {  function _actions_normalize_node_context($type, $node) {
789    switch($type) {    switch ($type) {
790      // An action that works on comments is being called in a node context.      // If an action that works on comments is being called in a node context,
791      case 'comment':      // the action is expecting a comment object. But we do not know which comment
792        // to give it. The first? The most recent? All of them? So comment actions
793        // in a node context are not supported.
794    
795      // An action that works on users is being called in a node context.      // An action that works on users is being called in a node context.
796      // Load the user object of the node's author.      // Load the user object of the node's author.
# Line 736  function _actions_normalize_node_context Line 802  function _actions_normalize_node_context
802  /**  /**
803   * Implementation of hook_nodeapi().   * Implementation of hook_nodeapi().
804   */   */
805  function actions_nodeapi($node, $op, $a3, $a4) {  function actions_nodeapi(&$node, $op, $a3, $a4) {
806    // We prevent recursion by tracking which operations have already been called.    // Keep objects for reuse so that changes actions make to objects can persist.
807      static $objects;
808      // Prevent recursion by tracking which operations have already been called.
809    static $recursion;    static $recursion;
810    if (!in_array($op, array('view', 'update', 'insert', 'delete')) || isset($recursion[$op])) {    // Support a subset of operations.
811      if (!in_array($op, array('view', 'update', 'presave', 'insert', 'delete')) || isset($recursion[$op])) {
812      return;      return;
813    }    }
814    $recursion[$op] = TRUE;    $recursion[$op] = TRUE;
# Line 754  function actions_nodeapi($node, $op, $a3 Line 823  function actions_nodeapi($node, $op, $a3
823    );    );
824    
825    // We need to get the expected object if the action's type is not 'node'.    // We need to get the expected object if the action's type is not 'node'.
826      // We keep the object in $objects so we can reuse it if we have multiple actions
827      // that make changes to an object.
828    foreach ($aids as $aid => $action_info) {    foreach ($aids as $aid => $action_info) {
829      if ($action_info['type'] != 'node') {      if ($action_info['type'] != 'node') {
830        $object = _actions_normalize_node_context($action_info['type'], $node);        if (!isset($objects[$action_info['type']])) {
831            $objects[$action_info['type']] = _actions_normalize_node_context($action_info['type'], $node);
832          }
833        // Since we know about the node, we pass that info along to the action.        // Since we know about the node, we pass that info along to the action.
834        $context['node'] = $node;        $context['node'] = $node;
835        $result = actions_do($aid, $context, $object, $a4, $a4);        $result = actions_do($aid, $objects[$action_info['type']], $context, $a4, $a4);
     // we may need this if node changes by reference are lost  
 /*    if (isset($result['node'])) {  
       $node = $result['node'];  
     }  
 */  
836      }      }
837      else {      else {
838        actions_do($aid, $context, $node, $a3, $a4);        actions_do($aid, $node, $context, $a3, $a4);
839      }      }
   
840    }    }
841  }  }
842    
843    /**
844     * When an action is called in a context that does not match its type,
845     * the object that the action expects must be retrieved. For example, when
846     * an action that works on nodes is called during the comment hook, the
847     * node object is not available since the comment hook doesn't pass it.
848     * So here we load the object the action expects.
849     *
850     * @param $type
851     *   The type of action that is about to be called.
852     * @param $comment
853     *   The comment that was passed via the comment hook.
854     * @return
855     *   The object expected by the action that is about to be called.
856     */
857  function _actions_normalize_comment_context($type, $comment) {  function _actions_normalize_comment_context($type, $comment) {
858    switch ($type) {    switch ($type) {
859      // An action that works with nodes is being called in a comment context.      // An action that works with nodes is being called in a comment context.
860      case 'node':      case 'node':
861        return node_load(array('nid' => $comment->nid));        return node_load(is_array($comment) ? $comment['nid'] : $comment->nid);
862    
863      // An action that works on users is being called in a comment context.      // An action that works on users is being called in a comment context.
864      case 'user':      case 'user':
865        return user_load(array('uid' => $comment->uid));        return user_load(array('uid' => is_array($comment) ? $comment['uid'] : $comment->uid));
866    }    }
867  }  }
868    
# Line 789  function _actions_normalize_comment_cont Line 870  function _actions_normalize_comment_cont
870   * Implementation of hook_comment().   * Implementation of hook_comment().
871   */   */
872  function actions_comment($a1, $op) {  function actions_comment($a1, $op) {
873    // We support a subset of ops.    // Keep objects for reuse so that changes actions make to objects can persist.
874    if (!in_array($a1, array('insert', 'update', 'delete', 'view'))) {    static $objects;
875      // We support a subset of operations.
876      if (!in_array($op, array('insert', 'update', 'delete', 'view'))) {
877      return;      return;
878    }    }
879    $aids = _actions_get_hook_aids('comment', $op);    $aids = _actions_get_hook_aids('comment', $op);
# Line 798  function actions_comment($a1, $op) { Line 881  function actions_comment($a1, $op) {
881      'hook' => 'comment',      'hook' => 'comment',
882      'op' => $op,      'op' => $op,
883    );    );
884      // We need to get the expected object if the action's type is not 'comment'.
885      // We keep the object in $objects so we can reuse it if we have multiple actions
886      // that make changes to an object.
887    foreach ($aids as $aid => $action_info) {    foreach ($aids as $aid => $action_info) {
888      if ($action_info['type'] != 'comment') {      if ($action_info['type'] != 'comment') {
889        $object = _actions_normalize_comment_context($a1);        if (!isset($objects[$action_info['type']])) {
890        // Since we know about the comment, we pass it along to the action.          $objects[$action_info['type']] = _actions_normalize_comment_context($action_info['type'], $a1);
891        $context['comment'] = $a1;        }
892        actions_do($aid, $context, $object);        // Since we know about the comment, we pass it along to the action
893          // in case it wants to peek at it.
894          $context['comment'] = (object) $a1;
895          actions_do($aid, $objects[$action_info['type']], $context);
896      }      }
897      else {      else {
898        actions_do($aid, $context, $a1);        $comment = (object) $a1;
899          actions_do($aid, $comment, $context);
900      }      }
901    }    }
902  }  }
# Line 820  function actions_cron() { Line 910  function actions_cron() {
910      'hook' => 'cron',      'hook' => 'cron',
911      'op' => '',      'op' => '',
912    );    );
913      // Cron does not act on any specific object.
914    actions_do(array_keys($aids), $context);    $object = NULL;
915      actions_do(array_keys($aids), $object, $context);
916  }  }
917    
918    /**
919     * When an action is called in a context that does not match its type,
920     * the object that the action expects must be retrieved. For example, when
921     * an action that works on nodes is called during the user hook, the
922     * node object is not available since the user hook doesn't pass it.
923     * So here we load the object the action expects.
924     *
925     * @param $type
926     *   The type of action that is about to be called.
927     * @param $account
928     *   The account object that was passed via the user hook.
929     * @return
930     *   The object expected by the action that is about to be called.
931     */
932  function _actions_normalize_user_context($type, $account) {  function _actions_normalize_user_context($type, $account) {
933    switch($type) {    switch ($type) {
934        // If an action that works on comments is being called in a user context,
935        // the action is expecting a comment object. But we have no way of
936        // determining the appropriate comment object to pass. So comment
937        // actions in a user context are not supported.
938    
939      // An action that works with nodes is being called in a user context.      // An action that works with nodes is being called in a user context.
940        // If a single node is being viewed, return the node.
941      case 'node':      case 'node':
942          // If we are viewing an individual node, return the node.
943      // An action that works with comments is being called in a user context        if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
944      case 'comment':          return node_load(array('nid' => arg(1)));
945          }
946    }    }
947  }  }
948    
# Line 839  function _actions_normalize_user_context Line 950  function _actions_normalize_user_context
950   * Implementation of hook_user().   * Implementation of hook_user().
951   */   */
952  function actions_user($op, &$edit, &$account, $category = NULL) {  function actions_user($op, &$edit, &$account, $category = NULL) {
953    // We support a subset of ops.    // Keep objects for reuse so that changes actions make to objects can persist.
954      static $objects;
955      // We support a subset of operations.
956    if (!in_array($op, array('login', 'logout', 'insert', 'update', 'delete', 'view'))) {    if (!in_array($op, array('login', 'logout', 'insert', 'update', 'delete', 'view'))) {
957      return;      return;
958    }    }
# Line 851  function actions_user($op, &$edit, &$acc Line 964  function actions_user($op, &$edit, &$acc
964    );    );
965    foreach ($aids as $aid => $action_info) {    foreach ($aids as $aid => $action_info) {
966      if ($action_info['type'] != 'user') {      if ($action_info['type'] != 'user') {
967        $object = _actions_normalize_user_context($account);        if (!isset($objects[$action_info['type']])) {
968            $objects[$action_info['type']] = _actions_normalize_user_context($action_info['type'], $account);
969          }
970        $context['account'] = $account;        $context['account'] = $account;
971        actions_do($aid, $context, $object);        actions_do($aid, $objects[$action_info['type']], $context);
972        }
973        else {
974          actions_do($aid, $account, $context, $category);
975      }      }
976    }    }
977    }
978    
979    actions_do($aids, $context, $account, $category);  /**
980     * Implementation of hook_taxonomy().
981     */
982    function actions_taxonomy($op, $type, $array) {
983      if ($type != 'term') {
984        return;
985      }
986      $aids = _actions_get_hook_aids('taxonomy', $op);
987      $context = array(
988        'hook' => 'taxonomy',
989        'op' => $op
990      );
991      foreach ($aids as $aid => $action_info) {
992        $taxonomy_object = (object) $array;
993        actions_do($aid, $taxonomy_object, $context);
994      }
995  }  }
996    
997  /**  /**
# Line 883  function actions_options($type = 'all') Line 1017  function actions_options($type = 'all')
1017    }    }
1018  }  }
1019    
 // TODO: move to core bootstrap sequence  
 require_once('./includes/actions.inc');  
 include_once(drupal_get_path('module', 'actions') . '/default_actions.inc');  
1020    /**
1021     * Implementation of hook_actions_delete().
1022     *
1023     * Remove all trigger entries for the given action, when deleted.
1024     */
1025    function actions_actions_delete($aid) {
1026      db_query("DELETE FROM {actions_assignments} WHERE aid = '%s'", $aid);
1027    }
1028    
1029    /**
1030     * Implementation of hook_hook_info().
1031     */
1032    function system_hook_info() {
1033      return array(
1034        'system' => array(
1035          'cron' => array(
1036            'run' => array(
1037              'runs when' => t('When cron runs'),
1038            ),
1039          ),
1040        ),
1041      );
1042    }
1043    
1044    /**
1045     * Implementation of hook_action_info().
1046     */
1047    function system_action_info() {
1048      return array(
1049        'system_message_action' => array(
1050          'type' => 'system',
1051          'description' => t('Display a message to the user'),
1052          'configurable' => TRUE,
1053          'hooks' => array(
1054            'nodeapi' => array('view', 'insert', 'update', 'delete'),
1055            'comment' => array('view', 'insert', 'update', 'delete'),
1056            'user' => array('view', 'insert', 'update', 'delete', 'login'),
1057            'taxonomy' => array('insert', 'update', 'delete'),
1058          ),
1059        ),
1060        'system_send_email_action' => array(
1061          'description' => t('Send e-mail'),
1062          'type' => 'system',
1063          'configurable' => TRUE,
1064          'hooks' => array(
1065            'nodeapi' => array('view', 'insert', 'update', 'delete'),
1066            'comment' => array('view', 'insert', 'update', 'delete'),
1067            'user' => array('view', 'insert', 'update', 'delete', 'login'),
1068            'taxonomy' => array('insert', 'update', 'delete'),
1069          )
1070        ),
1071        'system_goto_action' => array(
1072          'description' => t('Redirect to URL'),
1073          'type' => 'system',
1074          'configurable' => TRUE,
1075          'hooks' => array(
1076            'nodeapi' => array('view', 'insert', 'update', 'delete'),
1077            'comment' => array('view', 'insert', 'update', 'delete'),
1078            'user' => array('view', 'insert', 'update', 'delete', 'login'),
1079          )
1080        )
1081      );
1082    }
1083    
1084    /**
1085     * Return a form definition so the Send email action can be configured.
1086     *
1087     * @see system_send_email_action_validate()
1088     * @see system_send_email_action_submit()
1089     * @param $context
1090     *   Default values (if we are editing an existing action instance).
1091     * @return
1092     *   Form definition.
1093     */
1094    function system_send_email_action_form($context) {
1095      // Set default values for form.
1096      if (!isset($context['recipient'])) {
1097        $context['recipient'] = '';
1098      }
1099      if (!isset($context['subject'])) {
1100        $context['subject'] = '';
1101      }
1102      if (!isset($context['message'])) {
1103        $context['message'] = '';
1104      }
1105    
1106      $form['recipient'] = array(
1107        '#type' => 'textfield',
1108        '#title' => t('Recipient'),
1109        '#default_value' => $context['recipient'],
1110        '#maxlength' => '254',
1111        '#description' => t('The email address to which the message should be sent OR enter %author if you would like to send an e-mail to the author of the original post.', array('%author' => '%author')),
1112      );
1113      $form['subject'] = array(
1114        '#type' => 'textfield',
1115        '#title' => t('Subject'),
1116        '#default_value' => $context['subject'],
1117        '#maxlength' => '254',
1118        '#description' => t('The subject of the message.'),
1119      );
1120      $form['me