<?php
// $Id: taxonomy_access.module,v 1.82 2006/07/04 22:19:23 keve Exp $
// Based on original taxonomy_access.module made by pyromanfo
/**
 * Implementation of hook_help
 */
function taxonomy_access_help($section) {
  global $tac_user_roles;
  switch ($section) {
    // Brief message for administrators to explain what this module does
    case 'admin/modules#description': 
      return t('Allows users to specify how each category can be used by various roles.');

    // Creates the header content for the taxonomy_access settings page dependent upon
    // whether the node is enabled or disabled.
    // Note: the form that appears on this page is not generated by this fucntion.
    case 'admin/settings/taxonomy_access':
      $taxonomy_access_enabled = variable_get('taxonomy_access_enabled', FALSE);
      $message = '';

      // header message if module is enabled
      if ($taxonomy_access_enabled) { 
        $message .= '<p>'.t('The module is currently <u>ACTIVATED</u> properly.').'</p>';
        $message .= '<p>'.t("To disable the module properly: <ol><li><u>Deactivate</u> here. <em>(Save this configuration.)</em></li><li><u>Disable/Uninstall</u> it in the 'Modules Administration Page'.</li></ol>").'</p>';
      }

      // header message if module is not enabled
      else {
        $message .= '<p>'.t('The module is currently <u>DEACTIVATED</u>, but must also be disabled/uninstalled in the %module_page_link in order to be properly disabled.', array('%module_page_link' => l(t('modules admin page'), 'admin/modules'))).'<p>';
        $message .= '<p>'.t('For controlling the Taxonomy Access System, the module has to be <u>Activated</u> on this page.').'<p>';
      }
      return $message;

    case 'admin/help#taxonomy_access':
      $message = '<p>'.t('The Taxonomy Access Control module allows users to specify how each category can be used by various roles.').'</p>';
      $message .= '<p>'.t('On the category permissions page for each role, each category displays a list of the terms within it, each with five types of permission: <em>View, Update, Delete, Create</em> and <em>List</em>:').'</p>';
      $message .= '<ul>';
      $message .= '<li>'.t('<strong>VIEW</strong> enables the user to access content (nodes) with given term.').'</li>';
      $message .= '<li>'.t('<strong>UPDATE, DELETE</strong> enables the user to Update/Delete <u>ALL</u> nodes with the given term. <br><em>(These two permissions are <u>administrator permissions</u>, that should be given ONLY to e.g. content administrators.)</em>').'</li>';
      $message .= '<li>'.t('<strong>CREATE</strong> enables the user to set that term when adding a new node or when editing a node.').'</li>';
      $message .= '<li>'.t('<strong>LIST</strong> enables the user to view the name of the given term below the title of a node or in category lists.  It also controls whether a user can access the taxonomy page for the given term.  (e.g. "taxonomy/term/*")').'</li>';
      $message .= '</ul>';
      $message .= '<p>'.t('VIEW, UPDATE, and DELETE control the node access system.  LIST and CREATE control if a user can view and select a given term.  (Note: In previous versions of Taxonomy Access Control, there was no LIST permission; its functionality was controlled by the VIEW permission.)').'</p>';
      $message .= '<p>'.t('<strong>VIEW, UPDATE and DELETE have three options for each term: <u>A</u>llow, <u>I</u>gnore, and <u>D</u>eny.</strong>  Indicate which rights each role should have for each term.').'</p>';
      $message .= '<p>'.t('<strong>CREATE and LIST have only two options for each term:  YES (selected) or NO (deselected).</strong>  Indicate what each role should be allowed to do with each term.').'</p>';
      $message .= '<p>'.t('<strong>IMPORTANT NOTE:</strong><br><u>The DENY directives are processed after the ALLOW directives. (DENY overrides ALLOW.)</u>  So, if a multicategory node is in Categories "A" and "B" and a user has ALLOW permissions for VIEW in Category "A" and DENY permissions for VIEW in Category "B", then the user will NOT be permitted to VIEW the node. (DENY overrides ALLOW.)<br><u>Access is denied by default.</u> So, if a multicategory node is in Categories "C" and "D" and a user has IGNORE permissions for VIEW in both Category "C" and "D", then the user will NOT be permitted to VIEW the node.<br>(If you are familiar with Apache mod_access, this permission system works similar to directive: <em>ORDER ALLOW, DENY</em>)').'</p>';
      $message .= '<p>'.t('<strong>Allow/Ignore/Deny All</strong> or <strong>Select/Deselect All:</strong><br>Beside each vocabulary title there are dropdowns containing the options that can be set for individual terms.  Selecting one of these options using the dropdown effectively <u>selects that option for ALL of the individual terms inside that vocabulary when the options are saved.</u><br>Selecting "--" does not make any automatic changes to the permission of the terms in that vocabulary; only manual changes that you make will be saved.<br>NOTE:  This does <u>not</u> change the "Default" option (described below).').'</p>';
      $message .= '<p>'.t('<strong>Default:</strong><br>This option, just underneath the vocabulary title, <u>sets the permission that will automatically be given</u> to the role, <u>for any new terms</u> that are added within the vocabulary.  This includes terms that are added via free tagging.').'</p>';
      $message .= '<p>'.t('<strong>GOOD TO KNOW:</strong><br><strong>Input formats:</strong>  <u>Node editing/deleting is blocked</u>, even when user has <em>UPDATE/DELETE</em> permission to the node, <u>when user is not allowed to use a filter format</u> that the node was saved at.').'</p>';
      $message .= '<p>&nbsp;</p>';
      return $message;
      
    default:
      if (strpos($section,'admin/access/category') !== FALSE) {
        if (!variable_get('taxonomy_access_enabled', 0)) {
          return '<p>'.t("Taxonomy Access Control has to be <u>activated</u> on %settings_page, before configuring 'category permissions'.", array('%settings_page' => l(t('settings page'), 'admin/settings/taxonomy_access'))).'</p>';
        }
        $rid = arg(3);
        if (isset($tac_user_roles[$rid]) && $tac_user_roles[$rid]) {
          $output = t('<p><strong>Vocabulary Settings:</strong> Each vocabulary displays a list of the terms within it, each with five types of permission: <em>View, Update, Delete, Create</em> and <em>List</em>.</p><p>For a detailed description of these permissions and how to use them, see <a href="%taxonomy_access_help">Taxonomy Access Control help</a>. If you are new to Taxonomy Access Control, it is very important that you read the help page.</p>', array('%taxonomy_access_help' => url('admin/help/taxonomy_access')));
          return $output;
        }
        else {
          return '<p>'.t('In this area you will define the permissions that each <a href="%role">user role</a> has for each category.  Each category can have <em>View, Update, Delete, Create</em> and <em>List</em> permissions set for each user role.', array('%role' => url('admin/access/roles'), '%default' => url('admin/access/category/0'))).'</p>';
        }
      }
  }
}

