Mercurial > defr > drupal > core
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 } |