<?php

/**
 * @version 2.8.12
 * @package Joomla 2.5
 * @subpackage DT Register
 * @copyright Copyright (C) 2006 DTH Development
 * @copyright contact dthdev@dthdevelopment.com
 * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL
 */

defined ( '_JEXEC' ) or die ( 'Direct Access to this location is not allowed.' );

// Defines filter types used for a parameter in the cleanInput() function.
Define ( "CLEAN_INPUT_FILTER_ALPHABETIC", "alpha" );
Define ( "CLEAN_INPUT_FILTER_ALPHABETIC_AND_ACCENTED", "alpha and accented" );
Define ( "CLEAN_INPUT_FILTER_ALPHANUMERIC", "alphaNumeric" );
Define ( "CLEAN_INPUT_FILTER_ALPHANUMERIC_AND_ACCENTED", "alphaNumeric and accented" );
Define ( "CLEAN_INPUT_FILTER_NUMERIC", "numeric" );
Define ( "CLEAN_INPUT_FILTER_TEXT", "text" );
Define ( "CLEAN_INPUT_FILTER_WIDEST_ALLOWABLE_CHARACTER_RANGE", "text" );

Define ( "FIELD_VALID", "valid" );
Define ( "FIELD_INVALID", "invalid" );
Define ( "FIELD_INVALID_BAD_CHARACTERS", "bad characters" );
Define ( "FIELD_INVALID_BAD_FORMAT", "bad format" );
Define ( "FIELD_INVALID_MAXIMUM_LENGTH_EXCEEDED", "maximum exceeded" );
Define ( "FIELD_INVALID_MINIMUM_LENGTH_NOT_MET", "minimum not met" );
Define ( "FIELD_INVALID_REQUIRED_INPUT_VALUE_MISSING", "missing required value" );
Define ( "FIELD_INVALID_REQUIRED_INPUT_VALUE_NOT_SELECTED", "required value not selected" );

require_once (JPATH_SITE . '/components/com_dtregister/lib/class.payment.php');
class sagepay extends Payment {
	var $last_error; // holds the last error encountered
	var $ipn_log; // bool: log IPN results to text file?
	var $ipn_log_file; // filename of the IPN log
	var $ipn_data = array (); // array contains the POST values for IPN
	var $fields = array (); // array holds the fields to submit to Sagepay
	
	var $bywebservice = false;
	