/**
 * Implementation of hook_node_grants()
 * Gives access to taxonomies based on the taxonomy_access table
 */
function taxonomy_access_node_grants($user, $op) {
  return array('term_access' => array_keys(is_array($user->roles) ? $user->roles : array(1 => 'anonymous user')));
}

/**
 * Implementation of hook_menu
 */
function taxonomy_access_menu($may_cache) {
  $items = array();

  if ($may_cache) {
    $access = user_access('administer users');
  
    $items[] = array('path' => 'admin/access/category', 'title' => t('category permissions'),
      'callback' => 'taxonomy_access_admin_form', 'access' => $access, 'callback arguments'=>array(),
      'type' => MENU_LOCAL_TASK);
  }

  return $items;
}

function taxonomy_access_admin_form($edit = array()) {
  global $tac_user_roles;
  $rid = arg(3);
  $output = '';
  $tac_user_roles = user_roles();

  // Checking if TAC is activated
  if (!variable_get('taxonomy_access_enabled', 0)) {
    return '';
  }

  // Output the main page of category permissions
  if (!isset($tac_user_roles[$rid])) {
    // Render role/permission overview:
    $header = array(t('Role'), '&nbsp;' );
    foreach ($tac_user_roles as $rid => $name) {
     $rows[] = array($name, array('data'=>l(t("edit"),"admin/access/category/$rid"), 'align'=>'right'));
    }
    $output .= theme('table', $header, $rows);
  }

  // Output the permission matrix user form
  else { 
    $grant_types = array('view', 'update', 'delete', 'create', 'list');
    $node_grant_types = array('view', 'update', 'delete');

    // Get all category permissions
    $perm = taxonomy_access_get_grants($rid);
    $default = taxonomy_access_get_default_grants($rid);
    
    $forms['taxonomy_access'] = array( '#tree' => TRUE);

    // Do the row for uncategorized nodes
    $forms['taxonomy_access'][0] = array(
      '#type' => 'fieldset',
      '#title' => t('Uncategorized nodes'),
      '#weight' => 3,
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#tree' => TRUE,
    );
    $head = '<table><thead><tr><th>';
    $head .= t('Category');
    $head .= '</th><th colspan="4">';
    $head .= t('View');
    $head .= '</th><th colspan="4">';
    $head .= t('Update');
    $head .= '</th><th colspan="4">';
    $head .= t('Delete');
    $head .= '</th><th>';
    $head .= t('Create');
    $head .= '</th><th>';
    $head .= t('List');
    $head .= '</th></tr></thead><tbody>'."\n";
    $forms['taxonomy_access'][0]['term'][0] = array(
      '#prefix' => $head.'<tr class="even"><td>'.t('Uncategorized nodes').'</td>',
      '#suffix' => '<td>&nbsp;</td><td>&nbsp;</td></tr>'."\n".'</tbody></table>',
    );
    foreach ($node_grant_types as $grant) {
      $forms['taxonomy_access'][0]['term'][0][$grant] = array(
        '#type' => 'checkbox',
        '#default_value' => $perm[0][$grant],
        '#prefix' => '<td colspan="4">',
        '#suffix' => '</td>',
      );
    }

    $vocabs = taxonomy_get_vocabularies();
    $options1 = array( '3' => '--', '1' => t('Allow all'), '0' => t('Ignore all'), '2' => t('Deny all'));
    $options2 = array( '3' => '--', '1' => t('Select all'), '2' => t('Deselect all'));
    $radios = array('1' => t('Allow'), '0' => t('Ignore'), '2' => t('Deny'));

    foreach ($vocabs as $vocab) {
      $forms['taxonomy_access'][$vocab->vid] = array(
        '#type' => 'fieldset',
        '#title' => t($vocab->name),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#tree' => TRUE,
       );
      $label = '<td>&nbsp;<strong>'.t('A').'</strong></td><td>&nbsp;<strong>'.t('I').'</strong></td><td>&nbsp;<strong>'.t('D').'</strong></td><td>&nbsp;</td>';
      $forms['taxonomy_access'][$vocab->vid]['vocab'] = array(
        '#prefix' => $head.'<tr class="even"><td><b>'.t($vocab->name).'</b></td>',
        '#suffix' => '</tr>'."\n".'<tr class="even"><td>&nbsp;</td>'.$label.$label.$label.'<td colspan="2">&nbsp;</td>'."\n",
      );
      $forms['taxonomy_access'][$vocab->vid]['default'] = array(
        '#prefix' => '<tr class="even"><td><blockquote><em>'.t('Default').'</blockquote></em></td>',
       );
      $forms['taxonomy_access'][$vocab->vid]['term'] = array(
        '#prefix' => '</tr>'."\n".'<tr class="even"><td colspan="15">&nbsp;</td>'."\n",
        '#suffix' => '</tr></tbody></table>',
       );

      foreach (array('view', 'update', 'delete') as $grant) {
        $forms['taxonomy_access'][$vocab->vid]['vocab'][$grant] = array(
          '#type' => 'select', 
          '#options' => $options1,
          '#default_value' => -1,
          '#prefix' => '<td colspan="4">', '#suffix' => '</td>',
         );
        foreach ($radios as $value => $name) {
          $suffix = ($value == 2) ? '</td><td>&nbsp;&nbsp;' : '';
          $forms['taxonomy_access'][$vocab->vid]['default'][$grant][$value] = array(
            '#type' => 'radio', 
            '#name' => 'edit[taxonomy_access]['.$vocab->vid.'][default]['.$grant.']', 
            '#return_value' => $value,
            '#default_value' => $default[$vocab->vid][$grant],
            '#prefix' => '<td>', '#suffix' => $suffix.'</td>',
           );
        }
      }
      foreach (array('create', 'list') as $grant) {
        $forms['taxonomy_access'][$vocab->vid]['vocab'][$grant] = array(
          '#type' => 'select', 
          '#options' => $options2,
          '#prefix' => '<td>', '#suffix' => '</td>',
         );
        $forms['taxonomy_access'][$vocab->vid]['default'][$grant] = array(
          '#type' => 'checkbox', 
          '#default_value' => $default[$vocab->vid][$grant],
          '#prefix' => '<td>', '#suffix' => '</td>',
         );
       }

      // Do the rows for each term in vocabulary
      $terms = array();
      $terms = taxonomy_get_tree($vocab->vid);
      if (!$terms) {
        $forms['taxonomy_access'][$vocab->vid]['default']['#suffix'] = "</tr></tbody></table>";
      }
      else {
        foreach ($terms as $term) {
          $forms['taxonomy_access'][$vocab->vid]['term'][$term->tid] = array(
            '#prefix' => '<tr class="even"><td>'.str_repeat('-',$term->depth).$term->name.'</td>',
           );
          foreach (array('view', 'update', 'delete') as $grant) {
            foreach ($radios as $value => $name) {
              $suffix = ($value == 2) ? '</td><td>&nbsp;&nbsp;' : '';
              $forms['taxonomy_access'][$vocab->vid]['term'][$term->tid][$grant][$value] = array (
                '#name' => 'edit[taxonomy_access]['.$vocab->vid.'][term]['.$term->tid.']['.$grant.']', 
                '#return_value' => $value,
                '#type' => 'radio', 
                '#default_value' => $perm[$term->tid][$grant],
                '#prefix' => '<td>', '#suffix' => $suffix.'</td>',
               );
            }
          }
          foreach (array('create', 'list') as $grant) {
            $forms['taxonomy_access'][$vocab->vid]['term'][$term->tid][$grant] = array (
              '#type' => 'checkbox', 
              '#default_value' => $perm[$term->tid][$grant],
              '#prefix' => '<td>', '#suffix' => '</td>',
             );
          }
        }
      }
    }

    $forms['taxonomy_access']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save category permissions'),
      '#name' => 'save',
      '#button_type' => 'submit',
    );
    $output = '<h2>'.t('Permissions for')." '".$tac_user_roles[$rid]."'</h2><p>";
    $output .= drupal_get_form('taxonomy_access_admin_form', $forms);
  }
  return $output;
}

