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_mapCreates an associative array keyed by hashes of function names or IDs.
actions_deleteDeletes a single action from the database.
actions_doPerforms a given list of actions by executing their callback functions.
actions_function_lookupGiven a hash of an action array key, returns the key (function or ID).
actions_get_all_actionsRetrieves all action instances from the database.
actions_listDiscovers all available actions by invoking hook_action_info().
actions_loadRetrieves a single action from the database.
actions_saveSaves an action and its user-supplied parameter values to the database.
actions_synchronizeSynchronizes actions that are provided by modules in hook_action_info().

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. * Action functions are declared by modules by implementing hook_action_info().
  12. * Modules can cause action functions to run by calling actions_do(), and
  13. * trigger.module provides a user interface that lets administrators define
  14. * events that cause action functions to run.
  15. *
  16. * Each action function takes two to four arguments:
  17. * - $entity: The object that the action acts on, such as a node, comment, or
  18. * user.
  19. * - $context: Array of additional information about what triggered the action.
  20. * - $a1, $a2: Optional additional information, which can be passed into
  21. * actions_do() and will be passed along to the action function.
  22. *
  23. * @} End of "defgroup actions".
  24. */
  25. /**
  26. * Performs a given list of actions by executing their callback functions.
  27. *
  28. * Given the IDs of actions to perform, this function finds out what the
  29. * callback functions for the actions are by querying the database. Then
  30. * it calls each callback using the function call $function($object, $context,
  31. * $a1, $a2), passing the input arguments of this function (see below) to the
  32. * action function.
  33. *
  34. * @param $action_ids
  35. * The IDs of the actions to perform. Can be a single action ID or an array
  36. * of IDs. IDs of configurable actions must be given as numeric action IDs;
  37. * IDs of non-configurable actions may be given as action function names.
  38. * @param $object
  39. * The object that the action will act on: a node, user, or comment object.
  40. * @param $context
  41. * Associative array containing extra information about what triggered
  42. * the action call, with $context['hook'] giving the name of the hook
  43. * that resulted in this call to actions_do().
  44. * @param $a1
  45. * Passed along to the callback.
  46. * @param $a2
  47. * Passed along to the callback.
  48. * @return
  49. * An associative array containing the results of the functions that
  50. * perform the actions, keyed on action ID.
  51. *
  52. * @ingroup actions
  53. */
  54. function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 = NULL) {
  55. // $stack tracks the number of recursive calls.
  56. static $stack;
  57. $stack++;
  58. if ($stack > variable_get('actions_max_stack', 35)) {
  59. watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR);
  60. return;
  61. }
  62. $actions = array();
  63. $available_actions = actions_list();
  64. $actions_result = array();
  65. if (is_array($action_ids)) {
  66. $conditions = array();
  67. foreach ($action_ids as $action_id) {
  68. if (is_numeric($action_id)) {
  69. $conditions[] = $action_id;
  70. }
  71. elseif (isset($available_actions[$action_id])) {
  72. $actions[$action_id] = $available_actions[$action_id];
  73. }
  74. }
  75. // When we have action instances we must go to the database to retrieve
  76. // instance data.
  77. if (!empty($conditions)) {
  78. $query = db_select('actions');
  79. $query->addField('actions', 'aid');
  80. $query->addField('actions', 'type');
  81. $query->addField('actions', 'callback');
  82. $query->addField('actions', 'parameters');
  83. $query->condition('aid', $conditions, 'IN');
  84. $result = $query->execute();
  85. foreach ($result as $action) {
  86. $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
  87. $actions[$action->aid]['callback'] = $action->callback;
  88. $actions[$action->aid]['type'] = $action->type;
  89. }
  90. }
  91. // Fire actions, in no particular order.
  92. foreach ($actions as $action_id => $params) {
  93. // Configurable actions need parameters.
  94. if (is_numeric($action_id)) {
  95. $function = $params['callback'];
  96. if (function_exists($function)) {
  97. $context = array_merge($context, $params);
  98. $actions_result[$action_id] = $function($object, $context, $a1, $a2);
  99. }
  100. else {
  101. $actions_result[$action_id] = FALSE;
  102. }
  103. }
  104. // Singleton action; $action_id is the function name.
  105. else {
  106. $actions_result[$action_id] = $action_id($object, $context, $a1, $a2);
  107. }
  108. }
  109. }
  110. // Optimized execution of a single action.
  111. else {
  112. // If it's a configurable action, retrieve stored parameters.
  113. if (is_numeric($action_ids)) {
  114. $action = db_query("SELECT callback, parameters FROM {actions} WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject();
  115. $function = $action->callback;
  116. if (function_exists($function)) {
  117. $context = array_merge($context, unserialize($action->parameters));
  118. $actions_result[$action_ids] = $function($object, $context, $a1, $a2);
  119. }
  120. else {
  121. $actions_result[$action_ids] = FALSE;
  122. }
  123. }
  124. // Singleton action; $action_ids is the function name.
  125. else {
  126. if (function_exists($action_ids)) {
  127. $actions_result[$action_ids] = $action_ids($object, $context, $a1, $a2);
  128. }
  129. else {
  130. // Set to avoid undefined index error messages later.
  131. $actions_result[$action_ids] = FALSE;
  132. }
  133. }
  134. }
  135. $stack--;
  136. return $actions_result;
  137. }
  138. /**
  139. * Discovers all available actions by invoking hook_action_info().
  140. *
  141. * This function contrasts with actions_get_all_actions(); see the
  142. * documentation of actions_get_all_actions() for an explanation.
  143. *
  144. * @param $reset
  145. * Reset the action info static cache.
  146. * @return
  147. * An associative array keyed on action function name, with the same format
  148. * as the return value of hook_action_info(), containing all
  149. * modules' hook_action_info() return values as modified by any
  150. * hook_action_info_alter() implementations.
  151. *
  152. * @see hook_action_info()
  153. */
  154. function actions_list($reset = FALSE) {
  155. $actions = &drupal_static(__FUNCTION__);
  156. if (!isset($actions) || $reset) {
  157. $actions = module_invoke_all('action_info');
  158. drupal_alter('action_info', $actions);
  159. }
  160. // See module_implements() for an explanation of this cast.
  161. return (array) $actions;
  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. * Creates an associative array keyed by hashes of function names or IDs.
  191. *
  192. * Hashes are used to prevent actual function names from going out into HTML
  193. * forms and coming back.
  194. *
  195. * @param $actions
  196. * An associative array with function names or action IDs as keys
  197. * and associative arrays with keys 'label', 'type', etc. as values.
  198. * This is usually the output of actions_list() or actions_get_all_actions().
  199. * @return
  200. * An associative array whose keys are hashes of the input array keys, and
  201. * whose corresponding values are associative arrays with components
  202. * 'callback', 'label', 'type', and 'configurable' from the input array.
  203. */
  204. function actions_actions_map($actions) {
  205. $actions_map = array();
  206. foreach ($actions as $callback => $array) {
  207. $key = drupal_hash_base64($callback);
  208. $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
  209. $actions_map[$key]['label'] = $array['label'];
  210. $actions_map[$key]['type'] = $array['type'];
  211. $actions_map[$key]['configurable'] = $array['configurable'];
  212. }
  213. return $actions_map;
  214. }
  215. /**
  216. * Given a hash of an action array key, returns the key (function or ID).
  217. *
  218. * Faster than actions_actions_map() when you only need the function name or ID.
  219. *
  220. * @param $hash
  221. * Hash of a function name or action ID array key. The array key
  222. * is a key into the return value of actions_list() (array key is the action
  223. * function name) or actions_get_all_actions() (array key is the action ID).
  224. * @return
  225. * The corresponding array key, or FALSE if no match is found.
  226. */
  227. function actions_function_lookup($hash) {
  228. // Check for a function name match.
  229. $actions_list = actions_list();
  230. foreach ($actions_list as $function => $array) {
  231. if (drupal_hash_base64($function) == $hash) {
  232. return $function;
  233. }
  234. }
  235. $aid = FALSE;
  236. // Must be a configurable action; check database.
  237. $result = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchAll(PDO::FETCH_ASSOC);
  238. foreach ($result as $row) {
  239. if (drupal_hash_base64($row['aid']) == $hash) {
  240. $aid = $row['aid'];
  241. break;
  242. }
  243. }
  244. return $aid;
  245. }
  246. /**
  247. * Synchronizes actions that are provided by modules in hook_action_info().
  248. *
  249. * Actions provided by modules in hook_action_info() implementations are
  250. * synchronized with actions that are stored in the actions database table.
  251. * This is necessary so that actions that do not require configuration can
  252. * receive action IDs.
  253. *
  254. * @param $delete_orphans
  255. * If TRUE, any actions that exist in the database but are no longer
  256. * found in the code (for example, because the module that provides them has
  257. * been disabled) will be deleted.
  258. */
  259. function actions_synchronize($delete_orphans = FALSE) {
  260. $actions_in_code = actions_list(TRUE);
  261. $actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC);
  262. // Go through all the actions provided by modules.
  263. foreach ($actions_in_code as $callback => $array) {
  264. // Ignore configurable actions since their instances get put in when the
  265. // user adds the action.
  266. if (!$array['configurable']) {
  267. // If we already have an action ID for this action, no need to assign aid.
  268. if (isset($actions_in_db[$callback])) {
  269. unset($actions_in_db[$callback]);
  270. }
  271. else {
  272. // This is a new singleton that we don't have an aid for; assign one.
  273. db_insert('actions')
  274. ->fields(array(
  275. 'aid' => $callback,
  276. 'type' => $array['type'],
  277. 'callback' => $callback,
  278. 'parameters' => '',
  279. 'label' => $array['label'],
  280. ))
  281. ->execute();
  282. watchdog('actions', "Action '%action' added.", array('%action' => $array['label']));
  283. }
  284. }
  285. }
  286. // Any actions that we have left in $actions_in_db are orphaned.
  287. if ($actions_in_db) {
  288. $orphaned = array_keys($actions_in_db);
  289. if ($delete_orphans) {
  290. $actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll();
  291. foreach ($actions as $action) {
  292. actions_delete($action->aid);
  293. watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => $action->label));
  294. }
  295. }
  296. else {
  297. $link = l(t('Remove orphaned actions'), 'admin/config/system/actions/orphan');
  298. $count = count($actions_in_db);
  299. $orphans = implode(', ', $orphaned);
  300. watchdog('actions', '@count orphaned actions (%orphans) exist in the actions table. !link', array('@count' => $count, '%orphans' => $orphans, '!link' => $link), WATCHDOG_WARNING);
  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. db_merge('actions')
  330. ->key(array('aid' => $aid))
  331. ->fields(array(
  332. 'callback' => $function,
  333. 'type' => $type,
  334. 'parameters' => serialize($params),
  335. 'label' => $label,
  336. ))
  337. ->execute();
  338. watchdog('actions', 'Action %action saved.', array('%action' => $label));
  339. return $aid;
  340. }
  341. /**
  342. * Retrieves a single action from the database.
  343. *
  344. * @param $aid
  345. * The ID of the action to retrieve.
  346. * @return
  347. * The appropriate action row from the database as an object.
  348. */
  349. function actions_load($aid) {
  350. return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject();
  351. }
  352. /**
  353. * Deletes a single action from the database.
  354. *
  355. * @param $aid
  356. * The ID of the action to delete.
  357. */
  358. function actions_delete($aid) {
  359. db_delete('actions')
  360. ->condition('aid', $aid)
  361. ->execute();
  362. module_invoke_all('actions_delete', $aid);
  363. }