	var $countries = array (
	        'GB' => 'United Kingdom',
			'AF' => 'Afghanistan',
			'AL' => 'Albania',
			'DZ' => 'Algeria',
			'AS' => 'American Samoa',
			'AD' => 'Andorra',
			'AO' => 'Angola',
			'AI' => 'Anguilla',
			'AQ' => 'Antarctica',
			'AG' => 'Antigua and Barbuda',
			'AR' => 'Argentina',
			'AM' => 'Armenia',
			'AW' => 'Aruba',
			'AU' => 'Australia',
			'AT' => 'Austria',
			'AZ' => 'Azerbaijan',
			'BS' => 'Bahamas',
			'BH' => 'Bahrain',
			'BD' => 'Bangladesh',
			'BB' => 'Barbados',
			'BY' => 'Belarus',
			'BE' => 'Belgium',
			'BZ' => 'Belize',
			'BJ' => 'Benin',
			'BM' => 'Bermuda',
			'BT' => 'Bhutan',
			'BO' => 'Bolivia',
			'BA' => 'Bosnia and Herzegowina',
			'BW' => 'Botswana',
			'BV' => 'Bouvet Island',
			'BR' => 'Brazil',
			'IO' => 'British Indian Ocean Territory',
			'BN' => 'Brunei Darussalam',
			'BG' => 'Bulgaria',
			'BF' => 'Burkina Faso',
			'BI' => 'Burundi',
			'KH' => 'Cambodia',
			'CM' => 'Cameroon',
			'CA' => 'Canada',
			'XC' => 'Canary Islands',
			'CV' => 'Cape Verde',
			'KY' => 'Cayman Islands',
			'CF' => 'Central African Republic',
			'TD' => 'Chad',
			'CL' => 'Chile',
			'CN' => 'China',
			'CX' => 'Christmas Island',
			'CC' => 'Cocos Keeling Islands',
			'CO' => 'Colombia',
			'KM' => 'Comoros',
			'CG' => 'Congo',
			'CK' => 'Cook Islands',
			'CR' => 'Costa Rica',
			'CI' => 'Cote D\'Ivoire',
			'HR' => 'Croatia',
			'CU' => 'Cuba',
			'CY' => 'Cyprus',
			'CZ' => 'Czech Republic',
			'DK' => 'Denmark',
			'DJ' => 'Djibouti',
			'DM' => 'Dominica',
			'DO' => 'Dominican Republic',
			'TP' => 'East Timor',
			'XE' => 'East Timor',
			'EC' => 'Ecuador',
			'EG' => 'Egypt',
			'SV' => 'El Salvador',
			'GQ' => 'Equatorial Guinea',
			'ER' => 'Eritrea',
			'EE' => 'Estonia',
			'ET' => 'Ethiopia',
			'FK' => 'Falkland Islands Malvinas',
			'FO' => 'Faroe Islands',
			'FJ' => 'Fiji',
			'FI' => 'Finland',
			'FR' => 'France',
			'GF' => 'French Guiana',
			'PF' => 'French Polynesia',
			'TF' => 'French Southern Territories',
			'GA' => 'Gabon',
			'GM' => 'Gambia',
			'GE' => 'Georgia',
			'DE' => 'Germany',
			'GH' => 'Ghana',
			'GI' => 'Gibraltar',
			'GR' => 'Greece',
			'GL' => 'Greenland',
			'GD' => 'Grenada',
			'GP' => 'Guadeloupe',
			'GU' => 'Guam',
			'GT' => 'Guatemala',
			'GN' => 'Guinea',
			'GW' => 'Guinea-bissau',
			'GY' => 'Guyana',
			'HT' => 'Haiti',
			'HM' => 'Heard and Mc Donald Islands',
			'HN' => 'Honduras',
			'HK' => 'Hong Kong',
			'HU' => 'Hungary',
			'IS' => 'Iceland',
			'IN' => 'India',
			'ID' => 'Indonesia',
			'IR' => 'Iran Islamic Republic of',
			'IQ' => 'Iraq',
			'IE' => 'Ireland',
			'IL' => 'Israel',
			'IT' => 'Italy',
			'JM' => 'Jamaica',
			'JP' => 'Japan',
			'JE' => 'Jersey',
			'JO' => 'Jordan',
			'KZ' => 'Kazakhstan',
			'KE' => 'Kenya',
			'KI' => 'Kiribati',
			'KP' => 'Korea, Democratic People\'s Republic of',
			'KR' => 'Korea, Republic of',
			'KW' => 'Kuwait',
			'KG' => 'Kyrgyzstan',
			'LA' => 'Lao People\'s Democratic Republic',
			'LV' => 'Latvia',
			'LB' => 'Lebanon',
			'LS' => 'Lesotho',
			'LR' => 'Liberia',
			'LY' => 'Libya',
			'LI' => 'Liechtenstein',
			'LT' => 'Lithuania',
			'LU' => 'Luxembourg',
			'MO' => 'Macau',
			'MK' => 'Macedonia, The Former Yugoslav Republic of',
			'MG' => 'Madagascar',
			'MW' => 'Malawi',
			'MY' => 'Malaysia',
			'MV' => 'Maldives',
			'ML' => 'Mali',
			'MT' => 'Malta',
			'MH' => 'Marshall Islands',
			'MQ' => 'Martinique',
			'MR' => 'Mauritania',
			'MU' => 'Mauritius',
			'YT' => 'Mayotte',
			'MX' => 'Mexico',
			'FM' => 'Micronesia, Federated States of',
			'MD' => 'Moldova, Republic of',
			'MC' => 'Monaco',
			'MN' => 'Mongolia',
			'MS' => 'Montserrat',
			'MA' => 'Morocco',
			'MZ' => 'Mozambique',
			'MM' => 'Myanmar',
			'NA' => 'Namibia',
			'NR' => 'Nauru',
			'NP' => 'Nepal',
			'NL' => 'Netherlands',
			'AN' => 'Netherlands Antilles',
			'NC' => 'New Caledonia',
			'NZ' => 'New Zealand',
			'NI' => 'Nicaragua',
			'NE' => 'Niger',
			'NG' => 'Nigeria',
			'NU' => 'Niue',
			'NF' => 'Norfolk Island',
			'MP' => 'Northern Mariana Islands',
			'NO' => 'Norway',
			'OM' => 'Oman',
			'PK' => 'Pakistan',
			'PW' => 'Palau',
			'PS' => 'Palestinian Territory, occupied',
			'PA' => 'Panama',
			'PG' => 'Papua New Guinea',
			'PY' => 'Paraguay',
			'PE' => 'Peru',
			'PH' => 'Philippines',
			'PN' => 'Pitcairn',
			'PL' => 'Poland',
			'PT' => 'Portugal',
			'PR' => 'Puerto Rico',
			'QA' => 'Qatar',
			'RE' => 'Reunion',
			'RO' => 'Romania',
			'RU' => 'Russian Federation',
			'RW' => 'Rwanda',
			'KN' => 'Saint Kitts and Nevis',
			'LC' => 'Saint Lucia',
			'VC' => 'Saint Vincent and the Grenadines',
			'WS' => 'Samoa',
			'SM' => 'San Marino',
			'ST' => 'Sao Tome and Principe',
			'SA' => 'Saudi Arabia',
			'SN' => 'Senegal',
			'RS' => 'Serbia',
			'SC' => 'Seychelles',
			'SL' => 'Sierra Leone',
			'SG' => 'Singapore',
			'MF' => 'Sint Maarten French Antilles',
			'SX' => 'Sint Maarten Netherlands Antilles',
			'SK' => 'Slovakia',
			'SI' => 'Slovenia',
			'SB' => 'Solomon Islands',
			'SO' => 'Somalia',
			'ZA' => 'South Africa',
			'GS' => 'South Georgia and the South Sandwich Islands',
			'ES' => 'Spain',
			'LK' => 'Sri Lanka',
			'XB' => 'St. Barthelemy',
			'XU' => 'St. Eustatius',
			'SH' => 'St. Helena',
			'PM' => 'St. Pierre and Miquelon',
			'SD' => 'Sudan',
			'SR' => 'Suriname',
			'SJ' => 'Svalbard and Jan Mayen Islands',
			'SZ' => 'Swaziland',
			'SE' => 'Sweden',
			'CH' => 'Switzerland',
			'SY' => 'Syrian Arab Republic',
			'TW' => 'Taiwan',
			'TJ' => 'Tajikistan',
			'TZ' => 'Tanzania, United Republic of',
			'TH' => 'Thailand',
			'DC' => 'The Democratic Republic of Congo',
			'TG' => 'Togo',
			'TK' => 'Tokelau',
			'TO' => 'Tonga',
			'TT' => 'Trinidad and Tobago',
			'TN' => 'Tunisia',
			'TR' => 'Turkey',
			'TM' => 'Turkmenistan',
			'TC' => 'Turks and Caicos Islands',
			'TV' => 'Tuvalu',
			'UG' => 'Uganda',
			'UA' => 'Ukraine',
			'AE' => 'United Arab Emirates',
			'US' => 'United States',
			'UM' => 'United States Minor Outlying Islands',
			'UY' => 'Uruguay',
			'UZ' => 'Uzbekistan',
			'VU' => 'Vanuatu',
			'VA' => 'Vatican City State Holy See',
			'VE' => 'Venezuela',
			'VN' => 'Viet Nam',
			'VG' => 'Virgin Islands British',
			'VI' => 'Virgin Islands U.S.',
			'WF' => 'Wallis and Futuna Islands',
			'EH' => 'Western Sahara',
			'YE' => 'Yemen',
			'ZM' => 'Zambia',
			'ZW' => 'Zimbabwe' 
	);