function theme_taxonomy_access_admin_form($form) {
  $vids = array_keys($form['taxonomy_access']);
  $output = '';
  foreach ($vids as $vid) {
    if (is_integer($vid)) {
      $output .= form_render($form['taxonomy_access'][$vid]);
    }
  }
  $output .= form_render($form);
  return $output;
}

function taxonomy_access_admin_form_submit($form_id, $form_values) {
  global $tac_user_roles;
  $vids = array_keys($form_values['taxonomy_access']);
  $rid = arg(3);
  $grant_types = array('view', 'update', 'delete', 'create', 'list');
  $edit = $form_values['taxonomy_access'];


  foreach ($vids as $vid) {
    if (is_numeric($vid)) {
    // Process input data and use it to make changes to the database.
      if (is_array($edit[$vid]['term'])) {
        $tids = array_keys($edit[$vid]['term']);
        foreach ($tids as $tid) {
          // The $edit array is filled out with zeroes here to
          // reduce code complexity when we update the term_access table.
          if (!$edit[$vid]['term'][$tid]) {
            foreach ($grant_types as $grant) {
              $edit[$vid]['term'][$tid][$grant] = 0;
            }
          }
          else {
              foreach ($grant_types as $grant) {
                if (!isset($edit[$vid][$tid][$grant])) {
                  // Enter a '0' for any permission that has not been set or for any checkbox that has been left unchecked by the user
                  $edit[$vid][$tid][$grant] = 0;
                }
                else {
                  if ($grant == 'create' || $grant == 'list') {
                    // For any permission checkbox checked by user, set it to a value of '1'
                    $edit[$vid][$tid][$grant] = 1;
                  }
                }
              }
          }
          
          // Make the changes to the term_access table
          taxonomy_access_grant_update($tid, $rid, $edit[$vid]['term'][$tid]);
        }
      }

    // Now to the same for vocabularies
      if ($vid !=0) {
        // The $edit array is filled out with zeroes here to
        // reduce code complexity when we update the term_access table.
        $changed = false;
        if (!array_key_exists($vid, $edit)) {
          foreach ($grant_types as $grant) {
            $edit[$vid]['vocab'][$grant] = 0;
          }
        }
        else {
          foreach ($grant_types as $grant) {
            if ($edit[$vid]['vocab'][$grant] === NULL) {
              $edit[$vid]['vocab'][$grant] = 3;
            }
            else {
              if ($edit[$vid]['vocab'][$grant] > 2 || $edit[$vid]['vocab'][$grant] < 0) {
                $edit[$vid]['vocab'][$grant] = 3;
              }
              else {
                $changed = true;
              }
            }
            // Enter a '0' for any permission that has been left unchecked by the user
            // For any permission checked by user, set it to a value of '1'
            if ($grant == 'create' || $grant == 'list') {
              $edit[$vid]['default'][$grant] = $edit[$vid]['default'][$grant] ? 1 : 0;
            }
            else {
              $edit[$vid]['default'][$grant] = $edit[$vid]['default'][$grant][0];
            }
          }
        }
        // Make the changes to the term_access table
        if ($changed) {
          taxonomy_access_grant_vocab_update($vid, $rid, $edit);
        }
        // set the defaults
        taxonomy_access_defaults_update($vid,$rid,$edit[$vid]['default']);
      }
    }
  }

  // Reflect changes made to term_access table in node_access table
  _refresh_node_access_table($rid);

  // Clear the cache, as we might have changed the anonymous user's
  // permissions.
  cache_clear_all();
  // redirect to main category permissions menu
//  $roles = user_roles();
  drupal_set_message(t('Your permission settings for') . " '" . $tac_user_roles[$rid] . "' role have been saved.", 'status');
  drupal_goto('admin/access/category');

  return;
}

