<?php
// $Id: sms.module,v 1.8.2.9.2.3 2009/06/02 14:41:01 diggersf Exp $

/**
 * @file
 * The core of the SMS Framework. Provides gateway managment and API for
 * sending and receiving SMS messages.
 */
 
define('SMS_CARRIER_DEFAULT', 0);
define('SMS_CARRIER_OVERRIDDEN', 1);
define('SMS_CARRIER_NORMAL', 3);

/**
 * Sends a message using the active gateway.
 * 
 * @param $number
 *   The destination number.
 * 
 * @param $message
 *   The text of the messsage to send.
 * 
 * @param $options
 *   An array of dditional properties as defined by gateway modules.
 */
function sms_send($number, $message, $options = array()) {
  $gateway = sms_default_gateway();
  
  foreach (module_implements('sms_send') as $module) {
    $function = $module .'_sms_send';
    $function($number, $message, $options, $gateway);
  }

  if (function_exists($gateway['send'])) {
    $response = $gateway['send']($number, $message, $options);
  }
  return sms_handle_result($response, $number, $message);
}

/**
 * Callback for incoming messages. Allows gateways modules to pass messages in
 * a standard format for processing.
 * 
 * @param $number
 *   The sender's mobile number.
 * 
 * @param $message
 *   The content of the text message.
 */
function sms_incoming($number, $message, $options = array()) {
  if (module_exists('rules')) {
    rules_invoke_event('sms_incoming', $number, $message);
  }

  // Execute three phases
  module_invoke_all('sms_incoming', 'pre process', $number, $message, $options);
  module_invoke_all('sms_incoming', 'process', $number, $message, $options);
  module_invoke_all('sms_incoming', 'post process', $number, $message, $options);
}

/**
 * Returns the current default gateway.
 */
function sms_default_gateway() {
  return sms_gateways('gateway', variable_get('sms_default_gateway', 'log'));
}

/**
 * Implementation of hook_gateway_info().
 */
function sms_gateway_info() {
  return array(
    'log' => array(
      'name' => t('Log only'),
      'send' => 'sms_send_log',
    ),
  );
}

