<?php

/**
 * Ceon Advanced Shipper Fedex Calculation class. 
 *
 * @package     ceon_advanced_shipper
 * @author      Waqas Hussain <waqas20@gmail.com>
 * @author      Conor Kerr <zen-cart.advanced-shipper@dev.ceon.net>
 * @copyright   Copyright 2010-2011 Waqas Hussain
 * @copyright   Copyright 2007-2011 Ceon
 * @copyright   Portions Copyright 2003-2006 Zen Cart Development Team
 * @copyright   Portions Copyright 2003 osCommerce
 * @link        http://dev.ceon.net/web/zen-cart/advanced-shipper
 * @license     http://www.gnu.org/copyleft/gpl.html   GNU Public License V2.0
 * @version     $Id: class.AdvancedShipperFedExCalculator.php 822 2011-09-04 11:32:10Z conor $
 */

// {{{ AdvancedShipperFedExCalculator

/**
 * Connects to FedEx online calculator and gets quotes for the shipping methods enabled in the
 * configuration.
 *
 * @author      Waqas Hussain <waqas20@gmail.com>
 * @author      Conor Kerr <zen-cart.advanced-shipper@dev.ceon.net>
 * @copyright   Copyright 2010-2011 Waqas Hussain
 * @copyright   Copyright 2007-2011 Ceon
 * @copyright   Portions Copyright 2003-2006 Zen Cart Development Team
 * @copyright   Portions Copyright 2003 osCommerce
 * @license     http://www.gnu.org/copyleft/gpl.html   GNU Public License V2.0
 */
class AdvancedShipperFedExCalculator
{
	// {{{ properties
	
	/**
	 * The configuration settings for this instance.
	 *
	 * @var     array
	 * @access  public
	 */
	var $_config = null;
	
	// }}}
	
	
	// {{{ Class Constructor
	
	/**
	 * Create a new instance of the AdvancedShipperFedExCalculator class
	 *
	 * @param   array     $fedex_config   An associative array with the configuration settings for
	 *                                    this instance.
	 */
	function AdvancedShipperFedExCalculator($fedex_config)
	{
		$this->_config = $fedex_config;
	}
	
	// }}}
	
	
	// {{{ quote()

	/**
	 * Contacts Fedex and gets a quote for the specified weight and configuration settings.
	 *
	 * @access  public
	 * @param   float     $weight    The weight of the package to be shipped.
	 * @param   float     $price     The total price of the items in package to be shipped.
	 * @param   array     $min_max   Any minimum/maximum limits which should be applied to the final
	 *                               rate calculated.
	 * @return  none
	 */
	function quote($weight, $price, $min_max)
	{
		global $order;
		
		if ($weight < 0.1) {
			$weight = 0.1;
		}
		
		$rate_info = $this->_getQuote($weight, $min_max);
		
		return $rate_info;
	}
	
	// }}}
	
	
	// {{{ _getQuote()
	
