Document Workflow Module for Drupal

Early revision of a Drupal module implementing complex node workflows.

<?php

//Specify blocks generated by this module
function grad_docs_block_info() {
  $blocks['student_documents_block'] = array(
    'info' => t('Student Documents Block'),
    'cache' => DRUPAL_NO_CACHE,
  );
  
  $blocks['faculty_assigned_documents_block'] = array(
    'info' => t('Faculty Assigned Documents Block'),
    'cache' => DRUPAL_NO_CACHE,
  );
  
  $blocks['assigned_tasks_block'] = array(
    'info' => t('Assigned Tasks Block'),
    'cache' => DRUPAL_NO_CACHE,
  );
  
  return $blocks;
}

//Functions for generating the "My Documents" block
function grad_docs_block_view($delta = '') {
  global $user;
  $block = array();

  switch ($delta) {
    case 'student_documents_block':
      $block['subject'] = t('My Documents');
      $block['content'] = student_documents_block_content($user->uid, FALSE);
      break;
    
    case 'faculty_assigned_documents_block':
      $block['subject'] = t('Assigned Documents');
      $block['content'] = faculty_assigned_documents_block_content($user->uid);
      break;
    
    case 'assigned_tasks_block':
      $block['subject'] = t('Assigned Tasks');
      $block['content'] = assigned_tasks_block_content($user->uid);
  }

  return $block;
}

function grad_docs_menu() {
  $items['grad_docs/relationship_table'] = array(
    'title' => 'Administer Relationship Table',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('grad_docs_admin_relationship_table_form'),
    'type' => MENU_NORMAL_ITEM,
    'access arguments' => array('Administer Workflow Relationship Table')
  );

  $items['grad_docs/change_enrolment_form'] = array(
    'access arguments' => array('access content'),
    'page callback' => 'grad_docs_change_enrolment_form',
    //'page callback' => 'drupal_get_form',
    //'page arguments' => array('grad_docs_change_enrolment_form'),
    'title' => 'Change Enrolment Form',
    'type' => MENU_CALLBACK,
  );

  return $items;
}

function grad_docs_change_enrolment_form() {
  //Get uid of current user
  global $user;

  //Query for most recent 'course_advising_form's of current user
  $advising_form_nid = db_select('node', 'node')
          ->fields('node', array('nid'))
          ->condition('status', 1)
          ->condition('type', 'course_advising_form')
          ->condition('uid', $user->uid)
          ->orderBy('created', 'DESC')
          ->execute()->fetchField();

  $node = node_load($advising_form_nid);
  $node->status = 0;

  return docs_clone_form_prepopulate($node, 'Change Enrolment Form', 'enrolment_change_form');
}

function grad_docs_admin_paths() {
  if (variable_get('node_admin_theme')) {
    $paths = array(
      'grad_docs/change_enrolment_form' => TRUE,
    );
    return $paths;
  }
}

function docs_clone_form_prepopulate($original_node, $new_title = NULL, $new_type = NULL) {
  if (isset($original_node->nid)) {
    global $user;

    $node = clone $original_node;

    if (isset($new_title)) {
      $node->title = $new_title;
    }
    if (isset($new_type)) {
      $node->type = $new_type;
    }

    $node->nid = NULL;
    $node->vid = NULL;
    $node->tnid = NULL;

    // Anyonmymous users don't have a name.
    $node->name = isset($user->name) ? $user->name : NULL;
    $node->uid = $user->uid;
    $node->created = NULL;

    if (isset($node->book['mlid'])) {
      $node->book['mlid'] = NULL;
      $node->book['has_children'] = 0;
    }
    $node->path = NULL;
    $node->files = array();
    $node->title = t('Clone of !title', array('!title' => $node->title));

    drupal_set_title($node->title);

    if (variable_get('clone_reset_' . $node->type, FALSE)) {
      $node_options = variable_get('node_options_' . $node->type, array('status', 'promote'));

      // Fill in the default values.
      foreach (array('status', 'moderate', 'promote', 'sticky', 'revision') as $key) {
        // Cast to int since that's how they come out of the DB.
        $node->$key = (int) in_array($key, $node_options);
      }
    }

    // Let other modules do special fixing up.
    //$context = array('method' => 'prepopulate', 'original_node' => $original_node);
    // Make sure the file defining the node form is loaded.
    $form_state = array();
    $form_state['build_info']['args'] = array($node);
    form_load_include($form_state, 'inc', 'node', 'node.pages');

    return drupal_build_form($node->type . '_node_form', $form_state);
  }
}

