<?php

// $Id: ec_roles.module,v 1.1.2.12 2007/02/24 12:28:21 sammys Exp $

/**
 * Enables role assignments as the result of a product purchase.
 * @author Sammy Spets (thanks to Synerger Pty Ltd, Australia)
 */

define('ECROLES_VARNAME_DISALLOWED', 'ec_roles_disallowed');

/**
 * Implementation of hook_help().
 */
function ec_roles_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return '<b>EC:</b> '.t('Enables the sale of site access levels (roles)');
  }
}

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

  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/store/settings/ec_roles',
      'title' => 'role adjustments',
      'callback' => 'ec_roles_ec_settings',
      'access' => user_access('administer store'),
      'type' => MENU_NORMAL_ITEM,
    );
  }

  return $items;
}

function ec_roles_ec_settings() {
  $form[ECROLES_VARNAME_DISALLOWED] = array(
    '#type' => 'checkboxes',
    '#title' => t('Roles that can not be adjusted at product purchase or expiry'),
    '#default_value' => variable_get(ECROLES_VARNAME_DISALLOWED, array()),
    '#options' => user_roles(1),
    '#description' => t('Select the roles you want to hide from product edit forms.')
  );
  return system_settings_form('ec_roles_ec_settings_form', $form);
}

/**
 * Implementation of hook_nodeapi().
 */
function ec_roles_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  if ($node->type != 'product') {
    return;
  }

  switch ($op) {
    case 'load':
      ec_roles_load($node);
      break;
    case 'validate':
      ec_roles_validate($node);
      break;
    case 'insert':
    case 'update':
      ec_roles_save($node);
      break;
    case 'view':
      return theme('roles_view', $node);
      break;
  }
}

/**
 * Implementation of hook_transactionapi()
 */
function ec_roles_ec_transactionapi(&$txn, $op, $a3 = NULL, $a4 = NULL) {
  if ($op == 'load') {
    $items =& $txn->items;
    foreach  ($items as $key => $val) {
      ec_roles_load($items[$key]);
    }
  }
}

/**
 * Loads the given product with the values from the ec_roles table corresponding
 * to the product's vid.
 *
 * @param $node Product node
 */
function ec_roles_load(&$node) {
  $roles = db_fetch_array(db_query("SELECT buy_add, buy_del, exp_add, exp_del FROM {ec_roles} WHERE vid = %d", $node->vid));
  if (is_array($roles)) {
    $node->roles = array();
    foreach ($roles as $key => $value) {
      $node->roles[$key] = unserialize($value);
    }
  }
}

/**
 * Validates the given product form data.
 *
 * @param $node Product form data to validate
 */
function ec_roles_validate(&$node) {
  if (module_exist('ec_recurring')) {
    if (!ec_recurring_is_schedule_selected($node) && is_array($node->roles['exp_del']) && count(array_filter($node->roles['exp_del']))) {
      form_set_error('sid', t('A recurring schedule must be selected for roles to be removed at product expiry.'));
    }
  
    if (!ec_recurring_is_schedule_selected($node) && is_array($node->roles['exp_add']) && count(array_filter($node->roles['exp_add']))) {
      form_set_error('sid', t('A recurring schedule must be selected for roles to be added at product expiry.'));
    }
  }
}

/**
 * Saves the roles related information for the given product.
 *
 * @param $node Product to save roles information for
 * @return TRUE if roles information was saved successfully. Otherwise FALSE.
 */
function ec_roles_save(&$node) {
  $roles = drupal_map_assoc(array_keys(user_roles(1)));

  if (module_exist('ec_recurring')) {
    // make sure we don't save any expiry roles if the product doesn't
    // have an expiry schedule
    if (!ec_recurring_is_schedule_selected($node) && count(array_filter($node->exp_del))) {
      $node->exp_del = array();
      drupal_set_message(t('Setting for roles removed at expiry has been cleared because no recurring schedule was set.'));
    }
    if (!ec_recurring_is_schedule_selected($node) && count(array_filter($node->exp_add))) {
      $node->exp_add = array();
      drupal_set_message(t('Setting for roles added at expiry has been cleared because no recurring schedule was set.'));
    }
  }

  // serialize the arrays with only the selected roles.
  $buy_add = serialize(array_intersect_assoc($roles, $node->buy_add));
  $buy_del = serialize(array_intersect_assoc($roles, $node->buy_del));
  $exp_del = serialize(array_intersect_assoc($roles, $node->exp_del));
  $exp_add = serialize(array_intersect_assoc($roles, $node->exp_add));

  $affected = db_affected_rows(db_query("UPDATE {ec_roles} SET buy_add = '%s', buy_del = '%s', exp_del = '%s', exp_add = '%s' WHERE vid = %d", $buy_add, $buy_del, $exp_del, $exp_add, $node->vid));
  
  if (!$affected) {
    $affected = db_affected_rows(db_query("INSERT INTO {ec_roles} (vid, buy_add, buy_del, exp_del, exp_add) VALUES (%d, '%s', '%s', '%s', '%s')", $node->vid, $buy_add, $buy_del, $exp_del, $exp_add));
  }

  return $affected;
}