	function __construct() {
		global $sagepay_vendor, $sagepay_protocol, $Itemid;
		// initialization constructor. Called when class is created.
		
		parent::__construct ();
		
		$this->url = ($this->paymentmode == 'test') ? 'https://test.sagepay.com/gateway/service/vspform-register.vsp' : 'https://live.sagepay.com/gateway/service/vspform-register.vsp';
		
		$this->last_error = '';
		$this->ipn_log_file = dirname ( __FILE__ ) . "/sagepaylog.txt";
		$this->ipn_log = true;
		
		$this->add_field ( 'navigate', '' );
		$this->add_field ( 'TxType', 'PAYMENT' );
		$this->add_field ( 'Vendor', $sagepay_vendor );
		$this->add_field ( 'VPSProtocol', $sagepay_protocol );
		
		// $this->add_field('bn','PP-BuyNowBF:btn_buynowCC_LG.gif:NonHosted');
		$this->add_field ( 'charset', "utf-8" );
	}
	function add_field($field, $value) {
		$this->fields ["$field"] = $value;
	}
	function billingform() {
		ob_start ();
		
		$process = DT_Session::get ( 'Setting.process' );
		$paymentMethod = DT_Session::get ( $process . '.payment.method' );
		?>

<div style="clear: both">
	<strong><?php echo JText::_('DT_PAYMENT_INFORMATION'); ?></strong>
</div>
<div style="clear: both">
	<input size="30" type="hidden" name="paymentmethod"
		value="<?php echo $paymentMethod ?>" />
</div>

<dl>
	<dt class="dtlabel"><?php echo JText::_('DT_CARD_HOLDER_FIRSTNAME'); ?>:<span
			class="dtrequired"> * </span>
	</dt>
	<dd class="dtfield">
		<input size="30" id="billingFirstname" class="inputbox required"
			type="text" name="billing[firstname]"
			value="<?php echo isset($this->firstname) ? $this->firstname : '' ?>" />
	</dd>
</dl>

<dl>
	<dt class="dtlabel"><?php echo JText::_('DT_CARD_HOLDER_LASTNAME'); ?>:<span
			class="dtrequired"> * </span>
	</dt>
	<dd class="dtfield">
		<input size="30" id="billingLastname" class="inputbox required"
			type="text" name="billing[lastname]"
			value="<?php echo isset($this->lastname) ? $this->lastname : '' ?>" />
	</dd>
</dl>

<dl>
	<dt class="dtlabel"><?php echo JText::_('DT_BILLING_ADDRESS'); ?>:<span
			class="dtrequired"> * </span>
	</dt>
	<dd class="dtfield">
		<input size="30" id="billingAddress" class="inputbox required"
			type="text" name="billing[address]"
			value="<?php echo isset($this->address) ? $this->address : '' ?>" />
	</dd>
</dl>

<dl>
	<dt class="dtlabel"><?php echo JText::_('DT_BILLING_ADDRESS2'); ?>:
	</dt>
	<dd class="dtfield">
		<input size="30" id="billingAddress2" class="inputbox"
			type="text" name="billing[address2]"
			value="<?php echo isset($this->address2) ? $this->address2 : '' ?>" />
	</dd>
</dl>

<dl>
	<dt class="dtlabel"><?php echo JText::_('DT_CITY'); ?>:<span
			class="dtrequired"> * </span>
	</dt>
	<dd class="dtfield">
		<input size="30" id="billingCity" class="inputbox required"
			type="text" name="billing[city]"
			value="<?php echo isset($this->fcity) ? $this->city : '' ?>" />
	</dd>
</dl>

<dl>
	<dt class="dtlabel"><?php echo JText::_('DT_POST_CODE'); ?>:<span
			class="dtrequired"> * </span>
	</dt>
	<dd class="dtfield">
		<input size="30" id="billingZipcode" class="inputbox required"
			type="text" name="billing[zip]"
			value="<?php echo isset($this->zip) ? $this->zip : '' ?>" />
	</dd>
</dl>

<?php
		$country = (isset ( $this->country )) ? array_search ( $this->country, $this->countries ) : '';
		$countrydropdown = JHTML::_ ( 'select.genericlist', DtHtml::options ( $this->countries, JText::_ ( "DT_SELECT_COUNTRY" ) ), 'billing[country]', 'class="inputbox required"', 'value', 'text', array (
				$country 
		) );
		?>
<dl>
	<dt class="dtlabel"><?php echo JText::_('DT_COUNTRY'); ?>:<span
			class="dtrequired"> * </span>
	</dt>
	<dd class="dtfield"><?php echo $countrydropdown; ?></dd>
</dl>
		
<!--
<dl>
	<dt class="dtlabel"><?php echo JText::_('DT_STATE'); ?>:<span
			class="dtrequired"> * </span>
	</dt>
	<dd class="dtfield"><?php echo $statedropdown; ?></dd>
</dl>
-->

<dl>
	<dt class="dtlabel"><?php echo JText::_('DT_EMAIL'); ?>:<span
			class="dtrequired"> * </span>
	</dt>
	<dd class="dtfield">
		<input size="30" id="billingEmail" class="inputbox required"
			type="text" name="billing[email]"
			value="<?php echo isset($this->email) ? $this->email : '' ?>" />
	</dd>
</dl>

<?php
		foreach ( DT_Session::get ( $process . '.User' ) as $key => $user ) {
			if (! intval ( $key )) {
				continue;
			}
			break;
		}
		?>	
		<?php
		$data = ob_get_clean ();
		return $data;
	}
	function process() {
		global $currency_code,$Itemid,$sagepay_etype,$sagepay_password,$sagepay_email_to,$sagepay_vendor_email;
		
		$mosConfig_live_site = JURI::root ( false );
		
		//echo $currency_code;die;
		
		//if ($currency_code == "") {
			$currency_code = "GBP";
		//}
		
		$session_id = $this->saveSession ();
		
		$strPost = "VendorTxCode=" . $this->confirmNum;
		$strPost = $strPost . "&Amount=" . number_format($this->cart->getAmount() , 2, '.', '');
		$strPost = $strPost . "&Currency=".$currency_code;
		$strPost = $strPost."&Description=".$this->description;
		
		$strPost = $strPost . "&SuccessURL={$mosConfig_live_site}components/com_dtregister/success.php?return=$session_id&Itemid=$Itemid&task=restore";
		$strPost = $strPost . "&FailureURL={$mosConfig_live_site}components/com_dtregister/success.php?return=$session_id&Itemid=$Itemid&task=restore";
		
		if ($this->firstname || $this->lastname)
			$strPost = $strPost . "&CustomerName=" . $this->firstname . " " . $this->lastname;
		else
			$strPost = $strPost . "&CustomerName=";
		
		if ($sagepay_email_to == 0)
			$strPost = $strPost . "&SendEMail=0";
		else {
			if ($sagepay_email_to == 1) {
				$strPost = $strPost . "&SendEMail=1";
			} else {
				$strPost = $strPost . "&SendEMail=2";
			}
			if (strlen ( $this->email ) > 0)
				$strPost = $strPost . "&CustomerEMail=" . $this->email;
			if (($sagepay_vendor_email != "[your e-mail address]") && ($sagepay_vendor_email != ""))
				$strPost = $strPost . "&VendorEMail=" . $sagepay_vendor_email;
		}
		
		// Billing Details:
		$strPost = $strPost . "&BillingFirstnames=" . $this->firstname;
		$strPost = $strPost . "&BillingSurname=" . $this->lastname;
		$strPost = $strPost . "&BillingAddress1=" . $this->address;
		$strPost = $strPost . "&BillingCity=" . $this->city;
		$strPost = $strPost . "&BillingPostCode=" . $this->zip;
		$strPost = $strPost . "&BillingCountry=" . $this->country;
		if ($this->state && $this->country == 'US')
			$strPost = $strPost . "&BillingState=" . $this->state;
			// $strPost=$strPost . "&BillingPhone=" . $strBillingPhone;
			// Delivery Details:
		$strPost = $strPost . "&DeliveryFirstnames=" . $this->firstname;
		$strPost = $strPost . "&DeliverySurname=" . $this->lastname;
		$strPost = $strPost . "&DeliveryAddress1=" . $this->address;
		$strPost = $strPost . "&DeliveryCity=" . $this->city;
		$strPost = $strPost . "&DeliveryPostCode=" . $this->zip;
		$strPost = $strPost . "&DeliveryCountry=" . $this->country;
		if ($this->state && $this->country == 'US')
			$strPost = $strPost . "&DeliveryState=" . $this->state;
			// if (strlen($strDeliveryPhone) > 0) $strPost=$strPost . "&DeliveryPhone=" . $strDeliveryPhone;
		
		$strPost = $strPost . "&Basket=";
		
		//echo $strPost.'<br/><br/>';
		
		$strCrypt = $this->encryptAndEncode ( $strPost, $sagepay_etype, $sagepay_password );
		
		//echo $strCrypt.'<br/><br/>';die;
		
		$this->add_field ( 'Crypt', $strCrypt );
		
		$this->submit_sagepay_post ();
	}
	function submit_sagepay_post() {
		echo "<center><h3>" . JText::_ ( 'DT_SAGEPAY_REDIRECT_MSG' ) . "</h3></center>\n";
		echo "<form method=\"post\" name=\"formRegister\" action=\"" . $this->url . "\">\n";
		
		foreach ( $this->fields as $name => $value ) {
			echo "<input type=\"hidden\" name=\"$name\" value=\"$value\">";
		}
		?>

<script language="javascript">
			function rg_direc_to_sagepay(){
				document.formRegister.submit();
			}
			setTimeout("rg_direc_to_sagepay()",2000);
		</script>

<?php
		echo "</form>\n";
	}
	function success() {
		global $sagepay_password, $Itemid;
		
		$sagepay_data = JRequest::get ( 'get' );
		
		$strEncryptionPassword = $sagepay_password;
		$strCrypt = $sagepay_data ["crypt"];
		$strDecoded = $this->decodeAndDecrypt ( $strCrypt, $strEncryptionPassword );
		$values = $this->getToken ( $strDecoded );
		
		$this->ipn_data = $values;
		
		$strStatus = $values ['Status'];
		
		if ($strStatus == "OK") {
			$this->transactionId = substr($values ['VPSTxId'],1,-1);
			$process = DT_Session::get ( 'Setting.process' );
			DT_Session::set ( $process . '.payment.transactionId', $this->transactionId );
			$this->log_ipn_results ( true );
			return true;
		} else if ($strStatus == "NOTAUTHED")
			$this->last_error = "You payment was declined by the bank.  This could be due to insufficient funds, or incorrect card details.";
		else if ($strStatus == "ABORT")
			$this->last_error = "You chose to Cancel your order on the payment pages. If you wish to change your order and resubmit it you can do so here.";
		else if ($strStatus == "REJECTED")
			$this->last_error = "Your order did not meet our minimum fraud screening requirements. If you have questions about our fraud screening rules, or wish to contact us to discuss this.";
		else if ($strStatus == "INVALID" or $strStatus == "MALFORMED")
			$this->last_error = "We could not process your order because we have been unable to register your transaction with our Payment Gateway.";
		else if ($strStatus == "ERROR")
			$this->last_error = "We could not process your order because our Payment Gateway service was experiencing difficulties.";
		else
			$this->last_error = "The transaction process failed. Please contact us with the date and time of your order and we will investigate.";
		
		if ($this->last_error) {
			$baseurl = str_replace ( 'components/com_dtregister/', '', Juri::base () );
			$mainframe = JFactory::getApplication ();
			
			$this->log_ipn_results ( true );
			$mainframe->redirect ( $baseurl . "index.php?option=com_dtregister&controller=payment&task=cancel&Itemid=$Itemid", $this->last_error );
			return false;
		}
	}
	function log_ipn_results($success) {
		
		// if (!$this->ipn_log) return; // is logging turned off?
		// Timestamp
		$text = '[' . date ( 'm/d/Y g:i A' ) . '] - ';
		
		// Success or failure being logged?
		
		if ($success)
			$text .= "SUCCESS!\n";
		
		else
			$text .= 'FAIL: ' . $this->last_error . "\n";
			
			// Log the POST variables
		
		$text .= "POST Vars from Sagepay:\n";
		
		foreach ( $this->ipn_data as $key => $value ) {
			
			$text .= "$key=$value, ";
		}
		
		// Write to log
		
		$fp = fopen ( $this->ipn_log_file, 'a' );
		
		fwrite ( $fp, $text . "\n\n" );
		
		fwrite ( $fp, var_export ( $_SESSION, true ) . "\n\n" );
		
		fclose ( $fp ); // close file
	}
	function dump_fields() {
		echo "<h3>sagepay_class->dump_fields() Output:</h3>";
		
		echo "<table width=\"95%\" border=\"1\" cellpadding=\"2\" cellspacing=\"0\">

            <tr>

               <td bgcolor=\"black\"><b><font color=\"white\">Field Name</font></b></td>

               <td bgcolor=\"black\"><b><font color=\"white\">Value</font></b></td>

            </tr>";
		
		ksort ( $this->fields );
		
		foreach ( $this->fields as $key => $value ) {
			
			echo "<tr><td>$key</td><td>" . urldecode ( $value ) . "&nbsp;</td></tr>";
		}
		
		echo "</table><br>";
	}
	function notify() {
		if ($this->validate_ipn ()) {
			$this->transactionId = $_REQUEST ['TxAuthNo'];
			$process = DT_Session::get ( 'Setting.process' );
			DT_Session::set ( $process . '.payment.transactionId', $this->transactionId );
			return true;
		} else {
			// pr($this->validate_ipn());
			return false;
		}
	}
	
