<?php
include_once XOOPS_ROOT_PATH."/class/xoopsobject.php";

if (!defined('XOBJ_DTYPE_FLOAT')) define('XOBJ_DTYPE_FLOAT', 101);
if (!defined('XOBJ_DTYPE_CUSTOM')) define('XOBJ_DTYPE_CUSTOM', 102);
if (!defined('XOBJ_VCLASS_TFIELD')) define('XOBJ_VCLASS_TFIELD', 1);
if (!defined('XOBJ_VCLASS_ATTRIB')) define('XOBJ_VCLASS_ATTRIB', 2);
if (!defined('XOBJ_VCLASS_EXTRA')) define('XOBJ_VCLASS_EXTRA', 3);
if (!defined('XOBJ_DTYPE_STRING')) define('XOBJ_DTYPE_STRING', 1);
if (!defined('XOBJ_DTYPE_TEXT')) define('XOBJ_DTYPE_TEXT', 2);
if (!defined('XOBJ_DTYPE_BOOL')) define('XOBJ_DTYPE_BOOL', 103);
/**
* Generic Table Manupulation XoopsObject class
 *
 * @copyright copyright (c) 2004-2006 Kowa.ORG
 * @author Nobuki Kowa <Nobuki@Kowa.ORG>
 * @package XoopsTableObject
 */
