1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
|
<?php
// $Id: actions.inc,v 1.8.2.5 2008/10/16 12:45:53 goba Exp $
/**
* @file
* This is the actions engine for executing stored actions.
*/
/**
* Perform a given list of actions by executing their callback functions.
*
* Given the IDs of actions to perform, find out what the callbacks
* for the actions are by querying the database. Then call each callback
* using the function call $function($object, $context, $a1, $a2)
* where $function is the name of a function written in compliance with
* the action specification; that is, foo($object, $context).
*
* @param $action_ids
* The ID of the action to perform. Can be a single action ID or an array
* of IDs. IDs of instances will be numeric; IDs of singletons will be
* function names.
* @param $object
* Parameter that will be passed along to the callback. Typically the
* object that the action will act on; a node, user or comment object.
* If the action does not act on an object, pass a dummy object. This
* is necessary to support PHP 4 object referencing.
* @param $context
* Parameter that will be passed along to the callback. $context is a
* keyed array containing extra information about what is currently
* happening at the time of the call. Typically $context['hook'] and
* $context['op'] will tell which hook-op combination resulted in this
* call to actions_do().
* @param $a1
* Parameter that will be passed along to the callback.
* @param $a2
* Parameter that will be passed along to the callback.
*
* @return
* An associative array containing the result of the function that
* performs the action, keyed on action ID.
*/
function actions_do($action_ids, &$object, $context = NULL, $a1 = NULL, $a2 = NULL) {
static $stack;
$stack++;
if ($stack > variable_get('actions_max_stack', 35)) {
watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR);
return;
}
$actions = array();
$available_actions = actions_list();
$result = array();
if (is_array($action_ids)) {
$where = array();
$where_values = array();
foreach ($action_ids as $action_id) {
if (is_numeric($action_id)) {
$where[] = "OR aid = '%s'";
$where_values[] = $action_id;
}
elseif (isset($available_actions[$action_id])) {
$actions[$action_id] = $available_actions[$action_id];
}
}
// When we have action instances we must go to the database to
// retrieve instance data.
if ($where) {
$where_clause = implode(' ', $where);
// Strip off leading 'OR '.
$where_clause = '('. strstr($where_clause, " ") .')';
$result_db = db_query('SELECT * FROM {actions} WHERE '. $where_clause, $where_values);
while ($action = db_fetch_object($result_db)) {
$actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
$actions[$action->aid]['callback'] = $action->callback;
$actions[$action->aid]['type'] = $action->type;
}
}
// Fire actions, in no particular order.
foreach ($actions as $action_id => $params) {
if (is_numeric($action_id)) { // Configurable actions need parameters.
$function = $params['callback'];
$context = array_merge($context, $params);
$result[$action_id] = $function($object, $context, $a1, $a2);
}
// Singleton action; $action_id is the function name.
else {
$result[$action_id] = $action_id($object, $context, $a1, $a2);
}
}
}
// Optimized execution of single action.
else {
// If it's a configurable action, retrieve stored parameters.
if (is_numeric($action_ids)) {
$action = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = '%s'", $action_ids));
$function = $action->callback;
$context = array_merge($context, unserialize($action->parameters));
$result[$action_ids] = $function($object, $context, $a1, $a2);
}
// Singleton action; $action_ids is the function name.
else {
$result[$action_ids] = $action_ids($object, $context, $a1, $a2);
}
}
return $result;
}
/**
* Discover all action functions by invoking hook_action_info().
*
* mymodule_action_info() {
* return array(
* 'mymodule_functiondescription_action' => array(
* 'type' => 'node',
* 'description' => t('Save node'),
* 'configurable' => FALSE,
* 'hooks' => array(
* 'nodeapi' => array('delete', 'insert', 'update', 'view'),
* 'comment' => array('delete', 'insert', 'update', 'view'),
* )
* )
* );
* }
*
* The description is used in presenting possible actions to the user for
* configuration. The type is used to present these actions in a logical
* grouping and to denote context. Some types are 'node', 'user', 'comment',
* and 'system'. If an action is configurable it will provide form,
* validation and submission functions. The hooks the action supports
* are declared in the 'hooks' array.
*
* @param $reset
* Reset the action info static cache.
*
* @return
* An associative array keyed on function name. The value of each key is
* an array containing information about the action, such as type of
* action and description of the action, e.g.,
*
* @code
* $actions['node_publish_action'] = array(
* 'type' => 'node',
* 'description' => t('Publish post'),
* 'configurable' => FALSE,
* 'hooks' => array(
* 'nodeapi' => array('presave', 'insert', 'update', 'view'),
* 'comment' => array('delete', 'insert', 'update', 'view'),
* ),
* );
* @endcode
*/
function actions_list($reset = FALSE) {
static $actions;
if (!isset($actions) || $reset) {
$actions = module_invoke_all('action_info');
drupal_alter('action_info', $actions);
}
// See module_implements for explanations of this cast.
return (array)$actions;
}
/**
* Retrieve all action instances from the database.
*
* Compare with actions_list() which gathers actions by
* invoking hook_action_info(). The two are synchronized
* by visiting /admin/build/actions (when actions.module is
* enabled) which runs actions_synchronize().
*
* @return
* Associative array keyed by action ID. Each value is
* an associative array with keys 'callback', 'description',
* 'type' and 'configurable'.
*/
function actions_get_all_actions() {
$actions = array();
$result = db_query("SELECT * FROM {actions}");
while ($action = db_fetch_object($result)) {
$actions[$action->aid] = array(
'callback' => $action->callback,
'description' => $action->description,
'type' => $action->type,
'configurable' => (bool) $action->parameters,
);
}
return $actions;
}
/**
* Create an associative array keyed by md5 hashes of function names.
*
* Hashes are used to prevent actual function names from going out into
* HTML forms and coming back.
*
* @param $actions
* An associative array with function names as keys and associative
* arrays with keys 'description', 'type', etc. as values. Generally
* the output of actions_list() or actions_get_all_actions() is given
* as input to this function.
*
* @return
* An associative array keyed on md5 hash of function name. The value of
* each key is an associative array of function, description, and type
* for the action.
*/
function actions_actions_map($actions) {
$actions_map = array();
foreach ($actions as $callback => $array) {
$key = md5($callback);
$actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
$actions_map[$key]['description'] = $array['description'];
$actions_map[$key]['type'] = $array['type'];
$actions_map[$key]['configurable'] = $array['configurable'];
}
return $actions_map;
}
/**
* Given an md5 hash of a function name, return the function name.
*
* Faster than actions_actions_map() when you only need the function name.
*
* @param $hash
* MD5 hash of a function name
*
* @return
* Function name
*/
function actions_function_lookup($hash) {
$actions_list = actions_list();
foreach ($actions_list as $function => $array) {
if (md5($function) == $hash) {
return $function;
}
}
// Must be an instance; must check database.
$aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s' AND parameters <> ''", $hash));
return $aid;
}
/**
* Synchronize actions that are provided by modules.
*
* They are synchronized with actions that are stored in the actions table.
* This is necessary so that actions that do not require configuration can
* receive action IDs. This is not necessarily the best approach,
* but it is the most straightforward.
*/
function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
if (!$actions_in_code) {
$actions_in_code = actions_list();
}
$actions_in_db = array();
$result = db_query("SELECT * FROM {actions} WHERE parameters = ''");
while ($action = db_fetch_object($result)) {
$actions_in_db[$action->callback] = array('aid' => $action->aid, 'description' => $action->description);
}
// Go through all the actions provided by modules.
foreach ($actions_in_code as $callback => $array) {
// Ignore configurable actions since their instances get put in
// when the user adds the action.
if (!$array['configurable']) {
// If we already have an action ID for this action, no need to assign aid.
if (array_key_exists($callback, $actions_in_db)) {
unset($actions_in_db[$callback]);
}
else {
// This is a new singleton that we don't have an aid for; assign one.
db_query("INSERT INTO {actions} (aid, type, callback, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback, $array['type'], $callback, '', $array['description']);
watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['description'])));
}
}
}
// Any actions that we have left in $actions_in_db are orphaned.
if ($actions_in_db) {
$orphaned = array();
$placeholder = array();
foreach ($actions_in_db as $callback => $array) {
$orphaned[] = $callback;
$placeholder[] = "'%s'";
}
$orphans = implode(', ', $orphaned);
if ($delete_orphans) {
$placeholders = implode(', ', $placeholder);
$results = db_query("SELECT a.aid, a.description FROM {actions} a WHERE callback IN ($placeholders)", $orphaned);
while ($action = db_fetch_object($results)) {
actions_delete($action->aid);
watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->description)));
}
}
else {
$link = l(t('Remove orphaned actions'), 'admin/settings/actions/orphan');
$count = count($actions_in_db);
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);
}
}
}
/**
* Save an action and its associated user-supplied parameter values to the database.
*
* @param $function
* The name of the function to be called when this action is performed.
* @param $params
* An associative array with parameter names as keys and parameter values
* as values.
* @param $desc
* A user-supplied description of this particular action, e.g., 'Send
* e-mail to Jim'.
* @param $aid
* The ID of this action. If omitted, a new action is created.
*
* @return
* The ID of the action.
*/
function actions_save($function, $type, $params, $desc, $aid = NULL) {
$serialized = serialize($params);
if ($aid) {
db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters = '%s', description = '%s' WHERE aid = '%s'", $function, $type, $serialized, $desc, $aid);
watchdog('actions', 'Action %action saved.', array('%action' => $desc));
}
else {
// aid is the callback for singleton actions so we need to keep a
// separate table for numeric aids.
db_query('INSERT INTO {actions_aid} VALUES (default)');
$aid = db_last_insert_id('actions_aid', 'aid');
db_query("INSERT INTO {actions} (aid, callback, type, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);
watchdog('actions', 'Action %action created.', array('%action' => $desc));
}
return $aid;
}
/**
* Retrieve a single action from the database.
*
* @param $aid
* integer The ID of the action to retrieve.
*
* @return
* The appropriate action row from the database as an object.
*/
function actions_load($aid) {
return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = '%s'", $aid));
}
/**
* Delete a single action from the database.
*
* @param $aid
* integer The ID of the action to delete.
*/
function actions_delete($aid) {
db_query("DELETE FROM {actions} WHERE aid = '%s'", $aid);
module_invoke_all('actions_delete', $aid);
}
|