/[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.25.2.3 - (show annotations) (download) (as text)
Thu Oct 18 22:13:19 2007 UTC (2 years, 1 month ago) by jvandyk
Branch: DRUPAL-5
CVS Tags: DRUPAL-5--1-0
Changes since 1.25.2.2: +3 -9 lines
File MIME type: text/x-php
#184032 help hook improperly implemented
1 <?php
2 /* $Id: actions.module,v 1.25.2.2 2007/03/11 21:17:46 jvandyk Exp $ */
3
4 /**
5 * @file
6 * Enables functions to be stored as scripts to be triggered by other modules.
7 */
8
9 /**
10 * Implementation of hook_help().
11 */
12 function actions_help($section) {
13 switch ($section) {
14 case 'admin/build/actions':
15 return 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.');
16 case 'admin/build/actions/config':
17 return 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.');
18 }
19 }
20
21 /**
22 * Implementation of hook_menu().
23 */
24 function actions_menu($may_cache) {
25 $items = array();
26 $access = user_access('administer actions');
27
28 if ($may_cache) {
29 $items[] = array(
30 'path' => 'admin/build/actions',
31 'title' => t('Actions'),
32 'description' => t('Manage the actions defined for your site.'),
33 'access' => $access,
34 'callback' => 'actions_overview');
35
36 $items[] = array(
37 'path' => 'admin/build/actions/config',
38 'title' => t('Configure action'),
39 'access' => $access,
40 'weight' => -9,
41 'callback' => 'drupal_get_form',
42 'callback arguments' => array('actions_configure'),
43 'type' => MENU_CALLBACK);
44
45 $items[] = array(
46 'path' => 'admin/build/actions/delete',
47 'title' => t('Delete action'),
48 'access' => $access,
49 'callback' => 'drupal_get_form',
50 'callback arguments' => array('actions_delete_form'),
51 'weight' => -8,
52 'type' => MENU_CALLBACK);
53
54 $items[] = array(
55 'path' => 'admin/build/actions/orphan',
56 'title' => t('Delete action'),
57 'access' => $access,
58 'callback' => 'actions_remove_orphans',
59 'weight' => -8,
60 'type' => MENU_CALLBACK);
61 }
62
63 return $items;
64 }
65
66 /**
67 * Implementation of hook_perm().
68 */
69 function actions_perm() {
70 return array('administer actions');
71 }
72
73 /**
74 * Menu callback.
75 * Create the main action module page giving an overview of configured actions.
76 *
77 */
78 function actions_overview() {
79 $output = '';
80 $actions = actions_list();
81 actions_synchronize($actions);
82 $actions_map = actions_actions_map($actions);
83 $options = array();
84 $unconfigurable = array();
85
86 foreach ($actions_map as $key => $array) {
87 if ($array['configurable']) {
88 $options[$key] = $array['description'] . '...';
89 }
90 else {
91 $unconfigurable[] = $array;
92 }
93 }
94
95 if ($actions_map) {
96 $output .= drupal_get_form('actions_overview_form', $options);
97 }
98
99 $row = array();
100 $instances_present = db_fetch_object(db_query("SELECT aid FROM {actions} WHERE params != ''"));
101 $header = array(
102 array('data' => t('Action Type'), 'field' => 'type'),
103 array('data' => t('Description'), 'field' => 'description'),
104 array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')
105 );
106 $sql = 'SELECT * FROM {actions}';
107 $result = pager_query($sql . tablesort_sql($header), 50);
108 while ($data = db_fetch_object($result)) {
109 $row[] = array(
110 array('data' => $data->type),
111 array('data' => $data->description),
112 array('data' => $data->params ? l(t('configure'), "admin/build/actions/config/$data->aid") : ''),
113 array('data' => $data->params ? l(t('delete'), "admin/build/actions/delete/$data->aid") : '')
114 );
115 if ($data->params) {
116 $instances_present = TRUE;
117 }
118 }
119
120 if ($row) {
121 $pager = theme('pager', NULL, 50, 0);
122 if (!empty($pager)) {
123 $row[] = array(array('data' => $pager, 'colspan' => '3'));
124 }
125 $output .= '<h3>' . t('Actions available to Drupal:') . '</h3>';
126 $output .= theme('table', $header, $row);
127 }
128
129 return $output;
130 }
131
132 function actions_overview_form($options = array()) {
133 $form['action'] = array(
134 '#type' => 'select',
135 '#title' => t('Add new action'),
136 '#default_value' => '',
137 '#options' => $options,
138 '#description' => '',
139 );
140 $form['buttons']['submit'] = array(
141 '#type' => 'submit',
142 '#value' => t('Add new action'),
143 );
144 return $form;
145 }
146
147 function actions_overview_form_submit($form_id, $form_values) {
148 if ($form_values['action'] != '') {
149 return 'admin/build/actions/config/' . $form_values['action'];
150 }
151 }
152
153 /**
154 * Menu callback.
155 * Create the form for configuration of a single action.
156 * We provide the "Description" field. The rest of the form
157 * is provided by the action. We then provide the Save button.
158 */
159 function actions_configure($action = NULL) {
160 if ($action === NULL) {
161 drupal_goto('admin/build/actions');
162 }
163
164 $actions_map = actions_actions_map(actions_list());
165 $edit = array();
166
167 if (is_numeric($action)) {
168 // if the form has been filled out, ask the action's function to validate it
169 $dummy = array();
170 $aid = $action;
171 if ($aid) {
172 // load values from database
173 $data = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", intval($aid)));
174 $edit['actions_desc'] = $data->description;
175 $edit['actions_type'] = $data->type;
176 $function = $data->func;
177 $action = md5($data->func);
178 $params = unserialize($data->params);
179 if ($params) {
180 foreach ($params as $name => $val) {
181 $edit[$name] = $val;
182 }
183 }
184 }
185 }
186 else {
187 $function = $actions_map[$action]['function'];
188 $edit['actions_desc'] = $actions_map[$action]['description'];
189 }
190
191 $form = array();
192 $form['actions_desc'] = array(
193 '#type' => 'textfield',
194 '#title' => t('Description'),
195 '#default_value' => $edit['actions_desc'],
196 '#size' => '70',
197 '#maxlength' => '255',
198 '#description' => t('A unique description for this configuration of this action'),
199 '#weight' => -10
200 );
201 $form = array_merge($form, $function('form', $edit, $dummy));
202 $form['action'] = array(
203 '#type' => 'hidden',
204 '#value' => $action,
205 );
206 if ($aid) {
207 $form['aid'] = array(
208 '#type' => 'hidden',
209 '#value' => $aid,
210 );
211 }
212 $form['configured'] = array(
213 '#type' => 'hidden',
214 '#value' => '1',
215 );
216 $form['buttons']['submit'] = array(
217 '#type' => 'submit',
218 '#value' => t('Save'),
219 '#weight' => 13
220 );
221
222 return $form;
223 }
224
225 function actions_configure_validate($form_id, $form_values) {
226 $function = actions_key_lookup($form_values['action']);
227 $dummy = array();
228 $function('validate', $form_values, $dummy);
229 }
230
231 function actions_configure_submit($form_id, $form_values) {
232 $function = actions_key_lookup($form_values['action']);
233 $dummy = array();
234 $params = $function('submit', $form_values, $dummy);
235 $metadata = $function('metadata', $form_values, $dummy);
236 $aid = $form_values['aid'];
237 actions_save($function, $metadata['type'], $params, $form_values['actions_desc'], $aid);
238 drupal_set_message(t('The action has been successfully saved.'));
239
240 return 'admin/build/actions';
241 }
242
243 /**
244 * Menu callback.
245 * Create the form for confirmation of deleting an action.
246 *
247 */
248 function actions_delete_form($aid) {
249 if (!$aid) drupal_goto('admin/build/actions');
250 $action = actions_load($aid);
251 $form['aid'] = array(
252 '#type' => 'hidden',
253 '#value' => $aid
254 );
255
256 return confirm_form(
257 $form,
258 t('Really delete action !action?', array('!action' => theme('placeholder', $action->description))),
259 'admin/build/actions',
260 t('This cannot be undone.'),
261 t('Delete'),
262 t('Cancel')
263 );
264 }
265
266 function actions_delete_form_submit($form_id, $form_values) {
267 // note that in the future we need to check for dependencies here
268 $aid = $form_values['aid'];
269 $action = actions_load($aid);
270 actions_delete($aid);
271 $description = check_plain($action->description);
272 watchdog('user', t('Deleted action %aid (%action)', array('%aid' => $aid, '%action' => $description)));
273 drupal_set_message(t('Action !action was deleted', array('!action' => theme('placeholder', $description))));
274 return 'admin/build/actions';
275 }
276
277 /**
278 * Given the IDs of actions to perform, find out what the functions
279 * for the actions are by querying the database. Then call each function
280 * using the function call $function('do', $params, $a1, $a2, $a3, $a4)
281 * where $function is the name of a function written in compliance with
282 * the action specification. The $params parameter is an array of
283 * stored parameters that have been previously configured through the
284 * web using actions.module.
285 *
286 * @param $aids
287 * The ID of the action to perform. Can be a single action ID or an array
288 * of IDs. IDs of instances will be numeric; IDs of singletons will be
289 * function names.
290 * @param $a1
291 * Parameter that will be passed along to the callback.
292 * @param $a2
293 * Parameter that will be passed along to the callback.
294 * @param $a3
295 * Parameter that will be passed along to the callback.
296 * @param $a4
297 * Parameter that will be passed along to the callback.
298 *
299 * @return
300 * An associative array containing the result of the function that
301 * performs the action, keyed on action ID.
302 *
303 */
304 function actions_do($aids, $a1 = NULL, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
305 static $stack;
306 $stack++;
307 if ($stack > 25) {
308 watchdog('actions', t('Stack overflow; aborting.'), WATCHDOG_ERROR);
309 return;
310 }
311 $actions = array();
312 $available_actions = actions_list();
313 $result = array();
314 if (is_array($aids)) {
315 $where = '';
316 foreach ($aids as $aid) {
317 if (is_numeric($aid)) {
318 $where .= 'OR aid = ' . $aid . ' ';
319 }
320 elseif (isset($available_actions[$aid])) {
321 $actions[$aid] = $available_actions[$aid];
322 }
323 }
324
325 if ($where) { // we must go to the database to retrieve instance data
326 // strip off leading 'OR '
327 $where = $where ? '(' . strstr($where, " ") . ')' : '';
328 $result_db = db_query("SELECT * FROM {actions} WHERE $where");
329 while ($data = db_fetch_object($result_db)) {
330 $aid = $data->aid;
331 $actions[$aid] = $data->params ? unserialize($data->params) : array();
332 $actions[$aid]['function'] = $data->func;
333 $actions[$aid]['type'] = $data->type;
334 $actions[$aid]['batchable'] = $available_actions[$data->func]['batchable'];
335 }
336 }
337
338 // batch node functions to avoid unnecessary and costly node_load
339 // and node_save for each action; only the last one will do node_save
340 $batch = array();
341 foreach ($actions as $func => $metadata) {
342 if ($metadata['batchable']) {
343 $batch[$metadata['type']][] = $func;
344 }
345 }
346
347 // fire batched actions
348 // each type has its own batch
349 foreach ($batch as $type) {
350 // remove last batchable action so it will not receive 'defer'
351 array_pop($type);
352 foreach ($type as $action) {
353 if (is_numeric($action)) { // it needs parameters
354 $actions[$action]['defer'] = TRUE;
355 $result[$action] = $actions[$action]['function']('do', $actions[$action], $a1, $a2, $a3, $a4);
356 }
357 else { // singleton
358 $result[$action] = $action('do', array('defer' => TRUE), $a1, $a2, $a3, $a4);
359 }
360 // remove action we've already fired
361 unset($actions[$action]);
362 }
363 }
364
365 // fire remaining actions in no particular order
366 foreach ($actions as $aid => $params) {
367 if (is_numeric($aid)) { // it needs parameters
368 $function = $params['function'];
369 $result[$aid] = $function('do', $params, $a1, $a2, $a3, $a4);
370 }
371 else {
372 $result[$aid] = $aid('do', array(), $a1, $a2, $a3, $a4);
373 }
374 }
375 }
376 else { // optimized for single action
377 if (is_numeric($aids)) {
378 $data = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $aids));
379 $function = $data->func;
380 $result[$aids] = $function('do', unserialize($data->params), $a1, $a2, $a3, $a4);
381 }
382 else { // $aids is an actual function name
383 $result[$aids] = $aids('do', array(), $a1, $a2, $a4, $a4);
384 }
385 }
386 return $result;
387 }
388
389 /**
390 * Discover all action functions. These begin with 'action_'.
391 * An action function must be of the form
392 * 'action_' . module name . function name($op ...)
393 *
394 * action_example_foo($op, $context, $node) {
395 * switch($op) {
396 * case 'metadata':
397 * return array(
398 * 'type' = t('Node'),
399 * 'description' = t('Translate a node to French'),
400 * 'configurable' = false,
401 * 'batchable' = false)
402 * case 'do':
403 * $node->body = lang_translate($node->body, 'FR');
404 * node_save($node);
405 * }
406 * }
407 *
408 * The description is used in presenting possible actions to the user for
409 * configuration. The type is used to present these actions in a logical
410 * grouping.
411 *
412 *
413 * @return
414 * An associative array keyed on function name. The value of each key is
415 * an array containing information about the action, such as type of
416 * action and description of the action.
417 * E.g. $actions['actions_node_publish'] = ('description' => 'Publish a node' ... )
418 *
419 */
420 function actions_list() {
421 static $actions;
422 if (isset($actions)) {
423 return $actions;
424 }
425
426 $actions = array();
427
428 $all_func = get_defined_functions();
429 $functions = array_filter($all_func['user'], '_actions_isaction');
430 $dummy = array();
431 foreach ($functions as $function) {
432 $actions[$function] = $function('metadata', $dummy, $dummy);
433 }
434 return $actions;
435 }
436
437 /**
438 * Create an associative array keyed by md5 hashes of function names.
439 * Hashes are used to prevent actual function names from going out into
440 * HTML forms and coming back.
441 *
442 * @param $actions
443 * An associative array with function names as keys and associative
444 * arrays with keys 'description', 'type', etc. as values. Generally
445 * the output of actions_list() or actions_get_all_actions is given
446 * as input to this function.
447 *
448 * @return
449 * An associative array keyed on md5 hash of function name. The value of
450 * each key is an associative array of function, description, and type
451 * for the action.
452 */
453 function actions_actions_map($actions) {
454 $actions_map = array();
455 foreach ($actions as $func => $array) {
456 $key = md5($func);
457 $actions_map[$key]['function'] = $func;
458 $actions_map[$key]['description'] = $array['description'];
459 $actions_map[$key]['type'] = $array['type'];
460 $actions_map[$key]['configurable'] = $array['configurable'];
461 $actions_map[$key]['batchable'] = $array['batchable'];
462 }
463 return $actions_map;
464 }
465
466 /**
467 * Given an md5 hash of a function name, return the function name.
468 * Faster than actions_actions_map() when you only need the function name.
469 *
470 * @param $hash
471 * MD5 hash of a function name
472 *
473 * @return
474 * Function name
475 *
476 */
477 function actions_key_lookup($hash) {
478 foreach (actions_list() as $func => $array) {
479 if (md5($func) == $hash) {
480 return $func;
481 }
482 }
483
484 // must be an instance; must check database
485 $result = db_query("SELECT aid FROM {actions} WHERE params != ''");
486 while ($data = db_fetch_object($result)) {
487 if (md5($data->aid) == $hash) {
488 return $data->aid;
489 }
490 }
491 }
492
493 /**
494 * Save an action and its associated user-supplied parameter values to
495 * the database.
496 *
497 * @param $function
498 * The name of the function to be called when this action is performed.
499 * @param $params
500 * An associative array with parameter names as keys and parameter values
501 * as values.
502 * @param $desc
503 * A user-supplied description of this particular action, e.g. 'Send
504 * e-mail to Jim'
505 * @param $aid
506 * The ID of this action. If omitted, a new action is created.
507 *
508 * @return
509 * The ID of the action.
510 */
511 function actions_save($function, $type, $params, $desc, $aid = NULL) {
512 $serialized = serialize($params);
513 if ($aid) {
514 db_query("UPDATE {actions} SET func = '%s', type = '%s', params = '%s', description = '%s' WHERE aid = %d", $function, $type, $serialized, $desc, $aid);
515 watchdog('user', t("Action '%action' saved.", array('%action' => check_plain($desc))));
516 }
517 else {
518 $aid = db_next_id('{actions}_aid');
519 db_query("INSERT INTO {actions} (aid, func, type, params, description) VALUES (%d, '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);
520 watchdog('user', t("Action '%action' created.", array('%action' => check_plain($desc))));
521 }
522
523 return $aid;
524 }
525
526 /**
527 * Retrieve a single action from the database.
528 *
529 * @param $aid
530 * integer The ID of the action to retrieve.
531 *
532 * @return
533 * The appropriate action row from the database as an object.
534 */
535 function actions_load($aid) {
536 return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $aid));
537 }
538
539 /**
540 * Delete a single action from the database.
541 *
542 * @param $aid
543 * integer The ID of the action to retrieve.
544 *
545 */
546 function actions_delete($aid) {
547 db_query("DELETE FROM {actions} WHERE aid = %d", $aid);
548 }
549
550 /**
551 * Retrieve all action instances from the database.
552 * Compare with actions_list() which gathers actions from
553 * the PHP function namespace. The two are synchronized
554 * by visiting /admin/build/actions, which runs actions_synchronize().
555 *
556 * @return
557 * Associative array keyed by action ID. Each value is
558 * an associative array with keys 'function', 'description',
559 * and 'type'.
560 */
561 function actions_get_all_actions() {
562 $actions = array();
563 $result = db_query("SELECT * FROM {actions}");
564 while ($data = db_fetch_object($result)) {
565 $actions[$data->aid] = array('function' => $data->func, 'description' => $data->description, 'type' => $data->type);
566 }
567 return $actions;
568 }
569
570 /**
571 * Register an action in the actions registry. This function is called
572 * by modules that are using the actions. The actions registry
573 * keeps track of in which modules actions are used. This allows us
574 * to warn the administrator when an action is about to be deleted by
575 * pointing out the action is still being used by such and such a module.
576 *
577 */
578 function actions_register($aid, $module_name, $id) {
579 if (db_fetch_object(db_query("SELECT aid FROM {actions_registry} WHERE aid = %d AND module = '%s' AND id = %d", $aid, $module_name, $id))) return;
580 db_query("INSERT INTO {actions_registry} (aid, module, id) VALUES (%d, '%s', %d)", $aid, $module_name, $id);
581 }
582
583 /**
584 * Remove an action from the actions registry. This must be
585 * done by all modules that are using an action before an action
586 * is deleted. For example, if workflow.module is using action 14
587 * in a transition, workflow.module is responsible for calling
588 * actions_unregister when the transition is deleted.
589 *
590 * @return
591 * Associative array keyed by action ID. Each value is
592 * an associative array with keys 'function' and 'description'.
593 */
594 function actions_unregister($aid, $module_name, $id) {
595 db_query("DELETE FROM {actions_registry} WHERE aid = %d AND module = '%s' AND id = %d", $aid, $module_name, $id);
596 }
597
598 /**
599 * Synchronize actions that are provided by modules with actions
600 * that are stored in the actions table. This is necessary so that
601 * actions that do not require configuration can receive action IDs.
602 * This is not necessarily the best approach, but it is the most
603 * straightforward.
604 *
605 */
606 function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
607 if (!$actions_in_code) {
608 $actions_in_code = actions_list();
609 }
610 $actions_in_db = array();
611 $result = db_query("SELECT * FROM {actions} WHERE params = ''");
612 while ($data = db_fetch_object($result)) {
613 $actions_in_db[$data->func] = array('aid' => $data->aid, 'description' => $data->description);
614 }
615
616 // go through all the actions provided by modules
617 foreach ($actions_in_code as $func => $array) {
618 // ignore configurable actions since their instances get put in when user adds action
619 if (!$array['configurable']) {
620 // if we already have an action ID for this action
621 if (array_key_exists($func, $actions_in_db)) {
622 unset($actions_in_db[$func]);
623 }
624 else {
625 // this is a new singleton that we don't have an aid for; assign one
626 db_query("INSERT INTO {actions} (aid, type, func, params, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $func, $array['type'], $func, '', $array['description']);
627 drupal_set_message(t("Action '%action' added.", array('%action' => htmlspecialchars($array['description']))));
628 }
629 }
630 }
631
632 // any actions that we have left in $actions_in_db are orphaned
633 if ($actions_in_db) {
634 $orphaned = '';
635
636 foreach ($actions_in_db as $func => $array) {
637 if ($delete_orphans) {
638 db_query("DELETE FROM {actions} WHERE func = '%s'", $func);
639 drupal_set_message(t('Deleted orphaned action') . " '$func'.");
640 }
641 else {
642 $orphaned .= $func . ', ';
643 }
644 }
645
646 if (!$delete_orphans) {
647 $orphaned = rtrim(rtrim($orphaned, ' '), ',');
648 $link = l(t('Remove orphaned actions'), 'admin/build/actions/orphan');
649
650 drupal_set_message(t("%actionphrase in the actions table: ", array('%actionphrase' => format_plural(count($actions_in_db), 'One orphaned action exists', '%count orphaned actions exist'))) . ' (' . $orphaned . '). ' . $link, 'warning');
651 }
652 }
653 }
654
655 function actions_remove_orphans() {
656 actions_synchronize(actions_list(), TRUE);
657 drupal_goto('admin/build/actions');
658 }
659
660 /**
661 * Callback function for array_filter in actions_list()
662 */
663 function _actions_isaction($s) {
664 return substr($s, 0, 7) == 'action_';
665 }
666
667 include_once(drupal_get_path('module', 'actions') . '/actions.inc');

  ViewVC Help
Powered by ViewVC 1.1.2