function grad_docs_admin_relationship_table_form($form, &$form_state) {
  $module_path = drupal_get_path('module', 'grad_docs');
  drupal_add_css($module_path . '/css/grad_docs.css');

  $form = array();
  $form['#prefix'] = '<div id="Relationship_Table_Container">';
  $form['#suffix'] = '</div>';
  $form['#tree'] = TRUE;

  //Get all the user IDs belonging to the Staff/Faculty role
  $faculty_role = user_role_load_by_name('Faculty');
  $staff_role = user_role_load_by_name('Staff');
  $faculty_staff_condition = db_or();
  $faculty_staff_condition->condition('rid', $faculty_role->rid);
  $faculty_staff_condition->condition('rid', $staff_role->rid);

  $result = db_select('users_roles', 'ur')
      ->fields('ur', array('uid'))
      ->condition($faculty_staff_condition)
      ->execute();

  foreach ($result as $record) {
    $faculty_staff_user_ids[] = $record->uid;
  }

  //Load all the users in the Staff/Faculty role
  $users_array = user_load_multiple($faculty_staff_user_ids);
  $users_staff_faculty = array();

  foreach ($users_array as $user) {
    $user_id = $user->uid;

    $first_name = $user->field_profile_firstname;
    $last_name = $user->field_profile_lastname;

    $users_staff_faculty[$user_id] = t($last_name['und'][0]['value'] . ', ' . $first_name['und'][0]['value'] . ' (' . $user->name . ')');
  }

  //Get all the user IDs belonging to the Student role
  $student_role = user_role_load_by_name('Student');
  $result = db_select('users_roles', 'ur')
      ->fields('ur', array('uid'))
      ->condition('rid', $student_role->rid)
      ->execute();

  foreach ($result as $record) {
    $student_user_ids[] = $record->uid;
  }

  $users_array = user_load_multiple($student_user_ids);
  $users_students = array();
  
  foreach ($users_array as $user) {
    $user_id = $user->uid;

    $first_name = $user->field_profile_firstname;
    $last_name = $user->field_profile_lastname;

    $users_students[$user_id] = t($last_name['und'][0]['value'] . ', ' . $first_name['und'][0]['value'] . ' (' . $user->name . ')');
  }

  //Lets add the "All Students" option, with uid 0
  $users_students[0] = t('All Students');

  //These are the relationships that may be specified
  $relationships = array('Advisor' => 'Advisor', '2nd Reader' => '2nd Reader', 'OSAS Rep' => 'OSAS Rep', 'GPD Rep' => 'GPD Rep', 'MES Rep' => 'MES Rep');

  //First query from the user relationship table
  $database_relationship_query = db_select('grad_docs_user_relationships', 'rel_table')
      ->fields('rel_table', array('rel_type', 'faculty_uid', 'student_uid'));

  //Join in the User entries for the Student 
  $database_relationship_query->innerJoin('users', 'u_s', 'u_s.uid = rel_table.student_uid');

  //Join in the first and last names of the faculty member
  $database_relationship_query->innerJoin('users', 'u_f', 'u_f.uid = rel_table.faculty_uid');

  //1st Order the entries by the student last name, first name
  //2nd Order the entries by the faculty last name, first name
  //3rd Order the entries by the relationship type
  $database_relationship_rows = $database_relationship_query
      ->orderBy('u_s.uid')
      ->orderBy('rel_table.rel_type', 'ASC')
      ->execute();

  $existing_relationships = array();
  
  foreach ($database_relationship_rows as $database_rel_row) {
    $existing_relationships[] = array('rel_type' => $database_rel_row->rel_type,
      'faculty_uid' => $database_rel_row->faculty_uid,
      'student_uid' => $database_rel_row->student_uid);
  }

  //If the form is being build for the first time, add a row
  if (!isset($form_state['row_count'])) {
    $form_state['row_count'] = count($existing_relationships);
  }

  $form['relationship_container'] = array(
    '#prefix' => '<div id="Relationship_Container">',
    '#suffix' => '</div>'
  );

  //If a row count is specified, add rows of fields
  if (isset($form_state['row_count'])) {
    $row_count = 0;
    
    for ($r_i = 0; $r_i < $form_state['row_count']; $r_i++) {
      //Lets add another row
      $row_odd_even = 'relationship_row_odd';
      if ($row_count % 2 == 0)
        $row_odd_even = 'relationship_row_even';

      if (!isset($form_state['remove_row']) || !in_array($r_i, $form_state['remove_row'])) {
        $row_count++;

        $form['relationship_container']['relationship_row_' . $r_i] = array(
          '#prefix' => '<div class="relationship_row ' . $row_odd_even . ' relationship_row_' . $r_i . '" >',
          '#suffix' => '</div>'
        );

        $form['relationship_container']['relationship_row_' . $r_i]['count'] = array(
          '#markup' => '<span class="relationship_row_count">' . ($row_count) . '</span>'
        );

        $form['relationship_container']['relationship_row_' . $r_i]['role'] = array(
          '#type' => 'select',
          '#attributes' => array('class' => array('relationship_role')),
          '#options' => $relationships,
        );
        
        $form['relationship_container']['relationship_row_' . $r_i]['role']['#default_value']
            = (isset($existing_relationships[$r_i]) ? $existing_relationships[$r_i]['rel_type'] : 'Advisor');

        //This part of the form is the Student selection box
        $form['relationship_container']['relationship_row_' . $r_i]['student_member'] = array(
          '#type' => 'select',
          '#attributes' => array('class' => array('relationship_student')),
          '#options' => $users_students
        );
        
        $form['relationship_container']['relationship_row_' . $r_i]['student_member']['#default_value']
            = (isset($existing_relationships[$r_i]) ? $existing_relationships[$r_i]['student_uid'] : array());

        //This part of the form is the Faculty selection box
        $form['relationship_container']['relationship_row_' . $r_i]['staff_faculty_member'] = array(
          '#type' => 'select',
          '#attributes' => array('class' => array('relationship_faculty')),
          '#options' => $users_staff_faculty,
        );

        $form['relationship_container']['relationship_row_' . $r_i]['staff_faculty_member']['#default_value']
            = (isset($existing_relationships[$r_i]) ? $existing_relationships[$r_i]['faculty_uid'] : array());

        //This is the Remove button for each row
        $form['relationship_container']['relationship_row_' . $r_i]['remove_button'] = array(
          '#type' => 'submit',
          '#limit_validation_errors' => array(array('choice')),
          '#submit' => array('remove_relationship_button_submit'),
          '#default_value' => t('X'),
          '#name' => 'remove_button_' . $r_i,
          '#ajax' => array(
            'callback' => 'remove_relationship_button_callback',
            'wrapper' => 'Relationship_Container',
            'method' => 'replace',
            'effect' => 'fade')
        );
      }
    }
  }

  $form['add_another_relationship'] = array(
    '#type' => 'submit',
    /* '#executes_submit_callback' => FALSE, */
    '#limit_validation_errors' => array(array('choice')),
    '#submit' => array('add_another_relationship_button_submit'),
    '#default_value' => t('Add Another Row'),
    '#ajax' => array(
      'callback' => 'add_another_relationship_button_callback',
      'wrapper' => 'Relationship_Container',
      'method' => 'replace',
      'effect' => 'fade')
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#default_value' => t('Save Relationship table')
  );

  return $form;
}