	/*
	 * The getToken function. ** * NOTE: A function of convenience that extracts the value from the "name=value&name2=value2..." reply string ** * Works even if one of the values is a URL containing the & or = signs.
	 */
	function getToken($thisString) {
		
		// List the possible tokens
		$Tokens = array (
				"Status",
				"StatusDetail",
				"VendorTxCode",
				"VPSTxId",
				"TxAuthNo",
				"Amount",
				"AVSCV2",
				"AddressResult",
				"PostCodeResult",
				"CV2Result",
				"GiftAid",
				"3DSecureStatus",
				"CAVV",
				"AddressStatus",
				"CardType",
				"Last4Digits",
				"PayerStatus" 
		);
		
		// Initialise arrays
		$output = array ();
		$resultArray = array ();
		
		// Get the next token in the sequence
		for($i = count ( $Tokens ) - 1; $i >= 0; $i --) {
			// Find the position in the string
			$start = strpos ( $thisString, $Tokens [$i] );
			// If it's present
			if ($start !== false) {
				// Record position and token name
				$resultArray [$i]->start = $start;
				$resultArray [$i]->token = $Tokens [$i];
			}
		}
		
		// Sort in order of position
		sort ( $resultArray );
		// Go through the result array, getting the token values
		for($i = 0; $i < count ( $resultArray ); $i ++) {
			// Get the start point of the value
			$valueStart = $resultArray [$i]->start + strlen ( $resultArray [$i]->token ) + 1;
			// Get the length of the value
			if ($i == (count ( $resultArray ) - 1)) {
				$output [$resultArray [$i]->token] = substr ( $thisString, $valueStart );
			} else {
				$valueLength = $resultArray [$i + 1]->start - $resultArray [$i]->start - strlen ( $resultArray [$i]->token ) - 2;
				$output [$resultArray [$i]->token] = substr ( $thisString, $valueStart, $valueLength );
			}
		}
		
		// Return the ouput array
		return $output;
	}
	