function sms_send_log($number, $message, $options) {
  watchdog('sms', 'SMS message sent to %number with the text: @message', array('%number' => $number, '@message' => $message), WATCHDOG_INFO);
  return array('status' => TRUE);
}

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

  $items['admin/smsframework'] = array(
    'title' => 'SMS Framework',
    'description' => 'Control how your site uses SMS.',
    'position' => 'right',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array('administer smsframework'),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system')
  );

  $items['admin/smsframework/gateways'] = array(
    'title' => 'Gateway configuration',
    'description' => 'Configure gateways and chose the default gateway.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('sms_admin_default_form', NULL),
    'access arguments' => array('administer smsframework'),
    'file' => 'sms.admin.inc',
  );

  $items['admin/smsframework/gateways/%'] = array(
    'title callback' => 'sms_admin_gateway_title',
    'title arguments' => array(3),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('sms_admin_gateway_form', 3),
    'access arguments' => array('administer smsframework'),
    'type' => MENU_CALLBACK,
    'file' => 'sms.admin.inc',
  );

  $items['admin/smsframework/carriers'] = array(
    'title' => 'Carrier configuration',
    'description' => 'Configure supported carriers.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('sms_carriers_admin_form'),
    'access arguments' => array('administer smsframework'),
    'file' => 'sms.admin.inc',
  );

  $items['admin/smsframework/carriers/manage'] = array(
    'title' => 'Manage',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

  $items['admin/smsframework/carriers/add'] = array(
    'title' => 'Add',
    'description' => 'Configure supported carriers.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('sms_carriers_edit_form'),
    'access arguments' => array('administer smsframework'),
    'file' => 'sms.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );

  $items['admin/smsframework/carriers/%carrier'] = array(
    'title' => 'Edit carrier',
    'description' => 'Configure supported carriers.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('sms_carriers_edit_form', 3),
    'access arguments' => array('administer smsframework'),
    'type' => MENU_CALLBACK,
    'file' => 'sms.admin.inc',
  );

  $items['admin/smsframework/carriers/delete/%carrier'] = array(
    'title' => 'Edit carrier',
    'description' => 'Configure supported carriers.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('sms_carriers_delete_form', 4),
    'access arguments' => array('administer smsframework'),
    'type' => MENU_CALLBACK,
    'file' => 'sms.admin.inc',
  );

  return $items;
}

/**
 * Implementation of hook_perm().
 */
function sms_perm() {
  return array('administer smsframework');
}

/**
 * Implementation of hook_theme().
 */
function sms_theme() {
  $items['sms_admin_default_form'] =
  $items['sms_carriers_admin_form'] = array('arguments' => array('form' => NULL));

  return $items;
}

/**
 * SMS gateway menutitle callback.
 */
function sms_admin_gateway_title($gateway_id) {
  $gateway = sms_gateways('gateway', $gateway_id);
  return sprintf('%s gateway', $gateway['name']);
}

/**
 * Get a list of all gateways
 *
 * @param $op
 *   The format in which to return the list. When set to 'gateway' or 'name',
 *   only the specified gateway is returned. When set to 'gateways' or 'names',
 *   all gateways are returned.
 *
 * @param $gateway
 *   A gateway identifier string that indicates the gateway to return. Leave at default
 *   value (NULL) to return all gateways.
 *
 * @return
 *   Either an array of all gateways or a single gateway, in a variable format.
 */
function sms_gateways($op = 'gateways', $gateway = NULL) {
  list($_gateways, $_names) = _gateways_build();
  
  switch ($op) {
    case 'gateways':
      return $_gateways;
    case 'gateway':
      $return = $_gateways[$gateway];
      $return['identifier'] = $gateway;
      return $return;
    case 'names':
      return $_names;
    case 'name':
      return $_names[$gateway];
  }
}

function _gateways_build() {
  $_gateways = array();
  $_names = array();
  
  $gateway_array = module_invoke_all('gateway_info');
  foreach ($gateway_array as $identifier => $info) {
    $info['configuration'] = variable_get('sms_'. $identifier .'_settings', '');
    $_gateways[$identifier] = $info;
    $_names[$identifier] = $info['name'];
  }
  
  asort($_names);
  
  return array($_gateways, $_names);
}

/**
 * Get a list of all carriers
 */
function sms_carriers($domain = NULL) {
  $default_carriers = module_invoke_all('sms_carriers');
  $enabled_carriers = variable_get('sms_enabled_carriers', array());

  // Load default carriers from code
  foreach ($default_carriers as $id => $carrier) {
    $carriers[$id] = array('name' => $carrier, 'type' => SMS_CARRIER_DEFAULT);
  }

  // Load overrideen carriers from database
  $result = db_query("SELECT name, domain FROM {sms_carriers}");
  
  while ($carrier = db_fetch_array($result)) {
    if (in_array($carrier['domain'], array_keys($carriers))) {
      $type = SMS_CARRIER_OVERRIDDEN;
    }
    else {
      $type = SMS_CARRIER_NORMAL;
    }

    $carriers[$carrier['domain']] = array(
      'name' => $carrier['name'],
      'type' => $type,
    );
  }
  
  foreach($enabled_carriers as $carrier) {
    if (is_array($carriers[$carrier])) {
      $carriers[$carrier]['status'] = 1;
    }
  }

  if ($domain) {
    $carriers[$domain]['domain'] = $domain;
    return $carriers[$domain];
  }

  return $carriers;
}

/**
 * Load a single carrier
 */
function carrier_load($domain) {
  return sms_carriers($domain);
}

/**
 * Save a carrier.
 */
function carrier_save($domain, $edit) {
  if (!empty($domain)) {
    $carrier = carrier_load($domain);
    if ($carrier['type'] == SMS_CARRIER_DEFAULT) {
      $edit['status'] = 1;
      drupal_write_record('sms_carriers', $edit);
    }
    else if(!empty($edit['domain'])) {
      drupal_write_record('sms_carriers', $edit, 'domain');
      // TODO: we need more logic to figure out when someone is changing the domain name
    }
  }
  else {
    $edit['status'] = 1;
    drupal_write_record('sms_carriers', $edit);
  }
}

/**
 * Implementation of hook_sms_carriers()
 */
function sms_sms_carriers() {
  return array(
    'msg.acsalaska.com' => t('Alaska Communications Systems'),
    'message.alltel.com' => t('Alltel Wireless'),
    'txt.att.net' => t('AT&T/Cingular'),
    'mobile.celloneusa.com' => t('CellularOne'),
    'cwemail.com' => t('Centennial Wireless'),
    'sms.mycricket.com' => t('Cricket'),
    'messaging.sprintpcs.com' => t('Helio'),
    'page.nextel.com' => t('Nextel'),
    'qwestmp.com' => t('Qwest'),
    'messaging.sprintpcs.com' => t('Sprint'),
    'tmomail.net' => t('T-Mobile'),
    'vmobl.com' => t('Virgin Mobile'),
    'vtext.com' => t('Verizon'),
  );
}

function sms_handle_result($result, $number, $message) {
  if ($result['status']) {
    return TRUE;
  }
  else {
    $error_message = 'Sending SMS to %number failed.';
    $variables['%number'] = $number;
    if ($result['message']) {
      $error_message .= ' The gateway said '. $result['message'];
      if (!empty($result['variables'])) {
        $variables = array_merge($variables, $result['variables']);
      }
    }
    watchdog('sms', $error_message, $variables, WATCHDOG_ERROR);
    return FALSE;
  }
}

/**
 * Formats a number for display.
 */
function sms_format_number(&$number, $options = array()) {
  $gateway = sms_default_gateway();
  
  if ($gateway['format number'] && function_exists($gateway['format number'])) {
    return $gateway['format number']($number, $options);
  }
  else {
    return $number;
  }
}

function sms_formatter($number, $format = 1) {
  // Remove non-number characters
  $number = preg_replace("/[^0-9]/", '', $number);
  
  if (strlen($number) > 16) {
    if ($number[0] == 1) {
      $number = ltrim($number, 1);
    }
    else {
      return FALSE;
    }
  }
  elseif (strlen($number) < 10) {
    return FALSE;
  }

  return $number;
}

/**
 * Generates a SMS sending form and adds gateway defined elements. The form
 * array that is returned can be merged with an existing form using
 * array_merge().
 * 
 * @param $required
 *   Specify if the user is required to provide information for the fields.
 * 
 * @return $form
 */
function sms_send_form($required = FALSE) {
  $gateway = sms_default_gateway();
  $form['number'] = array(
    '#type' => 'textfield',
    '#title' => t('Mobile phone number'),
    '#size' => 40,
    '#maxlength' => 255,
    '#required' => $required,
  );

  // Add gateway defined fields
  if (function_exists($gateway['send form'])) {
    $form['gateway']['#tree'] = TRUE;
    $form['gateway'] = array_merge($gateway['send form']($required), $form['gateway']);
  }

  return $form;
}

function sms_send_form_validate($form, &$form_state) {
  if (!sms_formatter($form_state['values']['number'])) {
    form_set_error('number', t('You must enter a valid mobile phone number.'));
  }
}

/**
 * Validates a phone number. Passes number to active gateway for further
 * validation if neccessary.
 */
function sms_validate_number(&$number, $options = array()) {
  if (!strlen($number)) {
    return t('The mobile phone number is invalid.');
  }

  // Allow the active gateway to provide number validation
  $gateway = sms_default_gateway();
  if (function_exists($gateway['validate number']) && $error = $gateway['validate number']($number, $options)) {
    return $error;
  }
}

/**
 * Performs a simple send on submit. 
 */
function sms_send_form_submit($form, &$form_state) {
  $form_state['values']['number'] = sms_formatter($form_state['values']['number']);
  sms_send($form_state['values']['number'], $form_state['values']['message'], $form_state['values']['gateway']);
}