//Functions for adding another row to the relationships
function add_another_relationship_button_submit($form, &$form_state) {
  $form_state['row_count'] = $form_state['row_count'] + 1;
  $form_state['rebuild'] = TRUE;
}

function add_another_relationship_button_callback($form, &$form_state) {
  return $form['relationship_container'];
}

//Functions for removing a row from the table
function remove_relationship_button_submit($form, &$form_state) {
  $row_name = $form_state['triggering_element']['#array_parents'][1];
  $form_state['remove_row'][] = str_replace('relationship_row_', '', $row_name);
  $form_state['rebuild'] = TRUE;
}

function remove_relationship_button_callback($form, &$form_state) {
  return $form['relationship_container'];
}

//Function for saving the relationship table to the database
function grad_docs_admin_relationship_table_form_submit($form, &$form_state) {
  $relationship_rows = $form_state['values']['relationship_container'];

  $values = array();
  
  foreach ($relationship_rows as $relationship_row) {
    $values[] = array('rel_type' => $relationship_row['role'],
      'student_uid' => $relationship_row['student_member'],
      'faculty_uid' => $relationship_row['staff_faculty_member']);
  }

  //Delete all existing relationships
  db_delete('grad_docs_user_relationships')->execute();

  //Resave the whole relationship table
  $insert_query = db_insert('grad_docs_user_relationships')->fields(array('rel_type', 'student_uid', 'faculty_uid'));
  
  foreach ($values as $value) {
    $insert_query->values($value);
  }
  $insert_query->execute();

  drupal_set_message(t('Relationship Table has been saved.'));
}

