Code coverage for /20080809/includes/actions.inc

Line #Times calledCode
1
<?php
2
// $Id: actions.inc,v 1.13 2008/06/29 12:07:15 dries Exp $
3
4
/**
5
 * @file
6
 * This is the actions engine for executing stored actions.
7
 */
8
9
/**
10
 * Perform a given list of actions by executing their callback functions.
11
 *
12
 * Given the IDs of actions to perform, find out what the callbacks
13
 * for the actions are by querying the database. Then call each callback
14
 * using the function call $function($object, $context, $a1, $2)
15
 * where $function is the name of a function written in compliance with
16
 * the action specification; that is, foo($object, $context).
17
 *
18
 * @param $action_ids
19
 *   The ID of the action to perform. Can be a single action ID or an
array
20
 *   of IDs. IDs of instances will be numeric; IDs of singletons will be
21
 *   function names.
22
 * @param $object
23
 *   Parameter that will be passed along to the callback. Typically the
24
 *   object that the action will act on; a node, user or comment object.
25
 * @param $context
26
 *   Parameter that will be passed along to the callback. $context is a
27
 *   keyed array containing extra information about what is currently
28
 *   happening at the time of the call. Typically $context['hook'] and
29
 *   $context['op'] will tell which hook-op combination resulted in this
30
 *   call to actions_do().
31
 * @param $a1
32
 *   Parameter that will be passed along to the callback.
33
 * @param $a2
34
 *   Parameter that will be passed along to the callback.
35
 *
36
 * @return
37
 *   An associative array containing the result of the function that
38
 *   performs the action, keyed on action ID.
39
 */
402027
function actions_do($action_ids, $object = NULL, $context = NULL, $a1 =
NULL, $a2 = NULL) {
416
  static $stack;
426
  $stack++;
436
  if ($stack > variable_get('actions_max_stack', 35)) {
440
    watchdog('actions', 'Stack overflow: too many calls to actions_do().
Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR);
450
    return;
460
  }
476
  $actions = array();
486
  $available_actions = actions_list();
496
  $result = array();
506
  if (is_array($action_ids)) {
510
    $where = array();
520
    $where_values = array();
530
    foreach ($action_ids as $action_id) {
540
      if (is_numeric($action_id)) {
550
        $where[] = 'OR aid = %d';
560
        $where_values[] = $action_id;
570
      }
580
      elseif (isset($available_actions[$action_id])) {
590
        $actions[$action_id] = $available_actions[$action_id];
600
      }
610
    }
62
63
    // When we have action instances we must go to the database to
64
    // retrieve instance data.
650
    if ($where) {
660
      $where_clause = implode(' ', $where);
67
      // Strip off leading 'OR '.
680
      $where_clause = '(' . strstr($where_clause, " ") . ')';
690
      $result_db = db_query('SELECT * FROM {actions} WHERE ' .
$where_clause, $where_values);
700
      while ($action = db_fetch_object($result_db)) {
710
        $actions[$action->aid] = $action->parameters ?
unserialize($action->parameters) : array();
720
        $actions[$action->aid]['callback'] = $action->callback;
730
        $actions[$action->aid]['type'] = $action->type;
740
      }
750
    }
76
77
    // Fire actions, in no particular order.
780
    foreach ($actions as $action_id => $params) {
790
      if (is_numeric($action_id)) { // Configurable actions need
parameters.
800
        $function = $params['callback'];
810
        $context = array_merge($context, $params);
820
        $result[$action_id] = $function($object, $context, $a1, $a2);
830
      }
84
      // Singleton action; $action_id is the function name.
85
      else {
860
        $result[$action_id] = $action_id($object, $context, $a1, $a2);
87
      }
880
    }
890
  }
90
  // Optimized execution of single action.
91
  else {
92
    // If it's a configurable action, retrieve stored parameters.
936
    if (is_numeric($action_ids)) {
940
      $action = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid
= %d", $action_ids));
950
      $function = $action->callback;
960
      $context = array_merge($context, unserialize($action->parameters));
970
      $result[$action_ids] = $function($object, $context, $a1, $a2);
980
    }
99
    // Singleton action; $action_ids is the function name.
100
    else {
1016
      $result[$action_ids] = $action_ids($object, $context, $a1, $a2);
102
    }
103
  }
1046
  return $result;
1050
}
106
107
108
/**
109
 * Discover all action functions by invoking hook_action_info().
110
 *
111
 * mymodule_action_info() {
112
 *   return array(
113
 *     'mymodule_functiondescription_action' => array(
114
 *       'type' => 'node',
115
 *       'description' => t('Save node'),
116
 *       'configurable' => FALSE,
117
 *       'hooks' => array(
118
 *         'nodeapi' => array('delete', 'insert', 'update', 'view'),
119
 *         'comment' => array('delete', 'insert', 'update', 'view'),
120
 *       )
121
 *     )
122
 *   );
123
 * }
124
 *
125
 * The description is used in presenting possible actions to the user for
126
 * configuration. The type is used to present these actions in a logical
127
 * grouping and to denote context. Some types are 'node', 'user',
'comment',
128
 * and 'system'. If an action is configurable it will provide form,
129
 * validation and submission functions. The hooks the action supports
130
 * are declared in the 'hooks' array.
131
 *
132
 * @param $reset
133
 *   Reset the action info static cache.
134
 *
135
 * @return
136
 *   An associative array keyed on function name. The value of each key is
137
 *   an array containing information about the action, such as type of
138
 *   action and description of the action, e.g.,
139
 *
140
 *   @code
141
 *   $actions['node_publish_action'] = array(
142
 *     'type' => 'node',
143
 *     'description' => t('Publish post'),
144
 *     'configurable' => FALSE,
145
 *     'hooks' => array(
146
 *       'nodeapi' => array('presave', 'insert', 'update', 'view'),
147
 *       'comment' => array('delete', 'insert', 'update', 'view'),
148
 *     ),
149
 *   );
150
 *   @endcode
151
 */
