<?php

if (!defined('XOOPS_ROOT_PATH')) exit();

require_once XOOPS_ROOT_PATH . "/class/XCube_Validator.class.php";
require_once XOOPS_ROOT_PATH . "/class/XCube_FormFile.class.php";

//
// TODO The difference of array and no-array is too big.
// TODO Form object should have getValue(), isNull(), toString().
//

define("XCUBE_TOKEN_TYPE_SINGLE",1);
define("XCUBE_TOKEN_TYPE_MULTI",2);

/**
 * This is suggestion of a simple action form.
 * We do not force a module developer to use this.
 * You can learn more full-scale action forms from JAVA and .NET and other PHP.
 * And, you must use auto-generating tool when you need to ActionForm that is sub-class of this class.
 */
class XCube_ActionForm
{
	/**
	 * Array
	 */
	var $mFormProperties;

	/**
	 * Array
	 */
	var $mFieldProperties;
	
	/**
	 * NOTICE: This is temporary until we will decide the method of managing error.
	 */
	var $mErrorFlag=false;
	
	var $mErrorMessages=array();
	
	var $_mToken = null;

	function XCube_ActionForm()
	{
	}
	
	function prepare()
	{
	}
	
	function getTokenName()
	{
		return null;
	}
	
	function getToken()
	{
		if ($this->_mToken == null) {
			srand (microtime() * 100000);
			$this->_mToken = md5(XOOPS_SALT . uniqid(rand(), true));
			
			$_SESSION['XCUBE_TOKEN'][$this->getTokenName()] = $this->_mToken;
		}

		return $this->_mToken;
	}
	
	/**
	 * Return message when the validation of token is fail.
	 * 
	 * @return string
	 */
	function getTokenErrorMessage()
	{
		return _TOKEN_ERROR;
	}
	
	function set()
	{
		if(isset($this->mFormProperties[func_get_arg(0)])) {
			if (func_num_args() == 2) {
				$value = func_get_arg(1);
				$this->mFormProperties[func_get_arg(0)]->setValue($value);
			} elseif (func_num_args() == 3) {
				$index = func_get_arg(1);
				$value = func_get_arg(2);
				$this->mFormProperties[func_get_arg(0)]->setValue($index, $value);
			}
		}
	}

	/**
	 * @deprecated
	 */	
	function setVar()
	{
		if(isset($this->mFormProperties[func_get_arg(0)])) {
			if (func_num_args() == 2) {
					$this->mFormProperties[func_get_arg(0)]->setValue(func_get_arg(1));
			} elseif (func_num_args() == 3) {
					$this->mFormProperties[func_get_arg(0)]->setValue(func_get_arg(1), func_get_arg(2));
			}
		}
	}
	
	function get($key,$index=null)
	{
		return isset($this->mFormProperties[$key]) ? $this->mFormProperties[$key]->getValue($index) : null;
	}
	
	/**
	 * @deprecated
	 */
	function getVar($key,$index=null)
	{
		return $this->get($key,$index);
	}
	
	/**
	 * Return form properties of this member property.
	 * @return XCube_AbstractProperty[]
	 */
	function &getFormProperties()
	{
		return $this->mFormProperties;
	}

	function fetch()
	{
		foreach(array_keys($this->mFormProperties) as $name) {
			$this->mFormProperties[$name]->fetch();
		}
	}

	function validate()
	{
		//
		// check onetime & transaction token
		//
		if ($this->getTokenName() != null) {
			$token = xoops_getrequest(strtr($this->getTokenName(), '.', '_'));
			
			$flag = true;
			
			if (!isset($_SESSION['XCUBE_TOKEN'][$this->getTokenName()])) {
				$flag = false;
			}
			elseif ($_SESSION['XCUBE_TOKEN'][$this->getTokenName()] != $token) {
				unset($_SESSION['XCUBE_TOKEN'][$this->getTokenName()]);
				$flag = false;
			}
			
			if (!$flag) {
				$message = $this->getTokenErrorMessage();
				if ($message == null) {
					$this->mErrorFlag = true;
				}
				else {
					$this->addErrorMessage($message);
				}
			}
			
			//
			// clear token
			//
			unset($_SESSION['XCUBE_TOKEN'][$this->getTokenName()]);
		}
		
		foreach(array_keys($this->mFormProperties) as $name) {
			if(isset($this->mFieldProperties[$name])) {
				if ($this->mFormProperties[$name]->isArray()) {
					foreach (array_keys($this->mFormProperties[$name]->mProperties) as $_name) {
						$this->mFieldProperties[$name]->validate($this->mFormProperties[$name]->mProperties[$_name]);
					}
				}
				else {
					$this->mFieldProperties[$name]->validate($this->mFormProperties[$name]);
				}
			}
		}

		//
		// If this class has original validation methods, call it.
		//
		foreach(array_keys($this->mFormProperties) as $name) {
			$methodName="validate".ucfirst($name);
			if(method_exists($this,$methodName)) {
				// call_user_func(array($this,$methodName));
				$this->$methodName();
			}
		}
	}