/**
 * Returns an array (rid => role) of allowed roles for use in forms.
 *
 * @return Array of (rid => role name)
 */
function ec_roles_get_list() {
  $roles = user_roles(1);
  $disallowed = variable_get(ECROLES_VARNAME_DISALLOWED, array());
  foreach ($disallowed as $key => $val) {
    if ($val) {
      unset($roles[$key]);
    }
  }
  return $roles;
}

/**
 * Implementation of hook_form_alter().
 */
function ec_roles_form_alter($form_id, &$form) {
  if (product_form_is_product($form_id, $form)) {
    $form['product']['roles'] = array(
      '#type' => 'fieldset',
      '#title' => 'Role assignments',
      '#weight' => -12,
      '#collapsible' => TRUE
    );

    $roles = ec_roles_get_list();
    $form['product']['roles']['buy_add'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Roles assigned at purchase'),
      '#default_value' => $form['#node']->roles['buy_add'],
      '#options' => $roles,
      '#description' => t('The customer receives the roles selected here when payment is received.'),
    );
    $form['product']['roles']['buy_del'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Roles removed at purchase'),
      '#default_value' => $form['#node']->roles['buy_del'],
      '#options' => $roles,
      '#description' => t('Roles selected here will be removed from the customer when payment is received.'),
    );

    // this will only be accessible when the product is a recurring one
    $disable = (!is_array($form['#node']->schedule));
    $msg = (!$disable ? '' : ' '. t('<b>NOTE:</b> To enable this control save a recurring schedule for this product (requires %link module).', array('%link' => l('ec_recurring', 'admin/modules'))));
    $form['product']['roles']['exp_del'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Roles removed at expiry'),
      '#default_value' => ($disable ? array() : $form['#node']->roles['exp_del']),
      '#options' => $roles,
      '#attributes' => ($disable ? array('disabled' => TRUE) : array()),
      '#description' => t('Roles selected here will be removed from the customer when this product expires.').$msg,
    );
    $form['product']['roles']['exp_add'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Roles added at expiry'),
      '#default_value' => ($disable ? array() : $form['#node']->roles['exp_add']),
      '#options' => $roles,
      '#attributes' => ($disable ? array('disabled' => TRUE) : array()),
      '#description' => t('Roles selected here will be added to the customer when this product expires.').$msg,
    );
  }
}

/**
 * Implementation of hook_ecommerceapi()
 */
function ec_roles_ecommerceapi(&$txn, $op) {
  global $user;
  
  switch ($op) {
    case 'on payment completion':
      // only do the processing if the transaction has a uid
      // otherwise the processing must be handled by the useraccapi
      if ($txn['uid']) {
        ec_roles_process_purchase($txn);
        $txn['ec_roles_processed'] = TRUE;
      }
      break;
  }
}

/**
 * Implementation of hook_useraccapi()
 */
function ec_roles_useraccapi(&$txn, $op) {
  if ($op == 'account activated' && !$txn['ec_roles_processed']) {
    ec_roles_process_purchase($txn);
    $txn['ec_roles_processed'] = TRUE;
  }
}

/**
 * Processes the role changes at purchase time. This function does
 * no checking on the timing for the operation.
 *
 * @param $txn Transaction array (NOT OBJECT)
 */
function ec_roles_process_purchase(&$txn) {
  // Go through each of the products purchased
  $rolelist = user_roles(1);
  for ($i = 0; $i < count($txn['items']); $i++) {      
    $node =& $txn['items'][$i]; // easy referencing
       
    ec_roles_load($node);

    // only process it if there is an buy_add or buy_del value
    if (is_array($node->roles['buy_add']) || is_array($node->roles['buy_del'])) {
      ec_roles_atpurchase($txn, $node, $rolelist);
    }
  }
}

