Skip navigation
Help

actions.inc

  1. drupal
    1. 6 drupal/includes/actions.inc
    2. 7 drupal/includes/actions.inc

This is the actions engine for executing stored actions.

Functions & methods

NameDescription
actions_actions_mapCreate an associative array keyed by md5 hashes of function names.
actions_deleteDelete a single action from the database.
actions_doPerform a given list of actions by executing their callback functions.
actions_function_lookupGiven an md5 hash of a function name, return the function name.
actions_get_all_actionsRetrieves all action instances from the database.
actions_listDiscover all action functions by invoking hook_action_info().
actions_loadRetrieve a single action from the database.
actions_saveSave an action and its associated user-supplied parameter values to the database.
actions_synchronizeSynchronize actions that are provided by modules.

File

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