123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- <?php
- /**
- * @file
- * Enables actions to be fired upon a Workflow State change.
- *
- * N.B. This module's name is incorrect. It provides Triggers, not Actions.
- *
- * Why it's own module? Some sites prefer rules, some prefer actions,
- * all prefer a lower code footprint and better performance.
- * Additional credit to gcassie ( http://drupal.org/user/80260 ) for
- * the initial push to split actions out of core workflow.
- */
- /**
- * Implements hook_workflow_operations().
- */
- function workflow_actions_workflow_operations($op, $workflow = NULL, $state = NULL) {
- switch ($op) {
- case 'workflow':
- $actions = array();
- if ($workflow && $workflow->getStates()) {
- $wid = $workflow->getWorkflowId();
- $actions = array(
- 'workflow_overview_actions' => array(
- 'title' => t('Actions'),
- 'href' => "admin/structure/trigger/workflow/$wid",
- ),
- );
- }
- else {
- // Generate a dummy, if no states exist.
- $actions = array(
- 'workflow_overview_actions' => array(
- 'title' => '',
- 'href' => '',
- ),
- );
- }
- return $actions;
- }
- }
- /**
- * Implements hook_workflow().
- */
- function workflow_actions_workflow($op, $id, $new_sid, $entity, $force = FALSE, $entity_type = 'node', $field_name = '', $transition = NULL) {
- switch ($op) {
- case 'transition post':
- // Reminder: event 'transition post' does not occur for Workflow Field.
- _workflow_actions_do($transition);
- break;
- case 'transition delete':
- // @todo: implement delete triggers upon 'transition delete'.
- // The below code generates an error PDOException: SQLSTATE[42S22].
- // So, it is commented out as per d.o. #2200089. In 7.x-1.2, this trigger
- // was never invoked. So, 'delete trigger upon transition delete' is now
- // a feature request.
- // $tid = $id;
- // $actions = _workflow_actions_get_actions_by_tid($tid);
- // foreach ($actions as $aid) {
- // _workflow_actions_remove($tid, $aid);
- // }
- break;
- }
- }
- /**
- * Implements hook_entity_insert().
- *
- * Trigger the 'transition post' event for workflow_field.
- * Do this only for Trigger & Actions, since Rules has its own way.
- * And I ndo't like activating an extra hook for nothing.
- */
- function workflow_actions_entity_insert($entity, $type) {
- workflow_actions_entity_update($entity, $type);
- }
- /**
- * Implements hook_entity_update().
- *
- * @see WorkflowTransition->execute()
- */
- function workflow_actions_entity_update($entity, $entity_type) {
- // For workflow_field, the 'transition post' event is not triggered in
- // WorkflowTransition->execute(), since we are still IN a transition.
- // This is now triggered here.
- // (But without hook_workflow, since it crashes workflow_access.)
- // P.S. You should not mix workflow field and workflow node!!
- if (module_exists('workflowfield') && !module_exists('workflownode')) {
- if (isset($entity->workflow_transitions)) {
- foreach ($entity->workflow_transitions as &$transition) {
- // $transition->post_execute(); // equivalent with hook_entity_save().
- _workflow_actions_do($transition);
- }
- }
- }
- elseif (module_exists('workflownode')) {
- // This is already done in workflow_actions_workflow(). But we cannot move
- // that here, since node_save()/entity_save() isn't always triggered.
- }
- }
- /**
- * Implements hook_trigger_info().
- *
- * Expose each transition as a hook.
- */
- function workflow_actions_trigger_info() {
- static $pseudohooks = array();
- if ($pseudohooks) {
- return $pseudohooks;
- }
- // If we come from a specific workflow, only show triggers for that workflow.
- // Removed: it is confusing.
- $trigger_page = FALSE;
- $wid = 0;
- /*
- if (drupal_substr($_GET['q'], 0, 32) == 'admin/structure/trigger/workflow') {
- $trigger_page = TRUE;
- $wid = arg(4);
- }
- */
- $workflows = workflow_load_multiple($wid ? array($wid) : FALSE);
- // Get the mapping for Node API.
- // Build an array of Wid => array of types.
- $workflow_node_type_map = module_exists('workflownode') ? workflow_get_workflow_type_map() : array();
- foreach ($workflow_node_type_map as $type => $wid) {
- $group = 'workflow';
- $type_map[$wid][] = array('entity_type' => 'node', 'bundle' => $type, $group);
- }
- // Get the mapping for Field API.
- $fields = _workflow_info_fields($entity = NULL, $entity_type = '');
- foreach ($fields as $field_name => $field) {
- if ($field['type'] == 'workflow') {
- foreach ($field['bundles'] as $entity_type => $bundles) {
- foreach ($bundles as $bundle) {
- // Add the trigger to the approriate Tab on admin/structure/trigger.
- switch ($entity_type) {
- case 'node': $group = $entity_type; break;
- case 'taxonomy_term': $group = 'taxonomy'; break;
- default: $group = 'workflow'; break;
- }
- $type_map[$field['settings']['wid']][] = array(
- 'entity_type' => $entity_type,
- 'bundle' => $bundle,
- 'group' => $group,
- );
- }
- }
- }
- }
- // Initialize the Workflow tab on admin/structure/trigger/workflow.
- $pseudohooks['workflow'] = array();
- // Create a trigger for each possible combination.
- foreach ($workflows as $wid => $workflow) {
- $states = $workflow->getStates('CREATION');
- foreach ($workflow->getTransitions() as $config_transition) {
- $state = $states[$config_transition->sid];
- $target_state = $states[$config_transition->target_sid];
- if (!$state || !$target_state || !$state->isActive() || !$target_state->isActive()) {
- continue;
- }
- // Add hook for Node API.
- if (isset($type_map[$wid])) {
- $creation_flag = $state->isCreationState();
- foreach ($type_map[$wid] as $type_bundle) {
- // Add the trigger to the appropriate Tab on admin/structure/trigger.
- $group = isset($type_bundle['group']) ? $type_bundle['group'] : 'workflow';
- $label = t('When @entity_type %bundle moves %workflow from %state to %target_state',
- array(
- '@entity_type' => $type_bundle['entity_type'],
- '%bundle' => $type_bundle['bundle'],
- '%workflow' => $workflow->label(),
- '%state' => $state->label(),
- '%target_state' => $target_state->label(),
- )
- );
- $pseudohooks[$group]['workflow-' . $type_bundle['entity_type']
- . '-' . $type_bundle['bundle']
- . '-' . $config_transition->tid] = array(
- 'label' => $label,
- 'workflow_creation_state' => $creation_flag,
- );
- }
- }
- }
- }
- // $pseudohooks will not be set if no workflows have been assigned
- // to node types.
- if ($pseudohooks) {
- return $pseudohooks;
- }
- elseif ($trigger_page) {
- drupal_set_message(t('Either no transitions have been set up or this
- workflow has not yet been assigned to a content type. To enable the
- assignment of actions, edit the workflow to assign permissions for roles
- to do transitions. After that is completed, transitions will appear here
- and you will be able to assign actions to them.')
- );
- }
- else {
- return array();
- }
- }
- /**
- * Implements hook_drupal_alter().
- */
- function workflow_actions_action_info_alter(&$info) {
- $triggers = workflow_actions_trigger_info();
- if (empty($triggers)) {
- return;
- }
- // Loop through all available node actions and add them as triggers.
- foreach ($triggers as $groups) {
- foreach ($groups as $trigger_name => $data) {
- foreach (node_action_info() as $action => $data) {
- $info[$action]['triggers'][] = $trigger_name;
- }
- }
- }
- }
- /**
- * Helper function, that triggers actions.
- *
- * @param $transition.
- * A Workflow Transition object.
- */
- function _workflow_actions_do(&$transition) {
- $entity_type = $transition->entity_type;
- $entity = $transition->getEntity();
- $old_sid = $transition->old_sid;
- $new_sid = $transition->new_sid;
- // A transition occurred; fire off actions associated with this transition.
- $workflow = $transition->getWorkflow();
- $config_transitions = $workflow->getTransitionsBySidTargetSid($old_sid, $new_sid);
- $config_transition = reset($config_transitions);
- if ($config_transition) {
- list(, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
- $hook = 'workflow-' . $entity_type . '-' . $entity_bundle . '-' . $config_transition->tid;
- $aids = trigger_get_assigned_actions($hook);
- if (!$aids) {
- // With 7x.-2.x, the pseudohooks have an extra 'entity_type' in the name.
- // This is to be backwards compatible with 7.x-1.2
- $hook = 'workflow-' . $entity_bundle . '-' . $config_transition->tid;
- $aids = trigger_get_assigned_actions($hook);
- }
- if ($aids && !isset($transition->workflow_actions_done[$hook])) {
- // Avoid multiple executions if the entity_save is repeated in an action.
- $transition->workflow_actions_done[$hook] = TRUE;
- $context = array(
- 'hook' => $hook,
- 'entity_type' => $entity_type,
- );
- // We need to get the expected object if the action's type is not 'node'.
- // We keep the object in $objects so we can reuse it if we have
- // multiple actions that make changes to an object.
- $objects = array();
- foreach ($aids as $aid => $action_info) {
- if ($action_info['type'] != 'node') {
- if (!isset($objects[$action_info['type']])) {
- $objects[$action_info['type']] = _trigger_normalize_node_context($action_info['type'], $entity);
- }
- // Since we know the node, we pass it along to the action.
- $context['node'] = $entity;
- $result = actions_do($aid, $objects[$action_info['type']], $context);
- }
- else {
- actions_do($aid, $entity, $context);
- }
- }
- }
- }
- }
- /**
- * Remove an action assignment programmatically.
- *
- * Helpful when deleting a workflow.
- *
- * @param int $tid
- * Transition ID.
- * @param int $aid
- * Action ID.
- */
- function _workflow_actions_remove($tid, $aid) {
- foreach (_workflow_actions_get_trigger_assignments_by_aid($aid) as $data) {
- // Transition ID is the last part, e.g., foo-bar-1.
- $transition = array_pop(explode('-', $data->hook));
- if ($tid == $transition) {
- $hooks[] = $data->hook;
- }
- }
- foreach ($hooks as $hook) {
- _workflow_actions_delete_trigger_assignments_by_aid_op($aid, $hook);
- foreach (_workflow_actions_get_actions_by_aid($aid) as $action) {
- watchdog('workflow', 'Action %action has been unassigned.',
- array('%action' => $action->description));
- }
- }
- }
- /**
- * DB functions.
- */
- /**
- * Get all trigger assignments for workflow.
- */
- function _workflow_actions_get_trigger_assignments() {
- $results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow"');
- return $results->fetchAll();
- }
- /**
- * Get all trigger assignments for workflow and a given action.
- */
- function _workflow_actions_get_trigger_assignments_by_aid($aid) {
- $results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow" AND aid = ":aid"', array(':aid' => $aid));
- return $results->fetchAll();
- }
- /**
- * Delete assignments, by action and operation.
- */
- function _workflow_actions_delete_trigger_assignments_by_aid_op($aid, $op) {
- return db_delete('trigger_assignments')->condition('hook', 'workflow')->condition('hook', $op)->condition('aid', $aid)->execute();
- }
- /**
- * Get a specific action.
- */
- function _workflow_actions_get_actions_by_aid($aid) {
- $results = db_query('SELECT * FROM {actions} WHERE aid = ":aid"', array(':aid' => $aid));
- return $results->fetchAll();
- }
- /**
- * Gets the actions associated with a given transition.
- *
- * Array of action ids in the same format as _trigger_get_hook_aids().
- */
- function _workflow_actions_get_actions_by_tid($tid) {
- $aids = array();
- foreach (_workflow_actions_get_trigger_assignments() as $data) {
- // Transition ID is the last part, e.g., foo-bar-1.
- $transition = array_pop(explode('-', $data->hook));
- if ($tid == $transition) {
- // Specialized, TODO separate this SQL out later.
- $results = db_query('SELECT aa.aid, a.type FROM {trigger_assignments} aa
- LEFT JOIN {actions} a ON aa.aid = a.aid
- WHERE aa.hook = ":hook"
- ORDER BY weight', array(':hook' => $data->hook));
- foreach ($results as $action) {
- $aids[$action->aid]['type'] = $action->type;
- }
- }
- }
- return $aids;
- }
|