<?php
/*
 * file/Uploader.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.'DaoFileBin.class.php');
require_once(dirname(dirname(__FILE__))
.DIRECTORY_SEPARATOR.'util'
.DIRECTORY_SEPARATOR.'CharUtility.class.php');
require_once(dirname(dirname(__FILE__))
.DIRECTORY_SEPARATOR.'system'
.DIRECTORY_SEPARATOR.'DataFileWriter.class.php');
/**
 * ファイルアップロードに利用するDaoFileBinのコンテナオブジェクトクラスです
 */
class file_Uploader {
	/** アップロードフォームの要素名（デフォルト upfiles）	*/
	var $uploadFormName			= 'upfiles';
	/** 必須アップロードファイル数(デフォルト任意0個)	*/
	var $requireCount			= 0;
	/** ファイルサイズ最大値(デフォルト10MB)			*/
	var $maxFileSize			= 10485760;
	/** アップロード可能なファイル拡張子				*/
	var $extensions				= array('jpg','gif','png','swf');
	/** アップロードファイル情報を格納したDaoFileBinオブジェクトのハッシュ	*/
	var $fileBinObjectHash		= array();
	/** エラーなくアップロードされたかどうかのフラグ	*/
	var $canInsert				= false;
	
	/** 画像の場合の最大幅	*/
	var $imageMaxWidth			= 0;
	/** 画像の場合の最大高さ	*/
	var $imageMaxHeight			= 0;
	/** 画像サイズ調整時のフィルカラー ※nullならフィルをしない	*/
	var $imageFillColorR		= null;
	