	// Filters unwanted characters out of an input string based on type. Useful for tidying up FORM field inputs
	// Parameter strRawText is a value to clean.
	// Parameter filterType is a value from one of the CLEAN_INPUT_FILTER_ constants.
	function cleanInput($strRawText, $filterType) {
		$strAllowableChars = "";
		$blnAllowAccentedChars = FALSE;
		$strCleaned = "";
		$filterType = strtolower ( $filterType ); // ensures filterType matches constant values
		
		if ($filterType == CLEAN_INPUT_FILTER_TEXT) {
			$strAllowableChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,'/\\{}@():?-_&£$=%~*+\"\n\r";
			$strCleaned = $this->cleanInput2 ( $strRawText, $strAllowableChars, TRUE );
		} elseif ($filterType == CLEAN_INPUT_FILTER_NUMERIC) {
			$strAllowableChars = "0123456789 .,";
			$strCleaned = $this->cleanInput2 ( $strRawText, $strAllowableChars, FALSE );
		} elseif ($filterType == CLEAN_INPUT_FILTER_ALPHABETIC || $filterType == CLEAN_INPUT_FILTER_ALPHABETIC_AND_ACCENTED) {
			$strAllowableChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz";
			if ($filterType == CLEAN_INPUT_FILTER_ALPHABETIC_AND_ACCENTED)
				$blnAllowAccentedChars = TRUE;
			$strCleaned = $this->cleanInput2 ( $strRawText, $strAllowableChars, $blnAllowAccentedChars );
		} elseif ($filterType == CLEAN_INPUT_FILTER_ALPHANUMERIC || $filterType == CLEAN_INPUT_FILTER_ALPHANUMERIC_AND_ACCENTED) {
			$strAllowableChars = "0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
			if ($filterType == CLEAN_INPUT_FILTER_ALPHANUMERIC_AND_ACCENTED)
				$blnAllowAccentedChars = TRUE;
			$strCleaned = $this->cleanInput2 ( $strRawText, $strAllowableChars, $blnAllowAccentedChars );
		} else { // Widest Allowable Character Range
			$strAllowableChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,'/\\{}@():?-_&£$=%~*+\"\n\r";
			$strCleaned = $this->cleanInput2 ( $strRawText, $strAllowableChars, TRUE );
		}
		
		return $strCleaned;
	}
	
