<?php
  // $Id: civicrm_group_roles.module,v 1.1.4.2 2008/10/14 00:20:36 matt2000 Exp $

  /* @file
   * A simple module that adds a user to groups based on Role.
   */

  /**
   * Implementation of hook_help()
   */
function civicrm_group_roles_help($section, $args = array() ) 
{ 
    switch ($section) { 
    case 'admin/modules#description': 
        return t('Adds users to a matching CiviCRM group on account creation, update, or login for each of their Drupal roles.');
    default :
        return;
    } 
} 


/**
 * Implementation of hook_user().
 * This provides sync from Drupal -> CiviCRM
 * TO DO: Remove User From Group when role is removed
 */
function civicrm_group_roles_user($op, $edit, &$user, $category = NULL) 
{
    //$debug_mode = TRUE;
    
    if ($op == 'login' OR $op == 'after_update' OR $op == 'insert') {
        
        //get user's roles, ignoring built-in roles
        if ($op == 'insert') { // $user->roles isn't set on insert, we have to get them from the form directly...
            $all = user_roles(TRUE);
            $rids = $edit['roles']; //the insert form only stores role ids. :-P
            $rids = is_array($rids) ? $rids : array($rids);
            $roles = array_intersect_key($all, $rids);
        } else {
            $roles = array_diff($user->roles, array('anonymous user', 'authenticated user'));
        }
        
        if ($roles) { // make sure user has other roles other than authenticated
            
            //init
            civicrm_initialize(true);
            require_once 'api/v2/Contact.php';
            require_once 'api/v2/Group.php';
            require_once 'api/v2/GroupContact.php';
            
            //find the contact record
            $params = array('email' => $user->mail);
            $contact =& civicrm_contact_get($params);
            $contact = array_pop($contact);
            
            $contact_id = $contact['contact_id'];
            
            if ( !empty( $contact ) && isset($contact['contact_id'])) {
                
                //loop over user's roles
                foreach ($roles as $rid => $role) {
                    
                    //find the group(s) for the role
                    $query = db_query("SELECT group_id FROM {civicrm_group_roles_rules} WHERE role_id=%d", $rid);
                    
                    while ($gid = db_result($query)) {    
                        //add the contact
                        $contacts = array($contact);
                        $gparams = array('group_id' => $gid, 'contact_id' => $contact_id);
                        $result = civicrm_group_contact_add($gparams);
                        
                        if ($result['is_error'] > 0) {
                            $msg = 'Error: Unable to sync role @role';
                            $variables = array('@role' => $role);
                            watchdog('civicrm_group_roles', $msg, $variables);
                            if ($debug_mode) drupal_set_message(t($msg, $variables));
                        } elseif ($debug_mode && $result['added'] > 0) {
                            drupal_set_message("Added user $user->name to Group: $role");
                            drupal_set_message(var_export($result, TRUE));
                        } elseif ($debug_mode) {
                            drupal_set_message("User $user->name NOT added to Group: $role");
                            drupal_set_message(var_export($result, TRUE));
                        }
                    } //end while
                    
                    
                } //end foreach
                
            } else {
                $msg = 'CiviCRM contact not found for @mail';
                $variables = array( '@mail' => $user->mail );
                watchdog('civicrm',  $msg, $variables);
                if ($debug_mode) drupal_set_message(t($msg, $variables));
            } //end if $contact
        } //endif $roles

        //sync for civicrm to drupal for user role
        if ( $op == 'login' ) {
            //civicrm to drupal sync
            //assign user role if contact is present in group
            $roles = array_diff( $all = user_roles(TRUE), array( 'anonymous user', 'authenticated user' ) );
            civicrm_initialize(true);
            require_once 'api/v2/Contact.php';
            require_once 'api/v2/Group.php';
            require_once 'api/v2/GroupContact.php';
            require_once 'api/v2/UFGroup.php';

            //find the contact record
            $contact = civicrm_uf_match_id_get( $user->uid );

            $params  = array( 'contact_id' => $contact );
            //find the groups for contact
            $groups  = civicrm_group_contact_get( $params );

            foreach ( $groups as $key ) {
                $rid = db_result(db_query("SELECT role_id FROM {civicrm_group_roles_rules} WHERE group_id IN (%d)", $key['group_id'] ));
                if ( $rid ) {
                    $role_name = db_result(db_query('SELECT name FROM {role} WHERE rid = %d', $rid));
                    //assign the role for user
                    civicrm_group_roles_add_role($user->uid, $rid, $role_name);
                }
            }
        }
    } //endif $op
}


