Location: PHPKode > scripts > NuCaptcha (PHP) > nucaptcha-client-php-1.0.12882/nucaptcha-client-php-1.0.12882/php/leapmarketingclient.php
<?php
/**
 * @package   NuCaptcha PHP clientlib
 * @author    <hide@address.com> Leap Marketing Technologies Inc
 * @license   LGPL License 2.1 (see included license.txt)
 * @link      http://www.nucaptcha.com/api/php
 */

define("LEAP_LIBRARY_VERSION", 'Dev: $Change: 12820 $');

// check requirements
//// required for checks
require_once(dirname(__FILE__) . "/leaphelper.php");

// PHP_VERSION_ID is available as of PHP 5.2.7, if our 
// version is lower than that, then emulate it
if (!defined('PHP_VERSION_ID')) {
    $version = explode('.', PHP_VERSION);

    define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2]));
}

if( PHP_VERSION_ID < 50100 ) {
    die('<div style="color:red;">The NuCaptcha API requires PHP version 5.1 or higher. You are currently running PHP version ' . PHP_VERSION. '</div>');
}

if( !extension_loaded('mcrypt') ) {
    die('<div style="color:red;">The NuCaptcha API requires the PHP <a href="http://www.php.net/manual/en/book.mcrypt.php">mcrypt</a> module.</div>');
}

if(false == defined('LM_DEBUG'))
{
    define('LM_DEBUG', false);
}

// on windows platforms prior to 5.3.0, make sure
// srand has been called.
if(false === lmHelper::checkWindowsVersion(50300))
{
	srand();
}

define('LM_INVALID_CONNECTION_PERSISTENT_DATA', 'INVALID TOKEN CONNECTION');
define('LM_FAILURE_HTML', '<div id="nucaptcha-error-widget"><input type="hidden" name="lmsubmitted" value="1"/></div>');

// define some things here for the rest of the world
// In the non-client version, these are defined in leapglobal.config, which is loaded by leapcore.php
if (!defined('LM_DEFAULTCONFIG'))
{
	define('LM_DEFAULTCONFIG', '/etc/httpd/conf.d/leap/leap.config');
}

if (!defined('LM_TOKEN_NUMMESSAGES'))
{
    define('LM_TOKEN_NUMMESSAGES', 5);
}
if (!defined('LM_RANDOMFILE'))
{
    define('LM_RANDOMFILE', '/var/lib/leap/iv/random');
}
if(!defined('LM_FALLBACK_TOKEN_SERVER'))
{
	define('LM_FALLBACK_TOKEN_SERVER', 'http://token.alt.nucaptcha.com/' );
}
if(!defined('LM_FALLBACK_VALIDATE_SERVER'))
{
	define('LM_FALLBACK_VALIDATE_SERVER', 'http://validate.alt.nucaptcha.com/' );
}

/*
 * apps like wordpress require files to be included this way
 */
require_once(dirname(__FILE__) . "/leapstatuscodes.php");
require_once(dirname(__FILE__) . "/leapclientexception.php");
require_once(dirname(__FILE__) . "/leapperformance.php");
require_once(dirname(__FILE__) . "/leapglobalperformance.php");
require_once(dirname(__FILE__) . "/leapbase64.php");
require_once(dirname(__FILE__) . "/leaptextchunk.php");
require_once(dirname(__FILE__) . "/leapsymmetric.php");
require_once(dirname(__FILE__) . "/leaprpclite.php");
require_once(dirname(__FILE__) . "/leaptransactioninterface.php");
require_once(dirname(__FILE__) . "/leappublishertransaction.php");
require_once(dirname(__FILE__) . "/leaptransactionerror.php");
require_once(dirname(__FILE__) . "/leapconfigfile.php");
require_once(dirname(__FILE__) . "/leapresponse.php");
require_once(dirname(__FILE__) . "/leapvalidationinterface.php");
require_once(dirname(__FILE__) . "/leappublishervalidation.php");
require_once(dirname(__FILE__) . "/leapclusterpicker.php");
require_once(dirname(__FILE__) . "/leaperrorreporter.php");
require_once(dirname(__FILE__) . "/leapurlcoding.php");

