workflow_actions.module 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. /**
  3. * @file
  4. * Provide actions and triggers for workflows.
  5. * Why it's own module? Some sites prefer rules, some prefer actions,
  6. * all prefer a lower code footprint and better performance.
  7. * Additional creadit to gcassie ( http://drupal.org/user/80260 ) for
  8. * the initial push to split actions out of core workflow.
  9. */
  10. /**
  11. * Implements hook_hook_info().
  12. * Expose each transition as a hook.
  13. */
  14. function workflow_actions_trigger_info() {
  15. $states = array();
  16. foreach (workflow_get_workflow_states() as $data) {
  17. $states[$data->sid] = check_plain($data->state);
  18. }
  19. if (empty($states)) {
  20. return array();
  21. }
  22. $trigger_page = drupal_substr($_GET['q'], 0, 32) == 'admin/structure/trigger/workflow';
  23. // TODO these should be pulled into their own DB function calls.
  24. if ($trigger_page && $wid = arg(4)) {
  25. $result = db_query('SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid ' .
  26. 'FROM {workflow_type_map} tm ' .
  27. 'LEFT JOIN {workflows} w ON tm.wid = w.wid ' .
  28. 'LEFT JOIN {workflow_states} ws ON w.wid = ws.wid ' .
  29. 'LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid ' .
  30. 'WHERE w.wid = :wid AND ws.status = 1 AND wt.target_sid IS NOT NULL ' .
  31. 'ORDER BY tm.type, ws.weight', array(':wid' => $wid));
  32. }
  33. else {
  34. $result = db_query('SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid ' .
  35. 'FROM {workflow_type_map} tm ' .
  36. 'LEFT JOIN {workflows} w ON tm.wid = w.wid ' .
  37. 'LEFT JOIN {workflow_states} ws ON w.wid = ws.wid ' .
  38. 'LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid ' .
  39. 'WHERE ws.status = 1 AND wt.target_sid IS NOT NULL ' .
  40. 'ORDER BY tm.type, ws.weight');
  41. }
  42. $creation_state = t('(creation)');
  43. foreach ($result as $data) {
  44. $creation_flag = FALSE;
  45. if ($states[$data->sid] == $creation_state) {
  46. $creation_flag = TRUE;
  47. }
  48. $pseudohooks['workflow-' . $data->type . '-' . $data->tid] =
  49. array('label' => t('When %type moves from %state to %target_state',
  50. array('%type' => $data->type, '%state' => $states[$data->sid], '%target_state' => $states[$data->target_sid])),
  51. 'workflow_creation_state' => $creation_flag,
  52. );
  53. }
  54. // $pseudohooks will not be set if no workflows have been assigned
  55. // to node types.
  56. if (isset($pseudohooks)) {
  57. return array(
  58. 'workflow' => $pseudohooks,
  59. );
  60. }
  61. if ($trigger_page) {
  62. drupal_set_message(t('Either no transitions have been set up or this workflow has not yet been ' .
  63. 'assigned to a content type. To enable the assignment of actions, edit the workflow to assign ' .
  64. 'permissions for roles to do transitions. After that is completed, transitions will appear here ' .
  65. 'and you will be able to assign actions to them.'));
  66. }
  67. }
  68. /**
  69. * Implements hook_workflow().
  70. *
  71. * @param $hook
  72. * The current workflow operation: 'transition pre' or 'transition post'.
  73. * @param $old_state
  74. * The state ID of the current state.
  75. * @param $new_state
  76. * The state ID of the new state.
  77. * @param $node
  78. * The node whose workflow state is changing.
  79. */
  80. function workflow_actions_workflow($op, $old_state, $new_state, $node) {
  81. switch ($op) {
  82. case 'transition post':
  83. // A transition has occurred; fire off actions associated with this transition.
  84. if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state) ) {
  85. $hook = 'workflow-' . $node->type . '-' . $transition->tid;
  86. $aids = trigger_get_assigned_actions($hook);
  87. if ($aids) {
  88. $context = array(
  89. 'hook' => $hook,
  90. );
  91. // We need to get the expected object if the action's type is not 'node'.
  92. // We keep the object in $objects so we can reuse it if we have multiple actions
  93. // that make changes to an object.
  94. foreach ($aids as $aid => $action_info) {
  95. if ($action_info['type'] != 'node') {
  96. if (!isset($objects[$action_info['type']])) {
  97. $objects[$action_info['type']] = _trigger_normalize_node_context($action_info['type'], $node);
  98. }
  99. // Since we know about the node, we pass that info along to the action.
  100. $context['node'] = $node;
  101. $result = actions_do($aid, $objects[$action_info['type']], $context);
  102. }
  103. else {
  104. actions_do($aid, $node, $context);
  105. }
  106. }
  107. }
  108. }
  109. break;
  110. case 'transition delete':
  111. if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state) ) {
  112. $actions = workflow_actions_get_actions_by_tid($transition->tid);
  113. foreach ($actions as $aid) {
  114. workflow_actions_remove($transition->tid, $aid);
  115. }
  116. }
  117. break;
  118. }
  119. }
  120. /**
  121. * Implements hook_workflow_operations().
  122. * Called in workflow.admin.inc to add actions for states.
  123. */
  124. function workflow_actions_workflow_operations($level, $workflow = NULL, $state = NULL) {
  125. if ($workflow) {
  126. return array('workflow_overview_actions' => array(
  127. 'title' => t('Actions'),
  128. 'href' => 'admin/structure/trigger/workflow/' . $workflow->wid),
  129. );
  130. }
  131. }
  132. /**
  133. * Remove an action assignment programmatically.
  134. *
  135. * Helpful when deleting a workflow.
  136. *
  137. * @param $tid
  138. * Transition ID.
  139. * @param $aid
  140. * Action ID.
  141. */
  142. function workflow_actions_remove($tid, $aid) {
  143. $ops = array();
  144. foreach (workflow_actions_get_trigger_assignments_by_aid($aid) as $data) {
  145. // Transition ID is the last part, e.g., foo-bar-1.
  146. $transition = array_pop(explode('-', $data->hook));
  147. if ($tid == $transition) {
  148. $hooks[] = $data->hook;
  149. }
  150. }
  151. foreach ($hooks as $hook) {
  152. workflow_actions_delete_trigger_assignments_by_aid_op($aid, $hook);
  153. foreach (workflow_actions_get_actions_by_aid($aid) as $action) {
  154. watchdog('workflow', 'Action %action has been unassigned.',
  155. array('%action' => $action->description));
  156. }
  157. }
  158. }
  159. /**
  160. * DB functions.
  161. */
  162. /**
  163. * Get all trigger assingments for workflow.
  164. */
  165. function workflow_actions_get_trigger_assignments() {
  166. $results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow"');
  167. return $results->fetchAll();
  168. }
  169. /**
  170. * Get all trigger assignements for workflow and a given action.
  171. */
  172. function workflow_actions_get_trigger_assignments_by_aid($aid) {
  173. $results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow" AND aid = ":aid"', array(':aid' => $aid));
  174. return $results->fetchAll();
  175. }
  176. /**
  177. * Delete assignments, by action and operation.
  178. */
  179. function workflow_actions_delete_trigger_assignments_by_aid_op($aid, $op) {
  180. return db_delete('trigger_assignments')->condition('hook', 'workflow')->condition('hook', $op)->condition('aid', $aid)->execute();
  181. }
  182. /**
  183. * Get a specific action.
  184. */
  185. function workflow_actions_get_actions_by_aid($aid) {
  186. $results = db_query('SELECT * FROM {actions} WHERE aid = ":aid"', array(':aid' => $aid));
  187. return $results->fetchAll();
  188. }
  189. /**
  190. * Get the actions associated with a given transition.
  191. * Array of action ids in the same format as _trigger_get_hook_aids().
  192. */
  193. function workflow_actions_get_actions_by_tid($tid) {
  194. $aids = array();
  195. foreach (workflow_actions_get_trigger_assignments() as $data) {
  196. // Transition ID is the last part, e.g., foo-bar-1.
  197. $transition = array_pop(explode('-', $data->hook));
  198. if ($tid == $transition) {
  199. // Specialized, TODO seprate this SQL out later
  200. $results = db_query('SELECT aa.aid, a.type FROM {trigger_assignments} aa ' .
  201. 'LEFT JOIN {actions} a ON aa.aid = a.aid ' .
  202. 'WHERE aa.hook = ":hook" ' .
  203. 'ORDER BY weight', array(':hook' => $data->hook));
  204. foreach ($results as $action) {
  205. $aids[$action->aid]['type'] = $action->type;
  206. }
  207. }
  208. }
  209. return $aids;
  210. }
  211. /**
  212. * Implementation of hook_drupal_alter().
  213. */
  214. function workflow_actions_action_info_alter(&$info) {
  215. $transitions = workflow_actions_trigger_info();
  216. if (empty($transitions)) {
  217. return;
  218. }
  219. foreach ((array)$transitions['workflow'] as $transition => $data) {
  220. // Loop through all available node actions and add them as triggers.
  221. // But not if this has been flagged as a creation state.
  222. if ($data['workflow_creation_state'] != TRUE) {
  223. foreach (node_action_info() as $action => $data) {
  224. $info[$action]['triggers'][] = $transition;
  225. }
  226. }
  227. // Either way, unset the creation flag so we don't confuse anyone later.
  228. unset($transitions['workflow'][$transition]['workflow_creation_state']);
  229. }
  230. }