<?php
/*
 * system/AbstructData.class.php
 * 
 * CopyRight(C) 2010 Shopformer Development Team. All Right Reserved
 * URL: http://sourceforge.jp/projects/shopformer/
 * 
 * 
 * Mail: m_nakashima@users.sourceforge.jp
 * Auther: Masanori Nakashima
 * Last Update: 2010-06-18
 */
require_once(dirname(__FILE__)
.DIRECTORY_SEPARATOR.'login'
.DIRECTORY_SEPARATOR.'AbstractUser.class.php');
require_once(dirname(dirname(__FILE__))
.DIRECTORY_SEPARATOR.'util'
.DIRECTORY_SEPARATOR.'CharUtility.class.php');
/**
 * formerシリーズのシステムで扱うデータアクセスオブジェクトの抽象クラスです。
 * 
 * 本抽象クラスを拡張してデータアクセスオブジェクトを実装し必要なメソッドのみオーバーライドして実装することで、
 * シリアル化してのファイルへの保存、リクエストパラメータをメンバに設定、CSV形式へのメンバ変数の出力、CSV形式データをメンバ変数へセットなどの基本的なメソッドを提供します。
 * 本クラスはPHP4系でも動作することを目的としてabstract宣言されておらず、メソッドもインターフェースとして定義していません。今後のバージョンでPHP4対応は破棄し、
 * 抽象クラスとして宣言する予定ですのでメソッド説明文でオーバーライドを促しているメソッドは必ずオーバーライドしてください。
 * 
 * @package system
 * @category Data_Access_Object
 * @version 1.2.00
 * @copyright Copyright(c)2011, Shopformer Development Team. <shopformer-dev@lists.sourceforge.jp>
 * @author Masanori Nakashima
 * @access public
 */
