<?php
// $Id: uc_vat_number.module,v 1.1.2.7 2010/09/29 15:48:26 wimh Exp $

/**
 * @file
 * Defines a checkout pane that lets customers specify their VAT number
 * This is required for stores that sell to professionals in the European Union
 *
 */


/*******************************************************************************
 * Hook Functions
 ******************************************************************************/


/**
 * Implementation of hook_perm()
 */
function uc_vat_number_perm() {
  return array('Have to fill VAT number');
}


/**
 * Implementation of hook_requirements()
 */
function uc_vat_number_requirements($phase)
{
  $t = get_t();

  if ($phase == 'runtime') {
    global $soapclient_LIBRARY;
$soapclient_LIBRARY='nuSOAP';
    if (variable_get('uc_store_vat_number_soap', true) == true
        && $soapclient_LIBRARY != 'nuSOAP') {
      /* EU VAT service only plays nice with nuSOAP, PHP SOAP module gives unreliable results... */
      $requirements['uc_vat_number'] = array(
        'severity' => REQUIREMENT_ERROR,
        'title' => $t('Ubercart VAT Number'),
        'value' => $t('VAT validation not functional'),
        'description' => $t('VAT Number validation requires the SOAP Client module to use the <b>nuSOAP</b> library, you can configure this <a href="!url">here</a>.', array('!url' => url('admin/settings/soapclient/config'))),
      );
       watchdog('VAT validation not functional', $soapclient_LIBRARY);
    }
  }

  return $requirements;
}

/**
 * Implementation of hook_form_alter()
 */
function uc_vat_number_form_alter(&$form, $form_state, $form_id) {
  // Alter the global shop setting form to ask for the store VAT Number
  if ($form_id == 'uc_store_store_settings_form') {
    $form['uc_store_vat_number'] = array(
      '#type' => 'textfield',
      '#title' => t('Store VAT number'),
      '#description' => t('Your VAT number, this number have to be displayed in your invoices.'),
      '#default_value' => variable_get('uc_store_vat_number', ''),
      '#size' => 32,
      '#maxlength' => 64,
      '#required' => false,
      '#weight' => 1,
    );
    $form['uc_store_vat_number_soap'] = array(
        '#type' => 'checkbox',
        '#title' => t('Check the validity on the customer VAT Number via <a href="http://ec.europa.eu/taxation_customs/vies/lang.do?fromWhichPage=vieshome">Europa VAT Number validation webservice</a>'),
        '#weight' => 2,
        '#default_value' => variable_get('uc_store_vat_number_soap', true),
    );
    $form['uc_notify_store_help_page']['#weight'] = 9;
    $form['buttons']['#weight'] = 10;
  }

  // Alter the checkout form to check the Customer VAT number
  if($form_id == 'uc_cart_checkout_form') {
    //$form['panes']['billing']['billing_company']['#required'] = true;
    $form['#validate'][] = 'uc_vat_number_checkout_validate';
  }
}


/**
 * Implementation of hook_order_pane()
 */
function uc_vat_number_order_pane() {
  // Add a pane to the order edit/view pages with VAT numbers
  $panes[] = array(
    'id' => 'vat_number',
    'callback' => 'uc_vat_number_order_pane_vat_number',
    'title' => t('VAT Number'),
    'desc' => t('Show store and customer VAT numbers.'),
    'class' => 'pos-left',
    'weight' => 3,
    'show' => array('view', 'customer', 'edit'),
  );
  return $panes;
}


/**
 * VAT number order pane callback
 */
function uc_vat_number_order_pane_vat_number($op, $arg1) {
  switch ($op) {

    case 'view':
    case 'customer':
      return $arg1->data["vat_number"];

    case 'edit-form':
      $form['vat_number'] = array(
        '#type' => 'fieldset',
        '#title' => t("Customer VAT Number"),
        '#collapsible' => FALSE,
        '#collapsed' => FALSE,
      );
      $form['vat_number']['vat_number'] = uc_textfield(t("Customer VAT Number"), $arg1->data['vat_number'], FALSE);
      return $form;

    case 'edit-theme':
      $output = '<div id="vat_number_select"></div><table class="order-edit-table">';
      foreach (element_children($arg1['vat_number']) as $field) {
        $title = $arg1['vat_number'][$field]['#title'];
        $arg1['vat_number'][$field]['#title'] = NULL;
        $output .= '<tr><td class="oet-label">'. $title .':</td><td>'
                 . drupal_render($arg1['vat_number'][$field]) .'</td></tr>';
      }
      $output .= '</table>';
      return $output;

    case 'edit-process':
      $data = db_result(db_query("SELECT data FROM {uc_orders} WHERE order_id = %d", $arg1['orderid']));
      $data = unserialize($data);
      if ($arg1['vat_number'] != $data['vat_number'])
        $changes['vat_number'] = $arg1['vat_number'];
      return $changes;
  }
}