	// Filters unwanted characters out of an input string based on an allowable character set. Useful for tidying up FORM field inputs
	// Parameter strRawText is a value to clean.
	// Parameter "strAllowableChars" is a string of characters allowable in "strRawText" if its to be deemed valid.
	// Parameter "blnAllowAccentedChars" accepts a boolean value which determines if "strRawText" can contain Accented or High-order characters.
	function cleanInput2($strRawText, $strAllowableChars, $blnAllowAccentedChars) {
		$iCharPos = 0;
		$chrThisChar = "";
		$strCleanedText = "";
		
		// Compare each character based on list of acceptable characters
		while ( $iCharPos < strlen ( $strRawText ) ) {
			// Only include valid characters **
			$chrThisChar = substr ( $strRawText, $iCharPos, 1 );
			if (strpos ( $strAllowableChars, $chrThisChar ) !== FALSE) {
				$strCleanedText = $strCleanedText . $chrThisChar;
			} elseIf ($blnAllowAccentedChars == TRUE) {
				// Allow accented characters and most high order bit chars which are harmless **
				if (ord ( $chrThisChar ) >= 191) {
					$strCleanedText = $strCleanedText . $chrThisChar;
				}
			}
			
			$iCharPos = $iCharPos + 1;
		}
		
		return $strCleanedText;
	}
	
	/*
	 * Base 64 Encoding function ** * PHP does it natively but just for consistency and ease of maintenance, let's declare our own function *
	 */
	function base64Encode($plain) {
		// Initialise output variable
		$output = "";
		
		// Do encoding
		$output = base64_encode ( $plain );
		
		// Return the result
		return $output;
	}
	
	/*
	 * Base 64 decoding function ** * PHP does it natively but just for consistency and ease of maintenance, let's declare our own function *
	 */
	function base64Decode($scrambled) {
		// Initialise output variable
		$output = "";
		
		// Fix plus to space conversion issue
		$scrambled = str_replace ( " ", "+", $scrambled );
		
		// Do encoding
		$output = base64_decode ( $scrambled );
		
		// Return the result
		return $output;
	}
	
	/*
	 * The SimpleXor encryption algorithm ** * NOTE: This is a placeholder really. Future releases of Form will use AES or TwoFish. Proper encryption ** * This simple function and the Base64 will deter script kiddies and prevent the "View Source" type tampering ** * It won't stop a half decent hacker though, but the most they could do is change the amount field to something ** * else, so provided the vendor checks the reports and compares amounts, there is no harm done. It's still ** * more secure than the other PSPs who don't both encrypting their forms at all
	 */
	function simpleXor($InString, $Key) {
		// Initialise key array
		$KeyList = array ();
		// Initialise out variable
		$output = "";
		
		// Convert $Key into array of ASCII values
		for($i = 0; $i < strlen ( $Key ); $i ++) {
			$KeyList [$i] = ord ( substr ( $Key, $i, 1 ) );
		}
		
		// Step through string a character at a time
		for($i = 0; $i < strlen ( $InString ); $i ++) {
			// Get ASCII code from string, get ASCII code from key (loop through with MOD), XOR the two, get the character from the result
			// % is MOD (modulus), ^ is XOR
			$output .= chr ( ord ( substr ( $InString, $i, 1 ) ) ^ ($KeyList [$i % strlen ( $Key )]) );
		}
		
		// Return the result
		return $output;
	}
	