function student_documents_block_content($user_id, $colorbox_display = FALSE) {
  $form_types_array = new ContentTypeGroup('grad_doc_forms');
  $form_types_array = $form_types_array->content_types;

  $form_types_array_keys = array_keys($form_types_array);

  $items = array();
  $block_content = null;

  for ($form_types_index = 0; $form_types_index < count($form_types_array_keys); $form_types_index++) {
    $form_type = $form_types_array_keys[$form_types_index];
    $form_type_name = $form_types_array[$form_type]['name'];

    $query = new EntityFieldQuery;

    $nodes_of_type = $query
        ->entityCondition('entity_type', 'node')
        ->entityCondition('bundle', $form_type)
        ->propertyCondition('uid', $user_id)
        ->execute();

    if (isset($nodes_of_type['node'])) {
      $nodes_of_type = $nodes_of_type['node'];
      $block_content .= '<h3>' . $form_type_name . 's</h3>';

      foreach ($nodes_of_type as $node) {
        $full_node = node_load($node->nid);

        //Check if forms should be displayed in a colorbox overlay
        if ($colorbox_display === TRUE)
          $block_content .= l($full_node->title, 'node/' . $full_node->nid, array('attributes' => array('class' => 'colorbox-node'), 'query' => array('width' => 640, 'height' => 480)));
        else
          $block_content .= l($full_node->title, 'node/' . $full_node->nid);

        $block_content .= '<br /> ';
      }
    }
  }

  return $block_content;
}

function faculty_assigned_documents_block_content($user_id) {
  global $user;

  if (!in_array('Staff', $user->roles) && !in_array('Faculty', $user->roles))
    return null;

  $form_types_array = new ContentTypeGroup('grad_doc_forms');
  $form_types_array = $form_types_array->content_types;

  $form_types_array_keys = array_keys($form_types_array);

  $items = array();
  $block_content = null;

  for ($form_types_index = 0; $form_types_index < count($form_types_array_keys); $form_types_index++) {
    $form_type = $form_types_array_keys[$form_types_index];
    $form_type_name = $form_types_array[$form_type]['name'];

    $query = new EntityFieldQuery;

    $nodes_of_type = $query
        ->entityCondition('entity_type', 'node')
        ->entityCondition('bundle', $form_type)
        ->execute();

    if (isset($nodes_of_type['node'])) {
      $nodes_of_type = $nodes_of_type['node'];

      foreach ($nodes_of_type as $node) {
        $document_state = get_document_state($node->nid);

        $active_workflow = get_active_workflow($form_type);

        if (isset($active_workflow) && $active_workflow != FALSE) {
          //Get the current Assignee for this workflow based on the state
          $workflow_state_id = $active_workflow->field_workflow_stage;
          $workflow_state_id = $workflow_state_id['und'][$document_state - 1]['value'];

          $assignee_role = get_workflow_assignee($workflow_state_id);
          $full_node = node_load($node->nid);
          $node_author = $full_node->uid;

          //Get uid of assignee of node being checked
          $assignee_uid = get_assignee_uid($full_node);

          //If current user is assignee of node being check, add to list
          if ($user->uid == $assignee_uid) {
            $block_content .= '<h3>' . $form_type_name . 's</h3>';
            $block_content .= l($full_node->title, 'node/' . $full_node->nid) . '<br /> ';
          }
        }
      }
    }
  }

  if ($block_content == null) {
    $block_content = t('No assigned documents');
  }
  
  return $block_content;
}