/**
 * Implementation of hook_civicrm_post().
 * This provides sync from CiviCRM -> Drupal
 */
function civicrm_group_roles_civicrm_post( $op, $objectName, $objectId, &$objectRef )
{
    if ($objectName != 'GroupContact') {
        return; // We only care about Group contact operations, so bail on anything else.
    }

    $group_id = $objectId;
    
    //Do we have any Role sync rules for this group?
    $query = db_query("SELECT role_id FROM {civicrm_group_roles_rules} WHERE group_id=%d", $group_id);   
    while ($rid = db_result($query)) {
        
        $role_name = db_result(db_query('SELECT name FROM {role} WHERE rid = %d', $rid)); //we need this to properly save the user account below.
 				
        foreach ($objectRef AS $contact_id) {
            
            //Do we have a Drupal Account?
            require_once 'api/v2/UFGroup.php';
            $uid = civicrm_uf_id_get( $contact_id );
            if (!$uid) { //make sure we have an actual user account
                continue;
            }
            
            switch ($op) {
            case 'create':
            case 'edit':
                //Contact added or re-joined to group; add to corresponding role
                civicrm_group_roles_add_role($uid, $rid, $role_name);       
                break;
                
            case 'delete':
                //Contact is removed from group.
                //Remove the role, but only if the contact is in no other groups that grant this role
                if (!civicrm_role_granted_by_other_group($contact_id, $rid, $group_id)) {
                  civicrm_group_roles_del_role($uid, $rid, $role_name);
                }
                break;
            } //end switch
        } //end foreach
    }//end while
}

/**
 * Helper function to add a role to a given user
 * Copied from user.module function user_multiple_role_edit()
 * @param $uid The user id of the account to modify
 * @param $rid The role id being added
 */
function civicrm_group_roles_add_role($uid, $rid, $role_name)
{
    $account = user_load(array('uid' => (int)$uid));
    // Skip adding the role to the user if they already have it.
    if ($account !== FALSE && !isset($account->roles[$rid])) {
        $roles = $account->roles + array($rid => $role_name);
        user_save($account, array('roles' => $roles));
    }
}

/**
 * Helper function to remove a role from a given user
 * Copied from user.module function user_multiple_role_edit()
 * @param $uid The user id of the account to modify
 * @param $rid The role id being removd
 */
function civicrm_group_roles_del_role($uid, $rid, $role_name)
{
    
    $account = user_load(array('uid' => (int)$uid));
    // Skip removing the role from the user if they already don't have it.
    if ($account !== FALSE && isset($account->roles[$rid])) {
        $roles = array_diff($account->roles, array($rid => $role_name));
        user_save($account, array('roles' => $roles));
    }
}

/** 
 * Count the groups this contact is in which grant this role
 */
function civicrm_role_granted_by_other_group($contact_id, $rid, $group_id) {
  civicrm_initialize(true);
  require_once 'api/v2/GroupContact.php';
  
  //get all the groups this contact belongs to
  $params = array('contact_id' => $contact_id);
  $result = civicrm_group_contact_get($params);
  
  if (civicrm_error ($result )) {
    drupal_set_message(t($result['error_message']));
  } 
  else {
    //contact is not in *any* other groups so delete the role
    if (count($result)==0) {
      return (false);
    }
    //contact is in some groups, determine if any of them grant this role
    else{
      //get ids of the groups as , delim list
      foreach ($result as $grpid => $grp) {
        $grpsuserin.= $grp["group_id"].',';
      }        
      $grpsuserin = drupal_substr($grpsuserin, 0, -1);       
      
      //do any of them grant this role?
      $rolecount = db_result(db_query('SELECT count(*) FROM {civicrm_group_roles_rules} WHERE group_id in(%s) AND role_id=%d', $grpsuserin, $rid));
      
      //flip to boolean
      return ($rolecount>0);
    }
  }
}



