<?php
// $Id: workflow_access.module,v 1.6.4.4 2010/08/31 16:02:51 q0rban Exp $

/**
 * @file 
 *   Provides node access permissions based on workflow states.
 */

/**
 * Implementation of hook_menu().
 */
function workflow_access_menu() {
  $items = array();

  $items['admin/build/workflow/edit/%workflow/node-access'] = array(
    'title' => 'Node Access',
    'weight' => 2,
    'access arguments' => array('administer workflow'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('workflow_access_form', 4),
    'type' => MENU_LOCAL_TASK,
  );

  return $items;
}

/**
 * Implementation of hook_node_grants().
 *
 * Supply the workflow access grants. We are simply using
 * roles as access lists, so rids translate directly to gids.
 */
function workflow_access_node_grants($account, $op) {
  $grants = array();
  foreach ($account->roles as $rid => $name) {
    // The $account->roles will have an author index during some operations
    if($rid != "author") {
      $grants['workflow_access'][] = $rid;
    }
  }
  $grants['workflow_access_owner'][] = $account->uid;

  return $grants;
}

/**
 * Implementation of hook_node_access_records().
 *
 * Returns a list of grant records for the passed in node object.
 */
function workflow_access_node_access_records($node) {
  $grants = array();

  // If we have state information about this node, get permissions for this state.
  if ($state_name = workflow_node_current_state($node)) {
    $result = db_query("SELECT * FROM {workflow_access} WHERE state_name = '%s'", $state_name);
    while ($grant = db_fetch_object($result)) {
      $grants[] = array(
        'realm'        => ($grant->rid == -1) ? 'workflow_access_owner' : 'workflow_access',
        'gid'          => ($grant->rid == -1) ? $node->uid : $grant->rid,
        'grant_view'   => $grant->grant_view,
        'grant_update' => $grant->grant_update,
        'grant_delete' => $grant->grant_delete
      ); 
    }
  }

  return $grants;
}

/**
 * Implementation of hook_form_alter().
 *
 * Add a "three dimensional" (state, role, permission type) configuration 
 * interface to the workflow edit form.
 */
function workflow_access_form(&$form_state, $workflow) {
  // A list of roles available on the site and our 
  // special -1 role used to represent the node author.
  $rids = user_roles();
  $rids['-1'] = t('author');

  $form['workflow_access'] = array(
    '#tree' => TRUE,
  );

  // Add a table for every workflow state.
  $states = workflow_get_state_labels($workflow->name);
  foreach ($states as $state_name => $label) {
    if (workflow_is_system_state($label)) {
      // No need to set perms on creation.
      continue;
    }

    $view = $update = $delete = array();

    $result = db_query("SELECT * from {workflow_access} where state_name = '%s'", $state_name);
    $count = 0;
    while ($access = db_fetch_object($result)) {
      $count++;
      if ($access->grant_view) {
        $view[] = $access->rid;
      }
      if ($access->grant_update) {
        $update[] = $access->rid;
      }
      if ($access->grant_delete) {
        $delete[] = $access->rid;
      }
    }
    
    // Allow view grants by default for anonymous and authenticated users, 
    // if no grants were set up earlier.
    if (!$count) {
      $view = array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID);     
    }
    
    // TODO better tables using a #theme function instead of direct #prefixing
    $form['workflow_access'][$state_name] = array(
      '#type' => 'fieldset', 
      '#title' => $label,
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#tree' => TRUE,
    );
    $form['workflow_access'][$state_name]['view'] = array(
      '#type' => 'checkboxes',
      '#options' => $rids,
      '#default_value' => $view,
      '#title' => t('Roles who can view posts in this state'),
      '#prefix' => '<table width="100%" style="border: 0;"><tbody style="border: 0;"><tr><td>',
    );
    $form['workflow_access'][$state_name]['update'] = array(
      '#type' => 'checkboxes',
      '#options' => $rids,
      '#default_value' => $update,
      '#title' => t('Roles who can edit posts in this state'),
      '#prefix' => "</td><td>",
    );
    $form['workflow_access'][$state_name]['delete'] = array(
      '#type' => 'checkboxes',
      '#options' => $rids,
      '#default_value' => $delete,
      '#title' => t('Roles who can delete posts in this state'),
      '#prefix' => "</td><td>",
      '#suffix' => "</td></tr></tbody></table>",
    );
  }

  if (count(element_children($form['workflow_access'])) == 0) {
    $form['workflow_access'] = array(
      '#type' => 'markup',
      '#value' => t('There are no states defined for this workflow.'),
    );
    return $form;
  }

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

  return $form;
}

/**
 * Store permission settings for workflow states.
 */
function workflow_access_form_submit($form, $form_state) {
  foreach (element_children($form_state['values']['workflow_access']) as $state_name) {
    $access = $form_state['values']['workflow_access'][$state_name];
    $grants = array();
    db_query("DELETE FROM {workflow_access} WHERE state_name = '%s'", $state_name);
    foreach ($access['view'] as $rid => $checked) {
      $grants[] = array(
        'realm'        => ($rid == -1) ? 'workflow_access_owner' : 'workflow_access',
        'gid'          => ($rid == -1) ? $node->uid : $rid,
        'grant_view'   => (bool)$checked,
        'grant_update' => (bool)$access['update'][$rid],
        'grant_delete' => (bool)$access['delete'][$rid],
      );

      db_query("INSERT INTO {workflow_access} (state_name, rid, grant_view, grant_update, grant_delete) VALUES ('%s', %d, %d, %d, %d)", $state_name, $rid, (bool)$checked, (bool)$access['update'][$rid], (bool)$access['delete'][$rid]);
    }

    // Update all nodes having same workflow state to reflect new settings.
    $result = db_query("SELECT n.nid FROM {node} n LEFT JOIN {workflow_node} wn ON wn.nid = n.nid WHERE wn.state_name = '%s'", $state_name);
    while ($node = db_fetch_object($result)) {
      // TODO: this only works with workflow_access realm, not the workflow_access_owner realm?!
      node_access_write_grants($node, $grants, 'workflow_access');
    }
  }
  drupal_set_message(t('Workflow access permissions updated.'));
}

/**
 * Implementation of hook_workflow().
 *
 * Update grants when a node changes workflow state.
 */
function workflow_access_workflow($op, $old_state_name, $state_name, $node) {
  if ($op == 'transition post' && $old_state_name != $state_name) {
    node_access_acquire_grants($node);
  }
}