//Alter form pages to display Workflow details and controls
function grad_docs_page_alter(&$page) {
  global $user;

  $node = menu_get_object();
  if (!isset($node->nid) || !isset($node->entity_view_prepared)) {
    return;
  }

  //Get Form types
  $form_types_array = new ContentTypeGroup('grad_doc_forms');
  $form_types_array = $form_types_array->content_types;
  $form_types_array_keys = array_keys($form_types_array);

  //Check if node is a grad form, return here if it isn't
  if (!in_array($node->type, $form_types_array_keys)) {
    return;
  }

  //Get the workflow for this type of form
  $active_workflow = get_active_workflow($node->type);

  //If this content type has a workflow assigned to it then get the current state of this form from the DB
  if (isset($active_workflow) && $active_workflow != FALSE) {
    $document_state = get_document_state($node->nid);

    //Get the current Assignee for this workflow based on the state
    $workflow_state_id = $active_workflow->field_workflow_stage;
    $workflow_state_id = $workflow_state_id['und'][$document_state - 1]['value'];

    $assignee_role = get_workflow_assignee($workflow_state_id);

    //Lets make a container for the workflow stuff
    $page['content']['system_main']['nodes'][$node->nid]['workflow_details_controls'] = array(
      '#weight' => -1,
      '#prefix' => '<div id="workflow_details_controls">',
      '#suffix' => '</div>'
    );

    //Output the current assignee
    $page['content']['system_main']['nodes'][$node->nid]['workflow_details_controls']['workflow_assignee'] = array(
      '#weight' => 0,
      '#markup' => t('<div id="workflow_current_assignee"><strong>Currently with:</strong> <span>' . $assignee_role . '</span></div>')
    );

    //Get the approval target state, and the rejection target state
    $approval_state_target = db_select('field_data_field_approval_state', 'ap_s')
            ->fields('ap_s', array('field_approval_state_value'))
            ->condition('entity_id', $workflow_state_id)
            ->execute()->fetchField();
    $rejection_state_target = db_select('field_data_field_rejection_state', 'rj_s')
            ->fields('rj_s', array('field_rejection_state_value'))
            ->condition('entity_id', $workflow_state_id)
            ->execute()->fetchField();

    //Get the current User's roles, incase we deal with admin
    $user_roles = $user->roles;

    //Add buttons depending on current user and assignee
    switch ($assignee_role) {
      case 'Student':
        //Check that current user is indeed the author of the form
        if ($user->uid == $node->uid || in_array('administrator', $user_roles)) {
          if (!empty($approval_state_target)) {
            $buttons = array('submit' => array('name' => 'submit_button', 'value' => 'Submit', 'workflow_target' => $approval_state_target, 'form_nid' => $node->nid));
          }

          $page['content']['system_main']['nodes'][$node->nid]['workflow_details_controls']['workflow_buttons'] = drupal_get_form('grad_docs_workflow_form', $buttons);
        }
        break;
        
      case 'Published':
        break;
      
      default:
        //Get the uid of the assignee
        $assignee_uid = get_assignee_uid($node);

        //Check that the current user is the assignee, and load the buttons
        if ($assignee_uid == $user->uid || in_array('administrator', $user_roles)) {
          if (!empty($approval_state_target)) {
            $buttons['approve'] = array('name' => 'approve_button', 'value' => 'Approve', 'workflow_target' => $approval_state_target, 'form_nid' => $node->nid);
          }
          if (!empty($rejection_state_target)) {
            $buttons['reject'] = array('name' => 'reject_button', 'value' => 'Reject', 'workflow_target' => $rejection_state_target, 'form_nid' => $node->nid);
          }
          $page['content']['system_main']['nodes'][$node->nid]['workflow_details_controls']['workflow_buttons'] = drupal_get_form('grad_docs_workflow_form', $buttons);
        }
        break;
    }
  }
}