if( ! class_exists( 'XoopsTableObject' ) ) {

////@ �С�����󶥹����Τ����ɲ�
	define('XTOBJ_VERSION', 20060728);
	define('XTOBJ_FILE_PATH', str_replace(XOOPS_ROOT_PATH, '', __FILE__ ));

	class XoopsTableObject  extends XoopsObject
	{
		var $_extra_vars = array();
		var $_keys;
		var $_nameField;
		var $_autoIncrement;
		var $_listTableElements;
		var $_handler;
		var $_form;
		var $_list;

	    function initVar($key, $data_type, $value = null, $required = false, $maxlength = null, $options = '')
	    {
	    	parent::initVar($key, $data_type, $value, $required, $maxlength, $options);
	    	$this->vars[$key]['var_class'] = XOBJ_VCLASS_TFIELD;
	    }

		function setAttribute($key, $value)
		{
	        $this->vars[$key] = array('value' => $value, 'required' => false, 'data_type' => XOBJ_DTYPE_OTHER, 'maxlength' => null, 'changed' => false, 'options' => '');
			$this->vars[$key]['var_class'] = XOBJ_VCLASS_ATTRIB;
		}

		function XoopsTableObject()
		{
			//�ƥ��饹�Υ��󥹥ȥ饯���ƽ�
			$this->XoopsObject();
			$this->_handler = null;
		}

		function setKeyFields($keys)
		{
			$this->_keys = $keys;
		}

		function getKeyFields()
		{
			return $this->_keys;
		}

		function isKey($field)
		{
			return in_array($field,$this->_keys);
		}

		function cacheKey()
		{
			$recordKeys = $this->getKeyFields();
			$recordVars = $this->getVars();
			$cacheKey = array();
			foreach ($this->getKeyFields() as $k => $v) {
				$cacheKey[$v] = $this->getVar($v);
			}
			return(serialize($cacheKey));
		}

		function getKey($format = 's') {
			if (!array($this->_keys)) {
				return false;
			} else {
				return $this->getVar($this->_keys[0], $format);;
			}
		}

		//AUTO_INCREMENT°-�Υե�����ɤϥơ��֥�˰�Ĥ����ʤ�p��
		function setAutoIncrementField($fieldName)
		{
			$this->_autoIncrement = $fieldName;
		}

		function &getAutoIncrementField()
		{
			return $this->_autoIncrement;
		}

		function isAutoIncrement($fieldName)
		{
			return ($fieldName == $this->_autoIncrement);
		}

		function resetChenged()
		{
			foreach($this->vars as $k=>$v) {
				$this->vars[$k]['changed'] = false;
			}
		}

	    function assignVar($key, $value)
	    {
	        if (isset($value) && isset($this->vars[$key])) {
	            $this->vars[$key]['value'] =& $value;
	        } else {
	            $this->setExtraVar($key, $value);
	        }
	    }

		function &getExtraVar($key)
		{
			return $this->_extra_vars[$key];
		}

		function setExtraVar($key, $value)
		{
			$this->_extra_vars[$key] =& $value;
		}

		function setNameField($fieldname) {
			$this->nameField = $fieldname;
		}

		function getName($format = 's') {
			if ($this->nameField) {
				return $this->getVar($this->nameField, $format);
			} else {
				return false;
			}
		}

	    /**
	    * assign a value to a variable
	    *
	    * @access public
	    * @param string $key name of the variable to assign
	    * @param mixed $value value to assign
	    * @param bool $not_gpc
	    */
	    function setVar($key, $value, $not_gpc = false)
	    {
	        if (!empty($key) && isset($this->vars[$key])) {
	            $this->vars[$key]['value'] =& $value;
	            $this->vars[$key]['not_gpc'] = $not_gpc;
	            $this->vars[$key]['changed'] = true;
	            $this->setDirty();
	        }
	    }

	    /**
	    * returns a specific variable for the object in a proper format
	    *
	    * @access public
	    * @param string $key key of the object's variable to be returned
	    * @param string $format format to use for the output
	    * @return mixed formatted value of the variable
	    */
	    function &getVar($key, $format = 's')
	    {
			if (($this->vars[$key]['data_type'] == XOBJ_DTYPE_CUSTOM)) {
				//���̤��ѿ�Get������м¹�;
				$getMethod = 'getVar_'.$key;
				if(method_exists($this, $getMethod)) {
					$this->$getMethod($this->vars[$key]['value'],$format);
				} else {
					$this->vars[$key]['data_type'] = XOBJ_DTYPE_TXTBOX;
					$ret =& parent::getVar($key, $format);
					$this->vars[$key]['data_type'] = XOBJ_DTYPE_CUSTOM;
				}
			} else {
				$ret =& parent::getVar($key, $format);
			}
			if ($this->vars[$key]['data_type'] == XOBJ_DTYPE_TXTAREA && ($format=='e' || $format=='edit')) {
				$ret = preg_replace("/&amp;(#[0-9]+;)/i", '&$1', $ret);
			}
			return $ret;
		}

		function cleanVars() {
			$iret =parent::cleanVars();
			foreach ($this->vars as $k => $v) {
				$cleanv = $v['value'];
				if (!$v['changed']) {
				} else {
					$cleanv = is_string($cleanv) ? trim($cleanv) : $cleanv;
					switch ($v['data_type']) {
					case XOBJ_DTYPE_FLOAT:
						$cleanv = (float)($cleanv);
						$this->cleanVars[$k] =& $cleanv;
						break;
					default:
						break;
					}
					//���̤��ѿ�e��å�������м¹�;
					$checkMethod = 'checkVar_'.$k;
					if(method_exists($this, $checkMethod)) {
						$this->$checkMethod($cleanv);
					}
				}
				unset($cleanv);
			}
			if (count($this->_errors) > 0) {
				return false;
			}
			$this->unsetDirty();
			return true;
		}

		function &getVarArray($type='s') {
			$varArray=array();
	        foreach ($this->vars as $k => $v) {
				$varArray[$k]=$this->getVar($k,$type);
			}
			return $varArray;
		}
		//Following two functions are only for WordPress Module.
		function &exportWpObject() {
			$wp_object = (object) null;
	        foreach ($this->vars as $k => $v) {
	        	$wp_object->$k = $v['value'];
			}
	        foreach ($this->_extra_vars as $k => $v) {
	        	$wp_object->$k = $v;
			}
			return $wp_object;
		}
		function importWpObject(&$wp_object) {
	        foreach ($this->vars as $k => $v) {
	        	$this->setVar($k, $wp_object->$k, true);
			}
		}

	    /**#@+
	     * @deprecated
	     */
		function assignEditFormElement($name,$class,$params)
		{
			if (!$this->_form) $this->_form =& new XoopsTableObjectForm;
			$className = "XoopsForm". $class;
			$callstr = '$this->_form->addElement("'.$name.'", new XoopsForm'.$class.'(';
			$delim = '';
			for ($i=0;$i<count($params);$i++) {
				if (gettype($params[$i]) == "string") {
					$callstr .= $delim. '"'. $params[$i].'"';
				} else {
					$callstr .= $delim. $params[$i];
				}
				$delim = ', ';
			}
			$callstr .= '));';
			eval($callstr);
		}

	    /**#@+
	     * @deprecated
	     */
		function assignEditFormOptionArray($name,$options) {
			$this->_form->addOptionArray($name,$options);
		}

	    /**#@+
	     * @deprecated
	     */
		function &buildEditForm($caption,$name,$action,$token=0) {
			$this->_form->_caption = $caption;
			$this->_form->_name = $name;
			$this->_form->_action = $action;
			$this->_form->_token = $token;
			$this->_form->buildEditForm($this);
		}

	    /**#@+
	     * @deprecated
	     */
		function renderEditForm($caption, $name, $action, $token=0) {
			$this->_form->_caption = $caption;
			$this->_form->_name = $name;
			$this->_form->_action = $action;
			$this->_form->_token = $token;
			return $this->_form->render($this);
		}

	    /**#@+
	     * @deprecated
	     */
		function assignEditForm2Tpl(&$tpl, $caption, $name, $action, $token=0) {
			$this->_form->_caption = $caption;
			$this->_form->_name = $name;
			$this->_form->_action = $action;
			$this->_form->_token = $token;
			$this->_form->assign($this, $tpl);
		}

	    /**#@+
	     * @deprecated
	     */
		function assignListTableElement($name, $type, $caption, $width) {
			if (!$this->_list) $this->_list =& new XoopsTableObjectList;
			$this->_list->addElement($name, $type, $caption, $width);
		}

	    /**#@+
	     * @deprecated
	     */
		function &getListItems() {
			return $this->_list->getListItems($this);
		}

	    /**#@+
	     * @deprecated
	     */
		function &getListHeaders() {
			return $this->_list->getListHeaders();
		}

////@ SimpleControler���Խ����� / ��.���Խ�����򰷤���褦�ˤ��뤿���ɲ�
		/**
		 * Name of Field that contain record owner's uid.
		 * @var string
		 * @access private
		 */
		var $_recordOwnerField;

		/**
		 * Name of Field that contain created time
		 * @var string
		 * @access private
		 */
		var $_createdField;

		/**
		 * Name of Field that contain updated time
		 * @var string
		 * @access private
		 */
		var $_updatedField;

		/**
		 * Set name of record's owner Field.
		 * @param string $fieldname
		 */
		function setOwnerField($fieldName) {
			if (isset($this->vars[$fieldName])) {
				$this->_recordOwnerField = $fieldName;
			} else {
				die("Error on setOwnerField($fieldName). You don't initVar this field.");
			}
		}

		/**
		 * Set record owner's uid
		 * @param int $uid
		 */
		function setOwner($uid) {
			if (isset($this->_recordOwnerField)) {
				$this->setVar($this->_recordOwnerField, intval($uid));
			}
		}

		/**
		 * Return record owner
		 * @return int uid, bool false on not set
		 */
		function getOwner() {
			if (isset($this->_recordOwnerField)) {
				return $this->getVar($this->_recordOwnerField);
			} else {
				return -1;
			}
		}

		function setCreatedField($fieldName) {
			if (isset($this->vars[$fieldName])) {
				$this->_createdField = $fieldName;
			} else {
				die("Error on setCreatedField($fieldName). You don't initVar this field.");
			}
		}

		function setUpdatedField($fieldName) {
			if (isset($this->vars[$fieldName])) {
				$this->_updatedField = $fieldName;
			} else {
				die("Error on setUpdatedField($fieldName). You don't initVar this field.");
			}
		}

		function setTime() {
			if ($this->isNew()) {
				if (isset($this->_createdField)) {
					$this->setVar($this->_createdField, time());
				}
			} else {
				if (isset($this->_updatedField)) {
					$this->setVar($this->_updatedField, time());
				}
			}
		}
	}
}
if( ! class_exists( 'XoopsTableObjectHandler' ) ) {
	class XoopsTableObjectHandler  extends XoopsObjectHandler
	{
		var $tableName = null;
		var $tableAlias = null;
		var $useFullCache;
		var $cacheLimit;
		var $_entityClassName;
		var $_errors;
		var $_fullCached;
		var $_sql;

		function XoopsTableObjectHandler($db)
		{
			$this->_entityClassName = preg_replace("/handler$/i","", get_class($this));
			parent::XoopsObjectHandler($db);
			if ($this->tableName) {
				$this->tableName = $this->db->prefix($this->tableName);
			}
			$this->_errors = array();
		}

		function getErrors($html=true, $clear=true)
		{
			$error_str = "";
			$delim = $html ? "<br />\n" : "\n";
			if (count($this->_errors)) {
				$error_str = implode($delim, $this->_errors);
			}
			if ($clear) {
				$this->_errors = array();
			}
			return $error_str;
		}
		function setError($error_str)
		{
			$this->_errors[] = $error_str;
		}

		function setAlias($alias) {
			$this->tableAlias = $alias;
		}

		function getAlias() {
			return($this->tableAlias);
		}

		/**
		 * �쥳���ɥ��֥������Ȥ�8.
		 *
		 * @param	boolean $isNew �����쥳��������ե饰
		 *
		 * @return	object  {@link XoopsTableObject}
		 */
		function &create($isNew = true)
		{
			$record = new $this->_entityClassName;
			if ($isNew) {
				$record->setNew();
			}
			$record->_handler =& $this;
			return $record;
		}

		/**
		 * �쥳���ɤμ���(�ץ饤�ޥ꡼�����ˤ���ո����
		 *
		 * @param	mixed $key �����
		 *
		 * @return	object  {@link XoopsTableObject}, FALSE on fail
		 */
		function &get($keys)
		{
		    $ret = false;
			$record =& $this->create(false);
			$recordKeys = $record->getKeyFields();
			$recordVars = $record->getVars();

			if ($whereStr = $this->_key2where($keys)) {
				$sql = sprintf("SELECT * FROM %s WHERE %s",$this->tableName, $whereStr);

				if ( !$result =& $this->query($sql) ) {
					return $ret;
				}

				$numrows = $this->db->getRowsNum($result);
				if ( $numrows == 1 ) {
					$row = $this->db->fetchArray($result);
					$record->assignVars($row);
					$this->db->freeRecordSet($result);
					return $record;
				}
			}
			unset($record);
			return $ret;
		}

		function _key2where($keys) {
			$record =& $this->create(false);
			$recordKeys = $record->getKeyFields();
			$recordVars = $record->getVars();
			if (gettype($keys) != 'array') {
				if (count($recordKeys) == 1) {
					$keys = array($recordKeys[0] => $keys);
				} else {
					return false;
				}
			}
			$whereStr = "";
			$whereAnd = "";
			foreach ($record->getKeyFields() as $k => $v) {
				if (array_key_exists($v, $keys)) {
					$whereStr .= $whereAnd . "`$v` = ";
////@ ���ͥ����ξ���8�ǡ�������äƤ����ǽ����褦���ѹ�
//					if (($recordVars[$v]['data_type'] == XOBJ_DTYPE_INT) || ($recordVars[$v]['data_type'] == XOBJ_DTYPE_FLOAT)) {
//						$whereStr .= $keys[$v];
					if ($recordVars[$v]['data_type'] == XOBJ_DTYPE_INT) {
						$whereStr .= intval(intval($keys[$v]));
					} elseif ($recordVars[$v]['data_type'] == XOBJ_DTYPE_FLOAT) {
						$whereStr .= (float)$keys[$v];
////@			// end change
					} else {
						$whereStr .= $this->db->quoteString($keys[$v]);
					}
					$whereAnd = " AND ";
				} else {
					unset($record);
					return false;
				}
			}
			unset($record);
			return $whereStr;
		}

	    /**
	     * �쥳���ɤ���¸
	     *
	     * @param	object	&$record	{@link XoopsTableObject} object
	     * @param	bool	$force		POST�᥽�åɰʳ��Ƕ�)�����������ture
	     *
	     * @return	bool    .��λ��� TRUE
	     */
		function insert(&$record,$force=false,$updateOnlyChanged=false)
		{
			if ( get_class($record) != $this->_entityClassName ) {
				return false;
			}
			if ( !$record->isDirty() ) {
				return true;
			}
			if (!$record->cleanVars()) {
				$this->_errors += $record->getErrors();
				return false;
			}
			$vars = $record->getVars();
			if ($record->isNew()) {
				$fieldList = "(";
				$valueList = "(";
				$delim = "";
				foreach ($record->cleanVars as $k => $v) {
					if ($vars[$k]['var_class'] != XOBJ_VCLASS_TFIELD) {
						continue;
					}
					$fieldList .= $delim ."`$k`";
					if ($record->isAutoIncrement($k)) {
						$v = $this->getAutoIncrementValue();
					}
					if (preg_match("/^__MySqlFunc__/", $v)) {  // for value using MySQL function.
						$value = preg_replace('/^__MySqlFunc__/', '', $v);
					} elseif ($vars[$k]['data_type'] == XOBJ_DTYPE_INT) {
						if (!is_null($v)) {
							$v = intval($v);
							$v = ($v) ? $v : 0;
							$valueList .= $delim . $v;
						} else {
							$valueList .= $delim . 'null';
						}
					} elseif ($vars[$k]['data_type'] == XOBJ_DTYPE_FLOAT) {
						if (!is_null($v)) {
							$v = (float)($v);
							$v = ($v) ? $v : 0;
							$valueList .= $delim . $v;
						} else {
							$valueList .= $delim . 'null';
						}
					} else {
						if (!is_null($v)) {
							$valueList .= $delim . $this->db->quoteString($v);
						} else {
							$valueList .= $delim . $this->db->quoteString('');;
						}
					}
					$delim = ", ";
				}
				$fieldList .= ")";
				$valueList .= ")";
				$sql = sprintf("INSERT INTO %s %s VALUES %s", $this->tableName,$fieldList,$valueList);
			} else {
				$setList = "";
				$setDelim = "";
				$whereList = "";
				$whereDelim = "";
				foreach ($record->cleanVars as $k => $v) {
					if ($vars[$k]['var_class'] != XOBJ_VCLASS_TFIELD) {
						continue;
					}
					if (preg_match("/^__MySqlFunc__/", $v)) {  // for value using MySQL function.
						$value = preg_replace('/^__MySqlFunc__/', '', $v);
					} elseif ($vars[$k]['data_type'] == XOBJ_DTYPE_INT) {
						$v = intval($v);
						$value = ($v) ? $v : 0;
					} elseif ($vars[$k]['data_type'] == XOBJ_DTYPE_FLOAT) {
						$v = (float)($v);
						$value = ($v) ? $v : 0;
					} else {
						$value = $this->db->quoteString($v);
					}

					if ($record->isKey($k)) {
						$whereList .= $whereDelim . "`$k` = ". $value;
						$whereDelim = " AND ";
					} else {
						if ($updateOnlyChanged && !$vars[$k]['changed']) {
							continue;
						}
						$setList .= $setDelim . "`$k` = ". $value . " ";
						$setDelim = ", ";
					}
				}
				if (!$setList) {
					$record->resetChenged();
					return true;
				}
				$sql = sprintf("UPDATE %s SET %s WHERE %s", $this->tableName, $setList, $whereList);
			}
			if (!$result =& $this->query($sql, $force)) {
				return false;
			}
			if ($record->isNew()) {
				$idField=$record->getAutoIncrementField();
				$idValue=$this->db->getInsertId();
				$record->assignVar($idField,$idValue);
			}
			$record->resetChenged();
			return true;
		}

	    function updateByField(&$record, $fieldName, $fieldValue, $not_gpc=false)
	    {
	        $record->setVar($fieldName, $fieldValue, $not_gpc);
	        return $this->insert($record, true, true);
	    }

		/**
		 * �쥳���ɤκ��
		 *
	     * @param	object  &$record  {@link XoopsTableObject} object
	     * @param	bool	$force		POST�᥽�åɰʳ��Ƕ�)�����������ture
	     *
	     * @return	bool    .��λ��� TRUE
		 */
		function delete(&$record,$force=false)
		{
			if ( get_class($record) != $this->_entityClassName ) {
				return false;
			}
			if (!$record->cleanVars()) {
				$this->_errors[] = $this->db->error();
				return false;
			}
			$vars = $record->getVars();
			$whereList = "";
			$whereDelim = "";
			foreach ($record->cleanVars as $k => $v) {
				if ($record->isKey($k)) {
					if (($vars[$k]['data_type'] == XOBJ_DTYPE_INT)||($vars[$k]['data_type'] == XOBJ_DTYPE_FLOAT)) {
						$value = $v;
					} else {
						$value = $this->db->quoteString($v);
					}
					$whereList .= $whereDelim . "`$k` = ". $value;
					$whereDelim = " AND ";
				}
			}
			$sql = sprintf("DELETE FROM %s WHERE %s", $this->tableName, $whereList);
			if (!$result =& $this->query($sql, $force)) {
				return false;
			}
			return true;
		}

		/**
		 * �ơ��֥�ξ�︡��ˤ��ʣ��쥳���ɼ���
		 *
		 * @param	object	$criteria 	{@link XoopsTableObject} ������
		 * @param	bool $id_as_key		�ץ饤�ޥ꡼�������������Υ����ˤ������true
		 *
		 * @return	mixed Array			�����̥쥳���ɤ�����
		 */
		function &getObjects($criteria = null, $id_as_key = false, $fieldlist="", $distinct = false, $joindef = false, $having="")
		{
			$records = array();

			if ($result =& $this->open($criteria, $fieldlist, $distinct, $joindef, $having)) {
				while ($myrow = $this->db->fetchArray($result)) {
					$record =& $this->create(false);
					$record->assignVars($myrow);
					if (!$id_as_key) {
						$records[] =& $record;
					} else {
						$ids = $record->getKeyFields();
						$r =& $records;
						$count_ids = count($ids);
						for ($i=0; $i<$count_ids; $i++) {
							if ($i == $count_ids-1) {
								$r[$myrow[$ids[$i]]] =& $record;
							} else {
								if (!isset($r[$myrow[$ids[$i]]])) {
									$r[$myrow[$ids[$i]]] = array();
								}
								$r =& $r[$myrow[$ids[$i]]];
							}
						}
					}
					unset($record);
				}
				$this->db->freeRecordSet($result);
			}
			return $records;
		}

		/**
		 * �ơ��֥�ξ�︡��ˤ��ʣ��쥳���ɼ����Ѥ�Open �ʰ��٤ˤϼ��$��ʤ���
		 *
		 * @param	object	$criteria 	{@link XoopsTableObject} ������
		 *
		 * @return	mixed Array			�����̥쥳���ɤ�����
		 */
		function &open($criteria = null, $fieldlist="", $distinct = false, $joindef = false, $having="")
		{
			$limit = $start = 0;
			$whereStr = '';
			$orderStr = '';
			if ($distinct) {
				$distinct = "DISTINCT ";
			} else {
				$distinct = "";
			}
			if ($fieldlist) {
				if ($this->getAlias() != '') {
					$sql = 'SELECT '.$distinct.$fieldlist.' FROM '.$this->tableName.' AS '.$this->getAlias();
				} else {
					$sql = 'SELECT '.$distinct.$fieldlist.' FROM '.$this->tableName;
				}
			} else {
				if ($this->getAlias() != '') {
					$sql = 'SELECT '.$distinct.'* FROM '.$this->tableName.' AS '.$this->getAlias();
				} else {
					$sql = 'SELECT '.$distinct.'* FROM '.$this->tableName;
				}
			}
			if ($joindef) {
				if ($this->getAlias() != '') {
					$sql .= $joindef->render($this->getAlias());
				} else {
					$sql .= $joindef->render($this->tableName);
				}
			}
			if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
				$whereStr = $this->_renderWhere($criteria);
				$sql .= ' '.$whereStr;
			}
			if (isset($criteria) && (is_subclass_of($criteria, 'criteriaelement')||get_class($criteria)=='criteriaelement')) {
				if ($criteria->getGroupby() != ' GROUP BY ') {
					$sql .= ' '.$criteria->getGroupby();
					if(strlen($having) > 0){
						$sql .= ' HAVING '.$having;
					}
				}
				if ((is_array($criteria->getSort()) && count($criteria->getSort()) > 0)) {
					$orderStr = 'ORDER BY ';
					$orderDelim = "";
					foreach ($criteria->getSort() as $sortVar) {
						$orderStr .= $orderDelim . $sortVar.' '.$criteria->getOrder();
						$orderDelim = ",";
					}
					$sql .= ' '.$orderStr;
				} elseif ($criteria->getSort() != '') {
					$orderStr = 'ORDER BY '.$criteria->getSort().' '.$criteria->getOrder();
					$sql .= ' '.$orderStr;
				}
				$limit = $criteria->getLimit();
				$start = $criteria->getStart();
			}
			$resultSet =& $this->query($sql, false ,$limit, $start);
			return $resultSet;
		}

		function &getNext(&$resultSet)
		{
			if ($myrow = $this->db->fetchArray($resultSet)) {
				$record =& $this->create(false);
				$record->assignVars($myrow);
				return $record;
			} else {
				$result = false;
				return $result;
			}
		}

////@ ���ɽ�ξ��������$Ǥ���褦�ѹ�
		/**
		 * �ơ��֥�ξ�︡��ˤ���оݥ쥳���ɷ��
		 *
		 * @param	object	$criteria 		{@link XoopsTableObject} ������
		 * @param	XoopsJoinCriteria[optional]	$joindef	�����
		 * @return	integer					�����̥쥳���ɤη��
		 */
//	    function getCount($criteria = null)
	    function getCount($criteria = null, $joindef = false)
	    {
	        $sql = 'SELECT COUNT(*) FROM '.$this->tableName;
	//add 3 line
			if ($joindef) {
				$sql .= $joindef->render($this->tableName);
			}
////@ end change
	        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
	            $sql .= ' '.$this->_renderWhere($criteria);
	        }
	        $result =& $this->query($sql);
	        if (!$result) {
	            return 0;
	        }
	        list($count) = $this->db->fetchRow($result);
	        return $count;
	    }

		/**
		 * �ơ��֥�ξ�︡��ˤ��ʣ��쥳���ɰ�繹��(�оݥե�����ɤϰ�ĤΤ�)
		 *
		 * @param	string	$fieldname 	�����ե������̾
		 * @param	mixed	$fieldvalue	������
		 * @param	object	$criteria 	{@link XoopsTableObject} ������
	     * @param	bool	$force		POST�᥽�åɰʳ��Ƕ�)�����������ture
		 *
		 * @return	mixed Array			�����̥쥳���ɤ�����
		 */
	    function updateAll($fieldname, $fieldvalue, $criteria = null, $force=false)
	    {
	    	$record = $this->create();
	    	if ($record->vars[$fieldname]['data_type'] == XOBJ_DTYPE_INT) {
				$fieldvalue = intval($fieldvalue);
				$fieldvalue = ($fieldvalue) ? $fieldvalue : 0;
			} elseif ($record->vars[$fieldname]['data_type'] == XOBJ_DTYPE_FLOAT) {
				$fieldvalue = (float)($fieldvalue);
				$fieldvalue = ($fieldvalue) ? $fieldvalue : 0;
			} else {
				$fieldvalue = $this->db->quoteString($fieldvalue);
			}
	        $set_clause = $fieldname.' = '.$fieldvalue;
	        $sql = 'UPDATE '.$this->tableName.' SET '.$set_clause;
	        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
	            $sql .= ' '.$this->_renderWhere($criteria);
	        }
			if (!$result =& $this->query($sql, $force)) {
				return false;
			}
	        return true;
	    }

		/**
		 * �ơ��֥�ξ�︡��ˤ��ʣ��쥳���ɺ��
		 *
		 * @param	object	$criteria 	{@link XoopsTableObject} ������
	     * @param	bool	$force		POST�᥽�åɰʳ��Ƕ�)�����������ture
	     *
	     * @return	bool    .��λ��� TRUE
		 */
	    function deleteAll($criteria = null, $force=false)
	    {
	        $sql = 'DELETE FROM '.$this->tableName;
	        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
	            $sql .= ' '.$this->_renderWhere($criteria);
	        }
			if (!$result =& $this->query($sql, $force)) {
				return false;
			}
	        return true;
	    }

		function getName($keys, $format='s'){
			if ($GLOBALS['_xoopsTableCache']->exists($this->tableName.'__getName_'.$format, serialize($keys))) {
				return $GLOBALS['_xoopsTableCache']->get($this->tableName.'__getName_'.$format, serialize($keys));
			}
			if ($record =& $this->get($keys)) {
				$value = $record->getName($format);
				$GLOBALS['_xoopsTableCache']->set($this->tableName.'__getName_'.$format, serialize($keys), $value);
				return $value;
			} else {
				return false;
			}
		}

		function &getSelectOptionArray() {
			$objects =& $this->getObjects();
			$optionArray = array();
			foreach($objects as $object) {
				$optionArray[$object->getKey()] = $object->getName();
			}
			return $optionArray;
		}

		function getAutoIncrementValue()
		{
			return $this->db->genId(get_class($this).'_id_seq');
		}

		function &query($sql, $force=false, $limit=0, $start=0) {
			if (empty($GLOBALS['_xoopsTableQueryCount'])) {
				$GLOBALS['_xoopsTableQueryCount'] = 1;
			} else {
				$GLOBALS['_xoopsTableQueryCount']++;
			}
			if (!empty($GLOBALS['wpdb'])) {
				$GLOBALS['wpdb']->querycount++;
			}
			if ($force) {
				$result =& $this->db->queryF($sql, $limit, $start);
			} else {
				$result =& $this->db->query($sql, $limit, $start);
			}
			$this->_sql = $sql;
			$this->_start = $start;
			$this->_limit = $limit;

			if (!$result) {
				$this->_errors[] = $this->db->error();
				$result = false;
			}
			return $result;
		}

		function getLastSQL()
		{
			return $this->_sql;
		}

		function _renderWhere($criteria) {
		    $whereStr = $this->_makeCriteria4sql($criteria);
		    if ($whereStr) return 'WHERE '.$whereStr;
		    return '';
		}

		function _makeCriteria4sql($criteria)
    	{
    		$dmmyObj =& $this->create();
    		return $this->_makeCriteriaElement4sql($criteria, $dmmyObj);
    	}

    	/**
    	 * @param $criteria CriteriaElement
    	 * @param $obj XoopsSimpleObject
    	 */
    	function _makeCriteriaElement4sql($criteria, &$obj)
    	{
    		if (is_a($criteria, "CriteriaElement")) {
//    			if (is_a($criteria, "CriteriaCompo")) {
    			if (is_a($criteria, "CriteriaCompo") && (count($criteria->criteriaElements) > 0)) {
    				$queryString = "";
                    $maxCount = count($criteria->criteriaElements);
                    for ($i = 0; $i < $maxCount ; $i++) {
                        if ($i != 0) {
                            $queryString .= " " . $criteria->conditions[$i];
                        }
                        $queryString .= " " . $this->_makeCriteria4sql($criteria->criteriaElements[$i]);
                    }
    				return "(" . $queryString . ")";
    			} else {
    				//
    				// Render
    				//
    				if (method_exists($criteria, 'getName')) {
    				    $name = $criteria->getName();
    				    $value = $criteria->getValue();
    				    $operator = $criteria->getOperator();
    				} else {
    				    $name = $criteria->column;
    				    $value = $criteria->value;
    				    $operator = $criteria->operator;
    				}
                    if ($name != null) {
        				if (isset($obj->vars[$name])) {
        				    $type = $obj->vars[$name]['data_type'];
        				} else if (is_array($value) && array_key_exists('type',$value)) {
        				    $type = $value['type'];
        				    $value = $value['value'];
        				} else {
        				    $type = XOBJ_DTYPE_TXTBOX;
        				}
                        if (!in_array(strtoupper($operator), array('IN', 'NOT IN'))) {
        			    	$value = $this->_makeCriteriaValue4sql($value, $type);
        			    } else {
        			        if (!is_array($value) && preg_match('/^\(([^)]*)\)$/', trim($value), $match)) {
        			            $value = $this->_parseInCause($match[1]);
        			        }
        			        if (is_array($value)) {
        			            foreach (array_keys($value) as $key) {
        			                $value[$key] = $this->_makeCriteriaValue4sql($value[$key], $type);
        			            }
        			            $value = '('.implode(',', $value).')';
        			        } else {
        			            return null;
        			        }
        			    }
       					return $name . " " . $operator . " " . $value;
    				} else {
    					return null;
    				}
    			}
    		}
    	}

       	function _makeCriteriaValue4sql($value, $type) {
    		switch ($type) {
    			case XOBJ_DTYPE_BOOL:
    				$value = $value ? "1" : "0";
    				break;

    			case XOBJ_DTYPE_INT:
    			case XOBJ_DTYPE_STIME:
    			case XOBJ_DTYPE_MTIME:
    			case XOBJ_DTYPE_LTIME:
    				$value = intval($value);
    				break;

    			case XOBJ_DTYPE_FLOAT:
    				$value = floatval($value);
    				break;

    			case XOBJ_DTYPE_TXTBOX:
    			case XOBJ_DTYPE_TXTAREA:
    			case XOBJ_DTYPE_URL:
    			case XOBJ_DTYPE_EMAIL:
    			case XOBJ_DTYPE_SOURCE:
    			case XOBJ_DTYPE_OTHER:
    				$value = $this->db->quoteString($value);
    				break;
    			default:
    		}
    		return $value;
    	}

        function _parseInCause($str) {
           $result = Array();
           $ptr = 0;
           $len = strlen($str);
           while ($ptr < $len) {
               while (($ptr < $len) && (strpos(" \r\t\n",$str[$ptr]) !== false)) $ptr++;
               if ($str[$ptr] == '"') {
                   $ptr++;
                   $q = $ptr;
                   while (($ptr < $len) && ($str[$ptr] != '"')) {
                       if ($str[$ptr] == '\\') { $ptr+=2; continue; }
                       $ptr++;
                   }
                   $result[] = stripslashes(substr($str, $q, $ptr-$q));
                   $ptr++;
                   while (($ptr < $len) && (strpos(" \r\t\n",$str[$ptr]) !== false)) $ptr++;
                   $ptr++;
               } else if ($str[$ptr] == "'") {
                   $ptr++;
                   $q = $ptr;
                   while (($ptr < $len) && ($str[$ptr] != "'")) {
                       if ($str[$ptr] == '\\') { $ptr+=2; continue; }
                       $ptr++;
                   }
                   $result[] = stripslashes(substr($str, $q, $ptr-$q));
                   $ptr++;
                   while (($ptr < $len) && (strpos(" \r\t\n",$str[$ptr]) !== false)) $ptr++;
                   $ptr++;
               } else {
                   $q = $ptr;
                   while (($ptr < $len) && (strpos(",;",$str[$ptr]) === false)) {
                       $ptr++;
                   }
                   $result[] = stripslashes(trim(substr($str, $q, $ptr-$q)));
                   while (($ptr < $len) && (strpos(" \r\t\n",$str[$ptr]) !== false)) $ptr++;
                   $ptr++;
               }
           }
           return $result;
        }

		/**
		 * Number of records that is based on condition reference of a table.
		 * @param CriteriaElement[optional] $criteria {@link CriteriaElement}
		 * @param bool[optional] $distinct
		 * @param XoopsJoinCriteria[optional] $joindef {@link XoopsJoinCriteria}
		 * @param string[optional] $having
		 * @return int
		 */
		function getRowsNum($criteria = null, $distinct = false, $joindef = false, $having="")
		{
			$criteria->setStart(0);
			$criteria->setLimit(0);
			$criteria->setSort('');
			$fieldlist = "";
			if ($criteria->groupby != '') {
				$fieldlist = $criteria->groupby;
			}
			if ($result =& $this->open($criteria, $fieldlist, $distinct, $joindef, $having)) {
				if($numrows = $this->db->getRowsNum($result)) {
					return $numrows;
				}
			}
			return 0;
		}

		/**
		 * Return table alias(if exist) or table name
		 * @return string
		 */
		function getTableName() {
			if ($this->getAlias() != '') {
				return $this->getAlias();
			} else {
				return $this->tableName;
			}
		}
   	}


	class XoopsCachedTableObjectHandler  extends XoopsTableObjectHandler
	{
		var $tableName;
		var $useFullCache;
		var $cacheLimit;
		var $_entityClassName;
		var $_errors;
		var $_fullCached;
		var $_sql;

		function XoopsCachedTableObjectHandler($db)
		{
			$this->_entityClassName = preg_replace("/handler$/i","", get_class($this));
			$this->XoopsObjectHandler($db);
			$this->_errors = array();
			$this->useFullCache = true;
			$this->cacheLimit = 0;
			$this->_fullCached = false;
		}

		/**
		 * �쥳���ɤμ���(�ץ饤�ޥ꡼�����ˤ���ո����
		 *
		 * @param	mixed $key �����
		 *
		 * @return	object  {@link XoopsTableObject}, FALSE on fail
		 */
		function &get($keys)
		{
		    $ret = false;
			$record =& $this->create(false);
			$recordKeys = $record->getKeyFields();
			$recordVars = $record->getVars();
			if (gettype($keys) != 'array') {
				if (count($recordKeys) == 1) {
					$keys = array($recordKeys[0] => $keys);
				} else {
					return $ret;
				}
			}
			$whereStr = "";
			$whereAnd = "";
			$cacheKey = array();
			foreach ($record->getKeyFields() as $k => $v) {
				if (array_key_exists($v, $keys)) {
					$whereStr .= $whereAnd . "`$v` = ";
					if (($recordVars[$v]['data_type'] == XOBJ_DTYPE_INT) || ($recordVars[$v]['data_type'] == XOBJ_DTYPE_FLOAT)) {
						$whereStr .= $keys[$v];
					} else {
						$whereStr .= $this->db->quoteString($keys[$v]);
					}
					$whereAnd = " AND ";
					$cacheKey[$v] = $keys[$v];
				} else {
					return $ret;
				}
			}
			$cacheKey = serialize($cacheKey);
			if ($GLOBALS['_xoopsTableCache']->exists($this->tableName, $cacheKey)) {
				$record->assignVars($GLOBALS['_xoopsTableCache']->get($this->tableName, $cacheKey));
				return $record;
			}
			$sql = sprintf("SELECT * FROM %s WHERE %s",$this->tableName, $whereStr);

			if ( !$result =& $this->query($sql) ) {
				return $ret;
			}
			$numrows = $this->db->getRowsNum($result);
//		echo $numrows."<br/>";
			if ( $numrows == 1 ) {
				$row = $this->db->fetchArray($result);
				$record->assignVars($row);
				$GLOBALS['_xoopsTableCache']->set($this->tableName, $cacheKey, $row, $this->cacheLimit);
				$this->db->freeRecordSet($result);
				return $record;
			}
			unset($record);
			return $ret;
		}
	    /**
	     * �쥳���ɤ���¸
	     *
	     * @param	object	&$record	{@link XoopsTableObject} object
	     * @param	bool	$force		POST�᥽�åɰʳ��Ƕ�)�����������ture
	     *
	     * @return	bool    .��λ��� TRUE
	     */
		function insert(&$record,$force=false,$updateOnlyChanged=false)
		{
			if ( get_class($record) != $this->_entityClassName ) {
				return false;
			}
			if ( !$record->isDirty() ) {
				return true;
			}
			if (!$record->cleanVars()) {
				$this->_errors += $record->getErrors();
				return false;
			}
			$vars = $record->getVars();
			$cacheRow = array();
			if ($record->isNew()) {
				$fieldList = "(";
				$valueList = "(";
				$delim = "";
				foreach ($record->cleanVars as $k => $v) {
					if ($vars[$k]['var_class'] != XOBJ_VCLASS_TFIELD) {
						continue;
					}
					$fieldList .= $delim ."`$k`";
					if ($record->isAutoIncrement($k)) {
						$v = $this->getAutoIncrementValue();
					}
					if (preg_match("/^__MySqlFunc__/", $v)) {  // for value using MySQL function.
						$value = preg_replace('/^__MySqlFunc__/', '', $v);
					} elseif ($vars[$k]['data_type'] == XOBJ_DTYPE_INT) {
						if (!is_null($v)) {
							$v = intval($v);
							$v = ($v) ? $v : 0;
							$valueList .= $delim . $v;
						} else {
							$valueList .= $delim . 'null';
						}
					} elseif ($vars[$k]['data_type'] == XOBJ_DTYPE_FLOAT) {
						if (!is_null($v)) {
							$v = (float)($v);
							$v = ($v) ? $v : 0;
							$valueList .= $delim . $v;
						} else {
							$valueList .= $delim . 'null';
						}
					} else {
						if (!is_null($v)) {
							$valueList .= $delim . $this->db->quoteString($v);
						} else {
							$valueList .= $delim . $this->db->quoteString('');;
						}
					}
					$cacheRow[$k] = $v;
					$delim = ", ";
				}
				$fieldList .= ")";
				$valueList .= ")";
				$sql = sprintf("INSERT INTO %s %s VALUES %s", $this->tableName,$fieldList,$valueList);
			} else {
				$setList = "";
				$setDelim = "";
				$whereList = "";
				$whereDelim = "";
				foreach ($record->cleanVars as $k => $v) {
					if ($vars[$k]['var_class'] != XOBJ_VCLASS_TFIELD) {
						continue;
					}
					if (preg_match("/^__MySqlFunc__/", $v)) {  // for value using MySQL function.
						$value = preg_replace('/^__MySqlFunc__/', '', $v);
					} elseif ($vars[$k]['data_type'] == XOBJ_DTYPE_INT) {
						$v = intval($v);
						$value = ($v) ? $v : 0;
					} elseif ($vars[$k]['data_type'] == XOBJ_DTYPE_FLOAT) {
						$v = (float)($v);
						$value = ($v) ? $v : 0;
					} else {
						$value = $this->db->quoteString($v);
					}

					if ($record->isKey($k)) {
						$whereList .= $whereDelim . "`$k` = ". $value;
						$whereDelim = " AND ";
					} else {
						if ($updateOnlyChanged && !$vars[$k]['changed']) {
							continue;
						}
						$setList .= $setDelim . "`$k` = ". $value . " ";
						$setDelim = ", ";
					}
					$cacheRow[$k] = $v;
				}
				if (!$setList) {
					$record->resetChenged();
					return true;
				}
				$sql = sprintf("UPDATE %s SET %s WHERE %s", $this->tableName, $setList, $whereList);
			}
			if (!$result =& $this->query($sql, $force)) {
				return false;
			}
			if ($record->isNew()) {
				$idField=$record->getAutoIncrementField();
				$idValue=$this->db->getInsertId();
				$record->assignVar($idField,$idValue);
				$cacheRow[$idField] = $idValue;
			}
			if (!$updateOnlyChanged) {
				$GLOBALS['_xoopsTableCache']->set($this->tableName, $record->cacheKey() ,$cacheRow, $this->cacheLimit);
			} else {
				$GLOBALS['_xoopsTableCache']->reset($this->tableName, $record->cacheKey());
				$this->_fullCached = false;
			}
			$record->resetChenged();
			return true;
		}

		/**
		 * �쥳���ɤκ��
		 *
	     * @param	object  &$record  {@link XoopsTableObject} object
	     * @param	bool	$force		POST�᥽�åɰʳ��Ƕ�)�����������ture
	     *
	     * @return	bool    .��λ��� TRUE
		 */
		function delete(&$record,$force=false)
		{
			$GLOBALS['_xoopsTableCache']->reset($this->tableName, $record->cacheKey());
			return parent::delete($record,$force);
		}

		/**
		 * �ơ��֥�ξ�︡��ˤ��ʣ��쥳���ɼ���
		 *
		 * @param	object	$criteria 	{@link XoopsTableObject} ������
		 * @param	bool $id_as_key		�ץ饤�ޥ꡼�������������Υ����ˤ������true
		 *
		 * @return	mixed Array			�����̥쥳���ɤ�����
		 */
		function &getObjects($criteria = null, $id_as_key = false, $fieldlist="", $distinct = false, $joindef = false)
		{
			$records = array();
			//���ΤȤ���ϡ����˸��ꤵ�줿���Ǥ�������å����Ȥ��ʤ�
			if (($this->useFullCache) && ($this->_fullCached) && (empty($criteria))&& (!$fieldlist) && (!$distinct) && (!$joindef)) {
				foreach ($GLOBALS['_xoopsTableCache']->getFull($this->tableName) as $myrow) {
					$record =& $this->create(false);
					$record->assignVars($myrow);
					if (!$id_as_key) {
						$records[] =& $record;
					} else {
						$ids = $record->getKeyFields();
						$r =& $records;
						$count_ids = count($ids);
						for ($i=0; $i<$count_ids; $i++) {
							if ($i == $count_ids-1) {
								$r[$myrow[$ids[$i]]] =& $record;
							} else {
								$r[$myrow[$ids[$i]]] = array();
								$r =& $r[$myrow[$ids[$i]]];
							}
						}
					}
					unset($record);
				}
				return $records;
			}

			if ($result =& $this->open($criteria, $fieldlist, $distinct, $joindef)) {
				if (($this->useFullCache) && (empty($criteria)) && (!$fieldlist) && (!$distinct) && (!$joindef)) {
					$this->_fullCached = true;
				}
				while ($myrow = $this->db->fetchArray($result)) {
					$record =& $this->create(false);
					$record->assignVars($myrow);
					if (!$id_as_key) {
						$records[] =& $record;
					} else {
						$ids = $record->getKeyFields();
						$r =& $records;
						$count_ids = count($ids);
						for ($i=0; $i<$count_ids; $i++) {
							if ($i == $count_ids-1) {
								$r[$myrow[$ids[$i]]] =& $record;
							} else {
								if (!isset($r[$myrow[$ids[$i]]])) {
									$r[$myrow[$ids[$i]]] = array();
								}
								$r =& $r[$myrow[$ids[$i]]];
							}
						}
					}
					if (!$fieldlist) {
						$GLOBALS['_xoopsTableCache']->set($this->tableName, $record->cacheKey(), $myrow, $this->cacheLimit);
					}
					unset($record);
				}
				$this->db->freeRecordSet($result);
			}
			return $records;
		}

		function &getNext(&$resultSet, $setCache=true)
		{
			if ($myrow = $this->db->fetchArray($resultSet)) {
				$record =& $this->create(false);
				$record->assignVars($myrow);
				if ($setCache) {
					$GLOBALS['_xoopsTableCache']->set($this->tableName, $record->cacheKey(), $myrow, $this->cacheLimit);				}
				return $record;
			} else {
				$result = false;
				return $result;
			}
		}

		/**
		 * �ơ��֥�ξ�︡��ˤ��ʣ��쥳���ɰ�繹��(�оݥե�����ɤϰ�ĤΤ�)
		 *
		 * @param	string	$fieldname 	�����ե������̾
		 * @param	mixed	$fieldvalue	������
		 * @param	object	$criteria 	{@link XoopsTableObject} ������
	     * @param	bool	$force		POST�᥽�åɰʳ��Ƕ�)�����������ture
		 *
		 * @return	mixed Array			�����̥쥳���ɤ�����
		 */
	    function updateAll($fieldname, $fieldvalue, $criteria = null, $force=false)
	    {
	        $GLOBALS['_xoopsTableCache']->clear($this->tableName);
			$this->_fullCached = false;
			return parent::updateAll($fieldname, $fieldvalue, $criteria, $force);
	    }

		/**
		 * �ơ��֥�ξ�︡��ˤ��ʣ��쥳���ɺ��
		 *
		 * @param	object	$criteria 	{@link XoopsTableObject} ������
	     * @param	bool	$force		POST�᥽�åɰʳ��Ƕ�)�����������ture
	     *
	     * @return	bool    .��λ��� TRUE
		 */
	    function deleteAll($criteria = null, $force=false)
	    {
	        $GLOBALS['_xoopsTableCache']->clear($this->tableName);
			$this->_fullCached = false;
			return parent::deleteAll($record, $force);
	    }

		function getAutoIncrementValue()
		{
			return $this->db->genId(get_class($this).'_id_seq');
		}
	}

	class XoopsTableCache
	{
		var $cache;

		function set($table, $key, $row, $limit=0) {
			$this->cache[$table][$key] = $row;
			$cache_size = count($this->cache[$table]);
			if (($limit != 0) && $cache_size >$limit) {
				array_splice($this->cache[$table],1, $cache_size-$limit);
			}
		}
		function reset($table, $key) {
			unset($this->cache[$table][$key]);
		}
		function exists($table, $key) {
			return (!empty($this->cache[$table][$key]));
		}
		function &get($table,$key) {
			return $this->cache[$table][$key];
		}
		function &getFull($table) {
			return $this->cache[$table];
		}
		function clear($table) {
			$this->cache[$table] = array();
		}
	}

	class XoopsJoinCriteria
	{
		var $_table_name;
		var $_main_field;
		var $_sub_field;
		var $_join_type;
		var $_next_join;
		var $_table_alias; // thanks towdash

		function XoopsJoinCriteria($table_name, $main_field, $sub_field, $join_type='LEFT', $table_alias="")
		{
			$this->_table_name = $table_name;
			$this->_main_field = $main_field;
			$this->_sub_field = $sub_field;
			$this->_join_type = $join_type;
			$this->_next_join = false;
			$this->_table_alias = $table_alias;
		}

		function cascade(&$joinCriteria) {
			$this->_next_join =& $joinCriteria;
		}

		function render($main_table)
		{
			if($this->_table_alias == ""){
				$table_alias = $this->_table_name;
				$alias_def = "";
			} else {
				$table_alias = $this->_table_alias;
				$alias_def = " AS ".$table_alias;
			}
			$join_str = " ".$this->_join_type." JOIN ".$this->_table_name . $alias_def." ON ".$main_table.".".$this->_main_field."=".$table_alias.".".$this->_sub_field." ";
			if ($this->_next_join) {
				$join_str .= $this->_next_join->render($table_alias);
			}
			return $join_str;
		}

		function getMainAlias() {
			return $this->_main_alias;
		}

		/**
		 * Return table alias(if exist) or table name
		 * @return string tableName
		 */
		function getTableName() {
			if($this->_table_alias == ""){
				return $this->_table_name;
			} else {
				return $this->_table_alias;
			}
		}
	}

	$GLOBALS['_xoopsTableCache'] = new XoopsTableCache;
}

