/[drupal]/drupal/includes/actions.inc
ViewVC logotype

Contents of /drupal/includes/actions.inc

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


Revision 1.34 - (show annotations) (download) (as text)
Sun Oct 18 06:56:23 2009 UTC (5 weeks, 5 days ago) by webchick
Branch: MAIN
CVS Tags: DRUPAL-7-0-UNSTABLE-10, HEAD
Changes since 1.33: +2 -2 lines
File MIME type: text/x-php
#356074 by chx and Damien Tournoud: Provide a sequences API. Gets rid of stupid tables that only contain an incrementing ID, and fixes database import problems due to user ID 0.
1 <?php
2 // $Id: actions.inc,v 1.33 2009/10/10 17:29:16 webchick Exp $
3
4 /**
5 * @file
6 * This is the actions engine for executing stored actions.
7 */
8
9 /**
10 * @defgroup actions Actions
11 * @{
12 * Functions that perform an action on a certain system object.
13 *
14 * All modules should declare their action functions to be in this group and
15 * each action function should reference its configuration form, validate, and
16 * submit functions using \@see. Conversely, form, validate, and submit
17 * functions should reference the action function using \@see. For examples of
18 * this see comment_unpublish_by_keyword_action(), which has the following in
19 * its doxygen documentation:
20 *
21 * \@ingroup actions
22 * \@see comment_unpublish_by_keyword_action_form().
23 * \@see comment_unpublish_by_keyword_action_submit().
24 *
25 * @} End of "defgroup actions".
26 */
27
28 /**
29 * Performs a given list of actions by executing their callback functions.
30 *
31 * Given the IDs of actions to perform, this function finds out what the
32 * callback functions for the actions are by querying the database. Then
33 * it calls each callback using the function call $function($object, $context,
34 * $a1, $a2), passing the input arguments of this function (see below) to the
35 * action function.
36 *
37 * @param $action_ids
38 * The IDs of the actions to perform. Can be a single action ID or an array
39 * of IDs. IDs of configurable actions must be given as numeric action IDs;
40 * IDs of non-configurable actions may be given as action function names.
41 * @param $object
42 * The object that the action will act on: a node, user, or comment object.
43 * @param $context
44 * Associative array containing extra information about what triggered
45 * the action call, with $context['hook'] giving the name of the hook
46 * that resulted in this call to actions_do().
47 * @param $a1
48 * Passed along to the callback.
49 * @param $a2
50 * Passed along to the callback.
51 * @return
52 * An associative array containing the results of the functions that
53 * perform the actions, keyed on action ID.
54 */
55 function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 = NULL) {
56 // $stack tracks the number of recursive calls.
57 static $stack;
58 $stack++;
59 if ($stack > variable_get('actions_max_stack', 35)) {
60 watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR);
61 return;
62 }
63 $actions = array();
64 $available_actions = actions_list();
65 $actions_result = array();
66 if (is_array($action_ids)) {
67 $conditions = array();
68 foreach ($action_ids as $action_id) {
69 if (is_numeric($action_id)) {
70 $conditions[] = $action_id;
71 }
72 elseif (isset($available_actions[$action_id])) {
73 $actions[$action_id] = $available_actions[$action_id];
74 }
75 }
76
77 // When we have action instances we must go to the database to retrieve
78 // instance data.
79 if (!empty($conditions)) {
80 $query = db_select('actions');
81 $query->addField('actions', 'aid');
82 $query->addField('actions', 'type');
83 $query->addField('actions', 'callback');
84 $query->addField('actions', 'parameters');
85 $query->condition('aid', $conditions, 'IN');
86 $result = $query->execute();
87 foreach ($result as $action) {
88 $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
89 $actions[$action->aid]['callback'] = $action->callback;
90 $actions[$action->aid]['type'] = $action->type;
91 }
92 }
93
94 // Fire actions, in no particular order.
95 foreach ($actions as $action_id => $params) {
96 // Configurable actions need parameters.
97 if (is_numeric($action_id)) {
98 $function = $params['callback'];
99 if (function_exists($function)) {
100 $context = array_merge($context, $params);
101 $actions_result[$action_id] = $function($object, $context, $a1, $a2);
102 }
103 else {
104 $actions_result[$action_id] = FALSE;
105 }
106 }
107 // Singleton action; $action_id is the function name.
108 else {
109 $actions_result[$action_id] = $action_id($object, $context, $a1, $a2);
110 }
111 }
112 }
113 // Optimized execution of a single action.
114 else {
115 // If it's a configurable action, retrieve stored parameters.
116 if (is_numeric($action_ids)) {
117 $action = db_query("SELECT callback, parameters FROM {actions} WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject();
118 $function = $action->callback;
119 if (function_exists($function)) {
120 $context = array_merge($context, unserialize($action->parameters));
121 $actions_result[$action_ids] = $function($object, $context, $a1, $a2);
122 }
123 else {
124 $actions_result[$action_ids] = FALSE;
125 }
126 }
127 // Singleton action; $action_ids is the function name.
128 else {
129 $actions_result[$action_ids] = $action_ids($object, $context, $a1, $a2);
130 }
131 }
132 $stack--;
133 return $actions_result;
134 }
135
136 /**
137 * Discovers all available actions by invoking hook_action_info().
138 *
139 * This function contrasts with actions_get_all_actions(); see the
140 * documentation of actions_get_all_actions() for an explanation.
141 *
142 * @param $reset
143 * Reset the action info static cache.
144 * @return
145 * An associative array keyed on action function name, with the same format
146 * as the return value of hook_action_info(), containing all
147 * modules' hook_action_info() return values as modified by any
148 * hook_action_info_alter() implementations.
149 *
150 * @see hook_action_info()
151 */
152 function actions_list($reset = FALSE) {
153 static $actions;
154 if (!isset($actions) || $reset) {
155 $actions = module_invoke_all('action_info');
156 drupal_alter('action_info', $actions);
157 }
158
159 // See module_implements() for an explanation of this cast.
160 return (array)$actions;
161 }
162
163 /**
164 * Retrieves all action instances from the database.
165 *
166 * This function differs from the actions_list() function, which gathers
167 * actions by invoking hook_action_info(). The actions returned by this
168 * function and the actions returned by actions_list() are partially
169 * synchronized. Non-configurable actions from hook_action_info()
170 * implementations are put into the database when actions_synchronize() is
171 * called, which happens when admin/config/system/actions is visited. Configurable
172 * actions are not added to the database until they are configured in the
173 * user interface, in which case a database row is created for each
174 * configuration of each action.
175 *
176 * @return
177 * Associative array keyed by numeric action ID. Each value is an associative
178 * array with keys 'callback', 'label', 'type' and 'configurable'.
179 */
180 function actions_get_all_actions() {
181 $actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC);
182 foreach ($actions as &$action) {
183 $action['configurable'] = (bool) $action['parameters'];
184 unset($action['parameters']);
185 unset($action['aid']);
186 }
187 return $actions;
188 }
189
190 /**
191 * Creates an associative array keyed by md5 hashes of function names or IDs.
192 *
193 * Hashes are used to prevent actual function names from going out into HTML
194 * forms and coming back.
195 *
196 * @param $actions
197 * An associative array with function names or action IDs as keys
198 * and associative arrays with keys 'label', 'type', etc. as values.
199 * This is usually the output of actions_list() or actions_get_all_actions().
200 * @return
201 * An associative array whose keys are md5 hashes of the input array keys, and
202 * whose corresponding values are associative arrays with components
203 * 'callback', 'label', 'type', and 'configurable' from the input array.
204 */
205 function actions_actions_map($actions) {
206 $actions_map = array();
207 foreach ($actions as $callback => $array) {
208 $key = md5($callback);
209 $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
210 $actions_map[$key]['label'] = $array['label'];
211 $actions_map[$key]['type'] = $array['type'];
212 $actions_map[$key]['configurable'] = $array['configurable'];
213 }
214 return $actions_map;
215 }
216
217 /**
218 * Given an md5 hash of an action array key, returns the key (function or ID).
219 *
220 * Faster than actions_actions_map() when you only need the function name or ID.
221 *
222 * @param $hash
223 * MD5 hash of a function name or action ID array key. The array key
224 * is a key into the return value of actions_list() (array key is the action
225 * function name) or actions_get_all_actions() (array key is the action ID).
226 * @return
227 * The corresponding array key, or FALSE if no match is found.
228 */
229 function actions_function_lookup($hash) {
230 // Check for a function name match.
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 a configurable action; check database.
239 return db_query("SELECT aid FROM {actions} WHERE MD5(aid) = :hash AND parameters <> ''", array(':hash' => $hash))->fetchField();
240 }
241
242 /**
243 * Synchronizes actions that are provided by modules in hook_action_info().
244 *
245 * Actions provided by modules in hook_action_info() implementations are
246 * synchronized with actions that are stored in the actions database table.
247 * This is necessary so that actions that do not require configuration can
248 * receive action IDs.
249 *
250 * @param $delete_orphans
251 * If TRUE, any actions that exist in the database but are no longer
252 * found in the code (for example, because the module that provides them has
253 * been disabled) will be deleted.
254 */
255 function actions_synchronize($delete_orphans = FALSE) {
256 $actions_in_code = actions_list(TRUE);
257 $actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC);
258
259 // Go through all the actions provided by modules.
260 foreach ($actions_in_code as $callback => $array) {
261 // Ignore configurable actions since their instances get put in when the
262 // user adds the action.
263 if (!$array['configurable']) {
264 // If we already have an action ID for this action, no need to assign aid.
265 if (array_key_exists($callback, $actions_in_db)) {
266 unset($actions_in_db[$callback]);
267 }
268 else {
269 // This is a new singleton that we don't have an aid for; assign one.
270 db_insert('actions')
271 ->fields(array(
272 'aid' => $callback,
273 'type' => $array['type'],
274 'callback' => $callback,
275 'parameters' => '',
276 'label' => $array['label'],
277 ))
278 ->execute();
279 watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['label'])));
280 }
281 }
282 }
283
284 // Any actions that we have left in $actions_in_db are orphaned.
285 if ($actions_in_db) {
286 $orphaned = array_keys($actions_in_db);
287
288 if ($delete_orphans) {
289 $actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll();
290 foreach ($actions as $action) {
291 actions_delete($action->aid);
292 watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->label)));
293 }
294 }
295 else {
296 $link = l(t('Remove orphaned actions'), 'admin/config/system/actions/orphan');
297 $count = count($actions_in_db);
298 $orphans = implode(', ', $orphaned);
299 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), WATCHDOG_WARNING);
300 }
301 }
302 }
303
304 /**
305 * Saves an action and its user-supplied parameter values to the database.
306 *
307 * @param $function
308 * The name of the function to be called when this action is performed.
309 * @param $type
310 * The type of action, to describe grouping and/or context, e.g., 'node',
311 * 'user', 'comment', or 'system'.
312 * @param $params
313 * An associative array with parameter names as keys and parameter values as
314 * values.
315 * @param $label
316 * A user-supplied label of this particular action, e.g., 'Send e-mail
317 * to Jim'.
318 * @param $aid
319 * The ID of this action. If omitted, a new action is created.
320 * @return
321 * The ID of the action.
322 */
323 function actions_save($function, $type, $params, $label, $aid = NULL) {
324 // aid is the callback for singleton actions so we need to keep a separate
325 // table for numeric aids.
326 if (!$aid) {
327 $aid = db_next_id();
328 }
329
330 db_merge('actions')
331 ->key(array('aid' => $aid))
332 ->fields(array(
333 'callback' => $function,
334 'type' => $type,
335 'parameters' => serialize($params),
336 'label' => $label,
337 ))
338 ->execute();
339
340 watchdog('actions', 'Action %action saved.', array('%action' => $label));
341 return $aid;
342 }
343
344 /**
345 * Retrieves a single action from the database.
346 *
347 * @param $aid
348 * The ID of the action to retrieve.
349 * @return
350 * The appropriate action row from the database as an object.
351 */
352 function actions_load($aid) {
353 return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject();
354 }
355
356 /**
357 * Deletes a single action from the database.
358 *
359 * @param $aid
360 * The ID of the action to delete.
361 */
362 function actions_delete($aid) {
363 db_delete('actions')
364 ->condition('aid', $aid)
365 ->execute();
366 module_invoke_all('actions_delete', $aid);
367 }
368

  ViewVC Help
Powered by ViewVC 1.1.2