<?php
// $Id: workflow_access.module,v 1.6.2.2 2010/10/27 20:35:31 jvandyk Exp $

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

/**
 * 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) {
  return array(
    'workflow_access'       => array_keys($account->roles),
    'workflow_access_owner' => array($account->uid),
  );
}

/**
 * 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();
  $sid = db_result(db_query("SELECT sid FROM {workflow_node} WHERE nid = %d", $node->nid));

  // We have state information about this node, so get permissions for this state.
  if (is_numeric($sid)) {
    $result = db_query('SELECT * FROM {workflow_access} WHERE sid = %d', $sid);
    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,
        'priority'     => 0,
      ); 
    }
  }

  return $grants;
}

/**
 * Implementation of hook_node_access_explain().
 */
function workflow_access_node_access_explain($row) {
  static $interpretations = array();
  switch ($row->realm) {
    case 'workflow_access_owner':
      $interpretations[$row->gid] = t('Workflow access: author of the content may access');
      break;
    case 'workflow_access':
      $roles = user_roles();
      $interpretations[$row->gid] = t('Workflow access: %role may access', array('%role' => $roles[$row->gid]));
      break;
  }
  return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL);
}

/**
 * Implementation of hook_form_alter().
 *
 * Add a "three dimensional" (state, role, permission type) configuration 
 * interface to the workflow edit form.
 */
function workflow_access_form_workflow_edit_form_alter(&$form, $form_state) {
  // 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(
    '#type' => 'fieldset', 
    '#title' => t('Access control'),
    '#collapsible' => TRUE,
    '#tree' => TRUE,
  );

  // Add a table for every workflow state.
  $states = workflow_get_states($form['wid']['#value']);
  foreach ($states as $sid => $state) {
    
    if (workflow_is_system_state($state)) {
      // No need to set perms on creation.
      continue;
    }
        
    $view = $update = $delete = array();

    $result = db_query("SELECT * from {workflow_access} where sid = %d", $sid);
    $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'][$sid] = array(
      '#type' => 'fieldset', 
      '#title' => $state,
      '#collapsible' => TRUE,
      '#tree' => TRUE,
    );
    $form['workflow_access'][$sid]['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'][$sid]['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'][$sid]['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>",
    );
  }
  // Place our block comfortably down the page.
  $form['submit']['#weight'] = 10;
  $form['#submit'][] = 'workflow_access_form_submit';
}

/**
 * Store permission settings for workflow states.
 */
function workflow_access_form_submit($form, $form_state) {
  foreach ($form_state['values']['workflow_access'] as $sid => $access) {
    // Ignore irrelevant keys.
    if (!is_numeric($sid)) {
      continue; 
    }

    $grants = array();
    db_query("DELETE FROM {workflow_access} WHERE sid = %d", $sid);
    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} (sid, rid, grant_view, grant_update, grant_delete) VALUES (%d, %d, %d, %d, %d)", $sid, $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.sid = %d", $sid);
    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_sid, $sid, $node) {
  if ($op == 'transition post' && $old_sid != $sid) {
    node_access_acquire_grants($node);
  }
}