	// ** Wrapper function do encrypt an encode based on strEncryptionType setting **
	function encryptAndEncode($strIn, $strEncryptionType, $strEncryptionPassword) {
		if ($strEncryptionType == "XOR") {
			// ** XOR encryption with Base64 encoding **
			return $this->base64Encode ( $this->simpleXor ( $strIn, $strEncryptionPassword ) );
		} else {
			// ** AES encryption, CBC blocking with PKCS5 padding then HEX encoding - DEFAULT **
			// ** use initialization vector (IV) set from $strEncryptionPassword
			$strIV = $strEncryptionPassword;
			
			// ** add PKCS5 padding to the text to be encypted
			$strIn = $this->addPKCS5Padding ( $strIn );
			
			// ** perform encryption with PHP's MCRYPT module
			$strCrypt = mcrypt_encrypt ( MCRYPT_RIJNDAEL_128, $strEncryptionPassword, $strIn, MCRYPT_MODE_CBC, $strIV );
			
			// ** perform hex encoding and return
			return "@" . bin2hex ( $strCrypt );
		}
	}
	
	// ** Wrapper function do decode then decrypt based on header of the encrypted field **
	function decodeAndDecrypt($strIn, $strEncryptionPassword) {
		if (substr ( $strIn, 0, 1 ) == "@") {
			// ** HEX decoding then AES decryption, CBC blocking with PKCS5 padding - DEFAULT **
			// ** use initialization vector (IV) set from $strEncryptionPassword
			$strIV = $strEncryptionPassword;
			
			// ** remove the first char which is @ to flag this is AES encrypted
			$strIn = substr ( $strIn, 1 );
			
			// ** HEX decoding
			$strIn = pack ( 'H*', $strIn );
			
			// ** perform decryption with PHP's MCRYPT module
			return $this->removePKCS5Padding ( mcrypt_decrypt ( MCRYPT_RIJNDAEL_128, $strEncryptionPassword, $strIn, MCRYPT_MODE_CBC, $strIV ) );
		} else {
			// ** Base 64 decoding plus XOR decryption **
			return $this->simpleXor ( $this->base64Decode ( $strIn ), $strEncryptionPassword );
		}
	}
	
	// New function added 2011-12-29
	// Need to remove padding bytes from end of decoded string
	function removePKCS5Padding($decrypted) {
		$padChar = ord ( $decrypted [strlen ( $decrypted ) - 1] );
		return substr ( $decrypted, 0, - $padChar );
	}
	
	// ** PHP's mcrypt does not have built in PKCS5 Padding, so we use this
	function addPKCS5Padding($input) {
		$blocksize = 16;
		$padding = "";
		
		// Pad input to an even block size boundary
		$padlength = $blocksize - (strlen ( $input ) % $blocksize);
		for($i = 1; $i <= $padlength; $i ++) {
			$padding .= chr ( $padlength );
		}
		
		return $input . $padding;
	}
	
	// Inspects and validates user input for a name field. Returns TRUE if input value is valid as a name field.
	// Parameter "strInputValue" is the field value to validate.
	// Parameter "returnedResult" sets a result to a value from the list of field validation constants beginning with "FIELD_".
	function isValidNameField($strInputValue, &$returnedResult) {
		$strAllowableChars = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-.'&\\";
		$strInputValue = trim ( $strInputValue );
		$returnedResult = $this->validateString ( $strInputValue, $strAllowableChars, TRUE, TRUE, 20, - 1 );
		if ($returnedResult == FIELD_VALID) {
			return TRUE;
		} else {
			return FALSE;
		}
	}
	
	// Inspects and validates user input for an Address field.
	// Parameter "blnIsRequired" specifies whether "strInputValue" must have a non-null and non-empty value.
	function isValidAddressField($strInputValue, $blnIsRequired, &$returnedResult) {
		$strAllowableChars = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-.',/\\()&:+\n\r";
		$strInputValue = trim ( $strInputValue );
		$returnedResult = $this->validateString ( $strInputValue, $strAllowableChars, TRUE, $blnIsRequired, 100, - 1 );
		
		if ($returnedResult == FIELD_VALID) {
			return TRUE;
		} else {
			return FALSE;
		}
	}
	
	// Inspects and validates user input for a City field.
	function isValidCityField($strInputValue, &$returnedResult) {
		$strAllowableChars = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-.',/\\()&:+\n\r";
		$strInputValue = trim ( $strInputValue );
		$returnedResult = $this->validateString ( $strInputValue, $strAllowableChars, TRUE, TRUE, 40, - 1 );
		
		if ($returnedResult == FIELD_VALID) {
			return TRUE;
		} else {
			return FALSE;
		}
	}
	
	// Inspects and validates user input for a Postcode/zip field.
	function isValidPostcodeField($strInputValue, &$returnedResult) {
		$strAllowableChars = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-";
		$strInputValue = trim ( $strInputValue );
		$returnedResult = $this->validateString ( $strInputValue, $strAllowableChars, FALSE, TRUE, 10, - 1 );
		
		if ($returnedResult == FIELD_VALID) {
			return TRUE;
		} else {
			return FALSE;
		}
	}
	