1522027
function actions_list($reset = FALSE) {
153131
  static $actions;
154131
  if (!isset($actions) || $reset) {
155131
    $actions = module_invoke_all('action_info');
156131
    drupal_alter('action_info', $actions);
157131
  }
158
159
  // See module_implements for explanations of this cast.
160131
  return (array)$actions;
1610
}
162
163
/**
164
 * Retrieve all action instances from the database.
165
 *
166
 * Compare with actions_list() which gathers actions by
167
 * invoking hook_action_info(). The two are synchronized
168
 * by visiting /admin/build/actions (when actions.module is
169
 * enabled) which runs actions_synchronize().
170
 *
171
 * @return
172
 *   Associative array keyed by action ID. Each value is
173
 *   an associative array with keys 'callback', 'description',
174
 *   'type' and 'configurable'.
175
 */
1762027
function actions_get_all_actions() {
17760
  $actions = array();
17860
  $result = db_query("SELECT * FROM {actions}");
17960
  while ($action = db_fetch_object($result)) {
18060
    $actions[$action->aid] = array(
18160
      'callback' => $action->callback,
18260
      'description' => $action->description,
18360
      'type' => $action->type,
18460
      'configurable' => (bool) $action->parameters,
185
    );
18660
  }
18760
  return $actions;
1880
}
189
190
/**
191
 * Create an associative array keyed by md5 hashes of function names.
192
 *
193
 * Hashes are used to prevent actual function names from going out into
194
 * HTML forms and coming back.
195
 *
196
 * @param $actions
197
 *   An associative array with function names as keys and associative
198
 *   arrays with keys 'description', 'type', etc. as values. Generally
199
 *   the output of actions_list() or actions_get_all_actions() is given
200
 *   as input to this function.
201
 *
202
 * @return
203
 *   An associative array keyed on md5 hash of function name. The value of
204
 *   each key is an associative array of function, description, and type
205
 *   for the action.
206
 */
2072027
function actions_actions_map($actions) {
20848
  $actions_map = array();
20948
  foreach ($actions as $callback => $array) {
21048
    $key = md5($callback);
21148
    $actions_map[$key]['callback']     = isset($array['callback']) ?
$array['callback'] : $callback;
21248
    $actions_map[$key]['description']  = $array['description'];
21348
    $actions_map[$key]['type']         = $array['type'];
21448
    $actions_map[$key]['configurable'] = $array['configurable'];
21548
  }
21648
  return $actions_map;
2170
}
218
219
/**
220
 * Given an md5 hash of a function name, return the function name.
221
 *
222
 * Faster than actions_actions_map() when you only need the function name.
223
 *
224
 * @param $hash
225
 *   MD5 hash of a function name
226
 *
227
 * @return
228
 *   Function name
229
 */
2302027
function actions_function_lookup($hash) {
23130
  $actions_list = actions_list();
23230
  foreach ($actions_list as $function => $array) {
23330
    if (md5($function) == $hash) {
23430
      return $function;
2350
    }
23630
  }
237
238
  // Must be an instance; must check database.
2390
  $aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) =
'%s' AND parameters != ''", $hash));
2400
  return $aid;
2410
}
242
243
/**
244
 * Synchronize actions that are provided by modules.
245
 *
246
 * They are synchronized with actions that are stored in the actions
table.
247
 * This is necessary so that actions that do not require configuration can
248
 * receive action IDs. This is not necessarily the best approach,
249
 * but it is the most straightforward.
250
 */