function SetLeapErrorCode($code, $msg)
{
    Leap::SetErrorCode($code, $msg);
}

class Leap
{
	// Constants for template selection
	const TEMPLATE_DEFAULT        = 0;
	const TEMPLATE_FLASHONLY      = 1;
	const TEMPLATE_JAVASCRIPTONLY = 2;

	// Constants for leap player positioning
	const POSITION_LEFT   = 'left';
	const POSITION_RIGHT  = 'right';
	const POSITION_CENTER = 'center';
	
	// Constants for captcha purpose
		
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use for account creation captchas.
	 *
	 * @var string
	 */
	const PURPOSE_CREATE_ACCOUNT = 'create_account';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use for password reset captchas.
	 *
	 * @var string
	 */
	const PURPOSE_PASSWORD_RESET = 'password_reset';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use for login form captchas.
	 *
	 * @var string
	 */
	const PURPOSE_LOGIN = 'login';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use for captcha on posting new content (blog post creation, forum thread creation, wiki page creation etc)
	 *
	 * @var string
	 */
	const PURPOSE_POST = 'post';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use for captcha on commenting on existing content (blog comments, new article comments, forum replies etc)
	 *
	 * @var string
	 */
	const PURPOSE_COMMENT = 'comment';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use for captcha on editing existing content (editing existing posts, wiki editing etc)
	 *
	 * @var string
	 */
	const PURPOSE_EDIT = 'edit';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use for captcha on voting (article votes, likes/dislikes, polls etc) 
	 *
	 * @var string
	 */
	const PURPOSE_VOTE = 'vote';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use for captchas on actions that will send users content (newsletter subscribe/unsubscribe, forum watch etc) 
	 *
	 * @var string
	 */
	const PURPOSE_SEND =  'send';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use when captcha is protecting a generic action to ensure the user is human (ticket purchase, limited action etc) 
	 *
	 * @var string
	 */
	const PURPOSE_ACTION = 'action';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Use when no other purpose constants apply.
	 *
	 * @var string
	 */
	const PURPOSE_GENERIC = 'generic';
	
	/**
	 * Purpose constant passed into Leap::InitializeTransaction.
	 * Default purpose used when not explicitly set.
	 *
	 * @var string
	 */
	const PURPOSE_UNKNOWN = 'unknown';

	const LANGUAGE_ENGLISH = "eng";
	const LANGUAGE_FRENCH  = "fre";
	const LANGUAGE_GERMAN  = "deu";
	const LANGUAGE_SPANISH  = "spa";
	const LANGUAGE_ITALIAN  = "ita";
	const LANGUAGE_RUSSIAN  = "rus";
	const LANGUAGE_CHINESE  = "zho";

	/**
	 * Message modes used when communicating with NuCaptcha servers.
	 *
	 * MMODE_COMPRESSED_ENCRYPTED is the default.
	 *
	 * @var string
	 */
	const MMODE_COMPRESSED_ENCRYPTED  = "compressed-encrypted";
	const MMODE_ENCRYPTED  = "encrypted";

	// IMPORTANT: If you update any PURPOSE_ constants you will also need to update the validation code in lmPublisherTransaction ctor.
	
	/**
	 * The last error code (should be one of the leap error codes in leaperrorcodes.php)
	 * You can retrieve this with Leap::GetErrorCode()
	 *
	 * @var int
	 */
	static private $mLastErrorCode = LMSC_NOTRANSACTION;

	/**
	 * A message to go with the last error code, to provide more information
	 * You can retrieve this with Leap::GetErrorString()
	 *
	 * @var string
	 */
	static private $mLastErrorMessage = "InitializeTransaction Not Called.";

	/**
	 * Stores hints to be set for the next transaction
	 *
	 * @var Array
	 */
	static private $sHints = Array();

