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

Contents of /contributions/modules/actions/actions.module

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


Revision 1.31 - (hide annotations) (download) (as text)
Mon Mar 17 04:21:38 2008 UTC (20 months, 1 week ago) by jvandyk
Branch: MAIN
CVS Tags: DRUPAL-5--2-0
Changes since 1.30: +1340 -431 lines
File MIME type: text/x-php
backport of Drupal 6 actions and triggers by sorenjones and jvandyk
1 jvandyk 1.1 <?php
2 jvandyk 1.31 // $Id$
3 jvandyk 1.1
4     /**
5 jvandyk 1.31 * @file
6     * 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 jvandyk 1.1
12     /**
13 jvandyk 1.31 * Implementation of hook_help().
14     */
15 jvandyk 1.1 function actions_help($section) {
16 jvandyk 1.31 $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 jvandyk 1.1 switch ($section) {
18 jvandyk 1.31 case 'admin/settings/actions':
19     case 'admin/settings/actions/manage':
20     $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     $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 .= '<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     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 jvandyk 1.1 }
42     }
43    
44     /**
45 jvandyk 1.31 * Implementation of hook_menu().
46     */
47 jvandyk 1.1 function actions_menu($may_cache) {
48     $items = array();
49 jvandyk 1.31 $access = user_access('administer_actions');
50    
51 jvandyk 1.1 if ($may_cache) {
52 jvandyk 1.24 $items[] = array(
53 jvandyk 1.31 'path' => 'admin/settings/actions',
54     'title' => t('Actions'),
55     'description' => t('Manage the actions defined for your site.'),
56     'callback' => 'actions_manage',
57     'access' => $access,
58     );
59     $items[] = array(
60     'path' => 'admin/settings/actions/manage',
61     'title' => t('Manage actions'),
62     'description' => t('Manage the actions defined for your site.'),
63     'callback' => 'actions_manage',
64     'type' => MENU_DEFAULT_LOCAL_TASK,
65     'weight' => -2,
66     );
67     $items[] = array(
68     'path' => 'admin/settings/actions/configure',
69     'title' => t('Configure an advanced action'),
70     'callback' => 'drupal_get_form',
71     'callback arguments' => array('actions_configure'),
72     'type' => MENU_CALLBACK,
73     );
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 jvandyk 1.26 $items[] = array(
83 jvandyk 1.31 'path' => 'admin/settings/actions/orphan',
84     'title' => t('Remove orphans'),
85     'callback' => 'actions_remove_orphans',
86     'type' => MENU_CALLBACK,
87     );
88 jvandyk 1.26 $items[] = array(
89 jvandyk 1.31 'path' => 'admin/build/trigger',
90     'title' => t('Triggers'),
91 jvandyk 1.26 'description' => t('Tell Drupal when to execute actions.'),
92 jvandyk 1.31 'callback' => 'actions_assign',
93 jvandyk 1.26 'access' => $access,
94     );
95 jvandyk 1.31 // We don't use a menu wildcard here because these are tabs,
96     // not invisible items.
97 jvandyk 1.26 $items[] = array(
98 jvandyk 1.31 'path' => 'admin/build/trigger/node',
99     'title' => t('Content'),
100 jvandyk 1.26 'callback' => 'actions_assign',
101     'callback arguments' => array('node'),
102 jvandyk 1.31 'access' => $access,
103     'type' => MENU_LOCAL_TASK,
104 jvandyk 1.26 );
105     $items[] = array(
106 jvandyk 1.31 'path' => 'admin/build/trigger/user',
107     'title' => t('Users'),
108 jvandyk 1.26 'callback' => 'actions_assign',
109     'callback arguments' => array('user'),
110 jvandyk 1.31 'access' => $access,
111     'type' => MENU_LOCAL_TASK,
112 jvandyk 1.26 );
113     $items[] = array(
114 jvandyk 1.31 'path' => 'admin/build/trigger/comment',
115     'title' => t('Comments'),
116     'callback' => 'actions_assign',
117     'callback arguments' => array('comment'),
118 jvandyk 1.26 'access' => $access,
119 jvandyk 1.31 'type' => MENU_LOCAL_TASK,
120     );
121     $items[] = array(
122     'path' => 'admin/build/trigger/taxonomy',
123     'title' => t('Taxonomy'),
124 jvandyk 1.26 'callback' => 'actions_assign',
125 jvandyk 1.31 'callback arguments' => array('taxonomy'),
126     'access' => $access,
127     'type' => MENU_LOCAL_TASK,
128 jvandyk 1.26 );
129     $items[] = array(
130 jvandyk 1.31 'path' => 'admin/build/trigger/cron',
131 jvandyk 1.26 'title' => t('Cron'),
132     'callback' => 'actions_assign',
133     'callback arguments' => array('cron'),
134 jvandyk 1.31 'type' => MENU_LOCAL_TASK,
135 jvandyk 1.26 );
136 jvandyk 1.31
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 jvandyk 1.26 $items[] = array(
158 jvandyk 1.31 'path' => 'admin/build/trigger/unassign',
159 jvandyk 1.26 'title' => t('Unassign'),
160 jvandyk 1.31 'description' => t('Unassign an action from a trigger.'),
161     'callback' => 'drupal_get_form',
162     'callback arguments' => array('actions_unassign'),
163 jvandyk 1.26 'type' => MENU_CALLBACK,
164     );
165 jvandyk 1.31
166     return $items;
167 jvandyk 1.24 }
168 jvandyk 1.31 }
169 jvandyk 1.1
170 jvandyk 1.31 /**
171     * Implementation of hook_perm().
172     */
173     function actions_perm() {
174     return array('administer_actions');
175 jvandyk 1.1 }
176    
177     /**
178 jvandyk 1.31 * Access callback for menu system.
179     */
180     function actions_access_check($module) {
181     return (module_exists($module) && user_access('administer actions'));
182 jvandyk 1.1 }
183    
184     /**
185 jvandyk 1.31 * Menu callback. Display an overview of available and configured actions.
186 jvandyk 1.1 */
187 jvandyk 1.31 function actions_manage() {
188 jvandyk 1.1 $output = '';
189     $actions = actions_list();
190     actions_synchronize($actions);
191     $actions_map = actions_actions_map($actions);
192 jvandyk 1.31 $options = array(t('Choose an advanced action'));
193 jvandyk 1.1 $unconfigurable = array();
194 jvandyk 1.15
195 jvandyk 1.1 foreach ($actions_map as $key => $array) {
196     if ($array['configurable']) {
197 jvandyk 1.31 $options[$key] = $array['description'] .'...';
198 jvandyk 1.1 }
199     else {
200     $unconfigurable[] = $array;
201     }
202     }
203 jvandyk 1.15
204 jvandyk 1.1 $row = array();
205 jvandyk 1.31 $instances_present = db_fetch_object(db_query("SELECT aid FROM {actions} WHERE parameters != ''"));
206 jvandyk 1.16 $header = array(
207 jvandyk 1.31 array('data' => t('Action type'), 'field' => 'type'),
208 jvandyk 1.16 array('data' => t('Description'), 'field' => 'description'),
209     array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')
210 jvandyk 1.31 );
211 jvandyk 1.1 $sql = 'SELECT * FROM {actions}';
212     $result = pager_query($sql . tablesort_sql($header), 50);
213 jvandyk 1.31 while ($action = db_fetch_object($result)) {
214 jvandyk 1.1 $row[] = array(
215 jvandyk 1.31 array('data' => $action->type),
216     array('data' => $action->description),
217     array('data' => $action->parameters ? l(t('configure'), "admin/settings/actions/configure/$action->aid") : ''),
218     array('data' => $action->parameters ? l(t('delete'), "admin/settings/actions/delete/$action->aid") : '')
219 jvandyk 1.1 );
220     }
221 jvandyk 1.15
222 jvandyk 1.1 if ($row) {
223 jvandyk 1.18 $pager = theme('pager', NULL, 50, 0);
224 jvandyk 1.1 if (!empty($pager)) {
225     $row[] = array(array('data' => $pager, 'colspan' => '3'));
226     }
227 jvandyk 1.31 $output .= '<h3>'. t('Actions available to Drupal:') .'</h3>';
228 jvandyk 1.1 $output .= theme('table', $header, $row);
229     }
230 jvandyk 1.15
231 jvandyk 1.31 if ($actions_map) {
232     $output .= drupal_get_form('actions_manage_form', $options);
233     }
234    
235 jvandyk 1.15 return $output;
236 jvandyk 1.1 }
237    
238 jvandyk 1.28 /**
239     * Define the form for the actions overview page.
240     *
241 jvandyk 1.31 * @see actions_manage_form_submit()
242     * @ingroup forms
243 jvandyk 1.28 * @param $options
244     * An array of configurable actions.
245     * @return
246     * Form definition.
247     */
248 jvandyk 1.31 function actions_manage_form($options = array()) {
249     $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 jvandyk 1.24 '#type' => 'select',
257     '#default_value' => '',
258     '#options' => $options,
259     '#description' => '',
260     );
261 jvandyk 1.31 $form['parent']['buttons']['submit'] = array(
262 jvandyk 1.24 '#type' => 'submit',
263 jvandyk 1.31 '#value' => t('Create'),
264 jvandyk 1.24 );
265     return $form;
266     }
267    
268 jvandyk 1.31 /**
269     * Process actions_manage form submissions.
270     */
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 jvandyk 1.24 }
275     }
276    
277 jvandyk 1.1 /**
278 jvandyk 1.31 * Menu callback. Create the form for configuration of a single action.
279     *
280 jvandyk 1.1 * We provide the "Description" field. The rest of the form
281     * is provided by the action. We then provide the Save button.
282 jvandyk 1.30 * Because we are combining unknown form elements with the action
283     * configuration form, we use actions_ prefix on our elements.
284 jvandyk 1.29 *
285 jvandyk 1.31 * @see actions_configure_validate()
286     * @see actions_configure_submit()
287 jvandyk 1.29 * @param $action
288 jvandyk 1.31 * 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 jvandyk 1.1 */
294 jvandyk 1.24 function actions_configure($action = NULL) {
295     if ($action === NULL) {
296 jvandyk 1.31 drupal_goto('admin/settings/actions');
297 jvandyk 1.24 }
298    
299 jvandyk 1.1 $actions_map = actions_actions_map(actions_list());
300 jvandyk 1.24 $edit = array();
301 jvandyk 1.15
302 jvandyk 1.31 // Numeric action denotes saved instance of a configurable action;
303     // else we are creating a new action instance.
304     if (is_numeric($action)) {
305 jvandyk 1.24 $aid = $action;
306 jvandyk 1.30 // Load stored parameter values from database.
307     $data = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", intval($aid)));
308     $edit['actions_description'] = $data->description;
309     $edit['actions_type'] = $data->type;
310 jvandyk 1.31 $function = $data->callback;
311     $action = md5($data->callback);
312     $params = unserialize($data->parameters);
313 jvandyk 1.30 if ($params) {
314     foreach ($params as $name => $val) {
315     $edit[$name] = $val;
316 jvandyk 1.23 }
317 jvandyk 1.1 }
318     }
319 jvandyk 1.31 else {
320     $function = $actions_map[$action]['callback'];
321 jvandyk 1.30 $edit['actions_description'] = $actions_map[$action]['description'];
322     $edit['actions_type'] = $actions_map[$action]['type'];
323 jvandyk 1.15 }
324    
325 jvandyk 1.31 $form = array();
326 jvandyk 1.30 $form['actions_description'] = array(
327 jvandyk 1.15 '#type' => 'textfield',
328     '#title' => t('Description'),
329 jvandyk 1.30 '#default_value' => $edit['actions_description'],
330 jvandyk 1.15 '#maxlength' => '255',
331 jvandyk 1.31 '#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 jvandyk 1.15 '#weight' => -10
333     );
334 jvandyk 1.31 $action_form = $function .'_form';
335 jvandyk 1.29 $form = array_merge($form, $action_form($edit));
336 jvandyk 1.30 $form['actions_type'] = array(
337 jvandyk 1.29 '#type' => 'value',
338 jvandyk 1.30 '#value' => $edit['actions_type'],
339 jvandyk 1.29 );
340 jvandyk 1.30 $form['actions_action'] = array(
341 jvandyk 1.15 '#type' => 'hidden',
342 jvandyk 1.24 '#value' => $action,
343 jvandyk 1.15 );
344 jvandyk 1.31 // $aid is set when configuring an existing action instance.
345     if (isset($aid)) {
346 jvandyk 1.30 $form['actions_aid'] = array(
347 jvandyk 1.15 '#type' => 'hidden',
348     '#value' => $aid,
349     );
350 jvandyk 1.14 }
351 jvandyk 1.30 $form['actions_configured'] = array(
352 jvandyk 1.15 '#type' => 'hidden',
353     '#value' => '1',
354     );
355     $form['buttons']['submit'] = array(
356     '#type' => 'submit',
357     '#value' => t('Save'),
358     '#weight' => 13
359     );
360    
361 jvandyk 1.24 return $form;
362 jvandyk 1.15 }
363    
364 jvandyk 1.31 /**
365     * Validate actions_configure form submissions.
366     */
367 jvandyk 1.24 function actions_configure_validate($form_id, $form_values) {
368 jvandyk 1.31 $function = actions_function_lookup($form_values['actions_action']) .'_validate';
369 jvandyk 1.28 // Hand off validation to the action.
370 jvandyk 1.31 if (function_exists($function)) {
371     $function('validate', $form_values);
372     }
373 jvandyk 1.15 }
374    
375 jvandyk 1.31 /**
376     * Process actions_configure form submissions.
377     */
378 jvandyk 1.24 function actions_configure_submit($form_id, $form_values) {
379 jvandyk 1.30 $function = actions_function_lookup($form_values['actions_action']);
380 jvandyk 1.31 $submit_function = $function .'_submit';
381 jvandyk 1.29
382 jvandyk 1.28 // Action will return keyed array of values to store.
383 jvandyk 1.30 $params = $submit_function($form_id, $form_values);
384 jvandyk 1.31 $aid = isset($form_values['actions_aid']) ? $form_values['actions_aid'] : NULL;
385 jvandyk 1.29
386 jvandyk 1.30 actions_save($function, $form_values['actions_type'], $params, $form_values['actions_description'], $aid);
387 jvandyk 1.15 drupal_set_message(t('The action has been successfully saved.'));
388 jvandyk 1.24
389 jvandyk 1.31 return 'admin/settings/actions/manage';
390 jvandyk 1.1 }
391    
392     /**
393     * Create the form for confirmation of deleting an action.
394     *
395 jvandyk 1.31 * @ingroup forms
396     * @see actions_delete_form_submit()
397 jvandyk 1.1 */
398 jvandyk 1.31 function actions_delete_form($aid) {;
399 jvandyk 1.1 $action = actions_load($aid);
400 jvandyk 1.15 $form['aid'] = array(
401     '#type' => 'hidden',
402 jvandyk 1.31 '#value' => $action->aid,
403 jvandyk 1.24 );
404 jvandyk 1.31 return confirm_form($form,
405     t('Are you sure you want to delete the action %action?', array('%action' => $action->description)),
406     'admin/settings/actions/manage',
407 jvandyk 1.24 t('This cannot be undone.'),
408 jvandyk 1.31 t('Delete'), t('Cancel')
409 jvandyk 1.24 );
410 jvandyk 1.15 }
411    
412 jvandyk 1.31 /**
413     * Process actions_delete form submissions.
414     *
415     * Post-deletion operations for action deletion.
416     */
417 jvandyk 1.24 function actions_delete_form_submit($form_id, $form_values) {
418     $aid = $form_values['aid'];
419     $action = actions_load($aid);
420     actions_delete($aid);
421     $description = check_plain($action->description);
422 jvandyk 1.31 watchdog('user', 'Deleted action %aid (%action)', array('%aid' => $aid, '%action' => $description));
423     drupal_set_message(t('Action %action was deleted', array('%action' => $description)));
424     return 'admin/settings/actions/manage';
425 jvandyk 1.1 }
426    
427     /**
428 jvandyk 1.31 * Post-deletion operations for deleting action orphans.
429 jvandyk 1.1 *
430 jvandyk 1.31 * @param $orphaned
431     * An array of orphaned actions.
432 jvandyk 1.1 */
433 jvandyk 1.31 function actions_action_delete_orphans_post($orphaned) {
434     foreach ($orphaned as $callback) {
435     drupal_set_message(t("Deleted orphaned action (%action).", array('%action' => $callback)));
436 jvandyk 1.1 }
437     }
438    
439     /**
440 jvandyk 1.31 * Remove actions that are in the database but not supported by any enabled module.
441 jvandyk 1.1 */
442 jvandyk 1.31 function actions_remove_orphans() {
443     actions_synchronize(actions_list(), TRUE);
444     drupal_goto('admin/settings/actions/manage');
445 jvandyk 1.1 }
446    
447     /**
448 jvandyk 1.31 * Admin page callbacks for the trigger module.
449 jvandyk 1.1 */
450    
451     /**
452 jvandyk 1.31 * Build the form that allows users to assign actions to hooks.
453 jvandyk 1.1 *
454 jvandyk 1.31 * @param $type
455     * Name of hook.
456     * @return
457     * HTML form.
458 jvandyk 1.1 */
459 jvandyk 1.31 function actions_assign($type = NULL) {
460    
461     // If no type is specified we default to node actions, since they
462     // are the most common.
463     if (!isset($type)) {
464     drupal_goto('admin/build/trigger/node');
465 jvandyk 1.1 }
466 jvandyk 1.31 if ($type == 'node') {
467     $type = 'nodeapi';
468 jvandyk 1.15 }
469    
470 jvandyk 1.31 $output = '';
471     $hooks = module_invoke_all('hook_info');
472     foreach ($hooks as $module => $hook) {
473     if (isset($hook[$type])) {
474     foreach ($hook[$type] as $op => $description) {
475     $form_id = 'actions_'. $type .'_'. $op .'_assign_form';
476     $output .= drupal_get_form($form_id, $type, $op, $description['runs when']);
477 jvandyk 1.15 }
478 jvandyk 1.1 }
479     }
480 jvandyk 1.31 return $output;
481 jvandyk 1.26 }
482    
483     /**
484 jvandyk 1.31 * Confirm removal of an assigned action.
485 jvandyk 1.26 *
486     * @param $hook
487     * @param $op
488 jvandyk 1.31 * @param $aid
489     * The action ID.
490     * @ingroup forms
491     * @see actions_unassign_submit()
492 jvandyk 1.26 */
493 jvandyk 1.31 function actions_unassign($hook = NULL, $op = NULL, $aid = NULL, $form_values = NULL) {
494     if (!($hook && $op && $aid)) {
495     drupal_goto('admin/build/trigger/assign');
496     }
497    
498     $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 jvandyk 1.26 }
533     else {
534 jvandyk 1.31 drupal_goto('admin/build/trigger');
535 jvandyk 1.26 }
536     }
537    
538 jvandyk 1.28 /**
539 jvandyk 1.31 * Create the form definition for assigning an action to a hook-op combination.
540 jvandyk 1.28 *
541 jvandyk 1.31 * @param $form_values
542     * Information about the current form.
543 jvandyk 1.28 * @param $hook
544 jvandyk 1.31 * The name of the hook, e.g., 'nodeapi'.
545 jvandyk 1.28 * @param $op
546 jvandyk 1.31 * The name of the hook operation, e.g., 'insert'.
547     * @param $description
548     * A plain English description of what this hook operation does.
549 jvandyk 1.28 * @return
550 jvandyk 1.31 *
551     * @ingoup forms
552     * @see actions_assign_form_validate()
553     * @see actions_assign_form_submit()
554 jvandyk 1.28 */
555 jvandyk 1.31 function actions_assign_form($hook, $op, $description, $form_value = NULL) {
556 jvandyk 1.26 $form['hook'] = array(
557     '#type' => 'hidden',
558 jvandyk 1.31 '#value' => $hook,
559 jvandyk 1.26 );
560     $form['operation'] = array(
561     '#type' => 'hidden',
562 jvandyk 1.31 '#value' => $op,
563 jvandyk 1.26 );
564 jvandyk 1.31 // 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 jvandyk 1.27
568 jvandyk 1.28 $options = array();
569     $functions = array();
570 jvandyk 1.31 // Restrict the options list to actions that declare support for this hook-op
571     // combination.
572 jvandyk 1.28 foreach (actions_list() as $func => $metadata) {
573 jvandyk 1.31 if (isset($metadata['hooks']['any']) || (isset($metadata['hooks'][$hook]) && is_array($metadata['hooks'][$hook]) && (in_array($op, $metadata['hooks'][$hook])))) {
574 jvandyk 1.28 $functions[] = $func;
575     }
576     }
577     foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {
578 jvandyk 1.31 if (in_array($action['callback'], $functions)) {
579 jvandyk 1.28 $options[$action['type']][$aid] = $action['description'];
580     }
581     }
582 jvandyk 1.27
583 jvandyk 1.26 $form[$op] = array(
584     '#type' => 'fieldset',
585 jvandyk 1.31 '#title' => t('Trigger: ') . $description,
586 jvandyk 1.27 '#theme' => 'actions_display'
587 jvandyk 1.26 );
588 jvandyk 1.31 // Retrieve actions that are already assigned to this hook-op combination.
589 jvandyk 1.26 $actions = _actions_get_hook_actions($hook, $op);
590 jvandyk 1.27 $form[$op]['assigned']['#type'] = 'value';
591     $form[$op]['assigned']['#value'] = array();
592 jvandyk 1.26 foreach ($actions as $aid => $description) {
593 jvandyk 1.27 $form[$op]['assigned']['#value'][$aid] = array(
594     'description' => $description,
595 jvandyk 1.31 'link' => l(t('unassign'), "admin/build/trigger/unassign/$hook/$op/". md5($aid))
596     );
597 jvandyk 1.26 }
598    
599 jvandyk 1.31 $form[$op]['parent'] = array(
600     '#prefix' => "<div class='container-inline'>",
601     '#suffix' => '</div>',
602     );
603 jvandyk 1.26 // List possible actions that may be assigned.
604 jvandyk 1.27 if (count($options) != 0) {
605 jvandyk 1.30 array_unshift($options, t('Choose an action'));
606 jvandyk 1.31 $form[$op]['parent']['aid'] = array(
607 jvandyk 1.26 '#type' => 'select',
608     '#options' => $options,
609     );
610 jvandyk 1.31 $form[$op]['parent']['submit'] = array(
611 jvandyk 1.26 '#type' => 'submit',
612 jvandyk 1.31 '#value' => t('Assign')
613 jvandyk 1.26 );
614     }
615 jvandyk 1.28 else {
616     $form[$op]['none'] = array(
617 jvandyk 1.31 '#value' => t('No available actions for this trigger.')
618 jvandyk 1.28 );
619     }
620 jvandyk 1.26 return $form;
621     }
622    
623 jvandyk 1.31 /**
624     * 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 jvandyk 1.30 $aid = actions_function_lookup($form_values['aid']);
631 jvandyk 1.31 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 jvandyk 1.30 $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);
646 jvandyk 1.31 // 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 jvandyk 1.30 }
661 jvandyk 1.26 }
662    
663 jvandyk 1.27 /**
664 jvandyk 1.31 * Display actions assigned to this hook-op combination in a table.
665 jvandyk 1.27 *
666     * @param array $element
667     * The fieldset including all assigned actions.
668     * @return
669     * The rendered form with the table prepended.
670 jvandyk 1.31 *
671     * @ingroup themeable
672 jvandyk 1.27 */
673 jvandyk 1.26 function theme_actions_display($element) {
674 jvandyk 1.31 $header = array();
675     $rows = array();
676 jvandyk 1.27 if (count($element['assigned']['#value'])) {
677 jvandyk 1.31 $header = array(array('data' => t('Name')), array('data' => t('Operation')));
678 jvandyk 1.27 $rows = array();
679     foreach ($element['assigned']['#value'] as $aid => $info) {
680     $rows[] = array(
681     $info['description'],
682     $info['link']
683     );
684     }
685     }
686 jvandyk 1.28
687 jvandyk 1.31 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 jvandyk 1.1 }
695    
696 jvandyk 1.31
697 jvandyk 1.10 /**
698 jvandyk 1.31 * Get the actions that have already been defined for this
699     * type-hook-op combination.
700 jvandyk 1.26 *
701 jvandyk 1.31 * @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 jvandyk 1.10 */
712 jvandyk 1.31 function _actions_get_hook_actions($hook, $op, $type = NULL) {
713     $actions = array();
714     if ($type) {
715     $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);
716     }
717     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);
719     }
720     while ($action = db_fetch_object($result)) {
721     $actions[$action->aid] = $action->description;
722     }
723     return $actions;
724 jvandyk 1.26 }
725 jvandyk 1.27
726 jvandyk 1.31 /**
727     * Get the aids of actions to be executed for a hook-op combination.
728     *
729     * @param $hook
730     * The name of the hook being fired.
731     * @param $op
732     * The name of the operation being executed. Defaults to an empty string
733     * because some hooks (e.g., hook_cron()) do not have operations.
734     * @return
735     * An array of action IDs.
736     */
737     function _actions_get_hook_aids($hook, $op = '') {
738     $aids = array();
739     $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     while ($action = db_fetch_object($result)) {
741     $aids[$action->aid]['type'] = $action->type;
742 jvandyk 1.26 }
743 jvandyk 1.31 return $aids;
744 jvandyk 1.26 }
745    
746 jvandyk 1.31 /**
747     * Implementation of hook_theme().
748     */
749     function actions_theme() {
750     return array(
751     'actions_display' => array(
752     'arguments' => array('element'),
753     ),
754 jvandyk 1.26 );
755     }
756    
757 jvandyk 1.31 /**
758     * Implementation of hook_forms(). We reuse code by using the
759     * same assignment form definition for each node-op combination.
760     */
761     function actions_forms() {
762     $hooks = module_invoke_all('hook_info');
763     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 jvandyk 1.26 }
770 jvandyk 1.31
771     return $forms;
772 jvandyk 1.26 }
773    
774 jvandyk 1.31 /**
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 jvandyk 1.29 function _actions_normalize_node_context($type, $node) {
789 jvandyk 1.31 switch ($type) {
790     // If an action that works on comments is being called in a node context,
791     // 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 jvandyk 1.29
795     // An action that works on users is being called in a node context.
796     // Load the user object of the node's author.
797     case 'user':
798     return user_load(array('uid' => $node->uid));
799     }
800     }
801    
802 jvandyk 1.27 /**
803     * Implementation of hook_nodeapi().
804     */
805 jvandyk 1.31 function actions_nodeapi(&$node, $op, $a3, $a4) {
806     // 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 jvandyk 1.30 static $recursion;
810 jvandyk 1.31 // Support a subset of operations.
811     if (!in_array($op, array('view', 'update', 'presave', 'insert', 'delete')) || isset($recursion[$op])) {
812 jvandyk 1.30 return;
813     }
814     $recursion[$op] = TRUE;
815    
816 jvandyk 1.28 $aids = _actions_get_hook_aids('nodeapi', $op);
817 jvandyk 1.30 if (!$aids) {
818     return;
819     }
820 jvandyk 1.27 $context = array(
821     'hook' => 'nodeapi',
822     'op' => $op,
823     );
824 jvandyk 1.28
825 jvandyk 1.30 // We need to get the expected object if the action's type is not 'node'.
826 jvandyk 1.31 // We keep the object in $objects so we can reuse it if we have multiple actions
827     // that make changes to an object.
828 jvandyk 1.29 foreach ($aids as $aid => $action_info) {
829     if ($action_info['type'] != 'node') {
830 jvandyk 1.31 if (!isset($objects[$action_info['type']])) {
831     $objects[$action_info['type']] = _actions_normalize_node_context($action_info['type'], $node);
832     }
833 jvandyk 1.29 // Since we know about the node, we pass that info along to the action.
834     $context['node'] = $node;
835 jvandyk 1.31 $result = actions_do($aid, $objects[$action_info['type']], $context, $a4, $a4);
836 jvandyk 1.29 }
837     else {
838 jvandyk 1.31 actions_do($aid, $node, $context, $a3, $a4);
839 jvandyk 1.29 }
840     }
841     }
842    
843 jvandyk 1.31 /**
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