/**
 * Implementation of hook_checkout_pane().
 */
function uc_vat_number_checkout_pane()
{
  $panes[] = array(
    "id" => "vat_number",
    "callback" => "uc_vat_number_checkout_pane_vat",
    'process' => TRUE,
    "title" => t("VAT Number"),
    "weight" => 3,
  );
  return $panes;
}


/**
 * VAT number checkout pane callback
 *
 * More information at http://www.ubercart.org/docs/developer/245/checkout
 */
function uc_vat_number_checkout_pane_vat($op, &$arg1, $arg2)
{
  global $user;

  $order = $arg1;

  switch ($op)
  {
    case "view":
      // Add form to checkout pane

      $description = t("Enter your VAT number if applicable.");

      drupal_add_js(drupal_get_path('module', 'uc_vat_number') .'/uc_vat_number.js');
      drupal_add_js('var european_countries = [' . implode(',', uc_vat_number_european_countries()) . '];', 'inline');
      // force VAT Number refresh
      drupal_add_js('$(document).ready(function() { updateVatField(); });', 'inline');
      // override uc_payment's serializeOrder with our own to insert the VAT number
      drupal_add_js('var __serializeOrder = serializeOrder; var serializeOrder = uc_vat_number__serializeOrder;', 'inline');

      $content['vat_number'] = array(
        '#type' => 'textfield',
        '#title' => t('VAT Number'),
        '#description' => t('Required for professional customers in EU (eg: BE 0123.456.789).'),
        '#size' => 32,
        '#maxlength' => 32,
        '#weight' => 1,
        '#required' => user_access('Have to fill VAT number') ? TRUE : FALSE,
        '#default_value' => $order->data['vat_number'],
        '#autocomplete_path' => 'uc_vat_number/vat_autocomplete',
      );

      return array("description" => $description, "contents" => $content);

    case "process":
      // Save VAT number from checkout pane

      $order->data['vat_number'] = $arg2['vat_number'];
      break;

    case "review":
      // Show VAT number on checkout review

      if ($order->data['vat_number']) {
        $review[] = array('title' => t('Your VAT Number'), 'data' => $arg1->data['vat_number']);
        $review[] = array('title' => t('Our VAT Number'), 'data' => variable_get('uc_store_vat_number', ''));
      }
      return $review;

  }
}