//Helper functions for dealing with Workflows
function get_active_workflow($type_of_form) {
  //Get all the workflows
  $workflow_query = new EntityFieldQuery();
  $workflow_query->entityCondition('entity_type', 'node')
      ->entityCondition('bundle', 'workflow');
  $workflow_collection = $workflow_query->execute();

  if (empty($workflow_collection))
    return;

  //For each workflow, check if the name of this form is specified
  foreach ($workflow_collection['node'] as $workflow) {
    $full_workflow = node_load($workflow->nid);
    $form_types_field = $full_workflow->field_form_types;
    foreach ($form_types_field['und'] as $form_type) {
      if ($form_type['value'] == $type_of_form) {
        $active_workflow = $full_workflow;
      }
    }
  }

  if (isset($active_workflow))
    return $active_workflow;
  else
    return FALSE;
}

function get_document_state($nid) {
  $document_state = db_select('grad_docs_document_states', 'ds')
      ->fields('ds', array('document_state'))
      ->condition('document_nid', $nid)
      ->execute()
      ->fetchField();
  return $document_state;
}

function get_workflow_assignee($workflow_state_entity_id) {
  $assignee_role = db_select('field_data_field_assignee', 'ass')
      ->fields('ass', array('field_assignee_value'))
      ->condition('entity_id', $workflow_state_entity_id)
      ->execute()
      ->fetchField();
  return $assignee_role;
}

function get_assignee_uid($node) {
  //Get the workflow for this type of form
  $active_workflow = get_active_workflow($node->type);

  //Get the state of the current document
  $document_state = get_document_state($node->nid);

  if (!isset($active_workflow) || $active_workflow === FALSE)
    return FALSE;

  //Get the current Assignee for this workflow based on the state
  $workflow_state_id = $active_workflow->field_workflow_stage;
  $workflow_state_id = $workflow_state_id['und'][$document_state - 1]['value'];
  $assignee_role = get_workflow_assignee($workflow_state_id);

  //Get the uid of the assignee
  switch ($assignee_role) {
    case 'Student':
      $assignee_uid = $node->uid;
      break;
    
    case 'Published':
      $assignee_uid = FALSE;
      break;
    
    default:
      $assignee_uid = db_select('grad_docs_user_relationships', 'u_rel')
              ->fields('u_rel', array('faculty_uid'))
              ->condition('student_uid', $node->uid)
              ->condition('rel_type', $assignee_role)
              ->execute()->fetchField();

      //If no assignee was found, check if there is an assignee for all
      if ($assignee_uid === FALSE) {
        $assignee_uid = db_select('grad_docs_user_relationships', 'u_rel')
                ->fields('u_rel', array('faculty_uid'))
                ->condition('student_uid', 0)
                ->condition('rel_type', $assignee_role)
                ->execute()->fetchField();
      }
      break;
  }

  return $assignee_uid;
}

//A form showing the actual workflow buttons
function grad_docs_workflow_form($form, &$form_state, $buttons = array()) {
  $form = array();
  $form['#weight'] = 1;

  //Now add all the buttons
  foreach ($buttons as $button) {
    $form_state[$button['name'] . '_target'] = $button['workflow_target'];
    $form_state['current_nid'] = $button['form_nid'];

    $form[$button['name']] = array(
      '#type' => 'submit',
      '#value' => $button['value'],
      '#limit_validation_errors' => array(array('choice')),
      '#submit' => array('doc_workflow_next_state_submit'),
      '#name' => $button['name']
    );
  }

  return $form;
}