	/**
	 * Message mode used when communicating with NuCaptcha servers
	 * @var string
	 */
	static private $sMessageMode = self::MMODE_ENCRYPTED;

	/**
	 * @var lmTextChunkData
	 */
	static private $sClientKey        = false;

	static private $sMasterTokenServer    = 'http://token.nucaptcha.com';
	static private $sClusterRecord    = "clusters.nucaptcha.com";
	static private $sForceTokenServer = false;
	static private $sValidateOnError = true;
	static private $sTestAction = '';

	/**
	 * @var lmTransactionInterface
	 */
	static private $sLastTransaction = null;




	/** !EXPORT
	 * Parse a config file and set the values for this transactions.
	 *
	 * Ensure you store this configuration file in a location NOT ACCESSIBLE by web users. *
	 * The default config file location is stored in LM_DEFAULTCONFIG.  It's best to not *
	 * have your config inside your webroot.  If you must have it in your webroot, protect it *
	 * with an .htaccess file.
	 *
	 * You can retrive your config file from the Publisher Dashboard <http://console.nucaptcha.com> *
	 * and going to the download section.  Click the download link located at the top of the screen after you login.
	 *
	 * @param string $configFilePath - Path to the configuration file.  Download it at <http://console.nucaptcha.com>.
	 */
	public static function LoadConfigFile($configFilePath)
	{
		self::ResetConfig();

		// we want any errors here to bubble up.
		$localConfig = new lmConfigFile($configFilePath);
		self::SetClientKey($localConfig->GetGlobal('clientKey'));

		// the rest are optional
		try
		{
			self::SetClusterRecord($localConfig->GetGlobal('clusterRecord'));
		}
		catch(Exception $e)
		{

		}

		try
		{
			self::SetForceTokenServer($localConfig->GetGlobal('forceTokenServer'));
		}
		catch(Exception $e)
		{

		}
	}

	/** !EXPORT
	 * Set a client key for the transaction.  Alternative to LoadConfigFile.
	 *
	 * This is the preferred method of storing your client key in PHP as it removes the need *
	 * to share or secure your config file.
	 *
	 * You can retrive your client key from the Publisher Dashboard <http://console.nucaptcha.com> *
	 * and going to the download section.  Click the download link located at the top of the screen after you login.
	 *
	 * @param string $clientKey - Your private key.  Get it at <http://console.nucaptcha.com>.
	 */
	public static function SetClientKey($clientKey)
	{
		try
		{
			self::$sClientKey = lmTextChunk::Decode($clientKey, 'CLIENTKEY');
		}
		catch(Exception $e)
		{
			self::SetErrorCode(LMSC_CLIENTKEYNOTSET, 'Invalid client key: ' . $clientKey);
		}
	}

	/**
	 * Set the cluster record for the transaction. Used for testing.
	 * @param string clusterRecord
	 * @return
	 */
	public static function SetClusterRecord($clusterRecord)
	{
		self::$sClusterRecord = $clusterRecord;
	}

	/**
	 * Force a token server. Used for testing.
	 * @param forceTokenServer
	 * @return
	 */
	public static function SetForceTokenServer($forceTokenServer)
	{
		self::$sForceTokenServer = $forceTokenServer;
	}

	/**
	 * Get the current client key.
	 * @return lmTextChunkData
	 */
	static public function GetClientKey()
	{
		return self::$sClientKey;
	}

	/**
	 * Get the current cluster record.
	 * @return string
	 */
	public static function GetClusterRecord()
	{
		return self::$sClusterRecord;
	}

	/**
	 * Get the master token server record
	 */
	public static function GetMasterTokenServer()
	{
		return self::$sMasterTokenServer;
	}

	/**
	 * Get the configuration setting for the forced token server.
	 * @return string
	 */
	public static function GetForceTokenServer()
	{
		return self::$sForceTokenServer;
	}

	/**
	 * Gets the message mode used when communicating with NuCaptcha's servers
	 */
	public static function GetMessageMode()
	{
		return self::$sMessageMode;
	}