class system_AbstractData {
	/**
	 * POSTから値を設定するフィールド名配列
	 * @var array
	 * @access protected
	 */
	var $variablePostFieldNameArray	= array();
	/**
	 * 妥当性検査のエラーメッセージカラムハッシュ
	 * @var array
	 * @access protected
	 */
	var $columnErrorHash	= array();
	/**
	 * データ保存区分名を取得する抽象メソッドです
	 * 
	 * 本メソッドは必ずオーバーライドして実装してください。
	 * 
	 * 保存区分は本クラス実装インスタンスをシリアル化したデータをファイルにsaveメソッドで保存する際に、保存するルートディレクトリ名を指定するのに利用できます。
	 * 同系等のデータを分類して保存したい場合は実装してください。
	 * @return string
	 * @access public
	 */
	function getDataClassName(){
		return '';
	}
	/**
	 * データの保存区分名を設定する抽象メソッドです
	 * 
	 * 本メソッドは必ずオーバーライドして実装してください。
	 * 
	 * 保存区分は本クラス実装インスタンスをシリアル化したデータをファイルにsaveメソッドで保存する際に、保存するルートディレクトリ名を指定するのに利用できます。
	 * 同系等のデータを分類して保存したい場合は実装してください。
	 * @param string $dataClassName
	 * @access public
	 */
	function setDataClassName( $dataClassName ) {
	}
	/**
	 * データ固有のIDを取得する抽象メソッド
	 * 
	 * 本メソッドは必ずオーバーライドして実装してください。
	 * 
	 * 同系統データに対して固有の識別記号（ユニークキー）を文字列で戻すよう実装してください。saveメソッドで利用する際の保存パスのガイドとして利用され、
	 * この固有ＩＤを指定することでスムーズにファイルシステムから目的のデータをオブジェクトメンバとして復元することができます。オブジェクトデータの保存は本クラスのsaveメソッド、
	 * オブジェクトデータの読み込みはloadメソッドを利用してください。
	 * @return string データ固有ID文字列
	 * @access public
	 */
	function getUniqueId(){
		return false;
	}
	/**
	 * 本オブジェクトのデータをシステムアクセスユーザーが閲覧できるか確認します
	 * 
	 * 本メソッドは必ずオーバーライドして実装してください。
	 * 
	 * 本システムパッケージを利用して構築したウェブサイトにおいてアクセスユーザーを表す抽象クラス、system_login_AbstractUserを引数に取り、
	 * そのアクセスユーザーがこのデータを閲覧可能かをboolean型で戻します。
	 * @param spider_HttpRequest &$request spider_HttpRequestインスタンス参照
	 * @param system_login_AbstractUser $abstractUser system_login_AbstractUser実装クラスインスタンス
	 * @return boolean 閲覧可能ならtrue,それ以外はfalse
	 * @access public
	 */
	function canViewData( & $request, $abstractUser ) {
		return true;
	}
	/**
	 * 本オブジェクトのデータをシステムアクセスユーザーが編集できるか確認します
	 * 
	 * 本メソッドは必ずオーバーライドして実装してください。
	 * 
	 * 本システムパッケージを利用して構築したウェブサイトにおいてアクセスユーザーを表す抽象クラス、system_login_AbstractUserを引数に取り、
	 * そのアクセスユーザーがこのデータを編集可能かをboolean型で戻します。
	 * @param spider_HttpRequest &$request spider_HttpRequestインスタンス参照
	 * @param system_login_AbstractUser $abstractUser system_login_AbstractUser実装クラスインスタンス
	 * @return boolean 編集可能ならtrue,それ以外はfalse
	 * @access public
	 */
	function canEditData( & $request, $abstractUser ) {
		return true;
	}
	/**
	 * 本クラス実装インスタンスのメンバをシリアル化してファイルに保存します
	 * 
	 * データ保存区分と固有ＩＤの値に従って本オブジェクトのデータをシリアル化してファイルに保存します。
	 * 必要に応じてオーバーライドしてください。
	 * デフォルトの動作では、spider/dataフォルダ以下のクラスのパッケージ属性に応じたフォルダパスへ固有IDをファイル名として保存します。
	 * 固有IDが長い場合は第二引数で指定した分割文字数で分割してフォルダを作成してその階層化にファイルで保存することができます。
	 * 本システムが分散サーバー環境に設置されている場合、第三引数をtrueに指定することで全てのサーバーの同階層のファイルに保存することができます。
	 * @param spider_HttpRequest &$request spider_HttpRequestクラスのインスタンス
	 * @param int $explodeIdLength 0以下の場合分割しない
	 * @param boolean $saveSlaveServers trueならスレーブサーバのdataフォルダがマウントされている場合にスレーブサーバにも保存する
	 * @return boolean 保存に成功したらtrue, 失敗したらfalse
	 * @see load
	 * @see delete
	 * @access public
	 */
	function save( & $request, $explodeIdLength=0, $saveSlaveServers=false ) {
		// 相対フォルダパスを生成する
		$dirNameArray				= explode('_',get_class($this));
		// クラス名の部分は除去する
		array_pop( $dirNameArray );
		// データ分類名を追加する
		if( strlen( $this->getDataClassName() ) > 0 ) {
			array_push( $dirNameArray, $this->getDataClassName() );
		}
		// IDを分割する場合はファイル名とディレクトリ階層を変更する
		$saveFileName	= $this->getUniqueId();
		if( is_numeric( $explodeIdLength ) && $explodeIdLength > 0 ) {
			$pos	= 0;
			for( $pos=0; $pos<strlen($saveFileName)-$explodeIdLength; $pos=$pos+$explodeIdLength ) {
				$dirName	= substr( $saveFileName, $pos, $explodeIdLength );
				array_push( $dirNameArray, $dirName );
			}
			// 保存ファイル名は残りの文字列
			$saveFileName	= substr( $saveFileName, $pos );
		}
		// 各サーバーのデータフォルダに書き込む
		require_once(dirname(__FILE__).DIRECTORY_SEPARATOR.'DataFileWriter.class.php');
		$dataStrings	= serialize($this);
		$fileUri		= '/'.implode('/',$dirNameArray).'/'.$saveFileName;
		$fileWriter		= new system_DataFileWriter();
		if( $fileWriter->open( $request, $fileUri ) ) {
			// オープンできたらシリアル化して書き込み
			$fileWriter->write( $dataStrings );
			if( $fileWriter->close($request) ) {
				return true;
			}
		}
		return false;
	}
	/**
	 * データ保存区分と固有IDから保存データを本オブジェクトのメンバ値に読み込みます
	 * 
	 * saveメソッドと対をなす読み込みメソッドです。必要に応じてオーバーライドしてください。
	 * デフォルトの動作では、クラスのパッケージ属性に応じたフォルダパスへ固有IDをファイル名として
	 * unserializeして取得する。
	 * @param spider_HttpRequest &$request spider_HttpRequestクラスのインスタンス
	 * @param string $uniqueId 固有ID
	 * @param integer $explodeIdLength 0以下の場合分割しない
	 * @return boolean 読み込みに成功したらtrue, 失敗したらfalse
	 * @see save
	 * @see delete
	 * @access public
	 */
	function load( & $request, $uniqueId, $explodeIdLength=0 ) {
		// 相対フォルダパスを生成する
		$dirNameArray				= explode('_',get_class($this));
		// クラス名の部分は除去する
		array_pop( $dirNameArray );
		// データ分類名を追加する
		if( strlen( $this->getDataClassName() ) > 0 ) {
			array_push( $dirNameArray, $this->getDataClassName() );
		}
		// IDを分割する場合はファイル名とディレクトリ階層を変更する
		$saveFileName	= $uniqueId;
		if( is_numeric( $explodeIdLength ) && $explodeIdLength > 0 ) {
			$pos	= 0;
			for( $pos=0; $pos<strlen($saveFileName)-$explodeIdLength; $pos=$pos+$explodeIdLength ) {
				$dirName	= substr( $saveFileName, $pos, $explodeIdLength );
				array_push( $dirNameArray, $dirName );
			}
			// 保存ファイル名は残りの文字列
			$saveFileName	= substr( $saveFileName, $pos );
		}
		// 保存先ディレクトリ
		$targetDirPath	= DIR_PATH_DATA.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR,$dirNameArray);
		// 保存ファイル
		$targetFilePath	= $targetDirPath.DIRECTORY_SEPARATOR.$saveFileName;
		if( file_exists( $targetFilePath ) ) {
			$strings	= file_get_contents( $targetFilePath );
			$object		= unserialize( $strings );
			if( get_class($this) == get_class($object) ) {
				$var_array	= get_object_vars( $object );
				foreach( $var_array as $key=>$val ) {
					if( strlen($key) > 0 ) {
						$this->$key	= $val;
					}
				}
				return true;
			} else {
				return false;
			}
		}
		return false;
	}
	/**
	 * データ保存区分と固有IDから保存データします
	 * 
	 * 必要に応じてオーバーライドしてください
	 * 
	 * デフォルトの動作では、クラスのパッケージ属性に応じたフォルダパスへ固有IDをファイル名としてファイルを削除します
	 * @param spider_HttpRequest &$request spider_HttpRequestクラスのインスタンス
	 * @param string $uniqueId 固有ID
	 * @param integer $explodeIdLength 0以下の場合分割しない
	 * @param boolean $saveSlaveServers trueならスレーブサーバのdataフォルダがマウントされている場合にスレーブサーバも削除する
	 * @return boolean 削除に成功したらtrue, 失敗したらfalse
	 * @see save
	 * @see load
	 * @access public
	 */
	function delete( & $request, $uniqueId, $explodeIdLength=0, $deleteSlaveServers=false ) {
		// 相対フォルダパスを生成する
		$dirNameArray				= explode('_',get_class($this));
		// クラス名の部分は除去する
		array_pop( $dirNameArray );
		// データ分類名を追加する
		if( strlen( $this->getDataClassName() ) > 0 ) {
			array_push( $dirNameArray, $this->getDataClassName() );
		}
		// IDを分割する場合はファイル名とディレクトリ階層を変更する
		$saveFileName	= $this->getUniqueId();
		if( is_numeric( $explodeIdLength ) && $explodeIdLength > 0 ) {
			$pos	= 0;
			for( $pos=0; $pos<strlen($saveFileName)-$explodeIdLength; $pos=$pos+$explodeIdLength ) {
				$dirName	= substr( $saveFileName, $pos, $explodeIdLength );
				array_push( $dirNameArray, $dirName );
			}
			// 保存ファイル名は残りの文字列
			$saveFileName	= substr( $saveFileName, $pos );
		}
		// 分散サーバーのファイルも含めて削除する
		require_once(dirname(__FILE__).DIRECTORY_SEPARATOR.'DataFileWriter.class.php');
		$fileUri		= '/'.implode('/',$dirNameArray).'/'.$saveFileName;
		if( system_DataFileWriter::delete( $request, $fileUri ) ) {
			return true;
		}
		return false;
	}
	/**
	 * オブジェクトメンバ値を指定された順序のCSV行にして取得します
	 * @param hash $columnDefinition メンバ名をキー、順序番号を値とする連想配列
	 * @param boolean $quote カラム値をクォートするか指定します。デフォルトはtrueで省略可能です
	 * @return string メンバ値のカンマ区切り文字列
	 * @access public
	 */
	function getCsvString( $columnDefinition, $quote=true ) {
		$column_value_array	= array();
		foreach ( $columnDefinition as $key=>$order_number ) {
			$value	= $this->getColumnString( $key, $quote );
			$column_value_array[$order_number]	= $value;
		}
		// キーでソート
		ksort( $column_value_array );
		// カンマで結合
		$str	= implode(',',$column_value_array);
		return $str."\n";
	}
	/**
	 * CSVダウンロードの為のカラム取得メソッドです。
	 * メンバ名文字列を指定してメンバ値を取得します。
	 * @param string $key メンバ名
	 * @param boolean $quote カラム値をクォートするか指定します。デフォルトはtrueで省略可能です
	 * @param string $filter mb_convert_kanaで変換する文字列指定
	 * @return string メンバ値
	 * @link http://jp.php.net/manual/ja/function.mb-convert-kana.php
	 * @access protected
	 */
	function getColumnString( $key, $quote=true, $filter=null ) {
		$returnColumn	= $this->$key;
		$returnStrings	= '';
		if( is_array($returnColumn) ) {
			$returnStrings	= implode(',',$returnColumn);
		} else {
			$returnStrings	= $returnColumn;
		}
		if( strlen(trim($filter)) > 0 ) {
			$returnStrings	= mb_convert_kana( $returnStrings, $filter );
		}
		if( $quote ) {
			$returnStrings	= '"'.str_replace('"','""',$returnStrings).'"';
		}
		return $returnStrings;
	}
	/**
	 * 「メンバ変数名 => 値」の形式の連想配列でメンバ変数に値を設定します
	 * @param hash 「メンバ変数名 => 値」の形式の連想配列
	 * @access public
	 */
	function setFieldByHash( $hash ) {
		foreach( $hash as $fieldName => $value ) {
			$this->$fieldName	= $value;
		}
	}
	/**
	 * CSV形式のデータ行ラインからフィールドに値を設定します
	 * @param hash $orderDefinitionHash CSV行のカラム順序とメンバ名を紐づけた連想配列
	 * @param string $csvLine CSV行文字列
	 * @access public
	 */
	function setFieldByCsvLine( $orderDefinitionHash, $csvLine ) {
		$hash	= util_CharUtility::csv2Hash( $orderDefinitionHash, $csvLine );
		$this->setFieldByHash( $hash );
	}
	/**
	 * mb_convert_kanaを利用して全てのフィールド値を指定の変換ルールで変換します
	 * @param string $filter mb_convert_kanaで指定できる変換指定文字列
	 * @link http://jp.php.net/manual/ja/function.mb-convert-kana.php
	 * @access public
	 */
	function convertKanaFields( $filter ) {
		if( strlen(trim($filter)) > 0 ) {
			$var_array	= get_object_vars( $this );
			foreach( $var_array as $key=>$val ) {
				if( strlen($key) > 0 ) {
					if( !is_array($val) && !is_object($val) ) {
						$this->$key	= mb_convert_kana( $val, $filter );
					}
				}
			}
		}
	}
	//
	// 編集ロックに関するメソッド群
	//
	/**
	 * 本オブジェクトデータを他者が編集できないようにロックします。
	 * @param string $operatorId ロック操作の固有識別子
	 * @param int $lifeTime ロックをかける秒数を指定。指定しない場合は300秒
	 * @return boolean ロック取得に成功したらtrue、それ以外はfalse
	 * @see unlock
	 * @see isLock
	 * @see canLock
	 * @access public
	 */
	function lock( $operatorId, $lifeTime=300 ) {
		if( $filePath = $this->getLockFilePath() ) {
			$canLock	= false;
			if( file_exists( $filePath ) ) {
				// ロックファイルが存在するなら識別子を確認
				if( $str = @file_get_contents($filePath) ) {
					// ロックファイルの内容を取得出来たら
					list( $ownerStr, $nowLifeTime )	= explode("\r\n\r\n",$str);

					if( $ownerStr == $operatorId ) {
						// 識別子が同じならロックファイルのタイムスタンプを更新
						@touch( $filePath );
						return true;
					} else {
						// 識別子が異なる場合
						if( filemtime($filePath)+$nowLifeTime < time() ) {
							// ライフタイムを過ぎているなら取得可能
							$canLock	= true;
						}
					}
				} else {
					// ロックファイルの内容を取得できないならエラー
					return false;
				}
			} else {
				$canLock	= true;
			}
			if( !$canLock ) {
				return false;
			}
			// ロックファイルを作成する
			$lockContents	= $operatorId."\r\n\r\n".$lifeTime;
			$fp	= @fopen( $filePath, "w" );
			if( $fp ) {
				if( @flock( $fp, LOCK_EX ) ) {
					@fwrite( $fp, $lockContents );
					@flock( $fp, LOCK_UN );
					@fclose( $fp );
					@chmod( $filePath, SPIDER_PERMITTION_DATA_FILE );
					return true;
				} else {
					@fclose( $fp );
					@chmod( $filePath, SPIDER_PERMITTION_DATA_FILE );
					return false;
				} 
			} else {
				return false;
			}
		} else {
			return false;
		}
	}
	/**
	 * 本オブジェクトデータにかけられた編集ロックを解除します
	 * @param string $operatorId ロック操作の固有識別子
	 * @return boolean ロック解除に成功したらtrue
	 * @see lock
	 * @see isLock
	 * @see canLock
	 * @access public
	 */
	function unlock( $operatorId ) {
		if( $filePath = $this->getLockFilePath() ) {
			if( file_exists( $filePath ) ) {
				if( $str = @file_get_contents($filePath) ) {
					// ロックファイルの内容を取得出来たら
					list( $ownerStr, $nowLifeTime )	= explode("\r\n\r\n",$str);
					if( $ownerStr == $operatorId ) {
						// 識別子が同じならロックファイルを削除
						@unlink( $filePath );
						return true;
					} else {
						// 識別子が異なる場合
						if( filemtime($filePath)+$nowLifeTime < time() ) {
							// ライフタイムを過ぎているならロックファイルを削除
							@unlink( $filePath );
							return true;
						} else {
							// ライフタイムがあるなら自分のロックでないので解除できない
							return false;
						}
					}
				} else {
					return false;
				}
			} else {
				// ファイルがないならロックされていないのでtrueで返す
				return true;
			}
		} else {
			return false;
		}
	}
	/**
	 * 現在本オブジェクトデータに編集ロックがかかっているか確認します。
	 * @return boolean ロックがかかっているならtrue
	 * @see lock
	 * @see unlock
	 * @see canLock
	 * @access public
	 */
	function isLock() {
		if( $filePath = $this->getLockFilePath() ) {
			if( file_exists( $filePath ) ) {
				if( $str = @file_get_contents($filePath) ) {
					// ロックファイルの内容を取得出来たら
					list( $ownerStr, $nowLifeTime )	= explode("\r\n\r\n",$str);
					if( filemtime($filePath)+$nowLifeTime >= time() ) {
						// ライフタイム内ならロック
						return true;
					} else {
						return false;
					}
				} else {
					// パーミッションがないならロックをかけれないのでロック状態とする
					return true;
				}
			} else {
				// ファイルがないならロックされていない
				return false;
			}
		} else {
			// パーミッションがないならロックをかけれないのでロック状態とする
			return true;
		}
	}
	/**
	 * 指定オペレータIDで現在ロック取得が可能か確認します
	 * @param string $operatorId ロック操作の固有識別子
	 * @return boolean ロック取得可能ならtrue、それ以外はfalse
	 * @see lock
	 * @see unlock
	 * @see isLock
	 * @access public
	 */
	function canLock( $operatorId ) {
		if( $filePath = $this->getLockFilePath() ) {
			if( file_exists( $filePath ) ) {
				if( $str = @file_get_contents($filePath) ) {
					// ロックファイルの内容を取得出来たら
					list( $ownerStr, $nowLifeTime )	= explode("\r\n\r\n",$str);
					if( $ownerStr == $operatorId ) {
						// 識別子が同じならロック取得済みなのでtrue
						return true;
					} else {
						// 識別子が異なる場合
						if( filemtime($filePath)+$nowLifeTime < time() ) {
							// ライフタイムを過ぎているならロックファイルを削除してtrueで返す
							@unlink( $filePath );
							return true;
						} else {
							// ライフタイムがあるなら自分のロックでないのでロックできない
							return false;
						}
					}
				} else {
					// パーミッションがないならロックをかけれない
					return false;
				}
			} else {
				// ファイルがないならロックを取得可能
				return true;
			}
		} else {
			// パーミッションがないならロックをかけられない
			return false;
		}
	}
	/**
	 * ロックファイルのパスを取得します。
	 * ロックファイルの階層を作成するパーミッションがない場合はfalseを返します。
	 * 
	 * PHP4に併せている為指定していませんが将来的にこのメソッドはprotectedスコープに移行されます。
	 * 外部クラスからの呼び出しはおこなわないようにしてください。
	 * @return boolean ロックファイルパス/パーミッションがない場合はfalse
	 * @access protected
	 */
	function getLockFilePath() {
		$classNameElements	= explode('_',get_class($this));
		$filePath	= DIR_PATH_LOCK
			.DIRECTORY_SEPARATOR.array_shift($classNameElements);
		if(!file_exists($filePath)) {
			if( @mkdir($filePath,SPIDER_PERMITTION_DATA_FOLDER) ) {
				@chmod($filePath,SPIDER_PERMITTION_DATA_FOLDER);
			} else {
				return false;
			}
		}
		$filePath	= DIR_PATH_LOCK
			.DIRECTORY_SEPARATOR.array_shift($classNameElements);
		if(!file_exists($filePath)) {
			if( @mkdir($filePath,SPIDER_PERMITTION_DATA_FOLDER) ) {
				@chmod($filePath,SPIDER_PERMITTION_DATA_FOLDER);
			} else {
				return false;
			}
		}
		$filePath	.= DIRECTORY_SEPARATOR.'lock';
		if(!file_exists($filePath)) {
			if( @mkdir($filePath,SPIDER_PERMITTION_DATA_FOLDER) ) {
				@chmod($filePath,SPIDER_PERMITTION_DATA_FOLDER);
			} else {
				return false;
			}
		}
		$filePath	.= DIRECTORY_SEPARATOR.$this->getDataClassName();
		if(!file_exists($filePath)) {
			if( @mkdir($filePath,SPIDER_PERMITTION_DATA_FOLDER) ) {
				@chmod($filePath,SPIDER_PERMITTION_DATA_FOLDER);
			} else {
				return false;
			}
		}
		$filePath	.= DIRECTORY_SEPARATOR.$this->getUniqueId();
		return $filePath;
	}
	/**
	 * 現在本オブジェクトにロックがかかっている場合はロック識別子を返します。
	 * ロックがかかっていない場合はnullを返します。
	 * 
	 * PHP4に併せている為指定していませんが将来的にこのメソッドはprotectedスコープに移行されます。
	 * 外部クラスからの呼び出しはおこなわないようにしてください。
	 * @return mixed ロック識別子/null/false
	 * @access protected
	 */
	function getLockOperatorId() {
		if( $filePath = $this->getLockFilePath() ) {
			if( file_exists( $filePath ) ) {
				return @file_get_contents($filePath);
			} else {
				return null;
			}
		}
		return false;
	}
	/**
	 * カラムに対する妥当性検査エラーを登録します
	 * @param spider_HttpRequest &$request spider_HttpRequestインスタンス参照
	 * @param string columnName エラーメッセージを追加するメンバ名
	 * @param string $message エラーメッセージ
	 * @see hasColumnError
	 * @see clearColumnErrors
	 * @access public
	 */
	function addColumnError( & $request, $columnName, $message, $replaceHash=array() ){
		if(isset($GLOBALS['spider.messages'])
		&& is_array($GLOBALS['spider.messages'])
		&& isset($GLOBALS['spider.messages'][$message]) ){
			$message	= $GLOBALS['spider.messages'][$message];
			if(is_array($replaceHash)){
				foreach($replaceHash as $key => $val ){
					$message	= str_replace('{'.$key.'}',$val,$message);
				}
			}
		}
		if( !is_array($this->columnErrorHash[$columnName]) ) {
			$this->columnErrorHash[$columnName]	= array();
		}
		$request->addLocaledError($message,SPIDER_LOG_LEVEL_ERROR,$replaceHash);
		array_push($this->columnErrorHash[$columnName],$message);
	}
	/**
	 * 現在エラーがあるか確認します
	 * @return boolean 現在カラムに妥当性検査エラーが設定されているならtrue,それ以外はfalse
	 * @see addColumnError
	 * @see clearColumnErrors
	 * @access public
	 */
	function hasColumnError() {
		if( count($this->columnErrorHash) == 0 ) {
			return false;
		} else {
			foreach( $this->columnErrorHash as $colErrors ) {
				if( count($colErrors) > 0 ) {
					return true;
				}
			}
		}
		return false;
	}
	/**
	 * 妥当性検査エラーをクリアします
	 * @see addColumnError
	 * @see hasColumnError
	 * @access public
	 */
	function clearColumnErrors() {
		$this->columnErrorHash	= array();
	}
	/**
	 * フィールドデータの妥当性検査を実行します
	 * 
	 * 本メソッドは必ずオーバーライドして実装してください。
	 * @param spider_HttpRequest &$request spider_HttpRequestインスタンス参照
	 * @return boolean 妥当性検査に問題がある場合はカラムエラーを設定してfalseを戻してください
	 * @access public
	 */
	function validateFields( & $request ) {
		return true;
	}
	/**
	 * リクエストパラメータから入力値を取得してメンバに設定します
	 * @param spider_HttpRequest &$request spider_HttpRequestインスタンス参照
	 * @param string $namePrefix POSTやGETのキー名がメンバ名に指定のプレフィックスを追加する必要がある場合に指定
	 * @param int $arrayIndex リクエストパラメータが配列で渡されている場合に本オブジェクトにセットする順序番号
	 * @param string $convertKana mb_convert_kanaで指定できる変換指定文字列。デフォルトは'KVa'
	 * @param string リクエストパラメータの種別をpost/getで指定。デフォルトはpost
	 * @link http://jp.php.net/manual/ja/function.mb-convert-kana.php
	 * @access public
	 */
	function setRequestParams( & $request, $namePrefix='', $arrayIndex=0, $convertKana='KVa', $paramType='POST' ) {
		foreach( $this->variablePostFieldNameArray as $varName ) {
			$postName	= $namePrefix.$varName;
			if( preg_match('/^[gG][eE][tT]$/',$paramType) > 0 ) {
				if( isset($_GET[$postName]) ) {
					$postVal	= $_GET[$postName];
					if( is_array($postVal) && isset($postVal[$arrayIndex]) ) {
						$this->$varName	= mb_convert_kana(trim(stripslashes($postVal[$arrayIndex])),$convertKana);
					} else {
						$this->$varName	= mb_convert_kana(trim(stripslashes($postVal)),$convertKana);
					}
				}
			} else {
				if( isset($_POST[$postName]) ) {
					$postVal	= $_POST[$postName];
					if( is_array($postVal) && isset($postVal[$arrayIndex]) ) {
						$this->$varName	= mb_convert_kana(trim(stripslashes($postVal[$arrayIndex])),$convertKana);
					} else {
						$this->$varName	= mb_convert_kana(trim(stripslashes($postVal)),$convertKana);
					}
				}
			}
		}
	}
	/**
	 * オブジェクト比較して要素の違いを抽出します
	 * @param spider_HttpRequest &$request spider_HttpRequestインスタンス参照
	 * @param object $compareObject 比較対象オブジェクト
	 * @return array 違いのキーと値ハッシュ
	 */
	function getDifference( & $request, $compareObject ) {
		$defferenceHash	= array();
		$thisVars		= get_object_vars($this);
		$targetVars	= get_object_vars($compareObject);
		foreach( $thisVars as $key => $val ) {
			if( preg_match('/\\_org$/',$key) == 0 ){
				if( is_array($val) ) {
					// 配列の場合
					if( isset($targetVars[$key]) && is_array($targetVars[$key]) ){
						$defferenceHash[$key]	= array();
						foreach( $val as $k => $v ){
							if( is_scalar($v) ){
								// スカラーの場合そのまま比較
								if( isset($targetVars[$key]) && is_array($targetVars[$key])
								&& isset($targetVars[$key][$k]) ){
									if( $targetVars[$key][$k] == $v ){
									} else {
										$defferenceHash[$key][$k]	= $targetVars[$key][$k];
									}
								}
							} else if( is_object($v) && is_a($v,'system_AbstractData') ) {
								// オブジェクトの場合
								if( isset($targetVars[$key]) && is_array($targetVars[$key])
								&& isset($targetVars[$key][$k]) && is_object($targetVars[$key][$k]) ){
									$defferenceHash[$key][$k]	= $v->getDifference( $request, $targetVars[$key][$k] );
								}
							}
						}
					}
				} else if( is_scalar($val) ) {
					// スカラー値の場合そのまま比較
					if( isset($targetVars[$key]) ) {
						if( $targetVars[$key] == $val ){
						} else {
							$defferenceHash[$key]	= $targetVars[$key];
						}
					}
				} else if( is_object($val) && is_a($val,'system_AbstractData') ) {
					// オブジェクトの場合
					$array	= $val->getDifference( $request, $compareObject );
					if( is_array($array) && count($array) > 0 ) {
						$defferenceHash[$key]	= $val->getDifference( $request, $compareObject );
					}
				} else {
					// オブジェクト・配列・スカラー以外は比較しない
				}
			}
		}
		return $defferenceHash;
	}
	/**
	 * テキスト内のタグをオブジェクトメンバ値に書き換えたテキストを戻します
	 */
	function convertTextValues( & $request, $targetText, $prefix='' ){
		$thisVars		= get_object_vars($this);
		foreach( $thisVars as $key => $val ){
			$currentPrefix	= $prefix.'->'.$key;
			if(is_array($val)){
				$targetText	= system_AbstractData::convertValueArray($request,$targetText,$val,$currentPrefix);
			} else if(is_object($val)){
				if( is_a($val,'system_AbstractData') ){
					$targetText	= $val->convertTextValues( $request, $targetText, $currentPrefix );
				}
			} else if( is_null($val) || is_scalar($val)){
				$targetText = $this->convertValueScalar( $request, $targetText, $key, $val, $prefix );
			}
		}
		return $targetText;
	}
	/**
	 * 配列のタグを再帰的に変換する静的メソッド
	 */
	function convertValueArray( & $request, $targetText, $array ,$prefix='' ){
		foreach( $array as $key => $val ){
			$currentPrefix	= $prefix.'['.$key.']';
			if(is_array($val)){
				$targetText	= system_AbstractData::convertValueArray( $request, $targetText, $val ,$currentPrefix );
			} else if(is_object($val)){
				if( is_a($val,'system_AbstractData') ){
					$targetText	= $val->convertTextValues( $request, $targetText, $currentPrefix );
				}
			} else if(is_scalar($val)){
				$targetText	= str_replace('{'.$currentPrefix.'}',$val,$targetText);
			}
		}
		return $targetText;
	}
	/**
	 * タグのスカラー値を変換
	 */
	function convertValueScalar( & $request, $targetText, $key, $val, $prefix='' ){
		if( strlen($targetText) == 0 || strlen($key) == 0 ){
			return $targetText;
		}
		spider_Controller::loadClassDefinition('util_Mail');
		$currentPrefix	= $prefix.'->'.$key;
		$targetText	= util_Mail::_replaceScalarValue( $targetText, $currentPrefix, $val );
		$targetText	= util_Mail::_replaceScalarValue( $targetText, $prefix.'::'.$key, $val );
		return $targetText;
	}
}
?>