	/**
	 * Contacts FedEx, gets a quote, parses the response and builds the list of quotes.
	 *
	 * @access  protected
	 * @return  array|boolean   Array of results or boolean false if no results.
	 */
	function _getQuote($weight, $min_max)
	{
		global $db, $order, $currencies;
		
		if (strtolower($this->_config['server']) == 'p') {
			$fedex_url = 'https://gateway.fedex.com:443/xml';
		} else {
			$fedex_url = 'https://gatewaybeta.fedex.com:443/xml';
		}
		
		$fedex_key = $this->_config['fedex_key'];
		$fedex_password = $this->_config['fedex_password'];
		$fedex_account = $this->_config['fedex_account'];
		$fedex_meter = $this->_config['fedex_meter'];
		
		$shipper_country_sql = "
			SELECT
				countries_iso_code_2
            FROM
				" . TABLE_COUNTRIES . "
            WHERE
				countries_id = '" . (int) $this->_config['source_country'] . "';";
		
        $shipper_country_result = $db->Execute($shipper_country_sql);
		
		if (!$shipper_country_result->EOF) {
			$shipper_country = strtoupper($shipper_country_result->fields['countries_iso_code_2']);
		}
		
		$shipper_postcode = $this->_config['source_postcode'];
		$recipient_country = $order->delivery['country']['iso_code_2'];
		$recipient_postcode = $order->delivery['postcode'];
		$timestamp = gmdate('c');//'2010-08-06T21:48:17+06:00'
		$saturday_delivery = ($this->_config['shipping_saturday'] != 0);
		
		$drop_off_type = strtoupper($this->_config['drop_off_type']);
		
		$packaging_type = strtoupper($this->_config['packaging_type']);
		
		$get_account_rates = ($this->_config['rate_request_types'] == 1 ||
			$this->_config['rate_request_types'] == 3 ? true: false);
		
		$get_list_rates = ($this->_config['rate_request_types'] == 2 ||
			$this->_config['rate_request_types'] == 3 ? true: false);
		
		$weight_units = strtoupper($this->_config['weight_units']);
		
		$request = "
<RateRequest xmlns='http://fedex.com/ws/rate/v6'>
	<WebAuthenticationDetail>
		<UserCredential>
			<Key>$fedex_key</Key>
			<Password>$fedex_password</Password>
		</UserCredential>
	</WebAuthenticationDetail>
	<ClientDetail>
		<AccountNumber>$fedex_account</AccountNumber>
		<MeterNumber>$fedex_meter</MeterNumber>
	</ClientDetail>
	<TransactionDetail>
		<CustomerTransactionId>ActiveShipping</CustomerTransactionId>
	</TransactionDetail>
	
	<Version>
		<ServiceId>crs</ServiceId>
		<Major>6</Major>
		<Intermediate>0</Intermediate>
		<Minor>0</Minor>
	</Version>
	
	<ReturnTransitAndCommit>true</ReturnTransitAndCommit>
	" . ($saturday_delivery ? '<VariableOptions>SATURDAY_DELIVERY</VariableOptions>' : '') . "
	
	<RequestedShipment>
		<ShipTimestamp>$timestamp</ShipTimestamp>
		<DropoffType>$drop_off_type</DropoffType>
		<PackagingType>$packaging_type</PackagingType>
		
		<Shipper>
			<Address>
				<PostalCode>$shipper_postcode</PostalCode>
				<CountryCode>$shipper_country</CountryCode>
			</Address>
		</Shipper>
		<Recipient>
			<Address>
				<PostalCode>$recipient_postcode</PostalCode>
				<CountryCode>$recipient_country</CountryCode>
			</Address>
		</Recipient>
		
		" . ($get_account_rates ? '<RateRequestTypes>ACCOUNT</RateRequestTypes>' : '') . "
		" . ($get_list_rates ? '<RateRequestTypes>LIST</RateRequestTypes>' : '') . "
		
		<PackageCount>1</PackageCount>
		<RequestedPackages>
			<Weight>
				<Units>$weight_units</Units>
				<Value>$weight</Value>
			</Weight>
			<Dimensions>
				<Length>1</Length>
				<Width>1</Width>
				<Height>1</Height>
				<Units>CM</Units>
			</Dimensions>
		</RequestedPackages>
	</RequestedShipment>
</RateRequest>";
		
		advshipper::debug("Data being sent to FedEx: <br />\n<br />\n" .
			nl2br(htmlentities($request)) . "<br />\n<br />\n", true);
		
		$body = $this->_post($fedex_url, $request);
		
		if (!$body) {
			advshipper::debug('Failed to connect to FedEx' . "\n\n");
			
			return array('error' => 'Failed to connect to FedEx');
		}
		
		// Strip namespace prefixes
		$body =
			preg_replace(array('/<[a-zA-Z0-9]+:/', '/<\/[a-zA-Z0-9]+:/'), array('<', '</'), $body); 
		
		advshipper::debug("Data received from FedEx (with namespaces removed): <br />\n<br />\n" .
			nl2br(htmlentities($body)) . "<br />\n<br />\n", true);
		
		$xml = @simplexml_load_string($body);
		
		if (!$xml) {
			advshipper::debug('Unable to parse XML returned from FedEx' . "<br />\n<br />\n");
			
			return array(
				'error' => 'Invalid response from FedEx: '. $body
				);
		}
		
		if (strtolower($xml->getName()) == 'fault') {
			$error_code = $xml->detail->fault->errorCode;
			$reason = $xml->detail->fault->reason;
			
			return array(
				'error' => 'FedEx: ' . $error_code . ': ' . $reason
				);
		}
		
		$severity = $xml->HighestSeverity;
		$message = $xml->Notifications->Message;
		
		// Check for problem with postcode
		if ($severity == 'ERROR' &&
				strpos($message, 'Destination postal code missing or invalid') !== false) {
			return array(
				'error' => MODULE_ADVANCED_SHIPPER_ERROR_INVALID_POSTCODE
				);
		}
		
		if ($severity != 'SUCCESS' && $severity != 'NOTE' && $severity != 'WARNING') {
			advshipper::debug('Error occured when attempting to get response from FedEx. Severity' .
				' was ' . $severity . "<br />\n<br />\n");
			
			return array(
				'error' => "FedEx: $message"
				);
		}
		
		$rate_info = array();
		
		foreach ($xml->RateReplyDetails as $child) {
			$service_type = trim(strtolower((string) $child->ServiceType));
			
			$service_name = constant('MODULE_ADVANCED_SHIPPER_TEXT_FEDEX_' .
				str_replace('FEDEX_', '', strtoupper($service_type)));
			
			if ($this->_config['shipping_service_' . $service_type] == 0) {
				advshipper::debug('FedEx ' . $service_name . ' service not to be offered as it is' .
					" not enabled in the FedEx configuration for the region.<br />\n<br />\n",
					true);
				
				continue;
			}
			
			if ($child->AppliedOptions == 'SATURDAY_DELIVERY') {
				$service_name .= MODULE_ADVANCED_SHIPPER_TEXT_FEDEX_SATURDAY_DELIVERY;
			}
			
			$rate = (string) $child->RatedShipmentDetails->ShipmentRateDetail->TotalNetCharge->
				Amount;
			
			// Rate is always returned in currency of account holder. Must convert to currency in
			// use
			$rate_currency = strtoupper((string) $child->RatedShipmentDetails->ShipmentRateDetail->
				TotalNetCharge->Currency);
			
			// Get the current currency in which the customer is viewing prices
			$current_display_currency = strtoupper($_SESSION['currency']);
			
			if ($rate_currency != $current_display_currency) {
				// Must convert from FedEx currency to base currency
				$rate_currency_conv_rate = $currencies->get_value($rate_currency);
				
				if (is_null($rate_currency_conv_rate) || !is_numeric($rate_currency_conv_rate)) {
					// FedEx currency doesn't have a conversion value, can't proceed!
					return array(
						'error' => sprintf(
							MODULE_ADVANCED_SHIPPER_ERROR_FEDEX_CURRENCY_CONV_VALUE_MISSING,
							$rate_currency)
						);
				} else if ($rate_currency_conv_rate <= 0) {
					return array(
						'error' => sprintf(
							MODULE_ADVANCED_SHIPPER_ERROR_FEDEX_CURRENCY_CONV_VALUE_INVALID,
							$rate_currency)
						);
				}
				
				$rate_base_conv_rate = 1 / $currencies->get_value($rate_currency);
				
				$rate = number_format($rate * $rate_base_conv_rate, 2, '.', '');
			}
			
			if ($min_max != false) {
				// Apply the limit(s) to the rate
				$rate_limited = advshipper::calcMinMaxValue($rate, $min_max['min'],
					$min_max['max']);
				
				if ($rate_limited != $rate) {
					$rate = $rate_limited;
				}
			}
			
			$rate_info[] = array(
				'rate' => $rate,
				'rate_components_info' => array(
					array(
						'value_band_total' => $rate,
						'individual_value' => null,
						'num_individual_values' => $weight,
						'additional_charge' => null,
						'calc_method' => ADVSHIPPER_CALC_METHOD_FEDEX
						)
					),
				'rate_extra_title' => MODULE_ADVANCED_SHIPPER_TEXT_FEDEX_TITLE_PREFIX .
					$service_name
				);
		}
		
		if (count($rate_info) == 0) {
			// No quotes
			return false;
		}
		
		return $rate_info;
	}
	
	// }}}
	
	
	// {{{ _post()
	
	/**
	 * Do an HTTP POST call on a URL with given data.
	 *
	 * @access  protected
	 * @param   string    $url   The URL to send the request to.
	 * @param   string    $data  The data to send in the request.
	 * @return  string    Data or false on error
	 */
	function _post($url, $data)
	{
		$ch = curl_init($url) or die('Failed to load curl');
		
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
		//curl_set_opt($ch, CURLOPT_HEADER, FALSE);
		curl_setopt($ch, CURLOPT_POST, TRUE);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
		
		$data = curl_exec($ch);
		curl_close($ch);
		
		return $data;
	}
	
	// }}}
}

// }}}

?>