<?php
/*
	File: xajaxArgumentManager.inc.php

	Contains the xajaxArgumentManager class

	Title: xajaxArgumentManager class

	Please see <copyright.inc.php> for a detailed description, copyright
	and license information.
*/

/*
	@package xajax
	@version $Id: xajaxArgumentManager.inc.php 362 2007-05-29 15:32:24Z calltoconstruct $
	@copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
	@copyright Copyright (c) 2008-2009 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
	@license http://www.xajaxproject.org/bsd_license.txt BSD License
*/

if (!defined('XAJAX_METHOD_UNKNOWN')) define('XAJAX_METHOD_UNKNOWN', 0);
if (!defined('XAJAX_METHOD_GET')) define('XAJAX_METHOD_GET', 1);
if (!defined('XAJAX_METHOD_POST')) define('XAJAX_METHOD_POST', 2);

/*
	Class: xajaxArgumentManager

	This class processes the input arguments from the GET or POST data of
	the request.  If this is a request for the initial page load, no arguments
	will be processed.  During a xajax request, any arguments found in the
	GET or POST will be converted to a PHP array.
*/
class xajaxArgumentManager
{
	/*
		Array: aArgs

		An array of arguments received via the GET or POST parameter
		xjxargs.
	*/
	var $aArgs;

	/*
		Boolean: bDecodeUTF8Input

		A configuration option used to indicate whether input data should be
		UTF8 decoded automatically.
	*/
	var $bDecodeUTF8Input;

	/*
		String: sCharacterEncoding

		The character encoding in which the input data will be received.
	*/
	var $sCharacterEncoding;

	/*
		Integer: nMethod

		Stores the method that was used to send the arguments from the client.  Will
		be one of: XAJAX_METHOD_UNKNOWN, XAJAX_METHOD_GET, XAJAX_METHOD_POST
	*/
	var $nMethod;

	/*
		Array: aSequence

		Stores the decoding sequence table.
	*/
	var $aSequence;

	/*
		Function: convertStringToBool

		Converts a string to a bool var.

		Parameters:
			$sValue - (string):

		Returns:
			(bool) : true / false

	*/

	function convertStringToBool($sValue)
	{
		if (0 == strcasecmp($sValue, 'true'))
			return true;
		if (0 == strcasecmp($sValue, 'false'))
			return false;
		if (is_numeric($sValue))
		{
			if (0 == $sValue)
				return false;
			return true;
		}
		return false;
	}

	function argumentStripSlashes(&$sArg)
	{
		if (false == is_string($sArg))
			return;

		$sArg = stripslashes($sArg);
	}

