You are here

workflow_actions.module in Workflow 7

Provide actions and triggers for workflows. 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.

File

workflow_actions/workflow_actions.module
View source
<?php

/**
 * @file
 * Provide actions and triggers for workflows.
 * 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_hook_info().
 * Expose each transition as a hook.
 */
function workflow_actions_trigger_info() {
  $states = array();
  foreach (workflow_get_workflow_states() as $data) {
    $states[$data->sid] = check_plain($data->state);
  }
  if (empty($states)) {
    return array();
  }
  $trigger_page = drupal_substr($_GET['q'], 0, 32) == 'admin/structure/trigger/workflow';

  // TODO these should be pulled into their own DB function calls.
  if ($trigger_page && ($wid = arg(4))) {
    $result = db_query('SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid ' . 'FROM {workflow_type_map} tm ' . 'LEFT JOIN {workflows} w ON tm.wid = w.wid ' . 'LEFT JOIN {workflow_states} ws ON w.wid = ws.wid ' . 'LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid ' . 'WHERE w.wid = :wid AND ws.status = 1 AND wt.target_sid IS NOT NULL ' . 'ORDER BY tm.type, ws.weight', array(
      ':wid' => $wid,
    ));
  }
  else {
    $result = db_query('SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid ' . 'FROM {workflow_type_map} tm ' . 'LEFT JOIN {workflows} w ON tm.wid = w.wid ' . 'LEFT JOIN {workflow_states} ws ON w.wid = ws.wid ' . 'LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid ' . 'WHERE ws.status = 1 AND wt.target_sid IS NOT NULL ' . 'ORDER BY tm.type, ws.weight');
  }
  $creation_state = t('(creation)');
  foreach ($result as $data) {
    $creation_flag = FALSE;
    if ($states[$data->sid] == $creation_state) {
      $creation_flag = TRUE;
    }
    $pseudohooks['workflow-' . $data->type . '-' . $data->tid] = array(
      'label' => t('When %type moves from %state to %target_state', array(
        '%type' => $data->type,
        '%state' => $states[$data->sid],
        '%target_state' => $states[$data->target_sid],
      )),
      'workflow_creation_state' => $creation_flag,
    );
  }

  // $pseudohooks will not be set if no workflows have been assigned
  // to node types.
  if (isset($pseudohooks)) {
    return array(
      'workflow' => $pseudohooks,
    );
  }
  if ($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.'));
  }
}

/**
 * Implements hook_workflow().
 *
 * @param $hook
 *   The current workflow operation: 'transition pre' or 'transition post'.
 * @param $old_state
 *   The state ID of the current state.
 * @param  $new_state
 *   The state ID of the new state.
 * @param $node
 *   The node whose workflow state is changing.
 */
function workflow_actions_workflow($op, $old_state, $new_state, $node) {
  switch ($op) {
    case 'transition post':

      // A transition has occurred; fire off actions associated with this transition.
      if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state)) {
        $hook = 'workflow-' . $node->type . '-' . $transition->tid;
        $aids = trigger_get_assigned_actions($hook);
        if ($aids) {
          $context = array(
            'hook' => $hook,
          );

          // 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.
          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'], $node);
              }

              // Since we know about the node, we pass that info along to the action.
              $context['node'] = $node;
              $result = actions_do($aid, $objects[$action_info['type']], $context);
            }
            else {
              actions_do($aid, $node, $context);
            }
          }
        }
      }
      break;
    case 'transition delete':
      if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state)) {
        $actions = workflow_actions_get_actions_by_tid($transition->tid);
        foreach ($actions as $aid) {
          workflow_actions_remove($transition->tid, $aid);
        }
      }
      break;
  }
}

/**
 * Implements hook_workflow_operations().
 * Called in workflow.admin.inc to add actions for states.
 */
function workflow_actions_workflow_operations($level, $workflow = NULL, $state = NULL) {
  if ($workflow) {
    return array(
      'workflow_overview_actions' => array(
        'title' => t('Actions'),
        'href' => 'admin/structure/trigger/workflow/' . $workflow->wid,
      ),
    );
  }
}

/**
 * Remove an action assignment programmatically.
 *
 * Helpful when deleting a workflow.
 *
 * @param $tid
 *   Transition ID.
 * @param $aid
 *   Action ID.
 */
function workflow_actions_remove($tid, $aid) {
  $ops = array();
  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();
}

/**
 * Get 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;
}

/**
 * Implementation of hook_drupal_alter().
 */
function workflow_actions_action_info_alter(&$info) {
  $transitions = workflow_actions_trigger_info();
  if (empty($transitions)) {
    return;
  }
  foreach ((array) $transitions['workflow'] as $transition => $data) {

    // Loop through all available node actions and add them as triggers.
    // But not if this has been flagged as a creation state.
    if ($data['workflow_creation_state'] != TRUE) {
      foreach (node_action_info() as $action => $data) {
        $info[$action]['triggers'][] = $transition;
      }
    }

    // Either way, unset the creation flag so we don't confuse anyone later.
    unset($transitions['workflow'][$transition]['workflow_creation_state']);
  }
}

Functions

Namesort descending Description
workflow_actions_action_info_alter Implementation of hook_drupal_alter().
workflow_actions_delete_trigger_assignments_by_aid_op Delete assignments, by action and operation.
workflow_actions_get_actions_by_aid Get a specific action.
workflow_actions_get_actions_by_tid Get the actions associated with a given transition. Array of action ids in the same format as _trigger_get_hook_aids().
workflow_actions_get_trigger_assignments Get all trigger assignments for workflow.
workflow_actions_get_trigger_assignments_by_aid Get all trigger assignments for workflow and a given action.
workflow_actions_remove Remove an action assignment programmatically.
workflow_actions_trigger_info Implements hook_hook_info(). Expose each transition as a hook.
workflow_actions_workflow Implements hook_workflow().
workflow_actions_workflow_operations Implements hook_workflow_operations(). Called in workflow.admin.inc to add actions for states.