if( ! class_exists( 'XoopsTableObjectList' ) ) {
	class XoopsTableObjectList {
		var $_elements;

//		function addElement($name, $caption, $width, $ext='') {
		function addElement($name, $caption='', $width=0, $ext='') {
			$this->_elements[$name]['caption'] = $caption;
			$this->_elements[$name]['width'] = $width;
			$this->_elements[$name]['ext'] = $ext;
		}

		function &getListItems(&$object) {
			$items = array();
			foreach($this->_elements as $key=>$value) {
				$extItemMethod = 'extraItem_'.$key;
				$formatMethod = 'formatItem_'.$key;
				$item = array();
				$item['name'] = $key;
				$item['link'] ='';
				$item['linktitle'] ='';
				if(method_exists($this, $extItemMethod)) {
					$item['align'] = 'left';
					$item = array_merge($item, $this->$extItemMethod($object,$value));
				} else if(method_exists($this, $formatMethod)) {
					$item['value'] = $this->$formatMethod($object->getVar($key));
					$item['align'] = 'left';
////@ ͽ���Ѱդ����Ѵ�method���ñ�˻��ѤǤ���褦�ˤ���
				} elseif (isset($value['ext']['method'])) {
					$formatMethod = $value['ext']['method'];
					if(method_exists($this, $formatMethod)) {
						$item['value'] = $this->$formatMethod($object->getVar($key), $value['ext']);
					}
					if (!isset($item['value']) || $item['value'] === false) {
						$item['value'] = 'Error';
					}
					$item['align'] = 'left';
////@ ���ɽ��sub_table�����ͤ���$�����ʤ�TableObject��_extra_vars�򻲾ȤǤ���褦�ˤ���
				} elseif (isset($value['ext']['extraVar'])) {
					if ($value['ext']['extraVar'] == '__ExtraVar__') {
						$item['value'] = $object->getExtraVar($key);
					} else {
						$item['value'] = $value['ext']['extraVar'];
					}
					$item['align'] = 'left';
////@ end add
				} else {
					$item['value'] = $object->getVar($key);
					switch ($object->vars[$key]['data_type']) {
						case XOBJ_DTYPE_INT:
						case XOBJ_DTYPE_FLOAT:
							$item['align'] = 'right';
							break;
						default:
							$item['align'] = 'left';
							break;
					}
				}
////@ �桼�������ǡ����ξ����Ф�Ǥ���褦�ˤ���
				$item['style'] = '';
				if (isset($value['ext']['filter']) && ($value['ext']['filter']===true)) {
					if (isset($this->_filterCondition[$key])) {
						$item['style'] = 'style="color:#FF00FF"';
					} else {
						$filterValue = isset($value['ext']['extraVar']) ? $item['value'] : $object->getVar($key);
						$whereStr = '';
						foreach ($this->_filterCondition as $k=>$v) {
							$whereStr .= '&amp;'.htmlspecialchars($k.'='.$v,ENT_QUOTES);
						}
						$item['link'] = $this->getLinkUrl($value['ext']).'?op=list&amp;'.$key.'='.$filterValue.$whereStr;
						$item['linktitle'] = 'List only this '.$value['caption'];
					}
////@ �ꥹ�Ⱦ��Ǥ�դ�item�˥�󥯤��դ�����褦�ˤ���
				} elseif (isset($value['ext']['link'])) {
					if (preg_match('/^__(Edit|Delete|View)__$/', $value['ext']['link'], $matches)) {
						$linkUrl = xoops_getenv('PHP_SELF').'?op='.strtolower($matches[1]).'&amp;';
					} else {
						$linkUrl = $value['ext']['link'];
					}
					$whereStr = $delim = '';
					foreach ($object->getKeyFields() as $objectKey) {
						$whereStr .= $delim.$objectKey.'='.$object->getVar($objectKey);
						$delim = '&amp;';
					}
					$item['link'] = $linkUrl.$whereStr;
				}
////@ �ƥ�ץ졼�Ȥ�ͳ�ʥǥ�����Ǻ�.�Ǥ���褦��item����˥�������
//				$items[] = $item;
				$items[$key] = $item;
////@ end change
			}
			return $items;
		}

		function &getListHeaders($sort='', $order='') {
			$header = array();
			foreach($this->_elements as $key=>$value) {
				$head = array();
				$head['name'] = $key;
				if (isset($value['ext']['sort'])&&($value['ext']['sort']===true)) {
					$head['link'] = xoops_getenv('PHP_SELF').'?sort='.$key;
					if (($sort==$key)&&(strtolower($order)=='asc')) {
						$head['link'] .= '&amp;order=desc';
						$head['linktitle'] = 'Descending Sort';
						$head['style']='style="color:#00FF00"';
					} else if (($sort==$key)&&(strtolower($order)=='desc')) {
						$head['link'] .= '&amp;order=asc';
						$head['linktitle'] = 'Ascending Sort';
						$head['style']='style="color:#FFFF00"';
					} else {
						$head['link'] .= '&amp;order=asc';
						$head['linktitle'] = 'Ascending Sort';
						$head['style']='style="color:#DDFFDD"';
					}
				} else {
					$head['link'] ='';
				}
				$head['caption'] = $value['caption'];
				$head['width'] = $value['width'];
				$header[] = $head;
			}
			return $header;
		}

		function inkey($string) {
			return array_key_exists($string, $this->_elements);
		}

		function showList(&$objects, &$tpl, $name, $sort='', $order='') {
			$headers = $this->getListHeaders($sort, $order);
			$records = array();//@
			foreach ($objects as $object) {
				$rec['items'] =& $this->getListItems($object);
				$records[] = $rec;
			}
		    $tpl->assign('title',$name);
		    $tpl->assign('headers',$headers);
		    $tpl->assign('records',$records);
		}

		// Special List Item '__SimpleEditLink__'
		function extraItem___SimpleEditLink__(&$object,$element) {
////@		// change to use _getSimpleLinkItem method
			return $this->_getSimpleLinkItem('edit', $object, $element);
			$objectKey = $object->getKeyFields();
			$objectKey = $objectKey[0];
			$key = $object->getVar($objectKey);
			$item['link'] = xoops_getenv('PHP_SELF').'?op=edit&amp;'.$objectKey.'='.$key;
			$item['linktitle'] = 'Edit this record';
			$item['value'] = $element['ext']['caption'];
			$item['align'] = 'center';
			return $item;
		}

		// Special List Item '__SimpleDeleteLink__'
		function extraItem___SimpleDeleteLink__(&$object,$element) {
////@		// change to use _getSimpleLinkItem method
			return $this->_getSimpleLinkItem('delete', $object, $element);
			$objectKey = $object->getKeyFields();
			$objectKey = $objectKey[0];
			$key = $object->getVar($objectKey);
			$item['link'] = xoops_getenv('PHP_SELF').'?op=delete&amp;'.$objectKey.'='.$key;
			$item['linktitle'] = 'Delete this record';
			$item['value'] = $element['ext']['caption'];
			$item['align'] = 'center';
			return $item;
		}

		/**
		 * Condition of filtering.
		 * @var array $_filterCondition
		 * @access private
		 */
		var $_filterCondition = array();

		/**
		 * Set condition of filtering.
		 * @param array $filter
		 */
		function setFilterCondition($filter) {
			$this->_filterCondition = $filter;
		}

		/**
		 * Get elements of list.
		 * @return array
		 */
		function &getElements() {
			return $this->_elements;
		}

		/**
		 * return Special List Item
		 * (enable access control, multiple keys, using global constant for default caption)
		 *
		 * @param string $op ['edit'|'delete']
		 * @param object $object reference to {@link XoopsTableobject}
		 * @param array $element
		 * @return array $item array of html element
		 */
		function _getSimpleLinkItem($op, &$object, $element) {
			if (!isset($element['ext']['uid']) || ($element['ext']['uid'] > 0 && $object->getOwner() == $element['ext']['uid'])) {
				$whereStr = '';
				foreach ($object->getKeyFields() as $objectKey) {
					$whereStr .= '&amp;'.$objectKey.'='.$object->getVar($objectKey);
				}
				$item['link'] = $this->getLinkUrl($element['ext']).'?op='.$op.$whereStr;
				$item['linktitle'] = ucfirst(strtolower($op)).' this record';
				$item['value'] = isset($element['ext']['caption']) ? $element['ext']['caption'] : defined('_'.strtoupper($op)) ? constant('_'.strtoupper($op)) : $op;
			} else {
				$item['value'] = '';
			}
			$item['align'] = 'center';
			return $item;
		}

		/**
		 *
		 */
		function getLinkUrl($ext) {
			return isset($ext['link']) ? $ext['link'] : xoops_getenv('PHP_SELF');
		}

		/**
		 * Special List Item '__SimpleViewLink__'
		 * @param object $object reference to {@link XoopsTableObject}
		 * @param array $element
		 * @return array
		 */
		function extraItem___SimpleViewLink__(&$object,$element) {
			return $this->_getSimpleLinkItem('view', $object, $element);
		}

		/**
		 * Special List Item '__SimpleCount__'
		 * @param object $object reference to {@link XoopsTableObject}
		 * @param array $element
		 * @return array
		 */
		function extraItem___SimpleCount__(&$object,$element) {
			$item['value'] = $object->getExtraVar('__SimpleCount__');
			$item['align'] = 'right';
			return $item;
		}

		/**
		 * return fieldlist.
		 * @param string $main_table
		 * @return string
		 */
		function getFieldlist($objectKeys, $main_table = "", $sub_table = "") {
			$fieldlist = $delim = '';
			foreach ($objectKeys as $key) {
				$fieldlist .= $delim.$main_table.$key;
				$delim = ',';
			}
			foreach ($this->_elements as $key=>$value) {
				if (preg_match('/^__Simple(Edit|Delete|View)Link__$/', $key) || in_array($key, $objectKeys)) {
					continue;
				}
				if ($key == '__SimpleCount__') {
					$fieldlist .= $delim.'count(*) as __SimpleCount__';
				} elseif (!isset($value['ext']['extraVar'])) {
					$fieldlist .= $delim.$main_table.$key;
				} elseif ($value['ext']['extraVar'] == '__ExtraVar__') {
					if (preg_match('/^(count|sum|avg|min|max)_(\w+)_$/i', $key, $matches)) {
						$fieldlist .= $delim.$matches[1].'('.$matches[2].') as '.$key;
					} else {
						$fieldlist .= $delim.$sub_table.$key;
					}
				} else {
					//case $ext['extraVar']='xxxx', no column in database
					continue;
				}
				$delim = ',';
			}
			return $fieldlist;
		}

		/**
		 * format method for timestamp item
		 * @param mixed $value
		 * @param array $ext
		 * @return string
		 */
		function time2str($value, $ext) {
			$format = isset($ext['format']) ? $ext['format'] : 'l';
			return formatTimestamp($value, $format);
		}

		/**
		 * format method for using master table
		 * @param mixed $value
		 * @param array $ext
		 * @return string, false on fail
		 */
		function id2name($value, $ext) {
			$ret = false;
			if ($handler =& xoops_getmodulehandler($ext['class'])) {
				if (method_exists($handler, 'getName')) {
					$ret = $handler->getName($value);
				}
			}
			return $ret;
		}

		/**
		 * format method for uid item
		 * @param mixed $value uid is expected
		 * @param array $ext
		 * @return string, false on fail
		 */
		function uid2name($value, $ext) {
			$ret = false;
			if ($value == '0') {
				$ret = $GLOBALS['xoopsConfig']['anonymous'];
			} else {
				$member_handler =& xoops_gethandler('member');
				$user =& $member_handler->getUser($value);
				if ($user) {
					$ret = $user->getVar('uname');
				}
			}
			return $ret;
		}

		/**
		 * format method for simple array item
		 * @param mixed $value
		 * @param array $ext
		 * @return string, null on fail
		 */
		function key2val($value, $ext) {
			return $ext['var_arr'][$value];
		}
	}

	function intCriteriaVal($value) {
		return array('value'=>$value, 'type'=>XOBJ_DTYPE_INT, 0);
	}
	function strCriteriaVal($value) {
		return array('value'=>$value, 'type'=>XOBJ_DTYPE_TXTBOX, 0);
	}
	function floatCriteriaVal($value) {
		return array('value'=>$value, 'type'=>XOBJ_DTYPE_FLOAT, 0);
	}
}

