You are here

webform_workflow.module in Webform Workflow 7

A simple workflow module for webforms.

File

webform_workflow.module
View source
<?php

/**
 * @file
 * A simple workflow module for webforms.
 */
define('WEBFORM_WORKFLOW_ORIGINAL_SUBMITTER', 'wwos');

/**
 * Implements hook_entity_info().
 */
function webform_workflow_entity_info() {
  $entities = array();
  $entities['webform_workflow_state'] = array(
    'label' => t('Webform Workflow State'),
    'controller class' => 'WebformWorkflowStateController',
    'views controller class' => 'EntityDefaultViewsController',
    'uri callback' => 'webform_workflow_state_uri',
    'access callback' => 'webform_workflow_state_access',
    'base table' => 'webform_workflow_state',
    'entity keys' => array(
      'id' => 'wsid',
      'label' => 'label',
    ),
    'view callback' => 'entity_metadata_view_single',
    'static cache' => TRUE,
    'fieldable' => TRUE,
    'module' => 'webform_workflow',
    'metatag' => FALSE,
    'redirect' => FALSE,
    'inline entity form' => array(
      'controller' => 'WebformWorkflowStateInlineEntityFormController',
    ),
    'bundles' => array(
      'webform_workflow_state' => array(
        'label' => t('State'),
        'admin' => array(
          'path' => 'admin/structure/webform-workflow/states',
        ),
      ),
    ),
  );

  // Expose webform submissions as very basic entities purely so that Rules and
  // Views Bulk Operations can be used.
  $entities['webform_workflow_submission'] = array(
    'label' => t('Webform Submission'),
    'controller class' => 'EntityAPIController',
    'base table' => 'webform_submissions',
    'entity keys' => array(
      'id' => 'sid',
      'label' => 'sid',
    ),
    'fieldable' => FALSE,
    'module' => 'webform_workflow',
  );
  return $entities;
}

/**
 * Access callback for a workflow state.
 *
 * @param string $op
 *   The operation to perform. Usually 'view', 'create', 'update', or 'delete'.
 * @param object|NULL $state
 *   The state object.
 * @param object|NULL $account
 *   The user account.
 *
 * @return bool
 *   TRUE if access is granted, FALSE otherwise.
 */
function webform_workflow_state_access($op, $state = NULL, $account = NULL) {
  $account = $account ? $account : $GLOBALS['user'];

  // When creating a new state, or if no state is passed, allow access based on
  // whether the user is allowed to update the current webform node.
  if ($op === 'create' || !$state) {
    $node = menu_get_object('webform_menu');

    // Account for Inline Entity Form's AJAX callbacks, which mean that the node
    // ID is not available in the URL.
    if (!$node && !empty($_POST) && $_POST['form_id'] === 'webform_workflow_config_form' && drupal_valid_token($_POST['form_token'], $_POST['form_id'])) {
      return TRUE;
    }
    return FALSE;
  }

  // When checking access for an existing state, attempt to find whether it is
  // associated with a single webform node.
  $node = webform_workflow_state_get_node($state);
  if ($node) {
    return node_access('update', $node, $account);
  }

  // If the node could not be found, check the state's owner.
  return $state->uid && $account->uid === $state->uid;
}

/**
 * Get the node for a state.
 *
 * This will only work if the state is associated with a single node.
 *
 * @param object $state
 *   The state entity object.
 * @param bool $reset
 *   Whether to reset the cache for this check (default to FALSE).
 *
 * @return object|FALSE
 *   The node object which references the state via the Entity Reference field
 *   webform_workflow_states, or FALSE if no single node can be found.
 */
function webform_workflow_state_get_node($state, $reset = FALSE) {
  if (!$state->wsid) {
    return FALSE;
  }
  if (!$reset && !isset($state->_node)) {
    $query = new EntityFieldQuery();
    $query
      ->entityCondition('entity_type', 'node')
      ->fieldCondition('webform_workflow_states', 'target_id', $state->wsid)
      ->range(0, 2);
    $result = $query
      ->execute();
    if ($result && isset($result['node']) && count($result['node']) == 1) {
      $nids = array_keys($result['node']);
      $nid = reset($nids);
      $state->_node = node_load($nid);
    }
    else {
      return FALSE;
    }
  }
  return $state->_node;
}

/**
 * Entity URI callback for a workflow state.
 *
 * @param object $state
 *   The workflow state entity object.
 *
 * @return array
 *   An array containing a path in the key 'path'.
 */