	/**
	 * Sets the message mode used when communicating with NuCaptcha's servers
	 * @return string
	 */
	public static function SetMessageMode($mmode)
	{
		switch($mmode)
		{
			case self::MMODE_COMPRESSED_ENCRYPTED:
			case self::MMODE_ENCRYPTED:
				self::$sMessageMode = $mmode;
				break;
			default:
				throw new LeapException('Invalid message mode ' . $mmode, LMSC_INVALIDDATA . '.  See Leap::MMODE_* constants.');
		}
	}

	/** 
	 * Enables Test Mode. This allows sending the answer as 'correct' or 'wrong'. When the validation occurs,
	 * these values will be used to determine if the request is successful.
	 */
	public static function EnableTestMode()
	{
		self::$sHints['TEST_ANSWER'] = 1;
	}
	
	/**
	 * Turn off Test Mode.
	 */
	public static function DisableTestMode()
	{
		if(true === array_key_exists('TEST_ANSWER', self::$sHints))
		{
			unset(self::$sHints['TEST_ANSWER']);
		}
	}

	/**
	 * Specify how NuCaptcha validate should perform in the rare case of an error (ie connectivity issue with NuCaptcha servers, invalid client key etc)
	 * By default, validateOnError is set to true.  
	 * @param bool validateOnError specify if validate should succeed on an error.
	 */
	public static function SetValidateOnError($validateOnError)
	{
		self::$sValidateOnError = ($validateOnError ? true : false);
	}

	/**
	 * See SetValidateOnError
	 * @return bool
	 */
	public static function GetValidateOnError()
	{
		return self::$sValidateOnError;
	}

	/**
	 * Make sure a client key is set.
	 */
	private static function CheckForClientKey()
	{
		if (false == self::$sClientKey)
		{
			throw new LeapException('Client key has not yet been set.',	LMSC_CLIENTKEYNOTSET);
		}
	}

	/** !EXPORT
	 * This will get a token and create the lmTransaction object.
	 *	 
	 * @param string $userData			- Any data you want associated with this token
	 * @param bool $useSSL				- If the client uses SSL, set this to true to avoid browser warnings
	 * @param string $campaignProfile           - Campaign ID to request from token server. The server may not respond with the campaign if it's not available or campaign selection is not available in your account.
	 * @param string $purpose			- one of Leap::PURPOSE_GENERIC Leap::PURPOSE_CREATE_ACCOUNT Leap::PURPOSE_PASSWORD_RESET Leap::PURPOSE_LOGIN Leap::PURPOSE_POST Leap::PURPOSE_COMMENT Leap::PURPOSE_EDIT Leap::PURPOSE_VOTE Leap::PURPOSE_SEND Leap::PURPOSE_ACTION
	 * @param string $preferredLanguage - one of the Leap::LANGUAGE_ constants, indicating which language to render the player in, and to pick captchas for
	 * @return lmTransactionInterface 	- the transaction object created
	 */
	static public function InitializeTransaction($userData = null, $useSSL = false, $campaignProfile = null, $purpose = Leap::PURPOSE_UNKNOWN, $preferredLanguage=Leap::LANGUAGE_ENGLISH)
	{
		try
		{
			self::CheckForClientKey();

			$transactionObject = new lmPublisherTransaction(
							"SetLeapErrorCode",
							LM_FAILURE_HTML,
							$useSSL,
							$campaignProfile,
							null,
							$purpose,
							$preferredLanguage
					);

			self::$sLastTransaction =  self::InitializeTransactionObject($userData, $transactionObject);
			return self::$sLastTransaction;
		}
		catch (Exception $e)
		{
			self::HandleException($e);
			return self::createTransactionError($e);
		}
	}

