<?php
/**
* @version   $Id: order_validator.php 16378 2016-07-25 08:49:52Z alex $
* @package   In-Commerce
* @copyright   Copyright (C) 1997 - 2011 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 OrderValidator extends kValidator {

	public function __construct()
	{
		parent::__construct();

		$this->ErrorMsgs['credit_card_validation_error'] = '!lu_cc_validation_error!';
		$this->ErrorMsgs['credit_card_expired'] = '!lu_cc_expired!';
	}

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

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

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

		// 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'];
		}

		return '';

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

		return $msg;*/
	}

	/**
	 * 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)
	{
		// TODO: move to OrdersEventHandler OR extend kValidator class
		$res = true;

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

	/**
	 * 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)
	{
		$value = $this->dataSource->GetDBField($field);
		$cardtype_field = getArrayValue($params, 'cardtype_field');

		if ( !$cardtype_field || !$value || !$this->dataSource->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') && kUtil::constOn('DBG_PAYMENT_GW')) {
			$gw_data = $this->dataSource->getGatewayData();

			/** @var kGWBase $gateway_object */
			$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->dataSource->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->SetError($error_field, '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;
		}

		$this->SetError($error_field, '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->dataSource->requireCreditCard() ) {
			return true;
		}

		if ( !$this->Application->isAdminUser ) {
			// validate expiration date only for front
			if ( preg_match('/([\d]{2})\/([\d]{2})/', $this->dataSource->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->SetError($error_field, 'credit_card_expired');

					return false;
				}
			}
		}

		return true;
	}
}