function taxonomy_access_form_alter($form_id, &$form) {
  if ($form_id == 'system_modules') {
    // Validates module dependency
    if (!$_POST) {
      taxonomy_access_system_module_validate($form);
    }

    // Module cannot be disabled, until deactivated on settings page
    if (variable_get('taxonomy_access_enabled', 0)) {
      $form['status']['taxonomy'] = array('#type' => 'hidden', '#value' => 1, '#suffix' => t('active'));
      $form['description']['taxonomy']['#value'] .= '<br><strong>'.t('Cannot be disabled!').'</strong> '.t('Required by taxonomy_access module.');
      $form['status']['taxonomy_access'] = array('#type' => 'hidden', '#value' => 1, '#suffix' => t('active'));
      $form['description']['taxonomy_access']['#value'] .= '<br><strong>'.t('Cannot be disabled!').'</strong> '.t('First, <u>deactivate</u> on %settings_page', array('%settings_page' => l(t('settings page'), 'admin/settings/taxonomy_access')));
    }
  }
}

/**
* Validates module dependency: taxonomy.module
*/
function taxonomy_access_system_module_validate(&$form) {

  $module = 'taxonomy_access';
  $dependency = 'taxonomy';

  if (!in_array($dependency, $form['status']['#default_value'])) {
    $missing_dependency = TRUE;
  }

  if (in_array($module, $form['status']['#default_value']) && isset($missing_dependency)) {
    db_query("UPDATE {system} SET status = 0 WHERE type = 'module' AND name = '%s'", $module);
    $key = array_search($module, $form['status']['#default_value']);
    unset($form['status']['#default_value'][$key]);
    drupal_set_message(t('The module %module was deactivated--it requires the following disabled/non-existant modules to function properly: %dependencies', array('%module' => $module, '%dependencies' => $dependency)), 'error');
  }

}

/**
 * Updates permissions for a role for a term
 * @param $tid
 *   The term to add the permission for.
 * @param $role
 *   The role to add the permission to.
 *   Can be the name or the role id or blank for all term permissions.
 * @param $grants
 *   A hash of the grants in the form of $grants['perm'] = boolean
 *   A value of 1 will grant the permission for this user and term.
**/
function taxonomy_access_grant_update($tid, $role = null, $grants = null) {
  if (!isset($tid)) {
    return FALSE;
  }
  if (isset($role) && !is_numeric($role)) {
    $role = db_result(db_query("SELECT rid FROM {role} WHERE name='$role'"));
  }
  
  $ta_sql = "INSERT INTO {term_access} (tid";
  $ta_sql_values = " VALUES ($tid";
  if (isset($role)) {
    $ta_sql .= ",rid";
    $ta_sql_values .= ",$role";
  }
  $sql = "";
  if (isset($grants)) {
    foreach ($grants as $perm => $value) {
      $sql .= ",grant_$perm";
      $ta_sql_values .= is_array($value) ? ",".$value[0] : ",$value";
    }
    $sql .= ")";
    $ta_sql_values .= ")";
  }
  else {
    $sql .= ")";
    $ta_sql_values .= ")";
  }
  $ta_sql .= $sql . $ta_sql_values;

  db_query("DELETE FROM {term_access} WHERE tid=%d AND rid=%d", $tid, (isset($role) ? $role : 0));
  db_query($ta_sql);  // insert into term_access
}

