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

Contents of /contributions/modules/actions/actions.inc

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


Revision 1.24 - (show annotations) (download) (as text)
Mon Sep 8 17:43:08 2008 UTC (14 months, 2 weeks ago) by jvandyk
Branch: MAIN
CVS Tags: DRUPAL-5--2-6, HEAD
Branch point for: DRUPAL-5--2
Changes since 1.23: +2 -2 lines
File MIME type: text/x-php
#260957 by Dave Cohen: correct default type for
1 <?php
2 // $Id: actions.inc,v 1.23 2008/08/03 03:41:43 jvandyk 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 * If the action does not act on an object, pass a dummy object. This
26 * is necessary to support PHP 4 object referencing.
27 * @param $context
28 * Parameter that will be passed along to the callback. $context is a
29 * keyed array containing extra information about what is currently
30 * happening at the time of the call. Typically $context['hook'] and
31 * $context['op'] will tell which hook-op combination resulted in this
32 * call to actions_do().
33 * @param $a1
34 * Parameter that will be passed along to the callback.
35 * @param $a2
36 * Parameter that will be passed along to the callback.
37 *
38 * @return
39 * An associative array containing the result of the function that
40 * performs the action, keyed on action ID.
41 */
42 function actions_do($action_ids, &$object, $context = array(), $a1 = NULL, $a2 = NULL) {
43 static $stack;
44 $stack++;
45 if ($stack > variable_get('actions_max_stack', 35)) {
46 watchdog('actions', t('Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.'), WATCHDOG_ERROR);
47 return;
48 }
49 $actions = array();
50 $available_actions = actions_list();
51 $result = array();
52 if (is_array($action_ids)) {
53 $where = array();
54 $where_values = array();
55 foreach ($action_ids as $action_id) {
56 if (is_numeric($action_id)) {
57 $where[] .= 'OR aid = %d';
58 $where_values[] = $action_id;
59 }
60 elseif (isset($available_actions[$action_id])) {
61 $actions[$action_id] = $available_actions[$action_id];
62 }
63 }
64
65 // When we have action instances we must go to the database to
66 // retrieve instance data.
67 if ($where) {
68 $where_clause = implode(' ', $where);
69 // Strip off leading 'OR '.
70 $where_clause = '('. strstr($where_clause, " ") .')';
71 $result_db = db_query('SELECT * FROM {actions} WHERE '. $where_clause, $where_values);
72 while ($action = db_fetch_object($result_db)) {
73 $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
74 $actions[$action->aid]['callback'] = $action->callback;
75 $actions[$action->aid]['type'] = $action->type;
76 }
77 }
78
79 // Fire actions, in no particular order.
80 foreach ($actions as $action_id => $params) {
81 if (is_numeric($action_id)) { // Configurable actions need parameters.
82 $function = $params['callback'];
83 $context = array_merge($context, $params);
84 $result[$action_id] = $function($object, $context, $a1, $a2);
85 }
86 // Singleton action; $action_id is the function name.
87 else {
88 $result[$action_id] = $action_id($object, $context, $a1, $a2);
89 }
90 }
91 }
92 // Optimized execution of single action.
93 else {
94 // If it's a configurable action, retrieve stored parameters.
95 if (is_numeric($action_ids)) {
96 $action = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $action_ids));
97 $function = $action->callback;
98 $context = array_merge($context, unserialize($action->parameters));
99 $result[$action_ids] = $function($object, $context, $a1, $a2);
100 }
101 // Singleton action; $action_ids is the function name.
102 else {
103 $result[$action_ids] = $action_ids($object, $context, $a1, $a2);
104 }
105 }
106 return $result;
107 }
108
109 /**
110 * This dispatch function hands off structured Drupal arrays to type-specific
111 * *_alter implementations. It ensures a consistent interface for all altering
112 * operations.
113 *
114 * @param $type
115 * The data type of the structured array. 'form', 'links',
116 * 'node_content', and so on are several examples.
117 * @param $data
118 * The structured array to be altered.
119 * @param ...
120 * Any additional params will be passed on to the called
121 * hook_$type_alter functions.
122 */
123 function actions_alter($type, &$data) {
124 // PHP's func_get_args() always returns copies of params, not references, so
125 // actions_alter() can only manipulate data that comes in via the required first
126 // param. For the edge case functions that must pass in an arbitrary number of
127 // alterable parameters (hook_form_alter() being the best example), an array of
128 // those params can be placed in the __actions_alter_by_ref key of the $data
129 // array. This is somewhat ugly, but is an unavoidable consequence of a flexible
130 // actions_alter() function, and the limitations of func_get_args().
131 // @todo: Remove this in Drupal 7.
132 if (is_array($data) && isset($data['__actions_alter_by_ref'])) {
133 $by_ref_parameters = $data['__actions_alter_by_ref'];
134 unset($data['__actions_alter_by_ref']);
135 }
136
137 // Hang onto a reference to the data array so that it isn't blown away later.
138 // Also, merge in any parameters that need to be passed by reference.
139 $args = array(&$data);
140 if (isset($by_ref_parameters)) {
141 $args = array_merge($args, $by_ref_parameters);
142 }
143
144 // Now, use func_get_args() to pull in any additional parameters passed into
145 // the actions_alter() call.
146 $additional_args = func_get_args();
147 array_shift($additional_args);
148 array_shift($additional_args);
149 $args = array_merge($args, $additional_args);
150
151 foreach (module_implements($type .'_alter') as $module) {
152 $function = $module .'_'. $type .'_alter';
153 call_user_func_array($function, $args);
154 }
155 }
156
157 /**
158 * Discover all action functions by invoking hook_action_info().
159 *
160 * mymodule_action_info() {
161 * return array(
162 * 'mymodule_functiondescription_action' => array(
163 * 'type' => 'node',
164 * 'description' => t('Save node'),
165 * 'configurable' => FALSE,
166 * 'hooks' => array(
167 * 'nodeapi' => array('delete', 'insert', 'update', 'view'),
168 * 'comment' => array('delete', 'insert', 'update', 'view'),
169 * )
170 * )
171 * );
172 * }
173 *
174 * The description is used in presenting possible actions to the user for
175 * configuration. The type is used to present these actions in a logical
176 * grouping and to denote context. Some types are 'node', 'user', 'comment',
177 * and 'system'. If an action is configurable it will provide form,
178 * validation and submission functions. The hooks the action supports
179 * are declared in the 'hooks' array.
180 *
181 * @param $reset
182 * Reset the action info static cache.
183 *
184 * @return
185 * An associative array keyed on function name. The value of each key is
186 * an array containing information about the action, such as type of
187 * action and description of the action, e.g.,
188 *
189 * @code
190 * $actions['node_publish_action'] = array(
191 * 'type' => 'node',
192 * 'description' => t('Publish post'),
193 * 'configurable' => FALSE,
194 * 'hooks' => array(
195 * 'nodeapi' => array('presave', 'insert', 'update', 'view'),
196 * 'comment' => array('delete', 'insert', 'update', 'view'),
197 * ),
198 * );
199 * @endcode
200 */
201 function actions_list($reset = FALSE) {
202 static $actions;
203 if (!isset($actions) || $reset) {
204 $actions = module_invoke_all('action_info');
205 actions_alter('action_info', $actions);
206 }
207
208 // See module_implements for explanations of this cast.
209 return (array)$actions;
210 }
211
212 /**
213 * Explode a string of given tags into an array.
214 */
215 function actions_explode_tags($tags) {
216 // This regexp allows the following types of user input:
217 // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar
218 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
219 preg_match_all($regexp, $tags, $matches);
220 $typed_tags = array_unique($matches[1]);
221
222 $tags = array();
223 foreach ($typed_tags as $tag) {
224 // If a user has escaped a term (to demonstrate that it is a group,
225 // or includes a comma or quote character), we remove the escape
226 // formatting so to save the term into the database as the user intends.
227 $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag)));
228 if ($tag != "") {
229 $tags[] = $tag;
230 }
231 }
232
233 return $tags;
234 }
235
236 /**
237 * Implode an array of tags into a string.
238 */
239 function actions_implode_tags($tags) {
240 $encoded_tags = array();
241 foreach ($tags as $tag) {
242 // Commas and quotes in tag names are special cases, so encode them.
243 if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) {
244 $tag = '"'. str_replace('"', '""', $tag) .'"';
245 }
246
247 $encoded_tags[] = $tag;
248 }
249 return implode(', ', $encoded_tags);
250 }
251
252 /**
253 * Retrieve all action instances from the database.
254 *
255 * Compare with actions_list() which gathers actions by
256 * invoking hook_action_info(). The two are synchronized
257 * by visiting /admin/build/actions (when actions.module is
258 * enabled) which runs actions_synchronize().
259 *
260 * @return
261 * Associative array keyed by action ID. Each value is
262 * an associative array with keys 'callback', 'description',
263 * 'type' and 'configurable'.
264 */
265 function actions_get_all_actions() {
266 $actions = array();
267 $result = db_query("SELECT * FROM {actions}");
268 while ($action = db_fetch_object($result)) {
269 $actions[$action->aid] = array(
270 'callback' => $action->callback,
271 'description' => $action->description,
272 'type' => $action->type,
273 'configurable' => (bool) $action->parameters,
274 );
275 }
276 return $actions;
277 }
278
279 /**
280 * Create an associative array keyed by md5 hashes of function names.
281 *
282 * Hashes are used to prevent actual function names from going out into
283 * HTML forms and coming back.
284 *
285 * @param $actions
286 * An associative array with function names as keys and associative
287 * arrays with keys 'description', 'type', etc. as values. Generally
288 * the output of actions_list() or actions_get_all_actions() is given
289 * as input to this function.
290 *
291 * @return
292 * An associative array keyed on md5 hash of function name. The value of
293 * each key is an associative array of function, description, and type
294 * for the action.
295 */
296 function actions_actions_map($actions) {
297 $actions_map = array();
298 foreach ($actions as $callback => $array) {
299 $key = md5($callback);
300 $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
301 $actions_map[$key]['description'] = $array['description'];
302 $actions_map[$key]['type'] = $array['type'];
303 $actions_map[$key]['configurable'] = $array['configurable'];
304 }
305 return $actions_map;
306 }
307
308 /**
309 * Given an md5 hash of a function name, return the function name.
310 *
311 * Faster than actions_actions_map() when you only need the function name.
312 *
313 * @param $hash
314 * MD5 hash of a function name
315 *
316 * @return
317 * Function name
318 */
319 function actions_function_lookup($hash) {
320 $actions_list = actions_list();
321 foreach ($actions_list as $function => $array) {
322 if (md5($function) == $hash) {
323 return $function;
324 }
325 }
326
327 // Must be an instance; must check database.
328 $aid = db_result(db_query("SELECT callback FROM {actions} WHERE MD5(aid) = '%s' AND parameters <> ''", $hash));
329 return $aid;
330 }
331
332 /**
333 * Synchronize actions that are provided by modules.
334 *
335 * They are synchronized with actions that are stored in the actions table.
336 * This is necessary so that actions that do not require configuration can
337 * receive action IDs. This is not necessarily the best approach,
338 * but it is the most straightforward.
339 */
340 function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
341 if (!$actions_in_code) {
342 $actions_in_code = actions_list();
343 }
344 $actions_in_db = array();
345 $result = db_query("SELECT * FROM {actions} WHERE parameters = ''");
346 while ($action = db_fetch_object($result)) {
347 $actions_in_db[$action->callback] = array('aid' => $action->aid, 'description' => $action->description);
348 }
349
350 // Go through all the actions provided by modules.
351 foreach ($actions_in_code as $callback => $array) {
352 // Ignore configurable actions since their instances get put in
353 // when the user adds the action.
354 if (!$array['configurable']) {
355 // If we already have an action ID for this action, no need to assign aid.
356 if (array_key_exists($callback, $actions_in_db)) {
357 unset($actions_in_db[$callback]);
358 }
359 else {
360 // This is a new singleton that we don't have an aid for; assign one.
361 db_query("INSERT INTO {actions} (aid, type, callback, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback, $array['type'], $callback, '', $array['description']);
362 watchdog('actions', t('Action %action added.', array('%action' => filter_xss_admin($array['description']))));
363 }
364 }
365 }
366
367 // Any actions that we have left in $actions_in_db are orphaned.
368 if ($actions_in_db) {
369 $orphaned = array();
370 $placeholder = array();
371
372 foreach ($actions_in_db as $callback => $array) {
373 $orphaned[] = $callback;
374 $placeholder[] = "'%s'";
375 }
376
377 $orphans = implode(', ', $orphaned);
378
379 if ($delete_orphans) {
380 $placeholders = implode(', ', $placeholder);
381 $results = db_query("SELECT a.aid, a.description FROM {actions} a WHERE callback IN ($placeholders)", $orphaned);
382 while ($action = db_fetch_object($results)) {
383 actions_delete($action->aid);
384 watchdog('actions', t('Removed orphaned action %action from database.', array('%action' => filter_xss_admin($action->description))));
385 }
386 }
387 else {
388 $link = l(t('Remove orphaned actions'), 'admin/build/actions/orphan');
389 $count = count($actions_in_db);
390 watchdog('actions', '%message (%orphans). !link', array('%message' => format_plural($count, t('There is an orphaned action in your actions table'), t('There are orphaned actions in your actions table')), '%orphans' => $orphans, '!link' => $link), 'warning');
391 }
392 }
393 }
394
395 /**
396 * Save an action and its associated user-supplied parameter values to the database.
397 *
398 * @param $function
399 * The name of the function to be called when this action is performed.
400 * @param $params
401 * An associative array with parameter names as keys and parameter values
402 * as values.
403 * @param $desc
404 * A user-supplied description of this particular action, e.g., 'Send
405 * e-mail to Jim'.
406 * @param $aid
407 * The ID of this action. If omitted, a new action is created.
408 *
409 * @return
410 * The ID of the action.
411 */
412 function actions_save($function, $type, $params, $desc, $aid = NULL) {
413 $serialized = serialize($params);
414 if ($aid) {
415 db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters = '%s', description = '%s' WHERE aid = %d", $function, $type, $serialized, $desc, $aid);
416 watchdog('actions', t('Action %action saved.', array('%action' => $desc)));
417 }
418 else {
419 // aid is the callback for singleton actions so we need to keep a
420 // separate table for numeric aids.
421 db_query('INSERT INTO {actions_aid} VALUES (default)');
422 $aid = db_next_id('{actions_aid}_aid');
423 db_query("INSERT INTO {actions} (aid, callback, type, parameters, description) VALUES (%d, '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);
424 watchdog('actions', t('Action %action created.', array('%action' => $desc)));
425 }
426
427 return $aid;
428 }
429
430 /**
431 * Retrieve a single action from the database.
432 *
433 * @param $aid
434 * integer The ID of the action to retrieve.
435 *
436 * @return
437 * The appropriate action row from the database as an object.
438 */
439 function actions_load($aid) {
440 return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $aid));
441 }
442
443 /**
444 * Delete a single action from the database.
445 *
446 * @param $aid
447 * integer The ID of the action to delete.
448 */
449 function actions_delete($aid) {
450 db_query("DELETE FROM {actions} WHERE aid = %d", $aid);
451 module_invoke_all('actions_delete', $aid);
452 }

  ViewVC Help
Powered by ViewVC 1.1.2