function doc_workflow_next_state_submit($form, &$form_state) {
  global $user;

  $button_name = $form_state['triggering_element']['#array_parents'][0];
  $button_target = $form_state[$button_name . '_target'];

  $current_state = get_document_state($form_state['current_nid']);

  //Update the state of the document in the state table to the target state
  db_update('grad_docs_document_states')
      ->condition('document_nid', $form_state['current_nid'])
      ->fields(array('document_state' => $button_target))
      ->execute();

  $node = menu_get_object();

  //Get the workflow for this type of form
  $active_workflow = get_active_workflow($node->type);

  //Get the state of the current document
  $document_state = get_document_state($node->nid);

  //Get the current Assignee for this workflow based on the state
  $workflow_state_id = $active_workflow->field_workflow_stage;
  $workflow_state_id = $workflow_state_id['und'][$document_state - 1]['value'];
  $assignee_role = get_workflow_assignee($workflow_state_id);

  //If the document reached a final state, change publishing option
  if ($assignee_role == 'Published') {
    $node->status = 1;
    node_save($node);
  }

  //Lets also send an email notification
  $assignee_uid = get_assignee_uid($node);

  if ($assignee_uid) {
    $assignee_user = user_load($assignee_uid);
  }

  //Lets record this action in the document history table
  $button = explode('_', $button_name);
  document_history_entry($form_state['current_nid'], $user->uid, $assignee_uid, $current_state, $button_target, time(), $button[0]);

  //Reload the page
  $request_uri = explode('/grad_docs/', request_uri());
  drupal_goto($request_uri[1]);
}

function grad_docs_mail($key, &$message, $params) {
  switch ($key) {
    case 'document_assigned':
      $account = $params['account'];
      $subject = $params['subject'];
      $body = $params['body'];

      $message['to'] = $account->mail;
      $message['subject'] = $subject;
      $message['body'][] = $body;
      break;
  }
}

function document_history_entry($document_nid, $previous_user_uid, $current_user_uid, $previous_state, $current_state, $history_timestamp, $workflow_action) {
  db_insert('grad_docs_document_history')
      ->fields(array('document_nid' => $document_nid,
        'previous_user_uid' => $previous_user_uid,
        'current_user_uid' => $current_user_uid ? $current_user_uid : 0,
        'previous_state' => $previous_state,
        'current_state' => $current_state,
        'history_timestamp' => $history_timestamp,
        'workflow_action' => $workflow_action))
      ->execute();
}

function doc_workflow_button_callback($form, &$form_state) {
  $current_node = $form_state['current_nid'];
  $node = node_load($current_node);

  //Get the workflow for this type of form
  $active_workflow = get_active_workflow($node->type);

  //Get the state of the current document
  $document_state = get_document_state($node->nid);

  //Get the current Assignee for this workflow based on the state
  $workflow_state_id = $active_workflow->field_workflow_stage;
  $workflow_state_id = $workflow_state_id['und'][$document_state - 1]['value'];
  $assignee_role = get_workflow_assignee($workflow_state_id);

  //Generate the elements that will replace the workflow details
  $assignee_details = array('workflow_assignee' => array(
      '#weight' => 0,
      '#markup' => t('<div id="workflow_current_assignee"><strong>Currently with:</strong> <span>' . $assignee_role . '</span></div>')
    ),
    'page_details' => array(
      '#markup' => print_r($page, TRUE)
    )
  );

  return $assignee_details;
}