/**
 * Updates default permissions for a role for a vocabulary
 * @param $vid
 *   The vocab to add the permission for.
 * @param $role
 *   The role to add the permission to.
 *   Can be the name or the role id or blank for all term permissions.
 * @param $grants
 *   A hash of the grants in the form of $grants['perm'] = boolean
 *   A value of 1 will grant the permission for this user and term.
**/
function taxonomy_access_defaults_update($vid, $role = null, $grants = null) {
  if (!isset($vid)) {
    return FALSE;
  }
  if (isset($role) && !is_numeric($role)) {
    $role = db_result(db_query("SELECT rid FROM {role} WHERE name='$role'"));
  }
  
  $ta_sql = "INSERT INTO {term_access_defaults} (vid";
  $ta_sql_values = " VALUES ($vid";
  if (isset($role)) {
    $ta_sql .= ",rid";
    $ta_sql_values .= ",$role";
  }
  $sql = "";
  if (isset($grants)) {
    foreach ($grants as $perm => $value) {
      $sql .= ",grant_$perm";
      $ta_sql_values .= ",$value";
    }
    $sql .= ")";
    $ta_sql_values .= ")";
  }
  else {
    $sql .= ")";
    $ta_sql_values .= ")";
  }
  $ta_sql .= $sql . $ta_sql_values;

  db_query("DELETE FROM {term_access_defaults} WHERE vid=%d AND rid=%d", $vid, (isset($role) ? $role : 0));
  db_query($ta_sql);  // insert into term_access_defaults
}
/**
 * Updates permissions for a role for all the terms in a vocabulary
 * @param $vid
 *   The vocabulary to search for terms to add the permission for.
 * @param $role
 *   The role to add the permission to.
 *   Can be the name or the role id or blank for all term permissions.
 * @param $grants
 *   A hash of the grants in the form of $grants['perm'] = boolean
 *   A value of 1 will grant the permission for this user and term.
**/
function taxonomy_access_grant_vocab_update($vid, $rid = null, $edit = null) {
  $tree = taxonomy_get_tree($vid);
  $grant_types = array('view', 'update', 'delete', 'create', 'list');
  $vgrants = $edit[$vid]['vocab'];
  $grants = array();
  foreach ($tree as $term) {
    foreach ($grant_types as $grant) {
      if ($vgrants[$grant] > -1 && $vgrants[$grant] < 3) {
        $grants[$grant] = $vgrants[$grant];
      }
      else {
        $grants[$grant] = $edit[$term->vid]['term'][$term->tid][$grant];
      }
    }
    taxonomy_access_grant_update($term->tid,$rid,$grants);
  }
}

/**
 * Gets permissions for a given role
 * @param $role
 *   The role to retrieve the permissions for.
 *   Can be the name or the role id or blank for all term permissions.
 * @return
 *   A two dimensional hash of the form $grants[tid][grant] where
 *   tid is the term id and
 *   grant is the permission (i.e. 'view','delete',ect.)
 *   this entry in the hash is true if permission is granted, false otherwise
**/
function taxonomy_access_get_grants($role) {
  if (!isset($role)) {
    return false;
  }
  if (isset($role) && !is_numeric($role)) {
    $role = db_result(db_query("SELECT rid FROM {role} WHERE name='$role'"));
  }
  $result = db_query("SELECT * FROM {term_access} WHERE rid='$role'");
  $grants = array();
  while ($grant = db_fetch_array($result)) {
    $tid = $grant['tid'];
    foreach ($grant as $key => $grant_val) {
      if (strpos($key,'grant_') !== FALSE) {
        $grant_name = '';
        $grant_name = str_replace('grant_','',$key);
        if (!isset($grants[$tid][$grant_name]) || !($grants[$tid][$grant_name])) {
          // If there's conflicting DB rules, take the most lenient
          $grants[$tid][$grant_name] = $grant_val;
        }
      }
    }
  }
  return $grants;
}
/**
 * Gets default permissions for a given role
 * @param $role
 *   The role to retrieve the permissions for.
 *   Can be the name or the role id or blank for all term permissions.
 * @return
 *   A two dimensional hash of the form $grants[vid][grant] where
 *   vid is the vocab id and
 *   grant is the permission (i.e. 'view','delete',ect.)
 *   this entry in the hash is true if permission is granted, false otherwise
**/
function taxonomy_access_get_default_grants($role) {
  if (!isset($role)) {
    return false;
  }
  if (isset($role) && !is_numeric($role)) {
    $role = db_result(db_query("SELECT rid FROM {role} WHERE name='$role'"));
  }
  $result = db_query("SELECT * FROM {term_access_defaults} WHERE rid='$role'");
  $grants = array();
  while ($grant = db_fetch_array($result)) {
    $vid = $grant['vid'];
    foreach ($grant as $key => $grant_val) {
      if (strpos($key,'grant_') !== FALSE) {
        $grant_name = '';
        $grant_name = str_replace('grant_','',$key);
        if (!isset($grants[$vid][$grant_name]) || !($grants[$vid][$grant_name])) {
          // If there's conflicting DB rules, take the most lenient
          $grants[$vid][$grant_name] = $grant_val;
        }
      }
    }
  }
  return $grants;
}

/**
 * Implementation of hook_nodeapi().
 */