	/**
	 * コンストラクタ
	 */
	function file_Uploader( $uploadFormName='upfiles', $requireCount=0, $maxFileSize=10485760, $extensions=array('jpg','gif','png','swf') ) {
		$this->uploadFormName	= $uploadFormName;
		$this->requireCount		= $requireCount;
		$this->maxFileSize		= $maxFileSize;
		if( is_array($extensions) ) {
			$this->extensions	= $extensions;
		} else {
			$this->extensions	= array('jpg','gif','png');
		}
	}
	/**
	 * 画像ファイルアップロードの際の最大解像度を設定します
	 */
	function setImageLimit( $width=320, $height=320, $fillColor=null ) {
		$this->imageMaxWidth	= $width;
		$this->imageMaxHeight	= $height;
		$this->imageFillColor	= $fillColor;
	}
	/**
	 * 実行中のHTTPリクエストでファイルアップロードがされたか確認します
	 * @return アップロードファイルが一つ以上指定されていればtrue
	 */
	function isUploaded() {
		if( isset($_FILES[$this->uploadFormName]) && is_array($_FILES[$this->uploadFormName]) ) {
			if( isset($_FILES[$this->uploadFormName]['name'] ) ) {
				if( is_array($_FILES[$this->uploadFormName]['name']) ) {
					if( count($_FILES[$this->uploadFormName]['name']) > 0 ) {
						$this->canInsert	= false;
						return true;
					}
				} else if( strlen(trim($_FILES[$this->uploadFormName]['name'])) > 0 ){
					return true;
				}
			}
		}
		$this->canInsert	= false;
		return false;
	}
	/**
	 * アップロードファイルを検証します
	 * ファイルがアップロードされていない場合は検証は行いません
	 */
	function validate( & $request ) {
		$this->canInsert	= false;
		// 一時ファイルがあるなら除去します
		$this->deleteOldTempFiles( $request );
		// アップロードファイルがないならreturn true
		$upfiles	= & $_FILES[$this->uploadFormName];
		if( !is_array($upfiles) || count($upfiles) == 0 ) {
			return true;
		}
		// アップロードファイルがあるなら確認します
		$counter	= 1;
		$fileCount	= 0;
		foreach( $upfiles['tmp_name'] as $key => $tempName ) {
			if ( defined('UPLOAD_ERR_INI_SIZE') && UPLOAD_ERR_INI_SIZE == $upfiles['error'][$key] ) {
				$request->addError($counter.'番目のファイルサイズがサーバーの設定値'.ini_get('upload_max_filesize').'を超えています。');
			} else if ( defined('UPLOAD_ERR_FORM_SIZE') && UPLOAD_ERR_FORM_SIZE == $upfiles['error'][$key] ) {
				$request->addError($counter.'番目のファイルサイズが設定値'.$_POST['MAX_FILE_SIZE'].'を超えています。');
			} else if ( defined('UPLOAD_ERR_PARTIAL') && UPLOAD_ERR_PARTIAL == $upfiles['error'][$key] ) {
				$request->addError($counter.'番目のファイルが壊れているかアップロード中に壊れました。');
			} else if ( defined('UPLOAD_ERR_NO_FILE') && UPLOAD_ERR_NO_FILE == $upfiles['error'][$key] ) {
				// アップロードなしはエラーにしない
			} else if ( defined('UPLOAD_ERR_NO_TMP_DIR') && UPLOAD_ERR_NO_TMP_DIR == $upfiles['error'][$key] ) {
				$request->addError($counter.'番目のファイルをサーバーの一時ファイルに保存できませんでした。');
			} else if ( defined('UPLOAD_ERR_OK') && UPLOAD_ERR_OK != $upfiles['error'][$key] ) {
				$request->addError($counter.'番目のファイルアップロードに失敗しました。');
			} else if ( is_uploaded_file($tempName) ) {
				$fileCount++;
				$fileName	= basename($upfiles['name'][$key]);
				// 許可された拡張子かチェック
				$isAvailableExtension	= false;
				$fileNameLower			= strtolower($fileName);
				foreach( $this->extensions as $extension ) {
					$extensionLower	= strtolower($extension);
					if( preg_match('/\\.'.$extensionLower.'$/',$fileNameLower) > 0 ) {
						$isAvailableExtension	= true;
						break;
					}
				}
				if( !$isAvailableExtension ) {
					$request->addError($counter.'番目のファイルは許可されていないファイル形式です。');
				}
			} else {
				$request->addError($counter.'番目のファイルアップロードが原因不明のエラーで失敗しました。');
			}
			$counter++;
		}
		// アップロードされたファイル数のチェック
		if( $fileCount < $this->requireCount ) {
			$request->addError('ファイルを'.$this->requireCount.'個以上アップロードしてください。');
		}
		// エラーがあったらfalseでリターン
		if( $request->isError() ) {
			return false;
		} else {
			return true;
		}
	}
	/**
	 * アップロードファイルをアプリケーション一時フォルダに移動して情報をDaoFileBinオブジェクトに格納し保持します
	 */
	function upload( & $request ) {
		$this->canInsert	= false;
		// 一時ファイルがあるなら除去します
		$this->deleteOldTempFiles( $request );
		// アップロードファイルがないならreturn true
		$upfiles	= & $_FILES[$this->uploadFormName];
		if( !is_array($upfiles) || count($upfiles) == 0 ) {
			return true;
		}
		// アップロード一時フォルダのルートパスの作成
		$localTmpDirPath		= DIR_PATH_TMP.DIRECTORY_SEPARATOR.'file';
		if( !file_exists( $localTmpDirPath ) ) {
			if( @mkdir( $localTmpDirPath, 0777 ) ) {
				@chmod( $localTmpDirPath, 0777 );
			} else {
				$request->addError('アップロード一時フォルダの作成に失敗しました');
				return false;
			}
		}
		$localTmpDirPath		.= DIRECTORY_SEPARATOR.'local';
		if( !file_exists( $localTmpDirPath ) ) {
			if( @mkdir( $localTmpDirPath, 0777 ) ) {
				@chmod( $localTmpDirPath, 0777 );
			} else {
				$request->addError('アップロード一時フォルダの作成に失敗しました');
				return false;
			}
		}
		// 設定可能な追加フィールド
		$upfilesFileNameHash	= $_POST[$this->uploadFormName.'_file_names'];
		$upfilesFileOpenHash	= $_POST[$this->uploadFormName.'_open_flags'];
		$upfilesFileMimeHash	= $_POST[$this->uploadFormName.'_mime_types'];
		$upfilesFileAgentHash	= $_POST[$this->uploadFormName.'_target_agents'];
		$upfilesFileHeaderHash	= $_POST[$this->uploadFormName.'_response_headers'];

		$counter	= 1;
		$fileCount	= 0;
		foreach( $upfiles['tmp_name'] as $key => $tempName ) {
			if ( is_uploaded_file($tempName) ) {
				$fileBinObject	= new file_DaoFileBin();
				$fileBinObject->file_name			= basename($upfiles['name'][$key]);
				if( isset($upfilesFileNameHash[$key]) && strlen($upfilesFileNameHash[$key])>0 ) {
					$fileBinObject->file_name		= $upfilesFileNameHash[$key];
				}
				$fileBinObject->file_size			= filesize( $tempName );
				$fileBinObject->extension			= substr( $fileBinObject->file_name, strpos( $fileBinObject->file_name, '.' ) + 1 );
				$fileBinObject->mime_type			= $upfiles['type'][$key];
				if( isset($upfilesFileOpenHash[$key]) && strlen($upfilesFileOpenHash[$key])>0 ) {
					$fileBinObject->open_flag		= $upfilesFileOpenHash[$key];
				}
				if( isset($upfilesFileMimeHash[$key]) && strlen($upfilesFileMimeHash[$key])>0 ) {
					$fileBinObject->mime_type		= $upfilesFileMimeHash[$key];
				}
				if( isset($upfilesFileAgentHash[$key]) && strlen($upfilesFileAgentHash[$key])>0 ) {
					$fileBinObject->target_agent	= $upfilesFileAgentHash[$key];
					$fileBinObject->target_agent	= str_replace("\r\n","\n",$fileBinObject->target_agent);
					$fileBinObject->target_agent	= str_replace("\r","\n",$fileBinObject->target_agent);
				}
				if( isset($upfilesFileHeaderHash[$key]) && strlen($upfilesFileHeaderHash[$key])>0 ) {
					$fileBinObject->response_headers	= $upfilesFileHeaderHash[$key];
					$fileBinObject->response_headers	= str_replace("\r\n","\n",$fileBinObject->response_headers);
					$fileBinObject->response_headers	= str_replace("\r","\n",$fileBinObject->response_headers);
				}
				// MIMEタイプがimage/pjpegはimage/jpegで登録するよう統一
				$fileBinObject->mime_type			= preg_replace('/[iI][mM][aA][gG][eE]\\/[pP][jJ][pP][eE][gG]/','image/jpeg',$fileBinObject->mime_type);
				// アップロードファイルの一時IDを決定します。
				$fileBinObject->file_id	= date('Ymdhis').util_CharUtility::get_rundom_large_char(12);
				$localTmpFilePath	= $localTmpDirPath.DIRECTORY_SEPARATOR.$fileBinObject->file_id;
				while ( file_exists( $localTmpFilePath ) ) {
					$fileBinObject->file_id	= date('Ymdhis').util_CharUtility::get_rundom_large_char(12);
					$localTmpFilePath	= $localTmpDirPath.DIRECTORY_SEPARATOR.$fileBinObject->file_id;
				}
				// ファイルを移動します
				if ( @move_uploaded_file( $tempName, $localTmpFilePath ) ) {
					@chmod( $localTmpFilePath, SPIDER_PERMITTION_DATA_FILE );
					// 画像の場合のサイズ変更
					if( $fileBinObject->isImage() && ( $this->imageMaxWidth > 0 || $this->imageMaxHeight > 0 ) ) {
						if( util_File::resizeImage( $localTmpFilePath, $localTmpFilePath, $this->imageMaxWidth, $this->imageMaxHeight ) ) {
						} else {
							$request->addError($counter.'番目のファイルのサイズ変更に失敗しました。');
						}
					}
					if( !$request->isError() ) {
						// アップロードしたファイルをグローバル一時パスに移動
						$globalTmpFileUri	= $this->_getGlobalTmpRootUri().'/'.basename($localTmpFilePath);
						system_DataFileWriter::import( $request, $globalTmpFileUri, $localTmpFilePath, DIR_NAME_TMP );
						// 登録用一時URIに格納
						$fileBinObject->registTmpUri	= $globalTmpFileUri;
						// オブジェクトハッシュに登録
						$this->fileBinObjectHash[$key]	= $fileBinObject;
						// オブジェクトを拡張子.infoでシリアル化して保存
						$str	= serialize($fileBinObject);
						$infoTmpPath	= $localTmpFilePath.'.info';
						if( $fp = @fopen( $infoTmpPath, 'w' ) ) {
							if( @flock( $fp, LOCK_EX ) ) {
								@fwrite( $fp, $str );
								@flock( $fp, LOCK_UN );
							} else {
								$request->addError($counter.'番目のファイル情報ファイルロック取得に失敗しました。');
							} 
							@fclose( $fp );
							@chmod( $infoTmpPath, SPIDER_PERMITTION_DATA_FILE );
						} else {
							$request->addError($counter.'番目のファイル情報書き込みに失敗しました。');
						}
						// 情報ファイルとしてグローバル一時パスに格納
						$globalTmpInfoUri	= $globalTmpFileUri.'.info';
						system_DataFileWriter::import( $request, $globalTmpInfoUri, $infoTmpPath, DIR_NAME_TMP );
					}
					// エラー有無にかかわらずローカル一時ファイルを削除
					@unlink($localTmpFilePath);
					@unlink($infoTmpPath);
				} else {
					$request->addError($counter.'番目のファイルアップロード一時保存に失敗しました。');
				}
			}
			$counter++;
		}
		if( !$request->isError() ) {
			$this->canInsert	= true;
			return true;
		} else {
			return false;
		}
	}
	/**
	 * 一時ファイルとしてアップロード済みのファイルをデータベースに登録します
	 */
	function insert( & $request, & $abstractUser ) {
		$dbo	= $request->getAttribute( 'dbo' );
		if( $this->canInsert ) {
			if ( is_array( $this->fileBinObjectHash ) ) {
				$counter	= 1;
				foreach ( $this->fileBinObjectHash as $key=>$fileBinObject ) {
					$regFileUri		= $this->fileBinObjectHash[$key]->registTmpUri;
					if( strlen($regFileUri) > 1 ) {
						$regFilePath	= system_DataFileWriter::getRealAbsolutePath( $request, $regFileUri, DIR_NAME_TMP );
						if( file_exists($regFilePath) ) {
							$this->fileBinObjectHash[$key]->owner_id		= $abstractUser->getUniqueId();
							$this->fileBinObjectHash[$key]->modifier_id		= $abstractUser->getUniqueId();
							$this->fileBinObjectHash[$key]->updated_date	= date('Y-m-d H:i:s');
							$this->fileBinObjectHash[$key]->registered_date	= date('Y-m-d H:i:s');
							$this->fileBinObjectHash[$key]->file_id			= null;
							if( $dbo->setNextId( $this->fileBinObjectHash[$key], 'FDAAAA{num:18}') ) {
								if( $dbo->insert( $this->fileBinObjectHash[$key] ) ) {
									// インサートに成功したらファイルを保存フォルダに移動する
									if( $saveRootPath = $this->fileBinObjectHash[$key]->getSaveRootPath( $request, true ) ) {
										// 保存フォルダ作成に成功したらファイルを移動
										if( @copy( $regFilePath, $this->fileBinObjectHash[$key]->getOriginalFilePath( $request ) ) ) {
											system_DataFileWriter::delete( $request, $regFileUri, DIR_NAME_TMP );
											system_DataFileWriter::delete( $request, $regFileUri.'.info', DIR_NAME_TMP );
										} else {
											$request->addError($counter.'番目のファイルの保存に失敗しました');
											break;
										}
									}
								} else {
									$request->addError($counter.'番目のファイルデータベース登録に失敗しました');
									break;
								}
							} else {
								$request->addError($counter.'番目のファイルID発行に失敗しました');
								break;
							}
						}
					}
					$this->fileBinObjectHash[$key]->registTmpUri	= null;
					$counter++;
				}
				// エラーの有無に関わらず古い一時ファイルは削除する
				$this->deleteOldTempFiles( $request );
			}
			if( $request->isError() ) {
				return false;
			} else {
				return true;
			}
		} else {
			return false;
		}
	}
	/**
	 * アップロード済みの一時ファイルを削除します
	 */
	function deleteTempFiles( & $request ) {
		if( is_array( $this->fileBinObjectHash ) ) {
			foreach( $this->fileBinObjectHash as $key => $fileBinObject ) {
				if( get_class($fileBinObject) == get_class( new file_DaoFileBin() ) ) {
					if( strlen($fileBinObject->file_id) > 0 ) {
						$tmpFileUri	= $this->_getGlobalTmpRootUri().'/'.$fileBinObject->file_id;
						system_DataFileWriter::delete( $request, $tmpFileUri, DIR_NAME_TMP );
						system_DataFileWriter::delete( $request, $tmpFileUri.'.info', DIR_NAME_TMP );
					}
				}
			}
		}
		$this->fileBinObjectHash	= array();
	}
	/**
	 * file_Uploaderクラスオブジェクトでのアップロードの未処理一時ファイルを全て削除します
	 */
	function deleteOldTempFiles( & $request ) {
		// グローバル一時フォルダURI
		system_DataFileWriter::deleteDir( $request, $this->_getGlobalTmpRootUri(), DIR_NAME_TMP, 60*30 );
		// 一時ファイルキャッシュURL
		$globalTmpUri		= '/file/20';
		system_DataFileWriter::deleteDir( $request, $globalTmpUri, DIR_NAME_CACHE, 60*30 );
		// ローカル一時フォルダ削除
		$localTmpPath	= DIR_PATH_TMP.'/file/local';
		if( is_dir( $localTmpPath ) ) {
			if ($dh = opendir($localTmpPath)) {
				while (($fileName = readdir($dh)) !== false) {
					if( '.' != trim($fileName) && '..' != trim($fileName) ) {
						$fileRealPath	= $localTmpPath.'/'.$fileName;
						$fileType		= filetype($fileRealPath);
						if( preg_match( '/[dD][iI][rR]/', $fileType ) > 0 ) {
							// ディレクトリには何もしない
						} else {
							if( file_exists($fileRealPath) && filemtime($fileRealPath) < time() - (60*30) ) {
								// 30分以上古いファイルは削除
								@unlink($fileRealPath);
							}
						}
					}
				}
				closedir($dh);
			}
		}
		return true;
	}
	/**
	 * get global tmp root
	 */
	function _getGlobalTmpRootUri() {
		return '/file/global';
	}
}
?>