	// Inspects and validates user input for an email field.
	function isValidEmailField($strInputValue, &$returnedResult) {
		// The allowable e-mail address format accepted by the SagePay gateway must be RFC 5321/5322 compliant (see RFC 3696)
		$sEmailRegExpPattern = '/^[a-z0-9\xC0-\xFF\!#$%&amp;\'*+\/=?^_`{|}~\*-]+(?:\.[a-z0-9\xC0-\xFF\!#$%&amp;\'*+\/=?^_`{|}~*-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:[a-z]{2,3}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|at|coop|travel)$/';
		$strInputValue = trim ( $strInputValue );
		$returnedResult = $this->validateStringWithRegExp ( $strInputValue, $sEmailRegExpPattern, FALSE );
		
		if ($returnedResult == FIELD_VALID) {
			return TRUE;
		} else {
			return FALSE;
		}
	}
	
	// Inspects and validates user input for a phone field.
	function isValidPhoneField($strInputValue, &$returnedResult) {
		$strAllowableChars = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-()+";
		$strInputValue = trim ( $strInputValue );
		$returnedResult = $this->validateString ( $strInputValue, $strAllowableChars, FALSE, FALSE, 20, - 1 );
		
		if ($returnedResult == FIELD_VALID) {
			return TRUE;
		} else {
			return FALSE;
		}
	}
	
	// A generic function used to inspect and validate a string from user input.
	// Parameter "strInputValue" is the value to perform validation on.
	// Parameter "strAllowableChars" is a string of characters allowable in "strInputValue" if its to be deemed valid.
	// Parameter "blnAllowAccentedChars" accepts a boolean value which determines if "strInputValue" can contain Accented or High-order characters.
	// Parameter "blnIsRequired" accepts a boolean value which specifies whether "strInputValue" must have a non-null and non-empty value.
	// Parameter "intMaxLength" accepts an integer which specifies the maximum allowable length of "strInputValue". Set to -1 for this to be ignored.
	// Parameter "intMinLength" specifies the miniumum allowable length of "strInputValue". Set to -1 for this to be ignored.
	// Returns a result from one of the field validation constants that begin with "FIELD_"
	function validateString($strInputValue, $strAllowableChars, $blnAllowAccentedChars, $blnIsRequired, $intMaxLength, $intMinLength) {
		if ($blnIsRequired == TRUE && strlen ( $strInputValue ) == 0) {
			return FIELD_INVALID_REQUIRED_INPUT_VALUE_MISSING;
		} elseif (($intMaxLength != - 1) && (strlen ( $strInputValue ) > $intMaxLength)) {
			return FIELD_INVALID_MAXIMUM_LENGTH_EXCEEDED;
		} elseif ($strInputValue != cleanInput2 ( $strInputValue, $strAllowableChars, $blnAllowAccentedChars )) {
			return FIELD_INVALID_BAD_CHARACTERS;
		} elseif (($blnIsRequired == TRUE) && (strlen ( $strInputValue ) < $intMinLength)) {
			return FIELD_INVALID_MINIMUM_LENGTH_NOT_MET;
		} elseif (($blnIsRequired == FALSE) && (strlen ( $strInputValue ) > 0) && (strlen ( $strInputValue ) < $intMinLength)) {
			return FIELD_INVALID_MINIMUM_LENGTH_NOT_MET;
		} else {
			return FIELD_VALID;
		}
	}
	
	// A generic function to inspect and validate a string from user input based on a Regular Expression pattern.
	// Parameter "strInputValue" is the value to perform validation on.
	// Parameter "strRegExPattern" is a Regular Expression string pattern used to validate against "strInputValue".
	// Parameter "blnIsRequired" accepts a boolean value which specifies whether "strInputValue" must have a non-null and non-empty value.
	// Returns a result from one of the field validation constants that begin with "FIELD_"
	function validateStringWithRegExp($strInputValue, $strRegExPattern, $blnIsRequired) {
		if ($blnIsRequired == TRUE && strlen ( $strInputValue ) == 0) {
			return FIELD_INVALID_REQUIRED_INPUT_VALUE_MISSING;
		} elseif (strlen ( $strInputValue ) > 0) {
			if (preg_match ( $strRegExPattern, $strInputValue )) {
				return FIELD_VALID;
			} else {
				return FIELD_INVALID_BAD_FORMAT;
			}
		} else {
			return FIELD_VALID;
		}
	}
	
	// Maps a Field Validation constant value to a string representing a user friendly validation error message.
	// Parameter "strFieldLabelName" is the display name of the form field to use in the returned message.
	function getValidationMessage($fieldValidationCode, $strFieldLabelName) {
		$strReturn = "";
		
		switch ($fieldValidationCode) {
			case FIELD_INVALID_BAD_CHARACTERS :
				$strReturn = "Please correct " . $strFieldLabelName . " as it contains disallowed characters.";
				break;
			case FIELD_INVALID_BAD_FORMAT :
				$strReturn = "Please correct " . $strFieldLabelName . " as the format is invalid.";
				break;
			case FIELD_INVALID_MINIMUM_LENGTH_NOT_MET :
				$strReturn = "Please correct " . $strFieldLabelName . " as the value is not long enough.";
				break;
			case FIELD_INVALID_MAXIMUM_LENGTH_EXCEEDED :
				$strReturn = "Please correct " . $strFieldLabelName . " as the value is too long.";
				break;
			case FIELD_INVALID_REQUIRED_INPUT_VALUE_MISSING :
				$strReturn = "Please enter a value for " . $strFieldLabelName . " where requested below.";
				break;
			case FIELD_INVALID_REQUIRED_INPUT_VALUE_NOT_SELECTED :
				$strReturn = "Please select a value for " . $strFieldLabelName . " where requested below.";
				break;
		}

		return $strReturn;
	}
}