function uc_vat_number_menu()
{
  $items['uc_vat_number/vat_autocomplete'] = array(
    'title' => t('VAT number autocomplete callback'),
    'page callback' => 'uc_vat_number_vat_autocomplete',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}


function uc_vat_number_vat_autocomplete($string) {
  global $user;
  $matches = array();
  if ($user->uid && $result = db_query("SELECT data FROM {uc_orders} WHERE uid = %d", $user->uid)) {
    while ($data = db_fetch_array($result)) {
      $data = unserialize($data['data']);
      if ($data['vat_number'])
        $matches[check_plain($data['vat_number'])] = check_plain($data['vat_number']);
    }
  }
  ksort($matches);

  drupal_json($matches);
}



/**
* Implementation of hook_order().
*/
function uc_vat_number_order($op, &$arg1) {
  switch ($op) {
    case 'save':
      if (!isset($arg1->vat_number))
        break;

      // Save our VAT number set by hook_order_pane('edit-process')
      // The order was already saved, so don't just update $order->data now but do the DB query ourselves

      // Load up the existing data array.
      $data = db_result(db_query("SELECT data FROM {uc_orders} WHERE order_id = %d", $arg1->order_id));
      $data = unserialize($data);

      // Add the custom data into the data array
      $data['vat_number'] = $arg1->vat_number;

      // Save it again.
      db_query("UPDATE {uc_orders} SET data = '%s' WHERE order_id = %d", serialize($data), $arg1->order_id);

      break;
  }
}


/**
 * Implementation of hook_token_values(). (token.module)
 */
function uc_vat_number_token_values($type, $object = NULL) {
  switch ($type) {
    case 'global':
      $values['store-vat-number'] = variable_get('uc_store_vat_number', t('Your store VAT number'));
      break;

    case 'order':
      $order = $object;
      $values['order-vat-number'] = $order->data['vat_number'];
      break;

  }
  return $values;
}


/**
 * Implementation of hook_token_list(). (token.module)
 */
function uc_vat_number_token_list($type = 'all') {
  $tokens['global']['store-vat-number'] = t('Your store VAT number.');
  if ($type == 'order' || $type == 'ubercart' || $type == 'all') {
    $tokens['order']['order-vat-number'] = t('The VAT number of the customer.');
  }
  return $tokens;
}


/*******************************************************************************
 * Callback Functions, Forms, and Tables
 ******************************************************************************/

function uc_vat_number_checkout_validate($form, $form_state) {
  // Skip validation if the customer is not in EU country.
  if (!in_array($form_state['values']['panes']['billing']['billing_country'], uc_vat_number_european_countries())) {
    return;
  }

  if (empty($form_state['values']['panes']['vat_number']['vat_number']))
    if (user_access('Have to fill VAT number'))
      form_set_error('panes][billing][vat_number', t('VAT Number field is required.'));
    else
      return;

  if(variable_get('uc_store_vat_number_soap', true) == true) {
    $err = uc_vat_number_validate_vat($form_state['values']['panes']['vat_number']['vat_number']);
    if ($err) {
      form_set_error('panes][vat_number][vat_number', $err);
    }
  }
}

/* Check a VAT number. Return error message on error, FALSE on success. */

function uc_vat_number_validate_vat($vat_number) {
  global $user;

  $vat_number = preg_replace('/[ .]/', '', $vat_number);
  $countryCode = strtoupper(substr($vat_number, 0, 2));
  $vatNumber = strtoupper(substr($vat_number, 2));

  if(!preg_match('/^[A-Z]{2}$/', $countryCode) or !preg_match('/^[0-9A-Z+*]+$/', $vatNumber)) {
    return t('Your VAT Number syntax is not correct. You should have something like: BE 0123.456.789');
  }

  if (db_result(db_query(
    "SELECT vat_validation_id FROM {uc_vat_validation} WHERE vat_number = '%s' AND fetched > ".time()." - 86400",
    array("{$countryCode}{$vatNumber}")
  ))) {
    /* VAT number is in the cache, which means it was valid (we only store valid numbers) */
    return FALSE;
  }

  $client = soapclient_init_client("http://ec.europa.eu/taxation_customs/vies/services/checkVatService.wsdl", TRUE);
  if ($client['#error']) {
    watchdog('uc_vat_number', 'Error contacting VAT verification service: @error', array('@error' => $client['#error']), WATCHDOG_ERROR);
    if ($user->uid == 1)
      return t('Error when contacting the VAT Number verification service: @error', array('@error' => $client['#error']));
    else
      return t('Cannot contact VAT Number verification service');
  } else {
    $client = $client['#return'];
    $params = array('countryCode' => $countryCode, 'vatNumber' => $vatNumber);
    $result = $client->call("checkVat", $params);
    if ($result['#error']) {
      watchdog('uc_vat_number', 'Error contacting VAT verification service: @error', array('@error' => $result['#error']), WATCHDOG_ERROR);
      if ($user->uid == 1)
        return t('Error when contacting the VAT Number verification service: @error', array('@error' => $result['#error']));
      else
        return t('Error when contacting the VAT Number verification service');
    } else {
      $result = (array)$result['#return'];
      if($result['valid'] != "true") {
        return t('VAT Number verification failed, check it\'s validity on <a href="http://ec.europa.eu/taxation_customs/vies/vieshome.do">this website</a>');
      } else {
        db_query("INSERT INTO {uc_vat_validation} (vat_number, fetched, response) VALUES ('%s', %d, '%s')",
                 array("{$countryCode}{$vatNumber}", time(), serialize($result)));
        return FALSE;
      }
    }
  }
}


/*******************************************************************************
 * Implementation of uc_vat_number_condition_vat_number condition
 *
 * Application of VAT rule should be conditional on
 *    billing-country == store-country
 *    OR (billing-country == europe - store-country
 *        AND NOT "Order has a valid VAT number")
 ******************************************************************************/

/* TODO: since we have a maintained list of all european countries, it might be easier if we
   also provide the 'billing-country == europe' condition.
   and if store-country is accurate, we can even do the whole condition */

/**
* Implementation of hook_ca_condition().
*/
function uc_vat_number_ca_condition() {
  return array(
    'uc_vat_number_condition_vat_number' => array(
      '#title' => t("Order has a valid VAT number"),
      '#category' => t('Order: Billing address'),
      '#callback' => 'uc_vat_number_condition_vat_number',
      '#arguments' => array(
        'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
      ),
    ),
    'uc_vat_number_condition_billing_country_europe' => array(
      '#title' => t("Order has a billing country in Europe"),
      '#category' => t('Order: Billing address'),
      '#callback' => 'uc_vat_number_condition_billing_country_europe',
      '#arguments' => array(
        'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
      ),
    ),
    'uc_vat_number_condition_delivery_country_europe' => array(
      '#title' => t("Order has a shipping country in Europe"),
      '#category' => t('Order: Shipping address'),
      '#callback' => 'uc_vat_number_condition_delivery_country_europe',
      '#arguments' => array(
        'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
      ),
    ),
    'uc_vat_number_condition_should_apply_vat' => array(
      '#title' => t("Order should have VAT applied"),
      '#category' => t('Order'),
      '#callback' => 'uc_vat_number_condition_should_apply_vat',
      '#arguments' => array(
        'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
      ),
    ),
    'uc_vat_number_condition_billing_company_has_value' => array(
      '#title' => t("Order billing company has a value"),
      '#category' => t('Order: Billing address'),
      '#description' => t('Returns TRUE if the billing company has a value.'),
      '#callback' => 'uc_vat_number_condition_billing_company_has_value',
      '#arguments' => array(
        'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
      ),
    ),
  );
}

/**
* Returns true if the order has a VAT number
*/
function uc_vat_number_condition_vat_number($order, $settings) {
  if (empty($order->data['vat_number']))
    return FALSE;
  else
    return TRUE;
}

/**
* Settings form for uc_vat_number_condition_vat_number().
*/
function uc_vat_number_condition_vat_number_form($form_state, $settings = array(), $arguments = array()) {
  return array();
}

/**
* Returns true if the order has a billing country in the EU
*/
function uc_vat_number_condition_billing_country_europe($order, $settings) {
  if (in_array($order->billing_country, uc_vat_number_european_countries()))
    return TRUE;
  else
    return FALSE;
}

/**
* Settings form for uc_vat_number_condition_billing_country_europe().
*/
function uc_vat_number_condition_billing_country_europe_form($form_state, $settings = array(), $arguments = array()) {
  return array();
}

/**
* Returns true if the order has a shipping country in the EU
*/
function uc_vat_number_condition_delivery_country_europe($order, $settings) {
  if (in_array($order->delivery_country, uc_vat_number_european_countries()))
    return TRUE;
  else
    return FALSE;
}

/**
* Settings form for uc_vat_number_condition_delivery_country_europe().
*/
function uc_vat_number_condition_delivery_country_europe_form($form_state, $settings = array(), $arguments = array()) {
  return array();
}

/**
* Returns true if VAT should be applied to the order
*/
function uc_vat_number_condition_should_apply_vat($order, $settings) {
  $european_countries = uc_vat_number_european_countries();
  $store_country = variable_get('uc_store_country', '');
  if (!in_array($order->delivery_country, $european_countries))
    /* shipping to outside europe: no VAT */
    return FALSE;
  else if ($order->delivery_country == $store_country
      || $order->billing_country == $store_country)
    /* shipping to store's own country, or company in store's own country: no intra-community delivery */
    return TRUE;
  else if (in_array($order->delivery_country, $european_countries)
           && in_array($order->billing_country, $european_countries)
           && !empty($order->data['vat_number'])
    )
    /* intra-community, no VAT */
    return FALSE;
  else
    /* default: VAT applicable */
    return TRUE;
}

/**
* Returns true if the billing company has a value
*/
function uc_vat_number_condition_billing_company_has_value($order, $settings) {
  if (!empty($order->billing_company))
    return TRUE;
  else
    return FALSE;
}

/**
* Settings form for uc_vat_number_condition_should_apply_vat().
*/
function uc_vat_number_condition_should_apply_vat_form($form_state, $settings = array(), $arguments = array()) {
  return array();
}

/*
  List of European countries (in order of the array)
    40      Austria         AT
    56      Belgium         BE
    100     Bulgaria        BG
    196     Cyprus          CY
    203     Czech Republic  CZ
    208     Denmark         DK
    233     Estonia         EE
    246     Finland         FI
    250     France          FR
    276     Germany         DE
    300     Greece          GR
    348     Hungary         HU
    372     Ireland         IE
    380     Italy           IT
    428     Latvia          LV
    440     Lithuania       LT
    442     Luxembourg      LU
    470     Malta           MT
    528     Netherlands     NL
    616     Poland          PL
    620     Portugal        PT
    642     Romania         RO
    703     Slovakia        SK
    705     Slovenia        SI
    724     Spain           ES
    752     Sweden          SE
    826     United Kingdom  GB
*/

function uc_vat_number_european_countries() {
  return array(40, 56, 100, 196, 203, 208, 233, 246, 250, 276, 300, 348, 372, 380, 428, 440, 442, 470, 528, 616, 620, 642, 703, 705, 724, 752, 826);
}