	static private function DecodePublicPersistentData($persistentData, $unique_id)
	{
		// If unique_id is false, then the data isn't encoded.  Return the passed in data
		if (false === $unique_id) return $persistentData;

		// Decode and import inot the chunk
		$clientkey = Leap::GetClientKey();
		$chunk = lmTextChunk::Decode(lmSymmetricMessage::DecipherMessage($clientkey->GetChunk('SKEY'), $persistentData), 'PDPUBLIC');

		if( $chunk->ChunkExists("EREPORT") )
		{
			Leap::SetReportingMode($chunk->GetChunk("EREPORT"));
		}

		// Retrieve the data
		return $chunk->GetChunk('PSDATA');
	}

	/** !EXPORT
	 * This will validate the transaction and return true/false if it was correct.
	 *
	 * This function is for those that don't want to deal with an object. Just call it and get a true/false answer.
	 *
	 * The error code will give more detail if not correct.  (leaperrorcodes.php)
	 *
	 * @param string $persistentData	- Data stored to recreate the lmTransaction object
	 * @param string $response			- What the user responded with (false = figure it out ourself)
	 * @param string $unique_id			- ID that matches the one used in GetPersistentDataForPublicStorage() if that was used
	 * @return bool 					- true = success, false = failure.  Check the error code
	 */
	static public function ValidateTransaction($persistentData, $response = false, $unique_id = false)
	{
		try
		{
			self::CheckForClientKey();

			// If they used cookie storage, then decipher the persistentData
			$persistentData = self::DecodePublicPersistentData($persistentData, $unique_id);
			
			// Check for invalid persistent data
			if( $persistentData == null || !is_string($persistentData) )
			{
				throw new LeapException('Invalid persistent data', LMSC_INVALIDPERSISTENT);
			}

			if( $persistentData === '' )
			{
				throw new LeapException('Empty persistent data', LMSC_INVALIDPERSISTENT);
			}

			if( lmTransactionError::isTransactionError($persistentData) )
			{
				// Check for validate on error (enabled by default)
				if( true === self::$sValidateOnError )
				{
					return true;
				}
				throw new LeapException('Invalid persistent data (bad token)', LMSC_INVALIDPERSISTENT);
			}

			$validator = self::Validate($persistentData, $response);

			$vc = $validator->GetValidationCode();
			switch($vc)
			{
				case LMSC_WRONG: // follow through
				case LMSC_EMPTY:
					return false;
				case LMSC_CORRECT:
					return true;
			}
		}
		catch (Exception $e)
		{
			lmErrorReporter::SetErrorData('pdata', strval($persistentData));
			lmErrorReporter::SetErrorData('unique_id', strval($unique_id));
			self::HandleException($e);
		}

		return false;
	}

	/** !EXPORT
	 * This will validate the transaction and return the validation object.
	 *
	 * The returned object can be used to get the validation code, or the response code, for more information.
	 *
	 * The error code will give more detail if not correct. (leaperrorcodes.php)
	 *
	 * @param string $persistentData	- Data stored to recreate the lmTransaction object
	 * @param string $response			- What the user responded with (false = figure it out ourself)
	 * @param string $unique_id			- ID that matches the one used in GetPersistentDataForPublicStorage() if that was used
	 * @return lmValidationInterface 	- object to query about the validation
	 */
	static public function Validate($persistentData, $response = false, $unique_id = false)
	{
		$validator = false;

		$persistentData = self::DecodePublicPersistentData($persistentData, $unique_id);

		if(false === $response || null === $response)
		{
		    $response = self::GetResponse($persistentData);
		}

		$validator = new lmPublisherValidation();

		self::ValidateTransactionObject($persistentData, $response, $validator);

		return $validator;
	}