	/**
	 * If the action form keeps error messages or the error flag, return true.
	 * 
	 * @return bool
	 */
	function hasError()
	{
		return (count($this->mErrorMessages) > 0 || $this->mErrorFlag);
	}
	
	function addErrorMessage($message)
	{
		$this->mErrorMessages[]=$message;
	}
	
	function getErrorMessages()
	{
		return $this->mErrorMessages;
	}
}

class XCube_PropertyInterface
{
	function XCube_PropertyInterface($name)
	{
	}
	
	function fetch($key = null)
	{
	}
	
	function setValue($arg0 = null, $arg1 = null)
	{
	}

	function getValue($arg0 = null)
	{
	}
	
	/**
	 * @return bool
	 */
	function isArray()
	{
	}
}

class XCube_AbstractProperty extends XCube_PropertyInterface
{
	var $mName=null;
	var $mValue=null;
	
	function XCube_AbstractProperty($name)
	{
		$this->mName=$name;
	}

	function setValue($value, $dmy = null)
	{
		$this->mValue = $value;
	}

	function getValue($index = null)
	{
		return $this->mValue;
	}

	function fetch($key = null)
	{
		$value = null;

		if ($key !== null && is_array($_REQUEST[$this->mName]) && isset($_REQUEST[$this->mName][$key])) {
			$value = $_REQUEST[$this->mName][$key];
		}
		elseif (isset($_REQUEST[$this->mName]) && !is_array($_REQUEST[$this->mName])) {
			$value = $_REQUEST[$this->mName];
		}
		
	    if (get_magic_quotes_gpc()) {
			$this->mValue = stripslashes($value);
		}
		else {
			$this->mValue = $value;
		}
	}
	
	function isArray()
	{
		return false;
	}
	
	function isNull()
	{
		return (strlen(trim($this->mValue)) == 0);
	}
	
	function toNumber()
	{
		return $this->mValue;
	}
	
	function toString()
	{
		return $this->mValue;
	}
}

class XCube_AbstractArrayProperty extends XCube_PropertyInterface
{
	var $mName = null;
	var $mProperties = array();
	
	var $mPropertyClassName = null;
	
	function XCube_AbstractArrayProperty($name)
	{
		$this->mName = $name;
	}
	
	function fetch($key = null)
	{
		unset($this->mProperties);
		$this->mProperties = array();
		if (isset($_REQUEST[$this->mName]) && is_array($_REQUEST[$this->mName])) {
			foreach ($_REQUEST[$this->mName] as $_key => $_val) {
				$this->mProperties[$_key] =& new $this->mPropertyClassName($this->mName);
				$this->mProperties[$_key]->fetch($_key);
			}
		}
	}

	function setValue($index, $value)
	{
		if (!isset($this->mProperties[$index])) {
			$this->mProperties[$index] = new $this->mPropertyClassName($this->mName);
		}
		$this->mProperties[$index]->setValue($value);
	}

	function getValue($index=null)
	{
		if($index==null) {
			$ret = array();
			
			foreach ($this->mProperties as $name => $value) {
				$ret[$name] = $value->getValue();
			}

			return $ret;
		}

		return isset($this->mProperties[$index]) ? $this->mProperties[$index]->getValue() : null;
	}
	
	function isArray()
	{
		return true;
	}
}

class XCube_BoolProperty extends XCube_AbstractProperty
{
	function fetch($key = null)
	{
		parent::fetch($key);
		if (strlen(trim($this->mValue)) > 0) {
			$this->mValue = (intval($this->mValue)>0) ? 1 : 0;
		}
		else {
			$this->mValue = 0;
		}
	}
}

class XCube_BoolArrayProperty extends XCube_AbstractArrayProperty
{
	var $mPropertyClassName = "XCube_BoolProperty";
}

class XCube_IntProperty extends XCube_AbstractProperty
{
	function fetch($key = null)
	{
		parent::fetch($key);
		if (strlen(trim($this->mValue)) > 0) {
			$this->mValue = intval($this->mValue);
		}
		else {
			$this->mValue = null;
		}
	}
}

class XCube_IntArrayProperty extends XCube_AbstractArrayProperty
{
	var $mPropertyClassName = "XCube_IntProperty";
}

class XCube_FloatProperty extends XCube_AbstractProperty
{
	function fetch($key = null)
	{
		parent::fetch($key);
		if (strlen(trim($this->mValue)) > 0) {
			$this->mValue = floatval($this->mValue);
		}
		else {
			$this->mValue = null;
		}
	}
}

class XCube_FloatArrayProperty extends XCube_AbstractArrayProperty
{
	var $mPropertyClassName = "XCube_FloatProperty";
}

/**
 *  This class shows the property of string. Check whether a request includes control
 * code. If it does, stop own process.
 */