/**
 * Implementation of hook_menu().
 */
function civicrm_group_roles_menu( ) 
{
    $items[] = array();
    $items['admin/settings/civicrm_group_roles'] = array(
                                                         'title' => t('CiviGroup Roles Sync'),
                                                         'description' => t('Add/remove association rules and configure settings. Also perform manual synchronization.'),
                                                         'page callback' => 'civicrm_group_roles_show_rules',
                                                         'access callback' => 'user_access',
                                                         'access arguments' => array('access settings'),
                                                         'type' => MENU_NORMAL_ITEM,
                                                         );      
    $items['admin/settings/civicrm_group_roles/show_rules'] = array(
                                                                    'title' => t('List Association Rule(s)'),
                                                                    'access callback' => 'user_access',
                                                                    'access arguments' => array('access settings'),
                                                                    'weight' => -5,
                                                                    'type' => MENU_DEFAULT_LOCAL_TASK,
                                                                    );       
    $items['admin/settings/civicrm_group_roles/add_rule'] = array(
                                                                  'title' => t('Add Association Rule'),
                                                                  'page callback' => 'drupal_get_form',
                                                                  'page arguments' => array('civicrm_group_roles_add_rule_form'),
                                                                  'access callback' => 'user_access',
                                                                  'access arguments' => array('access settings'),
                                                                  'type' => MENU_LOCAL_TASK,
                                                                  ); 
    return $items;
}

/**
 * Implementation of hook_perm().
 */
function civicrm_group_roles_perm() {
    return array('access settings');
}


/**
 * Show stored association rules and delete rules.
 */
function civicrm_group_roles_show_rules($action = NULL, $id = NULL) 
{
    if ($action == 'delete') {
        if (is_numeric($id)) {
            $delete = db_query('DELETE FROM {civicrm_group_roles_rules} WHERE id = %d', $id);

            if ($delete) {
                drupal_set_message(t('Rule ID !1 was successfully deleted.', array('!1' => $id)));
            } else {
                drupal_set_message(t('There was an error deleting the association rule. Please check your database settings and try again. If you continue to get this error message then try to reinstall CiviGroup Roles Sync.'), $type = 'error');
            }
        }
    }
    // get drupal roles
    $roles = user_roles(TRUE);
    
    // get civicrm groups
    civicrm_initialize(true);
    require_once 'api/v2/Group.php';
    $params = array( );
    $groups =& civicrm_group_get($params);

    //Begin building main output table.
    $header = array(t('Rule ID'), t('Rule Name ("CiviCRM Group" <--> "Drupal Role")'), t('Operation'));
    $data = array( );
    
    $rules = db_query('SELECT * FROM {civicrm_group_roles_rules} ORDER BY id ASC');
    while ( $result = db_fetch_object($rules) ) {
        $data[] = array(
                        check_plain($result->id), 
                        check_plain($groups[$result->group_id]['title'] . " <--> " . $roles[$result->role_id]),
                        l(t('edit'), 'admin/settings/civicrm_group_roles/add_rule' . '/' . check_plain($result->id)) . '&nbsp;&nbsp;' . l(t('delete'), 'admin/settings/civicrm_group_roles/delete' . '/' . check_plain($result->id)),
                        );
    }
    
    if (!empty($data)) {
        $output = t('Use the "Add Association Rule" form to add new rules.');
        $output .= theme('table', $header, $data);
    } else {
        $output = t('There are no rules currently set. Use the "Add Association Rule" form to add one.');
    }
    return $output;
}

/**
 * Implementation of hook_form(). Add/edit association rules.
 *
 * @ingroup forms
 * @see civimember_roles_add_rule_validate()
 * @see civicrm_group_roles_add_rule_submit()
 */