	/** !EXPORT
	 * Parses $_POST for the user's response to the captcha.
	 * A lmResponse object is always returned, even if $_POST['lmanswer'] isn't set.
	 *
	 * You can check $response->GetAnswer() === false to see if it was found or not
	 *
	 * @param string $persistentData	- Data stored to recreate the transaction object
	 * @param boolean $fill				- true if you want the response object automatically filled with data from the $_POST variables
	 * @return lmResponse
	 */
	static public function GetResponse($persistentData, $fill = true)
	{
		if (is_string($persistentData))
		{
			try
			{
				// create the new response
				$a = new lmResponse($persistentData);

				// fill it in, if requested
				if($fill != false)
				{
					foreach($a->GetFields() AS $field)
					{
  						if(true === array_key_exists($field, $_POST))
						{
							$a->SetVar($field, $_POST[$field]);
						}
					}
				}

				return $a;
			}
			catch (Exception $e)
			{
				lmErrorReporter::SetErrorData('pdata', strval($persistentData));
				self::HandleException($e);
			}
		}
		
		// create a dummy response and return it
		return new lmResponse(null);
	}

	/**
	 * Sets the error code stored by the Leap class.
	 *
	 * Error codes are located in the file: leaperrorcodes.php
	 *
	 * @param int $code			- The LMSC_ error code to save
	 * @param string $message	- The message to save with it
	 * @return int				- The code
	 */
	static public function SetErrorCode($code, $message="")
	{
		self::$mLastErrorCode = $code;
		self::$mLastErrorMessage = $message;
		return $code;
	}

	// Static class.  Don't allow it to be constructed
	private function __construct()
	{
	}

	/**
	 * InitializeTransactionObject:
	 * This will get a token and create the lmTransaction object.
	 * Takes the transaction object already created. This function will call the Initialize
	 * method on the transaction, and return the transaction. If there is an exception, or a
	 * connection problem, this function will return a dummy error transaction object.
	 *
	 * @param string $userData				- Any data you want associated with this token
	 * @param lmTransactionInterface $t		- the transaction object to initialize and return
	 * @return lmTransactionInterface 		- the transaction object created
	 */
	static protected function InitializeTransactionObject($userData, $t)
	{
		try
		{
			// Create the symmetric key
			/*
			 * LEAP-1378
			 *
			 * Double encode here because lmSymmetricMessage() tries to decode
			 * it twice.
			 */
			$tokenkey	= lmBase64::EncodeBinary(lmSymmetricMessage::GenerateSymmetricKey());

			// Create the token request data
			$chunk = self::CreateTokenRequestData($userData, $tokenkey);

			// Default our error
			self::SetErrorCode(LMSC_OK);

			// Initialize the transaction
			$t->Initialize($chunk, $tokenkey);

			// default the error code
			self::SetErrorCode(LMSC_OK);

			// return the error code
			return $t;
		}
		catch (Exception $e)
		{
			self::HandleException($e);

			// handle this by using the error transaction type
			return self::createTransactionError($e);
		}
	}

	/**
	 * ValidateTransaction:
	 * This will validate the transaction. Use the $validator object to check whether or not it was true
	 * The error code will give more detail if not correct.
	 * This function takes an object derived from lmValidationInterface, and calls it's
	 * ValidateTransaction method.
	 *
	 * @param string $persistentData			- Data stored to recreate the lmTransaction object
	 * @param lmResponse $response				- What the user responded with (false = figure it out ourself)
	 * @param lmValidatorInterface $validator	- object that can validate
	 */
	static protected function ValidateTransactionObject($persistentData, lmResponse $response, &$validator)
	{
		if (!is_string($persistentData))
		{
			self::SetErrorCode(LMSC_INVALIDPERSISTENT, "Persistent data must be a string!");
			return;
		}

		try
		{
			$response = self::GetValidResponse($persistentData, $response);
			if (null === $response)
			{
				// error code already set in GetValidResponse()
				return;
			}

			$validator->ValidateTransaction($persistentData, $response);
			$code = $validator->GetValidationCode();

			self::SetErrorCode($code);
		}
		catch (Exception $e)
		{
			self::HandleException($e);
			return;
		}
	}