function taxonomy_access_nodeapi(&$node, $op, $arg = 0) {
  switch ($op) {
    case 'delete':
      // When a node is deleted, delete any relevant permissions.
      db_query('DELETE FROM {node_access} WHERE nid = %d AND realm = \'term_access\'', $node->nid);
      break;

    case 'submit':
      // When TAC grants 'update' access to edit node,
      // Changing $node->uid back to original creator (changed by node_submit)
      if (($node->nid) && !user_access('administer nodes') && (node_access('update', $node))) {
        // Populate the "authored by" field.
        $old_node = node_load($node->nid);
        if ($account = user_load(array('name' => $old_node->name))) {
          $node->uid = $account->uid;
        }
        else {
          $node->uid = 0;
        }
      }
      break;

    // Enter new data into node_access to set permissions for the new node
    case 'update':
      // intentional fall through to insert
    case 'insert':
      _refresh_node_access_table(NULL, $node->nid);
      break;
  }
}

/**
 * Implementation of hook_settings()
 */
function taxonomy_access_settings() {
  $form['taxonomy_access_enabled'] = array( '#title' => 'Taxonomy Access Control Activation',
    '#type' => 'radios', 
    '#default_value' => variable_get('taxonomy_access_enabled', 0),
    '#options' => array( 1 => t('Activate'), 0 => t('Deactivate'))
  );
  return $form;
}

/**
 * Submit the taxonomy_access_settings_form.
 */
function taxonomy_access_settings_form_submit($form_id, $form_values) {
  $op = isset($_POST['op']) ? $_POST['op'] : '';
  if ($op == t('Reset to defaults')) {
    variable_del('taxonomy_access_enabled');
  }
  else {
    $settings_changed = (variable_get('taxonomy_access_enabled', 0) == $form_values['taxonomy_access_enabled']) ? FALSE : TRUE;
    variable_set('taxonomy_access_enabled', $form_values['taxonomy_access_enabled']);
  }
  if ($settings_changed || $op == t('Reset to defaults')) {
    cache_clear_all();
    _taxonomy_access_update_db();
  }
  if ($op == t('Reset to defaults')) {
    drupal_set_message(t('The configuration options of taxonomy access have been reset to their default values.'));
    drupal_goto('admin/settings/taxonomy_access');
  }
  else {
    drupal_set_message(t('The configuration options of taxonomy access have been saved.'));
  }
  return;
}

/**
 * hook_taxonomy is called when changes are made to the taxonomy structure
**/
function taxonomy_access_taxonomy($op, $type, $edit = NULL) {
  if ($type == 'term') {
    switch($op) {
      case 'update':  // don't do anything, nothing on our end has changed
        break;

      case 'insert':  // add new default entries for the new category, don't have to touch node_access since no posts are in there yet
        foreach (user_roles() as $rid => $role) {
          $grants = db_fetch_object(db_query("SELECT * FROM {term_access_defaults} WHERE vid='%d' AND rid='%d'",$edit['vid'], $rid));
          db_query("DELETE FROM {term_access} WHERE tid='%d' AND rid='%d'",$edit['tid'],$rid);
          db_query('INSERT INTO {term_access} VALUES (\'%d\', \'%d\', %d, %d, %d, %d, %d)', $edit['tid'], $rid, $grants->grant_view,$grants->grant_update,$grants->grant_delete,$grants->grant_create,$grants->grant_list);
        }
        break;

      case 'delete': // delete everything from term_access and node_access
        db_query("DELETE FROM {term_access} WHERE tid='%d'",$edit->tid);
        _refresh_node_access_table();
        break;
    }
  }
  return;
}

/**
 * _user hook called when a user event occurs to check for new roles.  It would make sense to have a _role hook
 * instead.  However, that hook doesn't exist so we rely on the _user hook to determine if new roles have been added.
**/
function taxonomy_access_user($op, &$edit, &$user, $category = NULL) {
  if ($op == 'update' || $op == 'insert') {
    // Get list of existing roles
    $result = db_query('SELECT rid FROM {role}');
    while ($rids = db_fetch_array($result)) {
      $current_rids[] = $rids['rid'];
    }
    if (!in_array(0, $current_rids)) {
      $current_rids[] = 0;
    }

    // Get list of roles known to exist from term_access
    $known_rids = array();
    $result = db_query('SELECT DISTINCT rid FROM {term_access}');
    while($rids = db_fetch_array($result)) {
      $known_rids[] = $rids['rid'];
    }
    if (!in_array(0, $known_rids)) {
      $known_rids[] = 0;
    }
    
    if (array_diff($current_rids, $known_rids)) {
      _taxonomy_access_update_db($current_rids, $known_rids);
    } 
  }
}  
  