if( ! class_exists( 'XoopsTableObjectForm' ) ) {
	class XoopsTableObjectForm {
		var $_elements;
		var $_name;
		var $_caption;
		var $_action;
		var $_token = 0;

		function XoopsTableObjectForm($caption='', $name='', $action='', $token=0) {
			include_once XOOPS_ROOT_PATH.'/class/xoopsformloader.php';
			$this->_caption = $caption;
			$this->_name = $name;
			$this->_action = $action;
			$this->_token = $token;
			$this->_elements = array();
		}
		function setCaption($caption) {
			$this->_caption = $caption;
		}

		/**
		 * @param string $name
		 */
		function setName($name) {
			$this->_name = $name;
		}

		/**
		 * @param string $action
		 */
		function setAction($action) {
			$this->_action = $action;
		}

		/**
		 * @param int $token
		 */
		function setToken($token) {
			$this->_token = $token;
		}

//		function addElement($name, &$formElement) {
//			$this->_elements[$name] =& $formElement;

		/**
		 * Add an element to the form or container of elements.
		 * (add prefix to element's name/id if possible)
		 *
		 * @param string $name
		 * @param mixed $formElement object {@link XoopsFormElement} OR string
		 * @param string[optinal] $caption
		 * @param array[optinal] $ext array('container'=>string, 'required'=>bool)
		 */
		function addElement($name, $formElement, $caption="", $ext="") {
			if (is_a($formElement, 'XoopsFormElement')) {
				$ele_name = $formElement->getName(false);
				if ($name == '' && isset($ele_name)) {
					$name = $ele_name;
				}
				if ($this->_prefix != '' && ($ele_name)) {
					$formElement->setName($this->_prefix.$ele_name);
				}
			} else {
				if (!class_exists('XoopsFormElement')) {
					include_once XOOPS_ROOT_PATH.'/class/xoopsformloader.php';
				}
				if (!is_array($ext)) {
					$value = $ext;
				} else {
					$value = isset($ext['value']) ? $ext['value'] : "";
				}
				switch (strtolower($formElement)) {
				case 'hidden':
					$formElement =& new XoopsFormHidden($this->_prefix.$name, $value);
					break;
				case 'text':
				case 'textbox':
					$size = !empty($ext['size']) ? $ext['size'] : 20;
					$maxlength = isset($ext['maxlength']) ? $ext['maxlength'] : $size;
					$formElement =& new XoopsFormText($caption, $this->_prefix.$name, $size, $maxlength, $value);
					break;
				case 'textarea':
					$rows = !empty($ext['rows']) ? $ext['rows'] : 5;
					$cols = !empty($ext['cols']) ? $ext['cols'] : 50;
					$formElement =& new XoopsFormTextArea($caption, $this->_prefix.$name, $value, $rows, $cols);
					break;
				case 'dhtmltextarea':
					$rows = !empty($ext['rows']) ? $ext['rows'] : 5;
					$cols = !empty($ext['cols']) ? $ext['cols'] : 50;
					$hiddentext = isset($ext['hiddentext']) ? $ext['hiddentext'] : 'xoopsHiddenText';
					$formElement =& new XoopsFormDhtmlTextArea($caption, $this->_prefix.$name, $value, $rows, $cols, $hiddentext);
					break;
				case 'select_multi':
					$multiple = true;
				case 'select':
					$value = is_array($value) ? $value : null;
					$size = !empty($ext['size']) ? $ext['size'] : 1;
					$multiple = isset($multiple) ? $multiple : !empty($ext['multiple']) ? true : false;
					$formElement =& new XoopsFormSelect($caption, $this->_prefix.$name, $value, $size, $multiple);

					if ( !empty($ext['options']) && is_array($ext['options']) ) {
						$formElement->addOptionArray($ext['options']);
					}
					break;
				case 'radio':
					$value = ($value != '') ? $value : null;
					$formElement =& new XoopsFormRadio($caption, $this->_prefix.$name, $value);
					if ( !empty($ext['options']) && is_array($ext['options']) ) {
						$formElement->addOptionArray($ext['options']);
					}
					break;
				case 'radioyn':
				case 'yesno':
					$value = ($value == 1 || $value == 0) ? $value : null;
					$yes = isset($ext['yes']) ? $ext['yes'] : _YES;
					$no = isset($ext['no']) ? $ext['no'] : _NO;
					$formElement =& new XoopsFormRadioYN($caption, $this->_prefix.$name, $value, $yes, $no);
					break;
				case 'checkbox':
					$value = is_array($value) ? $value : null;
					$formElement =& new XoopsFormRadioYN($caption, $this->_prefix.$name, $value);
					break;
				case 'elementtray':
					$delimeter = isset($ext['delimeter']) ? $ext['delimeter'] : '&nbsp;';
					$formElement =& new XoopsFormElementTray($caption, $delimeter, $name);
					break;
				default:
					$formElement =& new XoopsFormLabel("Sorry form element &quot;$formElement&quot; is not supported.");
					break;
				}
			}
			if (isset($ext['description'])) {
				$formElement->setDescription($ext['description']);
			}
			if (!isset($this->_elements)) {
				$this->_elements = array();
			}
			if (empty($ext['container'])) {
				$this->_elements[$name] =& $formElement;
			} else {
				if (isset($this->_elements[$ext['container']]) && $this->_elements[$ext['container']]->isContainer()) {
					$required = empty($ext['required']) ? false : true ;
					$this->_formElements[$ext['container']]->addElement($formElement, $required);
				} else {
					$this->_elements[$name] =& new XoopsFormLabel("You have mistaken in the method of construction of ".$name);
				}
			}
			unset($formElement);
		}

		function addOptionArray($name,$options) {
			if (method_exists($this->_elements[$name], 'addOptionArray')) {
				$this->_elements[$name]->addOptionArray($options);
			}
		}

		function &buildEditForm(&$object) {
			if (!file_exists(XOOPS_ROOT_PATH.'/class/xoopsform/formtoken.php')) {
				$this->_token=0;
			}
////@ object�ξ��֤ʤɤˤ�ä�ɽ�����ѹ���ǽ�ˤ���
			$this->prepare($object);

			$formEdit =& new XoopsThemeForm($this->_caption, $this->_name, $this->_action);
			foreach ($this->_elements as $key=>$formElement) {
////@		// change in order that initial value of a form definition be effective.
				if ($formElement->isContainer()) {
					foreach ($formElement->_elements as $k=>$ele) {
						if (array_key_exists($k, $object->vars) && (!$object->isNew() || !isset($ele->_value) || $object->vars[$k]['changed'])) {
							$formElement->_elements[$k]->setValue($object->getVar($k,'e'));
						}
					}
				} elseif (array_key_exists($key, $object->vars) && (!$object->isNew() || !isset($formElement->_value) || $object->vars[$key]['changed'])) {
					$formElement->setValue($object->getVar($key,'e'));
				}
			// end change
			// add in order that object options value be effective.
				if (method_exists($formElement,'getOptions') && !$formElement->getOptions() && ($object->vars[$key]['options'] != '')) {
					foreach(explode('|', $object->vars[$key]['options']) as $option) {
						$formElement->addOption($option);
					}
				}
			// end add
			// change for additional posted var
//				$formEdit->addElement($formElement,$object->vars[$key]['required']);
				$required = array_key_exists($key, $object->vars) ? $object->vars[$key]['required'] : false;
				$formEdit->addElement($formElement, $required);
////@		// end change
				unset($formElement);
			}
			if ($object->isNew()) {
				if ($this->_token) {
					$formEdit->addElement(new XoopsFormToken(XoopsMultiTokenHandler::quickCreate($this->_name.'_insert')));
				}
				$formEdit->addElement(new XoopsFormHidden('op','insert'));
			} else {
				if ($this->_token) {
					$formEdit->addElement(new XoopsFormToken(XoopsMultiTokenHandler::quickCreate($this->_name.'_save')));
				}
				$formEdit->addElement(new XoopsFormHidden('op','save'));
			}
////@	// change to add go-back button.
			if (isset($this->_goBackScript)) {
				$tray = new XoopsFormElementTray('&nbsp;', '&nbsp;', 'submit');
				$tray->addElement(new XoopsFormButton('', 'submit', 'OK', 'submit'));
				$backbutton = new XoopsFormButton('', 'cancel_back', _BACK, 'button');
				$backbutton->setExtra('onclick="javascript:'.$this->_goBackScript.';"');
				$tray->addElement($backbutton);
				$formEdit->addElement($tray);
			} else {
				$formEdit->addElement(new XoopsFormButton('', 'submit', 'OK', 'submit'));
			}
////@	// end change

			return $formEdit;
		}

		function render(&$object) {
			$formEdit =& $this->buildEditForm($object);
			$str = $formEdit->render();
			return $str;
		}

		function assign(&$object, &$tpl) {
			$formEdit =& $this->buildEditForm($object);
			$formEdit->assign($tpl);
		}


		function showForm(&$object, &$tpl, $errmsg='') {
			$form =& $this->buildEditForm($object);
		    $form->assign($tpl);
		    $tpl->assign('title',$this->_caption);
		    $tpl->assign('formhtml',$this->render($object));
		    $tpl->assign('errmsg',$errmsg);
		}

		/**
		 * prefix of form element's name/id.
		 * @var string
		 * @access private
		 */
		var $_prefix = '';

		/**
		 * script of go_back button.
		 * @var string
		 * @access private
		 */
		var $_goBackScript;

		/**
		 * Set prefix of form element.
		 * @param string $prefix
		 */
		function setPrefix($prefix) {
			if (substr($prefix, -1) == '_') {
				$this->_prefix = $prefix;
			} else {
				$this->_prefix = $prefix.'_';
			}
		}

		/**
		 * Return prefix of form element.
		 * @return string
		 */
		function getPrefix() {
			return $this->_prefix;
		}

		/**
		 * Set show go_back button.
		 * @param string $url never set this arg by request!
		 */
		function setGoBack($url = null) {
			if (isset($url)) {
				$this->_goBackScript = "location.href='".$url."'";
			} else {
				$this->_goBackScript = 'history.go(-1)';
			}
		}

		/**
		 * set option array from master table.
		 * @param string $element_name
		 * @param string $handler_name
		 * @param string[optional] $module_dir
		 */
		function setOptionSource($element_name, $handler_name, $module_dir = null) {
			if (isset($this->_elements[$element_name]) && method_exists($this->_elements[$element_name], 'addOptionArray')) {
				if (($handler =& xoops_getmodulehandler($handler_name, $module_dir, true)) && method_exists($handler, 'getSelectOptionArray')) {
					$this->_elements[$element_name]->addOptionArray($handler->getSelectOptionArray());
				} else {
					$this->_elements[$element_name]->addOption(0, "handler file &quot;$handler_name.php&quot; is not exist Or is not subclass of &quot;XoopsTableObjectHandler&quot;.");
				}
			} else {
				die("Error on setOptionSource(). Form element &quot;$element_name&quot; is not exist Or does not have options.");
			}
		}

		/**
		 * Add the element's description.
		 *
		 * @param string $name
		 * @param string $description
		 */
		function addDescription($name, $description) {
			$this->_elements[$name]->setDescription($description);
		}

		/**
		 * Costum preparation.
		 * @abstract
		 * @param XoopsTableObject $object reference to object {@link XoopsTableObject}
		 */
		function prepare(&$object) {
		}
	}
}
?>