<?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 creadit 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 assingments 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 assignements 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 seprate 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']);
  }
}