function _taxonomy_access_update_db($current_rids = NULL, $old_rids = NULL) {
  if (!variable_get('taxonomy_access_enabled', FALSE)) {
    // We delete before insert to avoid inserting a duplicate entry into the database.
    // Without the DELETE query, this can happen when a site admin has already enabled the modules
    // from the settings page and goes back to it and resaves the enabled setting.
    db_query("DELETE FROM {node_access} WHERE nid=0 AND gid=0 AND realm='all'");
    db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)");
  }
  else { // the module was just enabled; node_access table must initialized or updated to reflect the changes since it was disabled:
         // Check for new nodes and assign them permissions in node_access table;
         // Check for deleted nodes and delelte them from node_access;
         // Check for new taxonomies and make appropriate entries into node_access table;
         // Check for new roles and make approprate entries into node_access and term_access table;
         // Check for deleted roles and make appropriate entries into node_access and term_access table;
         // Permissions for nodes will be the same as when the module was previously enabled.
    db_query('DELETE from {node_access} where nid=0 AND gid=0 AND realm=\'all\'');
    
    // BEGIN: term_access table housekeeping
    // Update the term_access table to reflect any changes that may have occured since module was disabled.
    $tids = array();
    $rids = array();

    // Create list of all term and role ids
    $query = db_query('SELECT tid FROM {term_data}');
    while ($result = db_fetch_array($query)) {
      $current_tids[] = $result['tid'];
    }
    $current_tids[] = 0;

    if (!$current_rids) {
      $query = db_query('SELECT rid FROM {role}');
      while ($result = db_fetch_array($query)) {
        $current_rids[] = $result['rid'];
      }
    // $current_rids[] = 0; no default non-roled access
    }
    $current_vids = array();
    $query = db_query('SELECT vid FROM {vocabulary}');
    while ($result = db_fetch_array($query)) {
      $current_vids[] = $result['vid'];
    }

    // Get old list of term and role ids from when term_access was disabled
    $old_tids[] = 0;
    $query = db_query('SELECT DISTINCT(tid) FROM {term_access}');
    while ($result = db_fetch_array($query)) {
      $old_tids[] = $result['tid'];
    }

    if (!$old_rids) {
      $query = db_query('SELECT DISTINCT(rid) FROM {term_access}');
      while($result = db_fetch_array($query)) {
        $old_rids[] = $result['rid'];
      }
    }
    $old_vids = array();
    $query = db_query('SELECT vid FROM {term_access_defaults}');
    while($result = db_fetch_array($query)) {
      $old_vids[] = $result['vid'];
    }

    // Get the difference between old and current
    if (is_array($old_tids))
      $delete_tids = array_diff($old_tids, $current_tids);
    if (is_array($old_rids))  
      $delete_rids = array_diff($old_rids, $current_rids);
    if (is_array($old_vids))  
      $delete_vids = array_diff($old_vids, $current_vids);

    // Delete all rows with role and term ids that no longer exist from the term_access table
    if (is_array($delete_rids)) {
      foreach ($delete_rids as $rid) {
        db_query('DELETE FROM {term_access} WHERE rid = %d', $rid);
      }
    }
    foreach ($delete_tids as $tid) {
      db_query('DELETE FROM {term_access} WHERE tid = %d', $tid);
    }

    // Set permissions for nodes without categories if they aren't already set
    $query = db_query('SELECT tid FROM {term_access} where tid = 0');
    if (!db_result($query) && is_array($current_rids)) {
      foreach ($current_rids as $rid) {
        $query = db_query('SELECT tid FROM {term_access} where tid=0 AND rid=%d', $rid);
        if (!db_fetch_object($query)) {
          db_query('INSERT INTO {term_access} VALUES (0, %d, 1, 0, 0, 1, 1)', $rid);
        }
      }
    }

    // Add new role and term ids to term_access table since module was last disabled and assign them default permissions
    $all_rids = $add_tids = $add_rids = $add_vids = array();
    $add_tids = array_diff($current_tids, $old_tids);
    $add_vids = array_diff($current_vids, $old_vids);
    if (is_array($current_rids) && is_array($old_rids)) {
      $add_rids = array_diff($current_rids, $old_rids);
      $all_rids = array_merge($add_rids, $current_rids);
    }

    // Add role permissions for each new taxonomy terms.
    // nysus : Default permissions assume all users can not read content in the new taxonomy
    // pyromanfo : Drupal default is actually view only, as is the case with node_access
    foreach ($add_tids as $tid) {
      if ($tid != 0) {
        foreach ($all_rids as $rid) {
          db_query('INSERT INTO {term_access} VALUES (%d, %d, 1, 0, 0, 1, 1)', $tid, $rid);
        }
      }
    }

    // Add role permissions for all old taxonomy terms.
    // nysus : Default permissions assume new role does not have access to content in any category
    // pyromanfo : Drupal default is actually view only, as is the case with node_access
    foreach ($current_tids as $tid) {
      if ($tid != 0) {
        if (is_array($add_rids)) {
          foreach ($add_rids as $rid) {
            db_query('INSERT INTO {term_access} VALUES (%d, %d, 1, 0, 0, 1, 1)', $tid, $rid);
          }
        }
      }
    }
    // END: term_access table housekeeping

    // BEGIN: node_access table housekeeping
    // Update the node_access table to reflect any changes that may have occured since module was disabled.

    // Get current list of all nodes
    $current_nids = array();
    $query = db_query('SELECT nid FROM {node}');
    while($result = db_fetch_array($query)) {
      $current_nids[] = $result['nid'];
    }

    // Get list of node ids in the node_access table when module was disabled
    $query = db_query('SELECT nid FROM {node_access} WHERE realm = \'term_access\'');
    $old_nids = array();
    while($result = db_fetch_array($query)) {
      $old_nids[] = $result['nid'];
    }
    $old_nids = array_unique($old_nids);

    // Get the difference between old and current
    $delete_nids = array_diff($old_nids, $current_nids);

    // Delete all node ids that no longer exist from the term_access table
    foreach ($delete_nids as $nid) {
      db_query('DELETE FROM {node_access} WHERE nid = %d AND realm = \'term_access\'', $nid);
    }

    // Setup defaults
    if (is_array($delete_rids)) {
      foreach ($delete_rids as $rid) {
        db_query("DELETE FROM {term_access_defaults} WHERE rid='%d'",$rid);
      }
    }
    if (is_array($delete_vids)) {
      foreach ($delete_vids as $vid) {
        db_query("DELETE FROM {term_access_defaults} WHERE vid='%d'",$vid);
      }
    }

    if (is_array($current_vids)) {
      foreach ($current_vids as $vid) {
        if (is_array($add_rids)) {
          foreach ($add_rids as $rid) {
            db_query("DELETE FROM {term_access_defaults} WHERE vid=%d AND rid=%d", $vocab->vid, $rid);
            db_query("INSERT INTO {term_access_defaults} VALUES (%d,%d,1,0,0,1,1)",$vocab->vid,$rid);
          }
        }
      }
    }

    if (is_array($add_vids)) {
      foreach ($add_vids as $vid) {
        if (is_array($current_rids)) {
          foreach ($current_rids as $rid) {
            db_query("DELETE FROM {term_access_defaults} WHERE vid=%d AND rid=%d", $vocab->vid, $rid);
            db_query("INSERT INTO {term_access_defaults} VALUES (%d,%d,1,0,0,1,1)",$vocab->vid,$rid);
          }
        }
      }
    }

    // Update node_access_table to reflect the changes made to term_access table
    _refresh_node_access_table();
  }
}

