<?php
/**
* @version	$Id: orders_item.php 13549 2010-05-11 21:03:39Z alex $
* @package	In-Commerce
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license	Commercial License
* This software is protected by copyright law and international treaties.
* Unauthorized reproduction or unlicensed usage of the code of this program,
* or any portion of it may result in severe civil and criminal penalties,
* and will be prosecuted to the maximum extent possible under the law
* See http://www.in-portal.org/commercial-license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class OrdersItem extends kDBItem
	{

		function OrdersItem()
		{
			parent::kDBItem();
			$this->ErrorMsgs['credit_card_validation_error'] = $this->Application->Phrase('lu_cc_validation_error');
			$this->ErrorMsgs['credit_card_expired'] = $this->Application->Phrase('lu_cc_expired');
		}

		/**
		 * Return error message for field
		 *
		 * @param string $field
		 * @return string
		 * @access public
		 */
		function GetErrorMsg($field)
		{
			if( $field != 'OrderNumber' ) return parent::GetErrorMsg($field);

			$number['error'] = parent::GetErrorMsg('Number');
			$number['pseudo'] = getArrayValue($this->FieldErrors['Number'], 'pseudo');

			$subnumber['error'] = parent::GetErrorMsg('SubNumber');
			$subnumber['pseudo'] = getArrayValue($this->FieldErrors['SubNumber'], 'pseudo');

			// if pseudo match & not empty -> return 1st
			// if one of pseudos not empty -> return it
			// if we got one pseudo "bad_type" and other pseudo "required", then return "bad_type" error message


			if( $number['pseudo'] && ($number['pseudo'] == $subnumber['pseudo']) )
			{
				return $number['error'];
			}

			if( $number['pseudo'] && !$subnumber['pseudo'] )
			{
				return $number['error'];
			}

			if( !$number['pseudo'] && $subnumber['pseudo'] )
			{
				return $subnumber['error'];
			}

			if( $number['pseudo'] == 'bad_type' )
			{
				return $number['error'];
			}

			if( $subnumber['pseudo'] == 'bad_type' )
			{
				return $subnumber['error'];
			}

//			$msg = '['.$number_error.'('.$number_pseudo.')] ['.$subnumber_error.'] ('.$subnumber_pseudo.')';
//
//			return $msg;
		}

		function SetFieldsFromHash($hash, $set_fields=null)
		{
			parent::SetFieldsFromHash($hash, $set_fields);

			$options = $this->GetFieldOptions('PaymentCCExpDate');
			if( $this->GetDirtyField($options['month_field']) || $this->GetDirtyField($options['year_field']) )
			{
				$this->SetDirtyField('PaymentCCExpDate', 0);
				$this->SetField('PaymentCCExpDate', 0);
			}
		}

		/**
		 * Returns gateway data based on payment type used in order
		 *
		 * @return Array
		 */
		function getGatewayData($pt_id=null)
		{
			// get Gateway fields
			if (!isset($pt_id) || !$pt_id) {
				$pt_id = $this->GetDBField('PaymentType');
			}
			$pt_table = $this->Application->getUnitOption('pt','TableName');
			$sql = 'SELECT GatewayId FROM %s WHERE PaymentTypeId = %s';
			$gw_id = $this->Conn->GetOne( sprintf($sql, $pt_table, $pt_id) );

			$sql = 'SELECT * FROM %s WHERE GatewayId = %s';
			$ret = $this->Conn->GetRow( sprintf($sql, TABLE_PREFIX.'Gateways', $gw_id) );

			// get Gateway parameters based on payment type
			$gwf_table = $this->Application->getUnitOption('gwf','TableName');
			$gwfv_table = $this->Application->getUnitOption('gwfv','TableName');
			$sql = 'SELECT gwfv.Value, gwf.SystemFieldName
					FROM %s gwf
					LEFT JOIN %s gwfv ON gwf.GWConfigFieldId = gwfv.GWConfigFieldId
					WHERE gwfv.PaymentTypeId = %s AND gwf.GatewayId = %s';
			$ret['gw_params'] = $this->Conn->GetCol( sprintf($sql, $gwf_table, $gwfv_table, $pt_id, $gw_id), 'SystemFieldName' );
			$ret['gw_params']['gateway_id'] = $gw_id;

			if ($this->GetDBField('IsRecurringBilling') && $this->Application->ConfigValue('Comm_AutoProcessRecurringOrders')) {
				if (isset($ret['gw_params']['shipping_control'])) {
					$ret['gw_params']['shipping_control'] = SHIPPING_CONTROL_DIRECT;
				}
			}

			return $ret;
		}

		/**
		 * Checks if tangible items are present in order
		 *
		 * @return bool
		 */
		function HasTangibleItems()
		{
			$sql = 'SELECT COUNT(*)
					FROM '.TABLE_PREFIX.'OrderItems orditems
					LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = orditems.ProductId
					WHERE (orditems.OrderId = '.$this->GetID().') AND (p.Type = '.PRODUCT_TYPE_TANGIBLE.')';
			return $this->Conn->GetOne($sql) ? true : false;
		}

		/**
		 * Calculates tax value of order items based on billing & shipping country specified
		 *
		 * @return double
		 */
		function getTaxPercent()
		{
			$cs_helper =& $this->Application->recallObject('CountryStatesHelper');
			/* @var $cs_helper kCountryStatesHelper */

			$shipping_country_id = $cs_helper->getCountryStateId($this->GetDBField('ShippingCountry'), DESTINATION_TYPE_COUNTRY);
			$shipping_state_id = $cs_helper->getCountryStateId($this->GetDBField('ShippingState'), DESTINATION_TYPE_STATE);
			$shipping_zip = (string) $this->GetDBField('ShippingZip');

			$billing_country_id = $cs_helper->getCountryStateId($this->GetDBField('BillingCountry'), DESTINATION_TYPE_COUNTRY);
			$billing_state_id =  $cs_helper->getCountryStateId($this->GetDBField('BillingState'), DESTINATION_TYPE_STATE);
			$billing_zip = (string) $this->GetDBField('BillingZip');

			/*
			$dest_ids = array_diff( array_unique( Array( $shipping_country_id, $shipping_state_id, $billing_country_id, $billing_state_id ) ), Array(0) );
			$dest_values = array_diff( array_unique( Array( $this->Conn->qstr($shipping_zip), $this->Conn->qstr($billing_zip) ) ), Array('\'\'') );
			*/

			$tax = false;
			$sql = 'SELECT tx.*
				FROM '.$this->Application->getUnitOption('tax', 'TableName').' tx
				LEFT JOIN '.$this->Application->getUnitOption('taxdst', 'TableName').' txd ON tx.TaxZoneId = txd.TaxZoneId
				WHERE
					(	txd.StdDestId IN ('.$shipping_country_id.','.$shipping_state_id.')
						AND
						( (txd.DestValue = "" OR txd.DestValue IS NULL)
							OR
							txd.DestValue = '.$this->Conn->qstr($shipping_zip).'
						)
					)
					OR
					(	txd.StdDestId IN ('.$billing_country_id.','.$billing_state_id.')
						AND
						( (txd.DestValue = "" OR txd.DestValue IS NULL)
							OR
							txd.DestValue = '.$this->Conn->qstr($billing_zip).'
						)
					)

				ORDER BY tx.TaxValue DESC';

			$tax = $this->Conn->GetRow($sql);
			if ($tax == false) {
				$tax['TaxValue'] = 0;
				$tax['ApplyToShipping'] = 0;
				$tax['ApplyToProcessing'] = 0;
			}

			return $tax;
		}

		function RecalculateTax()
		{
			$tax = $this->getTaxPercent();
			$this->SetDBField( 'VATPercent', $tax['TaxValue'] );
			$this->SetDBField( 'ShippingTaxable', $tax['ApplyToShipping']);
			$this->SetDBField( 'ProcessingTaxable', $tax['ApplyToProcessing']);
			$this->UpdateTotals();

			$subtotal = $this->GetDBField('AmountWithoutVAT');

			$query = 'SELECT SUM(Quantity * Price) FROM '.TABLE_PREFIX.'OrderItems AS oi
								LEFT JOIN '.TABLE_PREFIX.'Products AS p
								ON p.ProductId = oi.ProductId
								WHERE p.Type = 6 AND oi.OrderId = '.$this->GetDBField('OrderId');
			$tax_exempt = $this->Conn->GetOne($query);
			if ($tax_exempt) $subtotal -= $tax_exempt;

			$this->SetDBField( 'VAT', round($subtotal * $tax['TaxValue'] / 100, 2) );
			$this->UpdateTotals();
		}

		function UpdateTotals()
		{
			$total = 0;
			$total += $this->GetDBField('SubTotal');
			if ($this->GetDBField('ShippingTaxable')) $total += $this->GetDBField('ShippingCost');
			if ($this->GetDBField('ProcessingTaxable')) $total += $this->GetDBField('ProcessingFee');
			$this->SetDBField('AmountWithoutVAT', $total);

			$total += $this->GetDBField('VAT');

			if (!$this->GetDBField('ShippingTaxable')) $total += $this->GetDBField('ShippingCost');
			if (!$this->GetDBField('ProcessingTaxable')) $total += $this->GetDBField('ProcessingFee');

			$total += $this->GetDBField('InsuranceFee');

			$this->SetDBField('TotalAmount', $total);
		}

		function getTotalAmount()
		{
			return 	$this->GetDBField('SubTotal') +
					$this->GetDBField('ShippingCost') +
					$this->GetDBField('VAT') +
					$this->GetDBField('ProcessingFee') +
					$this->GetDBField('InsuranceFee') -
					$this->GetDBField('GiftCertificateDiscount');
		}


		/**
		 * Check field value by user-defined alghoritm
		 *
		 * @param string $field field name
		 * @param Array $params field options from config
		 * @return bool
		 */
		function CustomValidation($field, $params)
		{
			$res = true;

			$res = $res && $this->ValidateCCNumber($field, $params);
			$res = $res && $this->ValidateCCExpiration($field, $params);
			return $res;
		}

		function requireCreditCard()
		{
			$pt_table = $this->Application->getUnitOption('pt', 'TableName');
			$sql = 'SELECT RequireCCFields
					FROM '.$pt_table.' pt
					LEFT JOIN '.TABLE_PREFIX.'Gateways gw ON gw.GatewayId = pt.GatewayId
					WHERE pt.PaymentTypeId = '.$this->GetDBField('PaymentType');
			return $this->Conn->GetOne($sql);
		}

		/**
		 * Check if field value is valid credit card number against credit card type specified
		 *
		 * @param string $field field name
		 * @param Array $params field options from config
		 * @return bool
		 * @access private
		 */
		function ValidateCCNumber($field, $params)
		{
			$cardtype_field = getArrayValue($params, 'cardtype_field');
			$value = $this->GetDBField($field);

			if( !$cardtype_field || !$value || !$this->requireCreditCard() ) return true;

			if ($this->Application->ConfigValue('Comm_MaskProcessedCreditCards')) {
				$mask_found = strpos($value, str_repeat('X', 4)) !== false;
				if ($this->Application->isAdminUser && $mask_found) {
					// masked card numbers always appear valid in admin
					return true;
				}
			}

			if (defined('DEBUG_MODE') && constOn('DBG_PAYMENT_GW')) {
				$gw_data = $this->getGatewayData();
				$this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] );
				$gateway_object =& $this->Application->recallObject( $gw_data['ClassName'] );
				$test_numbers = $gateway_object->GetTestCCNumbers();
				if (in_array($value, $test_numbers)) return true;
			}

			$error_field = isset($params['error_field']) ? $params['error_field'] : $field;
			// '1' => 'Visa','2' => 'Mastercard', '3' => 'Amex', '4' => 'Discover', 5 => 'Diners Club', 6 => 'JBC'

			// Innocent until proven guilty
			$cc_valid = true;

			//  Get rid of any non-digits			
			$value = preg_replace('/[^\d]/', '', $value);

			//  Perform card-specific checks, if applicable
			switch( $this->GetDBField($cardtype_field) )
			{
				case 2: // MasterCard
					$cc_valid = preg_match('/^5[1-5].{14}$/', $value);
					break;

				case 1: // Visa
					$cc_valid = preg_match('/^4.{15}$|^4.{12}$/', $value);
					break;

				case 3: // American Express
					$cc_valid = preg_match('/^3[47].{13}$/', $value);
					break;

				case 4: // Discover
					$cc_valid = preg_match('/^6011.{12}$/', $value);
					break;

				case 5: // Diners Club
					$cc_valid = preg_match('/^30[0-5].{11}$|^3[68].{12}$/', $value);
					break;

				case 6: // JBC
					$cc_valid = preg_match('/^3.{15}$|^2131|1800.{11}$/', $value);
					break;

				default:
					$this->FieldErrors[$error_field]['pseudo'] = 'credit_card_validation_error';
					return false;
					break;
			}

			//  The Luhn formula works right to left, so reverse the number.
			$value = strrev($value);

			$total = 0;

			for($x = 0; $x < strlen($value); $x++)
			{
				$digit = substr($value, $x, 1);

				// If it's an odd digit, double it
				if( $x / 2 != floor($x/2) )
				{
					$digit *= 2;

					// If the result is two digits, add them
					if( strlen($digit) == 2 )
					{
						$digit = substr($digit, 0, 1) + substr($digit, 1, 1);
					}
				}

				// Add the current digit, doubled and added if applicable, to the Total
				$total += $digit;
			}

			//  If it passed (or bypassed) the card-specific check and the Total is
			//  evenly divisible by 10, it's cool!
			if ($cc_valid && $total % 10 == 0)
			{
				return true;
			}
			else
			{
				$this->FieldErrors[$error_field]['pseudo'] = 'credit_card_validation_error';
				return false;
			}
		}

		/**
		 * Check if field value is non-expired credit card expiration date
		 *
		 * @param string $field field name
		 * @param Array $params field options from config
		 * @return bool
		 * @access private
		 */
		function ValidateCCExpiration($field, $params)
		{
			$formatter = getArrayValue($params, 'formatter');
			if( ($formatter != 'kCCDateFormatter') || !$this->requireCreditCard() ) return true;

			if(!$this->Application->isAdminUser) {
				// validate expiration date only for front
				if (preg_match('/([\d]{2})\/([\d]{2})/', $this->GetDBField($field), $rets)) {
					$month = $rets[1];
					$year = $rets[2];
					$now_date = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y') );
					$day_count = adodb_date('t', adodb_mktime(0, 0, 0, $month, 1, $year) );
					$cc_date = adodb_mktime(23, 59, 59, $month, $day_count, $year);

					if ($cc_date < $now_date) {
						$error_field = isset($params['error_field']) ? $params['error_field'] : $field;
						$this->FieldErrors[$error_field]['pseudo'] = 'credit_card_expired';

						return false;
					}
				}
			}

			return true;
		}

		function getNextSubNumber()
		{
			$table = $this->Application->GetLiveName($this->TableName);
			$sql = 'SELECT MAX(SubNumber) FROM '.$table.' WHERE Number = '.$this->GetDBField('Number');
			return $this->Conn->GetOne($sql) + 1;
		}

		function ResetAddress($prefix)
		{
			$fields = Array('To','Company','Phone','Fax','Email','Address1','Address2','City','State','Zip','Country');
			foreach($fields as $field)
			{
				$this->SetDBField($prefix.$field, $this->Fields[$prefix.$field]['default']);
			}
		}

		function IsProfileAddress($address_type)
		{
			return $this->Application->GetVar($this->Prefix.'_IsProfileAddress');
		}

		// ===== Gift Certificates Related =====
		function RecalculateGift(&$event)
		{
			$gc_id = $this->GetDBField('GiftCertificateId');
			if ($gc_id < 1) {
				return;
			}

			$gc =& $this->Application->recallObject('gc', null, Array('skip_autoload' => true));
			/* @var $gc kDBItem */

			$gc->Load($gc_id);

			if ($gc->GetDBField('Status') == gcDISABLED) {
				// disabled GC
				$this->SetDBField('GiftCertificateId', 0);
				$this->SetDBField('GiftCertificateDiscount', 0);
				// disabled
				return;
			}

			$debit = $gc->GetDBField('Debit') + $this->GetDBField('GiftCertificateDiscount');

			$this->UpdateTotals();

			$total = $this->GetDBField('TotalAmount');
			$gift_certificate_discount = $debit >= $total ? $total : $debit;

			$this->SetDBField('TotalAmount', $total - $gift_certificate_discount);
			$this->GetDBField('GiftCertificateDiscount', $gift_certificate_discount);

			$debit -= $gift_certificate_discount;
			$gc->SetDBField('Debit', $debit);

			$gc->SetDBField('Status', $debit > 0 ? gcENABLED : gcUSED);
			$gc->Update();

			if ($gift_certificate_discount == 0) {
				$this->RemoveGiftCertificate($object);
				$event->SetRedirectParam('checkout_error', 108);
			}
			$this->SetDBField('GiftCertificateDiscount', $gift_certificate_discount);
		}

		function RemoveGiftCertificate()
		{
			$gc_id = $this->GetDBField('GiftCertificateId');

			$gc =& $this->Application->recallObject('gc', null, Array('skip_autoload' => true));
			/* @var $gc kDBItem */

			$gc->Load($gc_id);

			$debit = $gc->GetDBField('Debit') + $this->GetDBField('GiftCertificateDiscount');

			if ($gc->isLoaded() && ($debit > 0)) {
				$gc->SetDBField('Debit', $debit);
				$gc->SetDBField('Status', gcENABLED);
				$gc->Update();
			}

			$this->SetDBField('GiftCertificateId', 0);
			$this->SetDBField('GiftCertificateDiscount', 0);
		}
	}