/**
 * Process role additions at purchase time and set the roles
 * in the system.
 *
 * @param $txn Transaction for the purchase
 * @param $node Product for which roles are to be processed
 * @param $rolelist List of roles available in the system
 */
function ec_roles_atpurchase(&$txn, &$node, $rolelist) {
  $added = array();
  $removed = array();
  
  $disallowed = variable_get(ECROLES_VARNAME_DISALLOWED, array());

  foreach ($node->roles['buy_add'] as $rid) {
    if ($disallowed[$rid]) {
      continue;
    }

    if (!db_num_rows(db_query("SELECT * FROM {users_roles} WHERE uid = %d AND rid = %d", $txn['uid'], $rid))) {
      if (db_affected_rows(db_query("INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)", $txn['uid'], $rid))) {
        $added[] = $rolelist[$rid];
      } else {
        watchdog('ec_roles', t('Could not add purchased role "%role" to user %user.', array('%role' => $rolelist[$rid], '%user' => l($txn['uid'], "user/{$txn['uid']}/edit"))), WATCHDOG_ERROR, l(t('view transaction'), "store/transaction/view/{$txn['txnid']}"));
      }
    }
  }
  
  if (count($added)) {
    watchdog('ec_roles', t('Roles added to user %user: %roles', array('%user' => l($txn['uid'], "user/{$txn['uid']}/edit"), '%roles' => implode(', ', $added))), WATCHDOG_NOTICE, l(t('view transaction'), "store/transaction/view/{$txn['txnid']}"));
  }
  
  foreach ($node->roles['buy_del'] as $rid) {
    if ($disallowed[$rid]) {
      continue;
    }

    if (db_affected_rows(db_query("DELETE FROM {users_roles} (uid, rid) VALUES (%d, %d)", $txn['uid'], $rid))) {
      $removed[] = $rolelist[$rid];
    } else {
      watchdog('ec_roles', t('Could not remove purchased role "%role" from user %user.', array('%role' => $rolelist[$rid], '%user' => l($txn['uid'], "user/{$txn['uid']}/edit"))), WATCHDOG_ERROR, l(t('view transaction'), "store/transaction/view/{$txn['txnid']}"));
    }
  }
  
  if (count($removed)) {
    watchdog('ec_roles', t('Roles removed from user %user: %roles', array('%user' => l($txn['uid'], "user/{$txn['uid']}/edit"), '%roles' => implode(', ', $removed))), WATCHDOG_NOTICE, l(t('view transaction'), "store/transaction/view/{$txn['txnid']}"));
  }
}

/**
 * Process role additions at expiry time and delete the roles from
 * the system.
 *
 * This function increments $GLOBALS['expirations']['roles_removed'] each time
 * it removes a role.
 *
 * @param $node Product for which roles are processed. This function expects
 * $node->expiry_schedule to be set to the expiring entry from
 * ec_recurring_schedule and $node->uid to be the user that purchased the
 * product.
 */
function ec_roles_atexpiry($node) {
  global $expirations;
 
  $disallowed = variable_get(ECROLES_VARNAME_DISALLOWED, array());
  
  // Do nothing if the product was previously renewed
  if ($node->expiry_schedule['status'] == ECRECURRING_STATUS_RENEWED) {
    return;
  }

  if (!isset($expirations['roles_removed'])) {
    $expirations['roles_removed'] = 0;
  }
  
  foreach ((array)$node->roles['exp_del'] as $rid) {
    if ($disallowed[$rid]) {
      continue;
    }
    
    if (db_affected_rows(db_query("DELETE FROM {users_roles} WHERE uid = %d AND rid = %d", $node->uid, $rid))) {
      $expirations['roles_removed']++;
    }
  }

  if (!isset($expirations['roles_removed'])) {
    $expirations['roles_added'] = 0;
  }

  foreach ((array)$node->roles['exp_add'] as $rid) {
    if ($disallowed[$rid]) {
      continue;
    }
  
    if (!db_result(db_query("SELECT rid FROM {users_roles} WHERE uid = %d AND rid = %d", $node->uid, $rid)) && db_affected_rows(db_queryd("INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)", $node->uid, $rid))) {
      $expirations['roles_added']++;
    }
  }
}

/**
 * Implementation of hook_recurringapi().
 */
function ec_roles_recurringapi($op, &$obj, $val1 = NULL, $val2 = NULL) {
  switch ($op) {
    case 'on expiry':
      ec_roles_atexpiry($obj);
      break;
    case 'cron report':
      print "Roles removed: ".$GLOBALS['expirations']['roles_removed']."\n\n";
      break;
  }
}