// Update the node_access table to reflect any changes made to the term_access table
function _refresh_node_access_table($rid = NULL, $nid = NULL ) {
  if (isset($nid)) {
    $where_clauses[] =  "n.nid = $nid";
    $del_where_clauses[] =  "nid = $nid";
  }
  if (isset($rid)) {
    $where_clauses[] = "ta.rid = $rid";
    $del_where_clauses[] = "gid = $rid";
  }

  $del_where = (isset($del_where_clauses) && count($del_where_clauses) > 0) ? implode(' AND ', $del_where_clauses) . ' AND ' : '';
  db_query("DELETE FROM {node_access} WHERE $del_where realm='term_access'");

  $where = (isset($where_clauses) && count($where_clauses) > 0) ? ' WHERE ' . implode(' AND ', $where_clauses) : '';
  $result = db_query("SELECT n.nid, ta.rid, BIT_OR(ta.grant_view) AS grant_view, BIT_OR(ta.grant_update) AS grant_update, BIT_OR(ta.grant_delete) AS grant_delete FROM {term_node} n INNER JOIN {term_access} ta ON n.tid = ta.tid $where GROUP BY n.nid, ta.rid");

  while($row = db_fetch_object($result)) {
    _tac_node_access_save($row);
  }

  // Determine which nodes don't belong to a category
  $where = (isset($where_clauses) && count($where_clauses) > 0) ? ' AND ' . implode(' AND ', $where_clauses) : '';
  $result = db_query("SELECT n.nid, ta.* FROM {node} n LEFT JOIN {term_node} t ON t.nid=n.nid LEFT JOIN {term_access} ta ON ta.tid = 0 WHERE t.nid IS NULL $where", $rid);
  while($row = db_fetch_object($result)) {
    _tac_node_access_save($row);
  }
}

function _tac_node_access_save ($row) {
  if ($row) {
    $grant_view = ($row->grant_view == 1) ? 1 : 0;
    $grant_update = ($row->grant_update == 1) ? 1 : 0;
    $grant_delete = ($row->grant_delete == 1) ? 1 : 0;
    if ($grant_view || $grant_update || $grant_delete) {
      db_query('INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete) VALUES (%d, %d, \'term_access\', %d, %d, %d)', $row->nid, $row->rid, $grant_view, $grant_update, $grant_delete);
    }
  }
}

/**
 * Implementation of hook_db_rewrite_sql()
 */
function taxonomy_access_db_rewrite_sql($query, $table, $field) {
  if (!user_access('administer taxonomy') && ($field =='vid' || $field =='tid')) {
    global $user;
    $op = (arg(0) == 'node' && (arg(1) == 'add' || arg(2) == 'edit')) ? 'create' : 'list';
    
    // let's cache
    static $taxonomy_access_sql_clause;
    $clause = array();
    
    if (!isset($taxonomy_access_sql_clause)) {
      $taxonomy_access_sql_clause = array();
    }
    if (!isset($taxonomy_access_sql_clause[$op][$field]))  {
      if (isset($user) && is_array($user->roles)) {
        $rids = array_keys($user->roles);
      } 
      else {
        $rids[] = 1;
      }
      
      $sql = db_query("SELECT t.tid, d.vid, BIT_OR(t.grant_$op) AS grant_$op FROM {term_access} t INNER JOIN {term_data} d ON t.tid=d.tid WHERE t.rid in ('".implode("','",$rids)."') AND grant_$op = 1 group by t.tid, d.vid");
      while ($result = db_fetch_object($sql)) {
        $tids[]= $result->tid;
        $vids[$result->vid]= $result->vid;
      }
      $clause[$op]['tid'] = $tids ? implode("','",$tids) : '-1';
      $clause[$op]['vid']= $vids ? implode("','",$vids) : '-1';
      $taxonomy_access_sql_clause = $clause;
    }
    else {
      $clause[$op][$field] = $taxonomy_access_sql_clause[$op][$field];
    }
    $return['where'] = "$table.$field IN ('".$clause[$op][$field]."')";
    return $return;
  } 
  else {
    return array();
  }
}
?>