function grad_docs_node_access($node, $op, $account) {
  $form_types_array = new ContentTypeGroup('grad_doc_forms');
  $form_types_array = $form_types_array->content_types;
  $form_types_array_keys = array_keys($form_types_array);

  if (isset($node->type) && in_array($node->type, $form_types_array_keys)) {
    //Get the workflow for this type of form
    $active_workflow = get_active_workflow($node->type);

    //Get the state of the current document
    $document_state = get_document_state($node->nid);

    if (!isset($active_workflow) || $active_workflow === FALSE)
      return NODE_ACCESS_IGNORE;

    //Get the current Assignee for this workflow based on the state
    $workflow_state_id = $active_workflow->field_workflow_stage;
    $workflow_state_id = $workflow_state_id['und'][$document_state - 1]['value'];
    $assignee_role = get_workflow_assignee($workflow_state_id);

    $assignee_uid = get_assignee_uid($node);

    switch ($op) {
      case 'create':
        //No concern with creating form entries
        return NODE_ACCESS_IGNORE;
        break;
      
      case 'view':
        //Always allow node author to view form
        if ($node->uid == $account->uid)
          return NODE_ACCESS_ALLOW;

        //Check if current user is current assignee
        if (isset($assignee_uid) && $account->uid == $assignee_uid)
          return NODE_ACCESS_ALLOW;

        //Check if current user is related to form author, or all students
        $relationship_uids = db_select('grad_docs_user_relationships', 'u_rel')
            ->fields('u_rel', array('faculty_uid'))
            ->condition('student_uid', $node->uid)
            ->execute();

        foreach ($relationship_uids as $rel) {
          if ($rel->faculty_uid == $account->uid)
            return NODE_ACCESS_ALLOW;
        }

        //ATTENTION Combine this and the previous into one query
        $global_relationships = db_select('grad_docs_user_relationships', 'u_rel')
            ->fields('u_rel', array('faculty_uid'))
            ->condition('student_uid', '0')
            ->execute();

        foreach ($global_relationships as $glob_rel) {
          if ($glob_rel->faculty_uid == $account->uid)
            return NODE_ACCESS_ALLOW;
        }
        
        //Published nodes are always viewable
        if ($assignee_role == 'Published')
          return NODE_ACCESS_ALLOW;

        //Anyone else, denied
        return NODE_ACCESS_DENY;
        break;
        
      case 'update':
        //Nodes assigned to Student are editable by the student
        if ($assignee_role == 'Student' && $account->uid == $node->uid)
          return NODE_ACCESS_ALLOW;

        //Check if current user is current assignee
        if (isset($assignee_uid) && $account->uid == $assignee_uid)
          return NODE_ACCESS_ALLOW;

        //Anyone else, denied
        return NODE_ACCESS_DENY;
        break;
        
      case 'delete':
        return NODE_ACCESS_DENY;
        break;
    }

    //drupal_set_message(print_r($node, TRUE), TRUE);
    return NODE_ACCESS_DENY;
  }
  else {
    return NODE_ACCESS_IGNORE;
  }
}

//Functions that update the states table when form nodes are added or removed
function grad_docs_node_insert($node) {
  $form_types_array = new ContentTypeGroup('grad_doc_forms');
  $form_types_array = $form_types_array->content_types;
  $form_types_array_keys = array_keys($form_types_array);

  //Check if node is a grad form, create state entry if it is
  if (in_array($node->type, $form_types_array_keys)) {
    db_insert('grad_docs_document_states')
        ->fields(array(
          'document_nid' => $node->nid,
          'document_state' => '1'))
        ->execute();
  }
}

function grad_docs_node_delete($node) {
  $form_types_array = new ContentTypeGroup('grad_doc_forms');
  $form_types_array = $form_types_array->content_types;
  $form_types_array_keys = array_keys($form_types_array);

  //Remove all state entries for this node
  db_delete('grad_docs_document_states')
      ->condition('document_nid', $node->nid)
      ->execute();

  //Remove all history entries for this node
  db_delete('grad_docs_document_history')
      ->condition('document_nid', $node->nid)
      ->execute();
}

function grad_docs_permission() {
  $permissions = array('Administer Workflow Relationship Table' => array(
      'title' => t('Administer Workflow Relationship Table'),
    )
  );

  return $permissions;
}