actions.inc

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

This is the actions engine for executing stored actions.

File

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