Spike PHPCoverage Details: actions.inc

Line #FrequencySource Line
1 <?php
2 // $Id: actions.inc,v 1.9 2008/02/17 22:15:04 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  */
40 function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 = NULL) {
41   static $stack;
42   $stack++;
43   if ($stack > variable_get('actions_max_stack', 35)) {
44     watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', WATCHDOG_ERROR);
45     return;
46   }
47   $actions = array();
48   $available_actions = actions_list();
49   $result = array();
50   if (is_array($action_ids)) {
51     $where = array();
52     $where_values = array();
53     foreach ($action_ids as $action_id) {
54       if (is_numeric($action_id)) {
55         $where[] = 'OR aid = %d';
56         $where_values[] = $action_id;
57       }
58       elseif (isset($available_actions[$action_id])) {
59         $actions[$action_id] = $available_actions[$action_id];
60       }
61     }
62 
63     // When we have action instances we must go to the database to
64     // retrieve instance data.
65     if ($where) {
66       $where_clause = implode(' ', $where);
67       // Strip off leading 'OR '.
68       $where_clause = '('. strstr($where_clause, " ") .')';
69       $result_db = db_query('SELECT * FROM {actions} WHERE '. $where_clause, $where_values);
70       while ($action = db_fetch_object($result_db)) {
71         $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
72         $actions[$action->aid]['callback'] = $action->callback;
73         $actions[$action->aid]['type'] = $action->type;
74       }
75     }
76 
77     // Fire actions, in no particular order.
78     foreach ($actions as $action_id => $params) {
79       if (is_numeric($action_id)) { // Configurable actions need parameters.
80         $function = $params['callback'];
81         $context = array_merge($context, $params);
82         $result[$action_id] = $function($object, $context, $a1, $a2);
83       }
84       // Singleton action; $action_id is the function name.
85       else {
86         $result[$action_id] = $action_id($object, $context, $a1, $a2);
87       }
88     }
89   }
90   // Optimized execution of single action.
91   else {
92     // If it's a configurable action, retrieve stored parameters.
93     if (is_numeric($action_ids)) {
94       $action = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $action_ids));
95       $function = $action->callback;
96       $context = array_merge($context, unserialize($action->parameters));
97       $result[$action_ids] = $function($object, $context, $a1, $a2);
98     }
99     // Singleton action; $action_ids is the function name.
100     else {
101       $result[$action_ids] = $action_ids($object, $context, $a1, $a2);
102     }
103   }
104   return $result;
105 }
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  */
152 function actions_list($reset = FALSE) {
1531  static $actions;
1541  if (!isset($actions) || $reset) {
1551    $actions = module_invoke_all('action_info');
1561    drupal_alter('action_info', $actions);
157   }
158 
159   // See module_implements for explanations of this cast.
1601  return (array)$actions;
161 }
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  */
176 function actions_get_all_actions() {
177   $actions = array();
178   $result = db_query("SELECT * FROM {actions}");
179   while ($action = db_fetch_object($result)) {
180     $actions[$action->aid] = array(
181       'callback' => $action->callback,
182       'description' => $action->description,
183       'type' => $action->type,
184       'configurable' => (bool) $action->parameters,
185     );
186   }
187   return $actions;
188 }
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  */
207 function actions_actions_map($actions) {
208   $actions_map = array();
209   foreach ($actions as $callback => $array) {
210     $key = md5($callback);
211     $actions_map[$key]['callback']     = isset($array['callback']) ? $array['callback'] : $callback;
212     $actions_map[$key]['description']  = $array['description'];
213     $actions_map[$key]['type']         = $array['type'];
214     $actions_map[$key]['configurable'] = $array['configurable'];
215   }
216   return $actions_map;
217 }
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  */
230 function actions_function_lookup($hash) {
231   $actions_list = actions_list();
232   foreach ($actions_list as $function => $array) {
233     if (md5($function) == $hash) {
234       return $function;
235     }
236   }
237 
238   // Must be an instance; must check database.
239   $aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s' AND parameters != ''", $hash));
240   return $aid;
241 }
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  */
251 function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
2521  if (!$actions_in_code) {
2531    $actions_in_code = actions_list();
254   }
255   $actions_in_db = array();
2561  $result = db_query("SELECT * FROM {actions} WHERE parameters = ''");
2571  while ($action = db_fetch_object($result)) {
258     $actions_in_db[$action->callback] = array('aid' => $action->aid, 'description' => $action->description);
259   }
260 
261   // Go through all the actions provided by modules.
2621  foreach ($actions_in_code as $callback => $array) {
263     // Ignore configurable actions since their instances get put in
264     // when the user adds the action.
2651    if (!$array['configurable']) {
266       // If we already have an action ID for this action, no need to assign aid.
2671      if (array_key_exists($callback, $actions_in_db)) {
268         unset($actions_in_db[$callback]);
269       }
270       else {
271         // This is a new singleton that we don't have an aid for; assign one.
2721        db_query("INSERT INTO {actions} (aid, type, callback, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback, $array['type'], $callback, '', $array['description']);
2731        watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['description'])));
274       }
275     }
276   }
277 
278   // Any actions that we have left in $actions_in_db are orphaned.
2791  if ($actions_in_db) {
280     $orphaned = array();
281     $placeholder = array();
282 
283     foreach ($actions_in_db as $callback => $array) {
284       $orphaned[] = $callback;
285       $placeholder[] = "'%s'";
286     }
287 
288     $orphans = implode(', ', $orphaned);
289 
290     if ($delete_orphans) {
291       $placeholders = implode(', ', $placeholder);
292       $results = db_query("SELECT a.aid, a.description FROM {actions} a WHERE callback IN ($placeholders)", $orphaned);
293       while ($action = db_fetch_object($results)) {
294         actions_delete($action->aid);
295         watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->description)));
296       }
297     }
298     else {
299       $link = l(t('Remove orphaned actions'), 'admin/build/actions/orphan');
300       $count = count($actions_in_db);
301       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     }
303   }
304 }
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  */
323 function actions_save($function, $type, $params, $desc, $aid = NULL) {
324   $serialized = serialize($params);
325   if ($aid) {
326     db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters = '%s', description = '%s' WHERE aid = %d", $function, $type, $serialized, $desc, $aid);
327     watchdog('actions', 'Action %action saved.', array('%action' => $desc));
328   }
329   else {
330     // aid is the callback for singleton actions so we need to keep a
331     // separate table for numeric aids.
332     db_query('INSERT INTO {actions_aid} VALUES (default)');
333     $aid = db_last_insert_id('actions_aid', 'aid');
334     db_query("INSERT INTO {actions} (aid, callback, type, parameters, description) VALUES (%d, '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);
335     watchdog('actions', 'Action %action created.', array('%action' => $desc));
336   }
337 
338   return $aid;
339 }
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  */
350 function actions_load($aid) {
351   return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $aid));
352 }
353 
354 /**
355  * Delete a single action from the database.
356  *
357  * @param $aid
358  *   integer The ID of the action to delete.
359  */
360 function actions_delete($aid) {
361   db_query("DELETE FROM {actions} WHERE aid = %d", $aid);
362   module_invoke_all('actions_delete', $aid);
363 }