	/**
	 * HandleException:
	 * This handles an exception, creating an appropriate message, and saving it
	 * and the error code using self::SetErrorCode
	 *
	 * @param Exception $e				- the exception to deal with
	 * @return bool 					- true = success, false = failure.  Check the error code
	 */
	static public function HandleException($e)
	{
		$msg = $e->getMessage();
		if (LM_DEBUG)
		{
			$msg .= "\nLeap Library Debug Details:\n";
			$msg .= $e->getFile().":(".$e->getLine().")";
			$msg .= $e->getTraceAsString();
		}

		$ec = LMSC_UNKNOWN;
		if( method_exists($e, 'getCode') )
		{
			$ec = $e->getCode();
		}

		if( $ec != LMSC_CLIENTKEYNOTSET &&
			$ec != LMSC_INVALIDCONFIGFILE &&
			$ec != LMSC_PUBLISHER_DISABLED )
		{
			lmErrorReporter::ReportException($e, Array('leapErrorCode' => $ec));
		}
		self::SetErrorCode($ec, $msg);
	}

	/**
	 * CreateTokenRequestData:
	 * Creates and returns a token request chunk.
	 *
	 * @param string $userData			- Any data that the publisher wants to be sent to the Leap token server
	 * @param string $tokenkey			- the key that the token server should use to encrypt the token response with
	 * @return lmTextChunk 				- the text chunk for the token request
	 */
	static protected function CreateTokenRequestData( $userData, $tokenkey )
	{
		// ***
		// *** Create the Token Request
		// ***
		$chunk = lmHelper::CreateRequestChunk("TREQ");

		// Store the symmetric key
		$chunk->AddChunk("SKEY", $tokenkey);

		// check for hints
		$hints       = self::checkForHints($userData);
		$hintsString = null;
		
		if(true === is_array($hints))
		{
			// merge any global hints
			if(0 < sizeof(self::$sHints))
			{
				$hints = array_merge($hints, self::$sHints);	
			}
			
			// re-encode into urlcoding format
			$hintsString = lmUrlCoding::encodeStructure($hints);
			
			if(true === array_key_exists('userData', $hints))
			{
				$userData = $hints['userData'];
			}
			else
			{
				$userData = false;
			}
		}
		elseif(sizeof(self::$sHints))
		{
			// no hints passed in, but we have global hints.
			$hintsString = lmUrlCoding::encodeStructure(self::$sHints);
		}

		// Store the user id if it's a string.  Otherwise just skip past it.
		if ((false !== $userData) && is_string($userData))
		{
			$chunk->AddChunk("USERDATA", $userData);
		}
		
		if(null !== $hintsString)
		{
			$chunk->AddChunk('HINTS', $hintsString);
		}

		return $chunk;
	}
	
	/**
	 * Check to see if userData is really 'hints'
	 *
	 * @param string $userData
	 * @return array or false if userData is not a hint. Will also return false if json_decode is not available
	 */
	static private function checkForHints($userData)
	{
		// backwards compatible: check json first
		if(false === function_exists('json_decode'))
		{
			return false;
		}
		
		// Hints may be JSON or urlCoding. Check both.
		$hints = json_decode($userData, true);
		
		if(false === is_array($hints))
		{
			// now try urlcoding
			try
			{
				$hints = lmUrlCoding::decodeStructure($userData);
				return $hints;
			}
			catch(Exception $e)
			{
				return false;
			}
		}
		
		return $hints;
	}

	/**
	 * GetValidResponse:
	 * This takes in persistent data and a response (which can be false or invalid) and returns
	 * a valid lmResponse object or null. It first validates the passed in response object.
	 * If the input $response is invalid, null is returned to signal an error.
	 * If it's null, or false, a valid response object is created.
	 *
	 *
	 * @param string $persistentData	- Data stored to recreate the lmTransaction object
	 * @param lmResponse $response		- Response object, or null to create one.
	 * @return object 			- the response object; $response if valid, null if $response is invalid, or a new lmResponse object
	 */
	static protected function GetValidResponse($persistentData, lmResponse $response = null)
	{
	    // Do we want to figure out the response ourselves?
	    if ((false === $response) || is_null($response))
	    {
			$response = self::GetResponse($persistentData);
	    }

	    // if it's still false, no response
	    if(false === $response->GetAnswer())
	    {
			self::SetErrorCode(LMSC_EMPTY, "No answer from user");
			return null;
	    }

	    return $response;
	}