function civicrm_group_roles_add_rule_form($form, $edit_id = NULL) 
{
    // retrieve drupal roles
    $roles = user_roles(TRUE);

    // get civicrm groups
    civicrm_initialize(true);
    require_once "CRM/Core/PseudoConstant.php";
    $groups =& CRM_Core_PseudoConstant::group( );
    
    //Let's get rid of the authenticated role as it is a useless option for this module
    unset($roles[2]);
    
    //Begin add form
    $form = array();      
    $form['add_rule'] = array(
                              '#type' => 'fieldset',
                              '#title' => t('Association Rule'),
                              '#description' => t('Choose a CiviCRM Group and a Drupal Role below.'),
                              '#tree' => TRUE,
                              '#parents' => array('add_rule'), 
                              );   
    $form['add_rule']['select_group'] = array(
                                              '#type' => 'select',
                                              '#title' => t('CiviCRM Group'),
                                              '#options' => array(0 => t('-- Select --')) + $groups,
                                              '#required' => TRUE,
                                              );    
    $form['add_rule']['select_role'] = array(
                                             '#type' => 'select',
                                             '#title' => t('Drupal Role'),
                                             '#options' => array(0 => t('-- Select --')) + $roles,
                                             '#required' => TRUE,
                                             );    
    $form['submit'] = array(
                            '#type' => 'submit',
                            '#value' => t('Add association rule'),
                            );
    
    //Begin edit form
    if (!empty($edit_id) && is_numeric($edit_id)) {
        $default_values = db_fetch_object(db_query('SELECT * FROM {civicrm_group_roles_rules} WHERE id = %d', $edit_id));
        if (!$default_values) {
            drupal_set_message(t('There was an error in obtaining the association rule for edit. Please check your database settings and try again. If you continue to get this error message then try to reinstall CiviGroup Roles Sync.'));
        }
        
        
        //Alter above add form with default values.
        $form['add_rule']['select_group']['#default_value'] = check_plain($default_values->group_id);
        $form['add_rule']['select_role']['#default_value'] = check_plain($default_values->role_id);
        $form['edit_flag'] = array('#type' => 'hidden', '#value' => check_plain($edit_id));
        $form['submit']['#value'] = t('Edit association rule');
    }
    
    return $form;
}


/**
 * Implementation of hook_validate() for the add/edit rule form.
 */
function civicrm_group_roles_add_rule_form_validate($form, &$form_state) 
{
    //Make sure there is a CiviMember Membership Type and a Drupal Role selected.
    if (is_numeric($form_state['values']['add_rule']['select_group']) && is_numeric($form_state['values']['add_rule']['select_role'])) {
        if ($form_state['values']['add_rule']['select_group'] == 0 || $form_state['values']['add_rule']['select_role'] == 0) {
            form_set_error('add_rule', t('You must select both a CiviCRM Group and a Drupal Role from the "Association Rule" section.'));
        }
    } else {
        form_set_error('add_rule', t('Please select CiviCRM Group and Drupal Role.'));
    }
    
    //Validate edit flag if set
    if (isset($form_state['values']['edit_flag']) && !is_numeric($form_state['values']['edit_flag'])) {
        for_set_error('', t('Edit flag was not numeric.'));
    }
}

/**
 * Implementation of hook_submit() for the add/edit rule form.
 */
function civicrm_group_roles_add_rule_form_submit($form, &$form_state) 
{
    //If edit_flag is set then process as an edit form, if not then process as an add form.
    if (isset($form_state['values']['edit_flag'])) {
        $edit_rule = db_query('UPDATE {civicrm_group_roles_rules}
                          SET role_id = %d, group_id = %d
                          WHERE id = %d',
                              (int) $form_state['values']['add_rule']['select_role'],
                              (int) $form_state['values']['add_rule']['select_group'],
                              $form_state['values']['edit_flag']);
        if ($edit_rule) {
            drupal_set_message(t('Your association rule has been edited.'));
        } else {
            drupal_set_message(t('There was an error editing the association rule. Please check your database settings and try again. If you continue to get this error message then try to reinstall CiviGroup Roles Sync.'), $type = 'error');
        }
    } else {
        $add_rule = db_query('INSERT INTO {civicrm_group_roles_rules} (role_id, group_id )
                          VALUES( %d, %d)', 
                             (int) $form_state['values']['add_rule']['select_role'], 
                             (int) $form_state['values']['add_rule']['select_group'] 
                             );
        if ($add_rule) {
            drupal_set_message(t('Your association rule has been added.'));
        } else {
            drupal_set_message(t('There was an error adding the association rule. Please check your database settings and try again. If you continue to get this error message then try to reinstall CiviGroup Roles Sync.'), $type = 'error');
        }
    }
}