2512027
function actions_synchronize($actions_in_code = array(), $delete_orphans =
FALSE) {
25265
  if (!$actions_in_code) {
25365
    $actions_in_code = actions_list();
25465
  }
25565
  $actions_in_db = array();
25665
  $result = db_query("SELECT * FROM {actions} WHERE parameters = ''");
25765
  while ($action = db_fetch_object($result)) {
2584
    $actions_in_db[$action->callback] = array('aid' => $action->aid,
'description' => $action->description);
2594
  }
260
261
  // Go through all the actions provided by modules.
26265
  foreach ($actions_in_code as $callback => $array) {
263
    // Ignore configurable actions since their instances get put in
264
    // when the user adds the action.
26565
    if (!$array['configurable']) {
266
      // If we already have an action ID for this action, no need to assign
aid.
26765
      if (array_key_exists($callback, $actions_in_db)) {
2684
        unset($actions_in_db[$callback]);
2694
      }
270
      else {
271
        // This is a new singleton that we don't have an aid for; assign
one.
27262
        db_query("INSERT INTO {actions} (aid, type, callback, parameters,
description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback,
$array['type'], $callback, '', $array['description']);
27362
        watchdog('actions', "Action '%action' added.", array('%action' =>
filter_xss_admin($array['description'])));
274
      }
27565
    }
27665
  }
277
278
  // Any actions that we have left in $actions_in_db are orphaned.
27965
  if ($actions_in_db) {
2800
    $orphaned = array();
2810
    $placeholder = array();
282
2830
    foreach ($actions_in_db as $callback => $array) {
2840
      $orphaned[] = $callback;
2850
      $placeholder[] = "'%s'";
2860
    }
287
2880
    $orphans = implode(', ', $orphaned);
289
2900
    if ($delete_orphans) {
2910
      $placeholders = implode(', ', $placeholder);
2920
      $results = db_query("SELECT a.aid, a.description FROM {actions} a
WHERE callback IN ($placeholders)", $orphaned);
2930
      while ($action = db_fetch_object($results)) {
2940
        actions_delete($action->aid);
2950
        watchdog('actions', "Removed orphaned action '%action' from
database.", array('%action' => filter_xss_admin($action->description)));
2960
      }
2970
    }
298
    else {
2990
      $link = l(t('Remove orphaned actions'),
'admin/build/actions/orphan');
3000
      $count = count($actions_in_db);
3010
      watchdog('actions', format_plural($count, 'One orphaned action
(%orphans) exists in the actions table. !link', '@count orphaned actions
(%orphans) exist in the actions table. !link', array('@count' => $count,
'%orphans' => $orphans, '!link' => $link), 'warning'));
302
    }
3030
  }
30465
}
305
306
/**
307
 * Save an action and its associated user-supplied parameter values to the
database.
308
 *
309
 * @param $function
310
 *   The name of the function to be called when this action is performed.
311
 * @param $params
312
 *   An associative array with parameter names as keys and parameter
values
313
 *   as values.
314
 * @param $desc
315
 *   A user-supplied description of this particular action, e.g., 'Send
316
 *   e-mail to Jim'.
317
 * @param $aid
318
 *   The ID of this action. If omitted, a new action is created.
319
 *
320
 * @return
321
 *   The ID of the action.
322
 */
3232027
function actions_save($function, $type, $params, $desc, $aid = NULL) {
3240
  $serialized = serialize($params);
3250
  if ($aid) {
3260
    db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters
= '%s', description = '%s' WHERE aid = %d", $function, $type, $serialized,
$desc, $aid);
3270
    watchdog('actions', 'Action %action saved.', array('%action' =>
$desc));
3280
  }
329
  else {
330
    // aid is the callback for singleton actions so we need to keep a
331
    // separate table for numeric aids.
3320
    db_query('INSERT INTO {actions_aid} VALUES (default)');
3330
    $aid = db_last_insert_id('actions_aid', 'aid');
3340
    db_query("INSERT INTO {actions} (aid, callback, type, parameters,
description) VALUES (%d, '%s', '%s', '%s', '%s')", $aid, $function, $type,
$serialized, $desc);
3350
    watchdog('actions', 'Action %action created.', array('%action' =>
$desc));
336
  }
337
3380
  return $aid;
3390
}
340
341
/**
342
 * Retrieve a single action from the database.
343
 *
344
 * @param $aid
345
 *   integer The ID of the action to retrieve.
346
 *
347
 * @return
348
 *   The appropriate action row from the database as an object.
349
 */
3502027
function actions_load($aid) {
3510
  return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d",
$aid));
3520
}
353
354
/**
355
 * Delete a single action from the database.
356
 *
357
 * @param $aid
358
 *   integer The ID of the action to delete.
359
 */
3602027
function actions_delete($aid) {
3610
  db_query("DELETE FROM {actions} WHERE aid = %d", $aid);
3620
  module_invoke_all('actions_delete', $aid);
3630
}
3642027