	/** 
	 * This will return the LMSC_ define for the last request.
	 *
	 * Error codes are located in the file: leaperrorcodes.php
	 *
	 * @return int - LMSC_ error code of the last error.
	 */
	static public function GetErrorCode()
	{
		self::ForceConnectionCompletion();
		return self::$mLastErrorCode;
	}

	/** 
	 * This will return any string data associated with the last requests error code.
	 *
	 * @return string - The error message of the last error.
	 */
	static public function GetErrorString()
	{
		self::ForceConnectionCompletion();
		return self::$mLastErrorMessage;
	}

	/** !EXPORT
	 * This will return the LMSC_ define for the last request.
	 *
	 * Status codes are located in the file: leapstatuscodes.php
	 *
	 * @return int - LMSC_ status code of the last request.
	 */
	static public function GetStatusCode()
	{
		return self::GetErrorCode();
	}

	/** !EXPORT
	 * This will return any string data associated with the last requests status code.
	 *
	 * @return string - The status message of the last request.
	 */
	static public function GetStatusString()
	{
		return self::GetErrorString();
	}
	
	/**
	 * Make sure we've finished our connection by calling $t->GetLinks()
	 */
	static private function ForceConnectionCompletion()
	{
		if(null !== self::$sLastTransaction)
		{
			self::$sLastTransaction->ForceConnectionCompletion();
		}
	}

    /** !EXPORT
     * Determine if the form was submitted or if this is the first time loading the NuCaptcha.
     *
     * @return bool - True if submitted, false otherwise.
     */
    static public function WasSubmitted()
    {
		if(true === array_key_exists(LM_FIELD_SUBMITTED, $_POST))
		{
			return true;
		}
		else
		{
			return false;
		}
    }

	/**
	 * resets config state before loading configs.
	 */
	private static function ResetConfig()
	{
		self::$sClientKey        = false;
		self::$sClusterRecord    = 'clusters.nucaptcha.com';
		self::$sForceTokenServer = false;
	}
	
	/** !EXPORT
     * Get the version number
     *
     * @return string
     */
    static public function GetVersion()
    {
    	return '1.0.12882';
    }

	private static function createTransactionError($e)
	{
		return new lmTransactionError(LM_FAILURE_HTML, $e);
	}

	/**
	 * Sets the error reporting modes
	 * @param string $modes A CSV string of preferred reporting modes.  Uses the first supported method.
	 */
	public static function SetReportingMode($modes)
	{
		$supported = Array('NONE', 'HOPTOAD');
		$m = explode(',', $modes);
		$mode = lmErrorReporter::$MODE_NONE;
		for($i = 0; $i < count($m); $i++ )
		{
			for($j = 0; $j < count($supported); $j++)
			{
				if( $supported[$j] == $m[$i] )
				{
					$mode = $m[$i];
					break;
				}
			}
		}
		lmErrorReporter::SetReportMode($mode);
	}

	public static function GetReportingMode()
	{
		return lmErrorReporter::GetReportMode();
	}

	/**
	 * For internal test use only
	 * @param string $testAction
	 */
	public static function SetTestAction($testAction)
	{
		self::$sTestAction = $testAction;
	}

	/**
	 * For internal test use only
	 * @param string $testAction
	 */
	public static function GetTestAction()
	{
		return self::$sTestAction;
	}

	/**
	 * Get the unique id for this transaction
	 * @param string unencrypted persistent data.  if using public persistent data you must call Leap::DecodePublicPersistentData first
	 */
	public static function GetTransactionID($persistentData)
	{
		try
		{
			$validator = new lmPublisherValidation();
			return $validator->GetTransactionID($persistentData);
		}
		catch(Exception $e)
		{
		}
		return "";
	}
}

Return current item: NuCaptcha (PHP)