comparison includes/actions.inc @ 1:c1f4ac30525a 6.0

Drupal 6.0
author Franck Deroche <webmaster@defr.org>
date Tue, 23 Dec 2008 14:28:28 +0100
parents
children fff6d4c8c043
comparison
equal deleted inserted replaced
0:5a113a1c4740 1:c1f4ac30525a
1 <?php
2 // $Id: actions.inc,v 1.8 2007/12/31 14:51:04 goba 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 = NULL, $a1 = NULL, $a2 = NULL) {
43 static $stack;
44 $stack++;
45 if ($stack > variable_get('actions_max_stack', 35)) {
46 watchdog('actions', '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 /**
111 * Discover all action functions by invoking hook_action_info().
112 *
113 * mymodule_action_info() {
114 * return array(
115 * 'mymodule_functiondescription_action' => array(
116 * 'type' => 'node',
117 * 'description' => t('Save node'),
118 * 'configurable' => FALSE,
119 * 'hooks' => array(
120 * 'nodeapi' => array('delete', 'insert', 'update', 'view'),
121 * 'comment' => array('delete', 'insert', 'update', 'view'),
122 * )
123 * )
124 * );
125 * }
126 *
127 * The description is used in presenting possible actions to the user for
128 * configuration. The type is used to present these actions in a logical
129 * grouping and to denote context. Some types are 'node', 'user', 'comment',
130 * and 'system'. If an action is configurable it will provide form,
131 * validation and submission functions. The hooks the action supports
132 * are declared in the 'hooks' array.
133 *
134 * @param $reset
135 * Reset the action info static cache.
136 *
137 * @return
138 * An associative array keyed on function name. The value of each key is
139 * an array containing information about the action, such as type of
140 * action and description of the action, e.g.,
141 *
142 * @code
143 * $actions['node_publish_action'] = array(
144 * 'type' => 'node',
145 * 'description' => t('Publish post'),
146 * 'configurable' => FALSE,
147 * 'hooks' => array(
148 * 'nodeapi' => array('presave', 'insert', 'update', 'view'),
149 * 'comment' => array('delete', 'insert', 'update', 'view'),
150 * ),
151 * );
152 * @endcode
153 */
154 function actions_list($reset = FALSE) {
155 static $actions;
156 if (!isset($actions) || $reset) {
157 $actions = module_invoke_all('action_info');
158 drupal_alter('action_info', $actions);
159 }
160
161 // See module_implements for explanations of this cast.
162 return (array)$actions;
163 }
164
165 /**
166 * Retrieve all action instances from the database.
167 *
168 * Compare with actions_list() which gathers actions by
169 * invoking hook_action_info(). The two are synchronized
170 * by visiting /admin/build/actions (when actions.module is
171 * enabled) which runs actions_synchronize().
172 *
173 * @return
174 * Associative array keyed by action ID. Each value is
175 * an associative array with keys 'callback', 'description',
176 * 'type' and 'configurable'.
177 */
178 function actions_get_all_actions() {
179 $actions = array();
180 $result = db_query("SELECT * FROM {actions}");
181 while ($action = db_fetch_object($result)) {
182 $actions[$action->aid] = array(
183 'callback' => $action->callback,
184 'description' => $action->description,
185 'type' => $action->type,
186 'configurable' => (bool) $action->parameters,
187 );
188 }
189 return $actions;
190 }
191
192 /**
193 * Create an associative array keyed by md5 hashes of function names.
194 *
195 * Hashes are used to prevent actual function names from going out into
196 * HTML forms and coming back.
197 *
198 * @param $actions
199 * An associative array with function names as keys and associative
200 * arrays with keys 'description', 'type', etc. as values. Generally
201 * the output of actions_list() or actions_get_all_actions() is given
202 * as input to this function.
203 *
204 * @return
205 * An associative array keyed on md5 hash of function name. The value of
206 * each key is an associative array of function, description, and type
207 * for the action.
208 */
209 function actions_actions_map($actions) {
210 $actions_map = array();
211 foreach ($actions as $callback => $array) {
212 $key = md5($callback);
213 $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
214 $actions_map[$key]['description'] = $array['description'];
215 $actions_map[$key]['type'] = $array['type'];
216 $actions_map[$key]['configurable'] = $array['configurable'];
217 }
218 return $actions_map;
219 }
220
221 /**
222 * Given an md5 hash of a function name, return the function name.
223 *
224 * Faster than actions_actions_map() when you only need the function name.
225 *
226 * @param $hash
227 * MD5 hash of a function name
228 *
229 * @return
230 * Function name
231 */
232 function actions_function_lookup($hash) {
233 $actions_list = actions_list();
234 foreach ($actions_list as $function => $array) {
235 if (md5($function) == $hash) {
236 return $function;
237 }
238 }
239
240 // Must be an instance; must check database.
241 $aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s' AND parameters != ''", $hash));
242 return $aid;
243 }
244
245 /**
246 * Synchronize actions that are provided by modules.
247 *
248 * They are synchronized with actions that are stored in the actions table.
249 * This is necessary so that actions that do not require configuration can
250 * receive action IDs. This is not necessarily the best approach,
251 * but it is the most straightforward.
252 */
253 function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
254 if (!$actions_in_code) {
255 $actions_in_code = actions_list();
256 }
257 $actions_in_db = array();
258 $result = db_query("SELECT * FROM {actions} WHERE parameters = ''");
259 while ($action = db_fetch_object($result)) {
260 $actions_in_db[$action->callback] = array('aid' => $action->aid, 'description' => $action->description);
261 }
262
263 // Go through all the actions provided by modules.
264 foreach ($actions_in_code as $callback => $array) {
265 // Ignore configurable actions since their instances get put in
266 // when the user adds the action.
267 if (!$array['configurable']) {
268 // If we already have an action ID for this action, no need to assign aid.
269 if (array_key_exists($callback, $actions_in_db)) {
270 unset($actions_in_db[$callback]);
271 }
272 else {
273 // This is a new singleton that we don't have an aid for; assign one.
274 db_query("INSERT INTO {actions} (aid, type, callback, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback, $array['type'], $callback, '', $array['description']);
275 watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['description'])));
276 }
277 }
278 }
279
280 // Any actions that we have left in $actions_in_db are orphaned.
281 if ($actions_in_db) {
282 $orphaned = array();
283 $placeholder = array();
284
285 foreach ($actions_in_db as $callback => $array) {
286 $orphaned[] = $callback;
287 $placeholder[] = "'%s'";
288 }
289
290 $orphans = implode(', ', $orphaned);
291
292 if ($delete_orphans) {
293 $placeholders = implode(', ', $placeholder);
294 $results = db_query("SELECT a.aid, a.description FROM {actions} a WHERE callback IN ($placeholders)", $orphaned);
295 while ($action = db_fetch_object($results)) {
296 actions_delete($action->aid);
297 watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->description)));
298 }
299 }
300 else {
301 $link = l(t('Remove orphaned actions'), 'admin/build/actions/orphan');
302 $count = count($actions_in_db);
303 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'));
304 }
305 }
306 }
307
308 /**
309 * Save an action and its associated user-supplied parameter values to the database.
310 *
311 * @param $function
312 * The name of the function to be called when this action is performed.
313 * @param $params
314 * An associative array with parameter names as keys and parameter values
315 * as values.
316 * @param $desc
317 * A user-supplied description of this particular action, e.g., 'Send
318 * e-mail to Jim'.
319 * @param $aid
320 * The ID of this action. If omitted, a new action is created.
321 *
322 * @return
323 * The ID of the action.
324 */
325 function actions_save($function, $type, $params, $desc, $aid = NULL) {
326 $serialized = serialize($params);
327 if ($aid) {
328 db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters = '%s', description = '%s' WHERE aid = %d", $function, $type, $serialized, $desc, $aid);
329 watchdog('actions', 'Action %action saved.', array('%action' => $desc));
330 }
331 else {
332 // aid is the callback for singleton actions so we need to keep a
333 // separate table for numeric aids.
334 db_query('INSERT INTO {actions_aid} VALUES (default)');
335 $aid = db_last_insert_id('actions_aid', 'aid');
336 db_query("INSERT INTO {actions} (aid, callback, type, parameters, description) VALUES (%d, '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);
337 watchdog('actions', 'Action %action created.', array('%action' => $desc));
338 }
339
340 return $aid;
341 }
342
343 /**
344 * Retrieve a single action from the database.
345 *
346 * @param $aid
347 * integer The ID of the action to retrieve.
348 *
349 * @return
350 * The appropriate action row from the database as an object.
351 */
352 function actions_load($aid) {
353 return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $aid));
354 }
355
356 /**
357 * Delete a single action from the database.
358 *
359 * @param $aid
360 * integer The ID of the action to delete.
361 */
362 function actions_delete($aid) {
363 db_query("DELETE FROM {actions} WHERE aid = %d", $aid);
364 module_invoke_all('actions_delete', $aid);
365 }