function webform_workflow_state_uri($state) {
  return array(
    'path' => 'webform-workflow-state/' . $state->wsid,
  );
}

/**
 * Load a single workflow state.
 *
 * @param int $wsid
 *   The workflow state ID.
 *
 * @return object|FALSE
 *   A workflow state entity object, or FALSE if the $wsid was not found.
 */
function webform_workflow_state_load($wsid) {
  return entity_load_single('webform_workflow_state', $wsid);
}

/**
 * Implements hook_permission().
 */
function webform_workflow_permission() {
  return array(
    'administer webform workflow states' => array(
      'title' => t('Administer webform workflow states'),
      'description' => t('Alter the fields and display settings for workflow states.'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function webform_workflow_menu() {
  $items = array();
  $items['admin/structure/webform-workflow'] = array(
    'title' => 'Webform Workflow',
    'description' => 'Administer webform workflow settings.',
    'page callback' => 'system_admin_menu_block_page',
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
    'access arguments' => array(
      'administer webform workflow states',
    ),
  );
  $items['admin/structure/webform-workflow/states'] = array(
    'title' => 'States',
    'description' => 'Administer field and display settings for webform workflow states.',
    'page callback' => 'system_admin_menu_block_page',
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
    'access arguments' => array(
      'administer webform workflow states',
    ),
  );
  $items['node/%webform_menu/webform/workflow'] = array(
    'title' => 'Workflow',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'webform_workflow_config_form',
      1,
    ),
    'access callback' => 'node_access',
    'access arguments' => array(
      'update',
      1,
    ),
    'file' => 'includes/webform_workflow.forms.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 4,
  );
  $items['node/%webform_menu/submission/%webform_menu_submission/workflow-log'] = array(
    'title' => 'Workflow Log',
    'page callback' => 'webform_workflow_log_page',
    'page arguments' => array(
      1,
      3,
    ),
    'file' => 'includes/webform_workflow.pages.inc',
    'access callback' => 'webform_workflow_log_access',
    'access arguments' => array(
      1,
      3,
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 3,
  );
  $items['webform-workflow-state/%webform_workflow_state'] = array(
    'title callback' => 'entity_label',
    'title arguments' => array(
      'webform_workflow_state',
      1,
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'webform_workflow_state_form',
      1,
    ),
    'file' => 'includes/webform_workflow_state.forms.inc',
    'access callback' => 'webform_workflow_state_access',
    'access arguments' => array(
      'update',
      1,
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function webform_workflow_menu_alter(&$items) {
  $items['node/%webform_menu/webform-results']['page callback'] = 'webform_workflow_submissions_list';
  $items['node/%webform_menu/webform-results']['page arguments'] = array(
    1,
  );
  $items['node/%webform_menu/webform-results']['file'] = 'includes/webform_workflow.pages.inc';
  $items['node/%webform_menu/webform-results']['module'] = 'webform_workflow';
}

/**
 * Implements hook_webform_submission_render_alter().
 *
 * Alter the display of a webform submission.
 */
function webform_workflow_webform_submission_render_alter(&$renderable) {
  if ($renderable['#email']) {
    return;
  }
  $node = $renderable['#node'];
  $submission = $renderable['#submission'];
  if (!webform_workflow_is_enabled($node) || $submission->is_draft) {
    return;
  }
  global $user;
  $is_submitter = $user->uid && $user->uid == $submission->uid || isset($_SESSION['webform_submission'][$submission->sid]);
  $form_access = webform_results_access($node) || $is_submitter && !empty($node->webform_workflow->data['os_view_state']);
  if ($form_access) {
    module_load_include('inc', 'webform_workflow', 'includes/webform_workflow.forms');
    $renderable['workflow'] = drupal_get_form('webform_workflow_submission_state_form', $submission);
    $renderable['workflow']['#weight'] = -1;
  }
}

/**
 * Access callback for viewing the Workflow Log tab on a node or submission.
 */
function webform_workflow_log_access($node, $submission = NULL, $account = NULL) {
  return webform_workflow_is_enabled($node) && webform_results_access($node, $account) && !$submission->is_draft && webform_submission_access($node, $submission, 'view', $account);
}

/**
 * Get the state of a submission.
 *
 * @param object $submission
 *   The webform submission object.
 * @param bool $reset
 *   Whether to reset the static cache for this request.
 *
 * @return object|FALSE
 *   The workflow state entity, or FALSE if the submission does not have a
 *   state.
 */
function webform_workflow_state_load_by_submission($submission, $reset = FALSE) {
  if ($reset || !isset($submission->_webform_workflow_state)) {
    $wsid = db_query('SELECT wsid FROM {webform_workflow_submissions} WHERE sid = :sid', array(
      ':sid' => $submission->sid,
    ))
      ->fetchField();
    $state = $wsid ? webform_workflow_state_load($wsid) : FALSE;
    $submission->_webform_workflow_state = $state ? $state : webform_workflow_state_none();
  }
  return $submission->_webform_workflow_state;
}

/**
 * Get a default state called 'None'.
 *
 * @return object
 *   The state entity object (not saved).
 */
function webform_workflow_state_none() {
  return entity_create('webform_workflow_state', array(
    'label' => t('None'),
  ));
}

/**
 * Change the state of a submission.
 *
 * @param object $submission
 *   The webform submission.
 * @param object $new_state
 *   The new state for the submission.
 * @param string $message
 *   A log message for the transition (optional).
 * @param object $account
 *   The user account who made the transition (optional).
 * @param bool $notify
 *   Whether to notify the configured user(s) about this transition (defaults
 *   to TRUE).
 * @param bool $set_message
 *   Whether to display a message to the current user about this transition.
 */
function webform_workflow_transition($submission, $new_state, $message = NULL, $account = NULL, $notify = TRUE, $set_message = FALSE) {
  $previous_state = webform_workflow_state_load_by_submission($submission);
  if ($submission->is_draft) {
    drupal_set_message(t('Submission #@sid is a draft, so it cannot have a workflow state.', array(
      '@sid' => $submission->sid,
    )), 'warning');
    return FALSE;
  }
  if ($previous_state->wsid == $new_state->wsid) {
    return FALSE;
  }

  // Set the new state of the submission.
  $submission->_webform_workflow_state = $new_state;

  // Write the new state to the database, along with information about the
  // transition. Use a transaction to account for possible database errors.
  $transaction = db_transaction();
  try {
    db_merge('webform_workflow_submissions')
      ->key(array(
      'sid' => $submission->sid,
    ))
      ->fields(array(
      'wsid' => $new_state->wsid,
    ))
      ->execute();
    db_insert('webform_workflow_transition')
      ->fields(array(
      'nid' => $submission->nid,
      'sid' => $submission->sid,
      'uid' => $account ? $account->uid : NULL,
      'old_state_wsid' => $previous_state->wsid,
      'new_state_wsid' => $new_state->wsid,
      'message' => trim($message),
      'timestamp' => REQUEST_TIME,
    ))
      ->execute();
  } catch (Exception $e) {
    $transaction
      ->rollback();
    throw $e;
  }

  // Force the transaction to be committed now.
  unset($transaction);

  // Notify users about the transition by e-mail.
  $users_notified = array();
  if ($notify) {
    $users_notified = webform_workflow_notify_users($submission, array(
      'account' => $account,
      'message' => trim($message),
      'previous_state' => $previous_state,
      'new_state' => $new_state,
      'timestamp' => REQUEST_TIME,
    ));
  }

  // Display a message about the transition.
  if ($set_message) {
    $notice = t("Transitioned submission #@sid from %previous_state to %new_state.", array(
      '@sid' => $submission->sid,
      '%previous_state' => $previous_state->label,
      '%new_state' => $new_state->label,
    ));

    // Add information to the message about which users have been notified.
    if ($users_notified) {
      if (count($users_notified) == 1) {
        $account_notified = reset($users_notified);
        $notice .= ' ' . t('The user !user was notified by e-mail.', array(
          '!user' => drupal_placeholder(format_username($account_notified)) . ($account_notified->uid == $submission->uid ? ' ' . t('(the original submitter)') : ''),
        ));
      }
      else {
        $items = array();
        foreach ($users_notified as $account) {
          $item = check_plain(format_username($account));
          if ($account->uid == $submission->uid) {
            $item .= ' ' . t('(the original submitter)');
          }
          $items[] = $item;
        }
        natcasesort($items);
        $notice .= ' ' . t('The following users were notified by e-mail: !list', array(
          '!list' => theme('item_list', array(
            'items' => $items,
          )),
        ));
      }
    }
    drupal_set_message($notice, 'status', FALSE);
  }

  // Invoke the Rules event 'After a webform submission changes state'.
  if (module_exists('rules')) {
    $node = node_load($submission->nid);
    $pseudo_submission = entity_load_single('webform_workflow_submission', $submission->sid);
    rules_invoke_event('webform_workflow_transition', $node, $pseudo_submission, $previous_state, $new_state);
  }
  return TRUE;
}

/**
 * Implements hook_webform_submission_insert().
 */
function webform_workflow_webform_submission_insert($node, $submission) {

  // Set new (non-draft) submissions to the default state.
  if (webform_workflow_is_enabled($node) && !$submission->is_draft && !empty($node->webform_workflow->data['new_gets_first_state']) && !webform_workflow_state_load_by_submission($submission)->wsid) {
    $state = webform_workflow_state_get_default($node);
    if (!$state) {
      return;
    }
    $notify = !empty($node->webform_workflow->data['new_triggers_notification']);
    webform_workflow_transition($submission, $state, t('Set automatically on initial submission'), NULL, $notify, FALSE);
  }
}

/**
 * Implements hook_webform_submission_update().
 */
function webform_workflow_webform_submission_update($node, $submission) {

  // Set edited submissions to the default state.
  if (webform_workflow_is_enabled($node) && !$submission->is_draft && (!empty($node->webform_workflow->data['updated_gets_first_state']) && !$submission->is_insert || !empty($node->webform_workflow->data['new_gets_first_state']) && $submission->is_insert)) {
    $state = webform_workflow_state_get_default($node);
    if (!$state) {
      return;
    }
    webform_workflow_transition($submission, $state, t('Set automatically on update'));
  }
}

/**
 * Implements hook_webform_submission_presave().
 */
function webform_workflow_webform_submission_presave($node, &$submission) {

  // Since we can't determine if we're submitting the final form step (when
  // draft mode is enabled) or editing the existing submission in Webform
  // submission insert/update hooks (because those hooks are called after the
  // submission is saved), we need to do that in this presave hook and use it
  // later in update hook.
  $submission->is_insert = !$submission->is_draft && !$submission->completed;
}

/**
 * Implements hook_webform_submission_delete().
 *
 * Act after a submission has been deleted.
 */
function webform_workflow_webform_submission_delete($node, $submission) {

  // Delete all data related to the submission, even if workflow is no longer
  // enabled.
  db_delete('webform_workflow_submissions')
    ->condition('sid', $submission->sid)
    ->execute();
  db_delete('webform_workflow_transition')
    ->condition('sid', $submission->sid)
    ->execute();
}

/**
 * Get the default (first) workflow state for a node.
 *
 * @return object|FALSE
 *   The first configured workflow state, or FALSE if no states can be found.
 */
function webform_workflow_state_get_default($node) {
  $states = webform_workflow_get_available_states($node);
  return $states ? reset($states) : FALSE;
}

/**
 * Implements hook_entity_delete().
 */
function webform_workflow_entity_delete($entity, $type) {

  // When a state is deleted, update its entries in our other tables.
  if ($type === 'webform_workflow_state') {
    db_delete('webform_workflow_submissions')
      ->condition('wsid', $entity->wsid)
      ->execute();
    db_update('webform_workflow_transition')
      ->fields(array(
      'new_state_wsid' => NULL,
    ))
      ->condition('new_state_wsid', $entity->wsid)
      ->execute();
    db_update('webform_workflow_transition')
      ->fields(array(
      'old_state_wsid' => NULL,
    ))
      ->condition('old_state_wsid', $entity->wsid)
      ->execute();
  }
  elseif ($type === 'webform_workflow_submission') {
    $node = node_load($entity->nid);
    module_load_include('inc', 'webform', 'includes/webform.submissions');
    $submission = webform_get_submission($entity->nid, $entity->sid);
    if ($submission) {
      webform_submission_delete($node, $submission);
    }
  }
}

/**
 * Check whether an account has permission for a workflow state action.
 *
 * @param string $op
 *   An operation: 'view', 'edit', 'to', or 'from'.
 * @param object $node
 *   The Webform node.
 * @param object $submission
 *   The Webform submission to check access for, if applicable.
 * @param object $account
 *   The user account object (optional: defaults to the current user).
 *
 * @return bool
 *   Whether access should be granted.
 */
function webform_workflow_state_check_access($op, $state, $node, $submission = NULL, $account = NULL) {
  $account = $account ? $account : $GLOBALS['user'];
  $rids = array_keys($account->roles);

  // Test whether the $account is the original author of the submission.
  if ($submission) {
    $is_submitter = $account->uid && $account->uid == $submission->uid || $account->uid == $GLOBALS['user']->uid && isset($_SESSION['webform_submission'][$submission->sid]);
    if ($is_submitter) {
      $rids[] = WEBFORM_WORKFLOW_ORIGINAL_SUBMITTER;
    }
  }
  if (isset($state->permissions[$op]) && array_intersect($rids, $state->permissions[$op])) {
    return TRUE;
  }

  // Always grant access for changing 'from' the null state ('None').
  if ($op == 'from' && !$state->wsid && $state->label == t('None')) {
    return TRUE;
  }

  // For changing states ('to', or 'from'), always allow access if the user is
  // allowed to edit the webform itself.
  if (($op == 'to' || $op == 'from') && node_access('update', $node, $account)) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_webform_submission_access().
 *
 * N.B. this hook can only ever grant additional access. It cannot deny grants
 * already supplied by Webform (or other modules).
 */
function webform_workflow_webform_submission_access($node, $submission, $op, $account) {
  if (!$submission || !$submission->sid || !webform_workflow_is_enabled($node)) {
    return FALSE;
  }
  $state = webform_workflow_state_load_by_submission($submission);
  return webform_workflow_state_check_access($op, $state, $node, $submission, $account);
}

/**
 * Implements hook_node_load().
 */
function webform_workflow_node_load($nodes, $types) {
  if (count(array_intersect($types, webform_node_types())) == 0) {
    return;
  }

  // Load workflow data.
  $result = db_select('webform_workflow', 'ww')
    ->fields('ww', array(
    'nid',
    'workflow',
    'data',
  ))
    ->condition('nid', array_keys($nodes), 'IN')
    ->execute()
    ->fetchAllAssoc('nid', PDO::FETCH_OBJ);
  foreach ($result as $nid => $workflow) {
    if (empty($workflow->data)) {
      $workflow->data = array();
    }
    else {
      $workflow->data = unserialize($workflow->data);
    }
    if (empty($workflow->data['emails'])) {
      $workflow->data['emails'] = webform_workflow_get_default_email();
    }
    $nodes[$nid]->webform_workflow = $workflow;
  }
}

/**
 * Get default email content for a workflow state change notification.
 *
 * @return array
 *   An array containing the keys 'subject' and 'body'.
 */
function webform_workflow_get_default_email() {
  return array(
    'subject' => 'The submission #[submission:sid] for the form [node:title] has changed state to [submission:state]',
    'body' => "Form: [node:title] - [node:url]\n" . "Submission: #[submission:sid] - [submission:url]\n" . "State: [submission:state]\n" . "Previous state: [webform-workflow-transition:previous-state]\n" . "Changed by: [webform-workflow-transition:user]\n" . "Change time: [webform-workflow-transition:timestamp]\n" . "Log message: [webform-workflow-transition:message]",
  );
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function webform_workflow_form_node_form_alter(&$form, &$form_state) {
  if (isset($form['webform_workflow_states'])) {
    $form['webform_workflow_states']['#access'] = FALSE;
  }
}

/**
 * Check if workflow has been enabled for the current webform.
 *
 * @param object $node
 *   The webform node.
 *
 * @return bool
 *   Whether workflow is enabled.
 */
function webform_workflow_is_enabled($node) {
  return isset($node->webform_workflow) && $node->webform_workflow->workflow;
}

/**
 * Get all states attached to a webform.
 *
 * @param object $node
 *   The webform node.
 *
 * @return array
 *   An array of states.
 */
function webform_workflow_get_available_states($node) {
  if (empty($node->webform_workflow_states)) {
    return array();
  }
  return entity_metadata_wrapper('node', $node)->webform_workflow_states
    ->value();
}

/**
 * Delete a state from the database.
 *
 * @param int $wsid
 *   The workflow state id to delete.
 */
function webform_workflow_state_delete($wsid) {
  return entity_get_controller('webform_workflow_state')
    ->delete(array(
    $wsid,
  ));
}

/**
 * Get a list of user accounts to notify when the submission changes state.
 *
 * @param object $submission
 *   The webform submission.
 *
 * @return array
 *   An array of user accounts, keyed by UID. The anonymous user will never be
 *   included in this list.
 */
function webform_workflow_get_notify_users($submission) {
  $cache = drupal_static(__FUNCTION__, array());
  $state = webform_workflow_state_load_by_submission($submission);
  if (!$state) {
    return array();
  }
  $cache_key = $submission->sid . ':' . $state->wsid;
  if (isset($cache[$cache_key])) {
    return $cache[$cache_key];
  }
  $state_wrapper = entity_metadata_wrapper('webform_workflow_state', $state);
  $uids = array();

  // Add any users configured to be notified in the entity reference field
  // 'ww_state_notify_users'.
  if (!empty($state_wrapper->ww_state_notify_users)) {
    foreach ($state_wrapper->ww_state_notify_users as $account_wrapper) {
      $uid = $account_wrapper->uid
        ->value();
      if ($uid) {
        $uids[$uid] = $uid;
      }
    }
  }

  // Add the original submitter, depending on the Boolean field
  // ww_state_notify_os.
  if ($submission->uid && isset($state_wrapper->ww_state_notify_os) && $state_wrapper->ww_state_notify_os
    ->value()) {
    $uids[$submission->uid] = $submission->uid;
  }

  // Allow other modules to alter the list of users.
  drupal_alter('webform_workflow_notify_users', $uids, $submission, $state);
  $cache[$cache_key] = $uids;
  return user_load_multiple($uids);
}

/**
 * Notify users of a state transition by e-mail.
 *
 * @param object $submission
 *   The webform submission entity that has undergone the state transition.
 * @param array $transition
 *   Optional: information about the transition, including the keys 'account',
 *   'new_state', 'previous_state', 'timestamp', and 'message'.
 *
 * @return array|FALSE
 *   An array of the user accounts who have been notified, or FALSE if no-one
 *   has been notified.
 */
function webform_workflow_notify_users($submission, array $transition = array()) {
  $notify_users = webform_workflow_get_notify_users($submission);
  if (!$notify_users) {
    return FALSE;
  }
  $notify_emails = array();
  foreach ($notify_users as $account) {
    $notify_emails[] = $account->mail;
  }
  $node = node_load($submission->nid);
  $email_templates = $node->webform_workflow->data['emails'];

  // Note that we need to specify the 'webform-email' data so that Webform's
  // token implementation knows that the tokens are being used in the
  // context of an email.
  $token_data = array(
    'node' => $node,
    'webform-submission' => $submission,
    'webform-email' => array(),
  );
  if ($transition) {
    $token_data += array(
      'webform-workflow-transition' => $transition,
    );
  }
  $token_options = array(
    'clear' => TRUE,
    'sanitize' => FALSE,
  );
  $params = array(
    'subject' => token_replace($email_templates['subject'], $token_data, $token_options),
    'body' => token_replace($email_templates['body'], $token_data, $token_options),
  );
  drupal_mail('webform_workflow', 'state_transition', implode(',', $notify_emails), LANGUAGE_NONE, $params);
  return $notify_users;
}

/**
 * Implements hook_mail().
 */
function webform_workflow_mail($key, &$message, $params) {
  if ($key == 'state_transition') {
    $message['subject'] = $params['subject'];
    $message['body'][] = $params['body'];
  }
}

/**
 * Implements hook_theme().
 */
function webform_workflow_theme() {
  return array(
    'webform_workflow_state' => array(
      'variables' => array(
        'state' => NULL,
      ),
    ),
    'webform_workflow_state_color' => array(
      'variables' => array(
        'color' => NULL,
        'html' => FALSE,
        'content' => NULL,
        'classes_array' => array(),
      ),
    ),
  );
}

/**
 * Theme a webform workflow state.
 */
function theme_webform_workflow_state($variables) {
  return theme('webform_workflow_state_color', array(
    'content' => $variables['state']->label,
    'color' => $variables['state']->color,
    'classes_array' => array(
      'webform-workflow-state',
    ),
  ));
}

/**
 * Simple theme function to add color to some text.
 */
function theme_webform_workflow_state_color($variables) {
  $content = $variables['content'];
  if (empty($variables['html'])) {
    $content = check_plain($content);
  }
  $attributes = array(
    'class' => array(
      'webform-workflow-state-label',
    ),
  );
  $color_suffix = $variables['color'] ? $variables['color'] : 'none';
  $attributes['class'][] = drupal_html_class('webform-workflow-state-color-' . $color_suffix);
  $attributes['class'] = array_merge($variables['classes_array'], $attributes['class']);
  return '<span' . drupal_attributes($attributes) . '>' . $content . '</span>';
}

/**
 * Implements hook_views_api().
 */
function webform_workflow_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'webform_workflow') . '/includes',
  );
}

/**
 * Implements hook_action_info().
 */
function webform_workflow_action_info() {
  return array(
    'webform_workflow_change_submission_state' => array(
      'type' => 'webform_workflow_submission',
      'label' => t('Change workflow state'),
      'configurable' => TRUE,
    ),
  );
}

/**
 * Action configuration form for changing the workflow state of a submission.
 *
 * @see webform_workflow_action_info()
 */
function webform_workflow_change_submission_state_form($context) {
  $node = menu_get_object('webform_menu');
  if (!$node || !webform_workflow_is_enabled($node)) {
    drupal_set_message(t('Webform or workflow settings not found'), 'error', FALSE);
    return array();
  }
  $states = webform_workflow_get_available_states($node);
  if (!$states) {
    drupal_set_message(t('No states found'), 'warning', FALSE);
    return array();
  }
  $state_options = array();
  $state_colors = array();
  foreach ($states as $state) {
    $state_options[$state->wsid] = $state->label;
    if ($state->color) {
      $state_colors['state-' . $state->wsid] = $state->color;
    }
  }
  if (!$state_options) {
    drupal_set_message(t('No available states found'), 'warning', FALSE);
    return array();
  }
  $form['new_state'] = array(
    '#type' => 'select',
    '#title' => t('New state'),
    '#options' => $state_options,
    '#required' => TRUE,
  );
  if ($state_colors) {
    $form['new_state']['#attributes']['class'] = array(
      'webform-workflow-state-select',
    );
    $form['new_state']['#attached']['js'] = array(
      drupal_get_path('module', 'webform_workflow') . '/includes/webform_workflow.js',
      array(
        'data' => array(
          'webformWorkflow' => array(
            'stateColors' => $state_colors,
          ),
        ),
        'type' => 'setting',
      ),
    );
  }
  $form['message'] = array(
    '#type' => 'textarea',
    '#title' => t('Log message'),
    '#description' => t('Describe why you are making this change.'),
    '#required' => !empty($node->webform_workflow->data['require_log']),
  );
  return $form;
}

/**
 * Submit callback for the 'Change workflow state' action configuration form.
 *
 * @see webform_workflow_action_info()
 */
function webform_workflow_change_submission_state_submit($form, &$form_state) {
  return array(
    'new_state' => webform_workflow_state_load($form_state['values']['new_state']),
    'message' => $form_state['values']['message'] ? $form_state['values']['message'] : NULL,
  );
}

/**
 * Action callback for changing the workflow state of a submission.
 *
 * @param object $submission
 *   The submission, loaded via the Entity API as a
 *   'webform_workflow_submission' entity.
 * @param array $context
 *   Contextual information provided by the action configuration form. This must
 *   include the keys 'new_state' and 'message'.
 *
 * @see webform_workflow_action_info()
 */
function webform_workflow_change_submission_state($submission, array $context) {

  // Reload the submission as a proper Webform submission object.
  module_load_include('inc', 'webform', 'includes/webform.submissions');
  $submission = webform_get_submission($submission->nid, $submission->sid);
  $node = node_load($submission->nid);
  $current_state = webform_workflow_state_load_by_submission($submission);
  if ($current_state && !webform_workflow_state_check_access('from', $current_state, $node, $submission)) {
    drupal_set_message(t('You do not have permission to change the state of submission #@sid', array(
      '@sid' => $submission->sid,
    )));
    return;
  }
  $new_state = $context['new_state'];
  if (!webform_workflow_state_check_access('to', $new_state, $node, $submission)) {
    drupal_set_message(t('You do not have permission to change submission #@sid to state %state', array(
      '@sid' => $submission->sid,
      '%state' => $new_state->label,
    )));
    return;
  }
  webform_workflow_transition($submission, $new_state, $context['message'], $GLOBALS['user'], TRUE, TRUE);
}

/**
 * Implements hook_webform_results_download_submission_information_info().
 */
function webform_workflow_webform_results_download_submission_information_info() {
  return array(
    'state' => t('Workflow State'),
  );
}

/**
 * Implements hook_webform_results_download_submission_information_data().
 */
function webform_workflow_webform_results_download_submission_information_data($token, $submission) {
  if ($token == 'state') {
    $node = node_load($submission->nid);
    if (webform_workflow_is_enabled($node)) {
      $state = webform_workflow_state_load_by_submission($submission);
      return $state->label;
    }
  }
}

/**
 * Get a list of possible state colors.
 *
 * @return array
 *   An associative array of state colors. The array values are human-readable
 *   names and the array keys are machine names.
 */
function webform_workflow_state_color_options_list() {
  return array(
    '' => t('None'),
    'green' => t('Green'),
    'amber' => t('Amber'),
    'red' => t('Red'),
    'blue' => t('Blue'),
    'purple' => t('Purple'),
    'yellow' => t('Yellow'),
  );
}

/**
 * Implements hook_field_extra_fields().
 */
function webform_workflow_field_extra_fields() {
  $extra = array();
  $extra['webform_workflow_state']['webform_workflow_state']['form'] = array(
    'label' => array(
      'label' => t('Name'),
      'weight' => 0,
    ),
    'color' => array(
      'label' => t('Color'),
      'weight' => 1,
    ),
    'permissions' => array(
      'label' => t('Permissions'),
      'weight' => 2,
    ),
  );
  return $extra;
}

Functions

Namesort descending Description
theme_webform_workflow_state Theme a webform workflow state.
theme_webform_workflow_state_color Simple theme function to add color to some text.
webform_workflow_action_info Implements hook_action_info().
webform_workflow_change_submission_state Action callback for changing the workflow state of a submission.
webform_workflow_change_submission_state_form Action configuration form for changing the workflow state of a submission.
webform_workflow_change_submission_state_submit Submit callback for the 'Change workflow state' action configuration form.
webform_workflow_entity_delete Implements hook_entity_delete().
webform_workflow_entity_info Implements hook_entity_info().
webform_workflow_field_extra_fields Implements hook_field_extra_fields().
webform_workflow_form_node_form_alter Implements hook_form_FORM_ID_alter().
webform_workflow_get_available_states Get all states attached to a webform.
webform_workflow_get_default_email Get default email content for a workflow state change notification.
webform_workflow_get_notify_users Get a list of user accounts to notify when the submission changes state.
webform_workflow_is_enabled Check if workflow has been enabled for the current webform.
webform_workflow_log_access Access callback for viewing the Workflow Log tab on a node or submission.
webform_workflow_mail Implements hook_mail().
webform_workflow_menu Implements hook_menu().
webform_workflow_menu_alter Implements hook_menu_alter().
webform_workflow_node_load Implements hook_node_load().
webform_workflow_notify_users Notify users of a state transition by e-mail.
webform_workflow_permission Implements hook_permission().
webform_workflow_state_access Access callback for a workflow state.
webform_workflow_state_check_access Check whether an account has permission for a workflow state action.
webform_workflow_state_color_options_list Get a list of possible state colors.
webform_workflow_state_delete Delete a state from the database.
webform_workflow_state_get_default Get the default (first) workflow state for a node.
webform_workflow_state_get_node Get the node for a state.
webform_workflow_state_load Load a single workflow state.
webform_workflow_state_load_by_submission Get the state of a submission.
webform_workflow_state_none Get a default state called 'None'.
webform_workflow_state_uri Entity URI callback for a workflow state.
webform_workflow_theme Implements hook_theme().
webform_workflow_transition Change the state of a submission.
webform_workflow_views_api Implements hook_views_api().
webform_workflow_webform_results_download_submission_information_data Implements hook_webform_results_download_submission_information_data().
webform_workflow_webform_results_download_submission_information_info Implements hook_webform_results_download_submission_information_info().
webform_workflow_webform_submission_access Implements hook_webform_submission_access().
webform_workflow_webform_submission_delete Implements hook_webform_submission_delete().
webform_workflow_webform_submission_insert Implements hook_webform_submission_insert().
webform_workflow_webform_submission_presave Implements hook_webform_submission_presave().
webform_workflow_webform_submission_render_alter Implements hook_webform_submission_render_alter().
webform_workflow_webform_submission_update Implements hook_webform_submission_update().

Constants

Namesort descending Description
WEBFORM_WORKFLOW_ORIGINAL_SUBMITTER @file A simple workflow module for webforms.