	function argumentDecodeXML(&$sArg)
	{
		if (false == is_string($sArg))
			return;

		if (0 == bytes($sArg))
			return;

		$nStackDepth = 0;
		$aStack = array();
		$aArg = array();

		$nCurrent = 0;
		$nLast = 0;
		$aExpecting = array();
		$nFound = 0;
		list($aExpecting, $nFound) = $this->aSequence['start'];

		$nLength = bytes($sArg);

		$sKey = '';
		$mValue = '';

		while ($nCurrent < $nLength)
		{
			$bFound = false;

			foreach ($aExpecting as $sExpecting => $nExpectedLength)
			{
				if ($sArg[$nCurrent] == $sExpecting[0])
				{
					if ($sExpecting == cut_bytes($sArg, $nCurrent, $nExpectedLength))
					{
						list($aExpecting, $nFound) = $this->aSequence[$sExpecting];

						switch ($nFound)
						{
						case 3:	// k
							$sKey = '';
							break;
						case 4:	// /k
							$sKey = str_replace(
								array('<'.'![CDATA[', ']]>'),
								'',
								cut_bytes($sArg, $nLast, $nCurrent - $nLast)
								);
							break;
						case 5:	// v
							$mValue = '';
							break;
						case 6:	// /v
							if ($nLast < $nCurrent)
							{
								$mValue = str_replace(
									array('<'.'![CDATA[', ']]>'),
									'',
									cut_bytes($sArg, $nLast, $nCurrent - $nLast)
									);

								$cType = cut_bytes($mValue, 0, 1);
								$sValue = cut_bytes($mValue, 1);
								switch ($cType) {
									case 'S': $mValue = false === $sValue ? '' : $sValue;  break;
									case 'B': $mValue = $this->convertStringToBool($sValue); break;
									case 'N': $mValue = floatval($sValue); break;
									case '*': $mValue = null; break;
								}
							}
							break;
						case 7:	// /e
							$aArg[$sKey] = $mValue;
							break;
						case 1:	// xjxobj
							++$nStackDepth;
							array_push($aStack, $aArg);
							$aArg = array();
							array_push($aStack, $sKey);
							$sKey = '';
							break;
						case 8:	// /xjxobj
							if (1 < $nStackDepth) {
								$mValue = $aArg;
								$sKey = array_pop($aStack);
								$aArg = array_pop($aStack);
								--$nStackDepth;
							} else {
								$sArg = $aArg;
								return;
							}
							break;
						}
						$nCurrent += $nExpectedLength;
						$nLast = $nCurrent;
						$bFound = true;
						break;
					}
				}
			}

			if (false == $bFound)
			{
				if (0 == $nCurrent)
				{
					$sArg = str_replace(
						array('<'.'![CDATA[', ']]>'),
						'',
						$sArg
						);

					$cType = cut_bytes($sArg, 0, 1);
					$sValue = cut_bytes($sArg, 1);
					switch ($cType) {
						case 'S': $sArg = false === $sValue ? '' : $sValue;  break;
						case 'B': $sArg = $this->convertStringToBool($sValue); break;
						case 'N': $sArg = floatval($sValue); break;
						case '*': $sArg = null; break;
					}

					return;
				}

//				for larger arg data, performance may suffer using concatenation
//				$sText .= $sArg[$nCurrent];
				$nCurrent++;
			}
		}

		$objLanguageManager =& xajaxLanguageManager::getInstance();

		trigger_error(
			$objLanguageManager->getText('ARGMGR:ERR:01')
			. $sExpecting
			. $objLanguageManager->getText('ARGMGR:ERR:02')
			. $sArg
			, E_USER_ERROR
			);
	}

	function argumentDecodeUTF8_iconv(&$mArg)
	{
		if (is_array($mArg))
		{
			foreach (array_keys($mArg) as $sKey)
			{
				$sNewKey = $sKey;
				$this->argumentDecodeUTF8_iconv($sNewKey);

				if ($sNewKey != $sKey)
				{
					$mArg[$sNewKey] = $mArg[$sKey];
					unset($mArg[$sKey]);
					$sKey = $sNewKey;
				}

				$this->argumentDecodeUTF8_iconv($mArg[$sKey]);
			}
		}
		else if (is_string($mArg))
			$mArg = iconv("UTF-8", $this->sCharacterEncoding.'//TRANSLIT', $mArg);
	}

	function argumentDecodeUTF8_mb_convert_encoding(&$mArg)
	{
		if (is_array($mArg))
		{
			foreach (array_keys($mArg) as $sKey)
			{
				$sNewKey = $sKey;
				$this->argumentDecodeUTF8_mb_convert_encoding($sNewKey);

				if ($sNewKey != $sKey)
				{
					$mArg[$sNewKey] = $mArg[$sKey];
					unset($mArg[$sKey]);
					$sKey = $sNewKey;
				}

				$this->argumentDecodeUTF8_mb_convert_encoding($mArg[$sKey]);
			}
		}
		else if (is_string($mArg))
			$mArg = mb_convert_encoding($mArg, $this->sCharacterEncoding, "UTF-8");
	}

	function argumentDecodeUTF8_utf8_decode(&$mArg)
	{
		if (is_array($mArg))
		{
			foreach (array_keys($mArg) as $sKey)
			{
				$sNewKey = $sKey;
				$this->argumentDecodeUTF8_utf8_decode($sNewKey);

				if ($sNewKey != $sKey)
				{
					$mArg[$sNewKey] = $mArg[$sKey];
					unset($mArg[$sKey]);
					$sKey = $sNewKey;
				}

				$this->argumentDecodeUTF8_utf8_decode($mArg[$sKey]);
			}
		}
		else if (is_string($mArg))
			$mArg = utf8_decode($mArg);
	}