class XCube_StringProperty extends XCube_AbstractProperty
{
	function fetch($key = null)
	{
		parent::fetch($key);
		if (preg_match_all("/[\\x00-\\x1f]/", $this->mValue, $matches, PREG_PATTERN_ORDER)) {
			foreach ($matches[0] as $match) {
				die("Get control code :" . ord($match));
			}
		}
	}
	
	function toNumber()
	{
		return intval($this->mValue);
	}
}

class XCube_StringArrayProperty extends XCube_AbstractArrayProperty
{
	var $mPropertyClassName = "XCube_StringProperty";
}

/**
 *  This class shows the property of text. Check whether a request includes control
 * code. If it does, stop own process.
 */
class XCube_TextProperty extends XCube_AbstractProperty
{
	function fetch($key = null)
	{
		parent::fetch($key);
		$matches = array();
		$allow_codes = array(9,10,13);

		if (preg_match_all("/[\\x00-\\x09]|[\\x0b-\\x0c]|[\\x0e-\\x1f]/", $this->mValue, $matches,PREG_PATTERN_ORDER)) {
			foreach ($matches[0] as $match) {
				if (!in_array(ord($match),$allow_codes) && ord($match)<32)
					die("Get control code :" . ord($match));
			}
		}
	}
	
	function toNumber()
	{
		return intval($this->mValue);
	}
}

class XCube_TextArrayProperty extends XCube_AbstractArrayProperty
{
	var $mPropertyClassName = "XCube_TextProperty";
}

class XCube_FileProperty extends XCube_AbstractProperty
{
	function XCube_FileProperty($name)
	{
		parent::XCube_AbstractProperty($name);
		$this->mValue =& new XCube_FormFile($name);
	}
	
	function fetch($key = null)
	{
		if (!is_object($this->mValue)) {
			return false;
		}
		
		$this->mValue->fetch();
		if (!$this->mValue->hasUploadFile()) {
			$this->mValue = null;
		}
	}
	
	function isNull()
	{
		if (!is_object($this->mValue)) {
			return false;
		}
		
		return !$this->mValue->hasUploadFile();
	}
	
	function toString()
	{
		return null;
	}
	
	function toNumber()
	{
		return null;
	}
}

class XCube_ImageFileProperty extends XCube_FileProperty
{
	function XCube_ImageFileProperty($name)
	{
		parent::XCube_AbstractProperty($name);
		$this->mValue =& new XCube_FormImageFile($name);
	}
}

class XCube_FieldProperty
{
	var $mForm;
	
	var $mDepends;
	var $mMessages;
	var $mVariables;

	function XCube_FieldProperty(&$form)
	{
		$this->mForm=&$form;
	}
	
	function setDependsByArray($dependsArr)
	{
		foreach($dependsArr as $dependName){
			$instance =& XCube_DependClassFactory::factoryClass($dependName);
			if($instance!==null)
				$this->mDepends[$dependName]=&$instance;

			unset($instance);
		}
	}
	
	function addMessage($name,$message)
	{
		if(func_num_args()>=2) {
			$args=func_get_args();
			$this->mMessages[$args[0]]['message']=$args[1];
			for($i=0;isset($args[$i+2]);$i++) {
				$this->mMessages[$args[0]]['args'][$i]=$args[$i+2];
			}
		}
	}
	
	function renderMessage($name)
	{
		if(!isset($this->mMessages[$name]))
			return null;

		$message=$this->mMessages[$name]['message'];

		if(isset($this->mMessages[$name]['args'])) {
			for($i=0;$i<count($this->mMessages[$name]['args']);$i++) {
				$message=str_replace("{".$i."}",$this->mMessages[$name]['args'][$i],$message);
			}
		}
		
		return $message;
	}

	function addVar($name,$value)
	{
		$this->mVariables[$name]=$value;
	}
	
	/**
	 * TODO This class already has form property instance.
	 */
	function validate(&$form)
	{
		if(is_array($this->mDepends) && count($this->mDepends)>0) {
			foreach($this->mDepends as $name => $depend) {
				if(!$depend->isValid($form, $this->mVariables)) {
					// Error
					// NOTICE: This is temporary until we will decide the method of managing error.
					$this->mForm->mErrorFlag=true;
					
					// TEST!!
					$this->mForm->addErrorMessage($this->renderMessage($name));
				}
				else {
					// OK
				}
			}
		}
	}
}

class XCube_DependClassFactory
{
	function &factoryClass($dependName)
	{
		static $_cache;
		
		if (!is_array($_cache)) {
			$_cache = array();
		}
		
		if (!isset($_cache[$dependName])) {
			// or switch?
			$class_name = "XCube_" . ucfirst($dependName) . "Validator";
			if(class_exists($class_name)) {
				$_cache[$dependName] =& new $class_name();
			}
		}

		return $_cache[$dependName];
	}
}

?>