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