	/*
		Constructor: xajaxArgumentManager

		Initializes configuration settings to their default values and reads
		the argument data from the GET or POST data.
	*/
	function xajaxArgumentManager()
	{
		$this->aArgs = array();

		$this->bDecodeUTF8Input = false;
		$this->sCharacterEncoding = 'UTF-8';
		$this->nMethod = XAJAX_METHOD_UNKNOWN;

		$this->aSequence = array(
			'<'.'k'.'>' => array(array(
				'<'.'/k'.'>' => 4
				), 3),
			'<'.'/k'.'>' => array(array(
				'<'.'v'.'>' => 3,
				'<'.'/e'.'>' => 4
				), 4),
			'<'.'v'.'>' => array(array(
				'<'.'xjxobj'.'>' => 8,
				'<'.'/v'.'>' => 4
				), 5),
			'<'.'/v'.'>' => array(array(
				'<'.'/e'.'>' => 4,
				'<'.'k'.'>' => 3
				), 6),
			'<'.'e'.'>' => array(array(
				'<'.'k'.'>' => 3,
				'<'.'v'.'>' => 3,
				'<'.'/e'.'>' => 4
				), 2),
			'<'.'/e'.'>' => array(array(
				'<'.'e'.'>' => 3,
				'<'.'/xjxobj'.'>' => 9
				), 7),
			'<'.'xjxobj'.'>' => array(array(
				'<'.'e'.'>' => 3,
				'<'.'/xjxobj'.'>' => 9
				), 1),
			'<'.'/xjxobj'.'>' => array(array(
				'<'.'/v'.'>' => 4
				), 8),
			'start' => array(array(
				'<'.'xjxobj'.'>' => 8
				), 9)
			);

		if (isset($_POST['xjxargs'])) {
			$this->nMethod = XAJAX_METHOD_POST;
			$this->aArgs = $_POST['xjxargs'];
		} else if (isset($_GET['xjxargs'])) {
			$this->nMethod = XAJAX_METHOD_GET;
			$this->aArgs = $_GET['xjxargs'];
		}

		if (1 == get_magic_quotes_gpc())
			array_walk($this->aArgs, array(&$this, 'argumentStripSlashes'));

		array_walk($this->aArgs, array(&$this, 'argumentDecodeXML'));
	}

	/*
		Function: getInstance

		Returns:

		object - A reference to an instance of this class.  This function is
			used to implement the singleton pattern.
	*/
	function &getInstance()
	{
		static $obj;
		if (!$obj) {
			$obj = new xajaxArgumentManager();
		}
		return $obj;
	}

	/*
		Function: configure

		Accepts configuration settings from the main <xajax> object.

		Parameters:


		The <xajaxArgumentManager> tracks the following configuration settings:

			<decodeUTF8Input> - (boolean): See <xajaxArgumentManager->bDecodeUTF8Input>
			<characterEncoding> - (string): See <xajaxArgumentManager->sCharacterEncoding>
	*/
	function configure($sName, $mValue)
	{
		if ('decodeUTF8Input' == $sName) {
			if (true === $mValue || false === $mValue)
				$this->bDecodeUTF8Input = $mValue;
		} else if ('characterEncoding' == $sName) {
			$this->sCharacterEncoding = $mValue;
		}
	}

	/*
		Function: getRequestMethod

		Returns the method that was used to send the arguments from the client.
	*/
	function getRequestMethod()
	{
		return $this->nMethod;
	}

	/*
		Function: process

		Returns the array of arguments that were extracted and parsed from
		the GET or POST data.
	*/
	function process()
	{
		if ($this->bDecodeUTF8Input)
		{
			$sFunction = '';

			if (function_exists('iconv'))
				$sFunction = "iconv";
			else if (function_exists('mb_convert_encoding'))
				$sFunction = "mb_convert_encoding";
			else if ($this->sCharacterEncoding == "ISO-8859-1")
				$sFunction = "utf8_decode";
			else {
				$objLanguageManager =& xajaxLanguageManager::getInstance();
				trigger_error(
					$objLanguageManager->getText('ARGMGR:ERR:03')
					, E_USER_NOTICE
					);
			}

			$mFunction = array(&$this, 'argumentDecodeUTF8_' . $sFunction);

			array_walk($this->aArgs, $mFunction);

			$this->bDecodeUTF8Input = false;
		}

		return $this->aArgs;
	}
}
