<?php
/*
 * member/mail/Send.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(dirname(__FILE__))
.DIRECTORY_SEPARATOR.'PackageConfig.class.php');
/**
 * spider : メンバーメール 配信実行用アクションモジュールクラス
 * 
 * @package member メンバー管理パッケージ
 * @subpackage mail メールパッケージ
 * @version 1.0.0
 * @author  <m_nakashima@users.sourceforge.jp>
 * @since PHP 4.0
 */
class member_mail_Send extends system_login_ModuleBase {
	/** オブジェクトID	*/
	var $mail_object_id;
	/** メールオブジェクトのハッシュ: [メールID]=>[メールオブジェクト]	*/
	var $mailObjectHash	= array();
	/** メンバー設定オブジェクト	*/
	var $configuration_object;
	/** 大量配信SMTPサーバ情報の配列	*/
	var $smtp_server_info_array	= null;
	/** このオブジェクトからの配信数	*/
	var $sent_count				= 0;
	
	/**
	 * コンストラクタ
	 */
	function member_mail_Send(){
		$this->mailObjectHash	= array();
		$this->mail_object_id	= time().util_CharUtility::get_rundom_key(16);
		array_push($this->require_module_array,'database2.Connect');
	}
	/**
	 * executeメソッド
	 */
	function execute( & $request ) {
		// パラメータ取得
		$dbo				= $request->getAttribute( 'dbo' );
		$mailId				= trim(stripslashes($_GET['magid']));
		$sendOnceDefault	= trim(stripslashes($_GET['sao']));
		// 
		if( preg_match('/^([1-9]|[1-9][0-9])$/',$sendOnceDefault) == 0 ) {
			if( defined('MEMBER_MAIL_SEND_COUNT_DEFAULT') && preg_match('/^(|[1-9])[1-9]$/',MEMBER_MAIL_SEND_COUNT_DEFAULT) > 0 ) {
				$sendOnceDefault	= MEMBER_MAIL_SEND_COUNT_DEFAULT;
			} else {
				$sendOnceDefault	= 10;
			}
		}
		$result_hash	= $this->execute_send_mail( $request, $sendOnceDefault, false, 1, $mailId );
		$request->setAttribute('member.mail.send.result_hash',$result_hash);
	}
	/**
	 * 未送信のメールキューに対して送信処理を行います。
	 */
	function execute_send_mail( &$request, $sendOnceDefault=10, $display_cli_message=false, $nolist_sleep_sec=300, $mailId=null ) {
		$dbo			= $request->getAttribute( 'dbo' );
		// 一回当たりの送信数入力をチェック
		if( preg_match('/^([1-9]|[1-9][0-9])$/',$sendOnceDefault) == 0 ) {
			if( defined('MEMBER_MAIL_SEND_COUNT_DEFAULT') && preg_match('/^(|[1-9])[1-9]$/',MEMBER_MAIL_SEND_COUNT_DEFAULT) > 0 ) {
				$sendOnceDefault		= MEMBER_MAIL_SEND_COUNT_DEFAULT;
			} else {
				$sendOnceDefault		= 10;
			}
		}
		$sendOnceMobile	= 2;
		if( defined('MEMBER_MAIL_SEND_COUNT_MOBILE') && preg_match('/^(|[1-9])[1-9]$/',MEMBER_MAIL_SEND_COUNT_MOBILE) > 0 ) {
			$sendOnceMobile		= MEMBER_MAIL_SEND_COUNT_MOBILE;
		}
		
		// 本オブジェクトで配信ロック取得を試みる
		if( false === $this->getDeliveryLock() ) {
			$this->writeSentLog( 'stopped. another process is delivering!', $display_cli_message );
			return false;
		}
		// 準備済みで未送信のメールをセレクト
		$sql	= 'SELECT '.TABLE_NAME_MEMBER_MAIL_LOG.'.*'
			.', '.TABLE_NAME_MEMBER_MAIL.'.status_flag '
			.' FROM '.TABLE_NAME_MEMBER_MAIL_LOG
			.' LEFT OUTER JOIN '.TABLE_NAME_MEMBER_MAIL
			.' ON '.TABLE_NAME_MEMBER_MAIL.'.mail_id='.TABLE_NAME_MEMBER_MAIL_LOG.'.mail_id'
			.' WHERE '
			.TABLE_NAME_MEMBER_MAIL_LOG.'.delivery_status=0'
			.' AND '
			.TABLE_NAME_MEMBER_MAIL.'.status_flag=1'
			.' AND '
			.TABLE_NAME_MEMBER_MAIL.'.reserve_date<NOW()'
		;
		if( strlen($mailId) > 0 ) {
			$sql	.= ' AND '
				.TABLE_NAME_MEMBER_MAIL.'.mail_id='.$dbo->quote($mailId);
		}
		$dbo->setLimit( $sendOnceDefault+$sendOnceMobile, 0 );
		$rows	= $dbo->queryAll($sql,'hash');
		if( $rows === false ) {
			$this->releaseDeliveryLock();
			$this->writeSentLog( 'Fail to query delivery list!', $display_cli_message );
			$sentResultArray	= false;
		} else {
			if( count($rows) == 0 ) {
				$this->writeSentLog('no delivery list. sleep '.$nolist_sleep_sec.' sec..', $display_cli_message );
			}
			$sentResultArray	= array();
			$defaultSendCount	= 0;
			$mobileSendCount	= 0;
			foreach( $rows as $row ) {
				$log_number	= trim( $row['log_number'] );
				$mail_id	= trim( $row['mail_id'] );
				$member_id	= trim( $row['member_id'] );
				$deliveryAddress	= trim( $row['delivery_address'] );
				$isMobile			= false;
				foreach( $GLOBALS['MOBILE_MAIL_DOMAIN_ARRAY'] as $mailDomain ) {
					if( preg_match('/'.util_CharUtility::escapeRegxStr($mailDomain).'$/',$deliveryAddress) > 0 ) {
						$isMobile	= true;
						break;
					}
				}
				$canSend	= false;
				if( $isMobile ) {
					// 携帯アドレスの場合
					if( $mobileSendCount < $sendOnceMobile ) {
						// 一度の処理数を超えていないなら送信
						$canSend	= true;
						$mobileSendCount++;
					}
				} else {
					// パソコンアドレスの場合
					if( $defaultSendCount < $sendOnceDefault ) {
						// 一度の処理数を超えていないなら送信
						$canSend	= true;
						$defaultSendCount++;
					}
				}
				if( $canSend ) {
					// 送信
					$result		= $this->send_mail_member( $request, $log_number, $mail_id, $member_id, $display_cli_message );
					if( false === $result ) {
						$msg	= 'Fatal. Lock undefined error.'
							.':mail_id['.$mail_id.']:member_id['.$member_id.']'
						;
						$this->writeSentLog($msg,$display_cli_message);
						break;
					} else {
						array_push( $sentResultArray, $result );
					}
				}
			}
		}
		// 送信メールオブジェクトの未配信メール数が0になっていたらステータスを変更する
		$sql	= 'SELECT * FROM '.TABLE_NAME_MEMBER_MAIL.' WHERE status_flag=1 AND reserve_date<NOW()';
		$rows	= $dbo->queryAll($sql,'hash');
		if( $rows === false ) {
		} else {
			foreach( $rows as $row ) {
				$sql	= 'SELECT COUNT(log_number) FROM '.TABLE_NAME_MEMBER_MAIL_LOG
					.' WHERE '.TABLE_NAME_MEMBER_MAIL_LOG.'.delivery_status=0'
					.' AND '.TABLE_NAME_MEMBER_MAIL_LOG.'.mail_id='.$dbo->quote($row['mail_id'])
				;
				$count	= $dbo->queryOne( $sql );
				if( false === $count ) {
					$request->addLocaledError('database2.error.common',SPIDER_LOG_LEVEL_FATAL,array($sql));
				} else if( $count == 0 ) {
					$sql	= 'UPDATE '.TABLE_NAME_MEMBER_MAIL.' SET status_flag=2, completed_date=NOW() WHERE mail_id='.$dbo->quote($row['mail_id']);
					if( false === $dbo->query( $sql ) ) {
						$request->addLocaledError('database2.error.update',SPIDER_LOG_LEVEL_FATAL,array($sql));
					}
				}
			}
		}
		$this->releaseDeliveryLock();
		return $sentResultArray;
	}
	/**
	 * 1件のメールキューを送信する処理を行います。
	 */
	function send_mail_member( & $request, $log_number, $mail_id, $member_id, $display_cli_message=false ) {

		$dbo			= $request->getAttribute( 'dbo' );

		if( !$this->canDelivering() ) {
			// 本オブジェクトでロックを取得できていないならエラー
			return false;
		}
		
		// 状態変数
		$deliveryStatus	= 1;
		$logMessage		= '';
		$delivery_address	= '';
		$compare_member_mail	= spider_Controller::createObject('member_DaoMemberMail');
			
		// 会員パッケージ設定読み込み
		if( get_class(spider_Controller::createObject('member_PackageConfig')) != get_class($this->configuration_object) ) {
			$this->configuration_object	= spider_Controller::createObject('member_PackageConfig');
			if( $this->configuration_object->load( $request ) ) {
			} else {
				$deliveryStatus	= 254;
			}
		}
		
		// ログファイルパスの作成確認
		if( 1 == $deliveryStatus ) {
			$log_file	= $this->getSentLogPath();
			if( false === $log_file ) {
				$deliveryStatus	= 254;
			}
		}
		
		// 送信設定の取り出し
		$mailSendClassName	= 'PHP';
		$mailSendParams		= array();
		if( strlen($this->configuration_object->definitionHash['MEMBER_MAIL_SEND_METHOD']) > 0 ) {
			$mailSendClassName	= $this->configuration_object->definitionHash['MEMBER_MAIL_SEND_METHOD'];
			$mailSendParams		= $this->configuration_object->definitionHash['MEMBER_MAIL_SEND_METHOD_OPTIONS'];			
		}
		if( preg_match('/^[sS][mM][tT][pP]$/',$mailSendClassName) > 0 ) {
			$mailSendClassName	= 'SMTP';
		} else if( preg_match('/^[sS][eE][nN][dD][mM][aA][iI][lL]$/',$mailSendClassName) > 0 ) {
			$mailSendClassName	= 'SendMail';
		}
		
		// 送信メール内容の取得
		$mailObject	= null;
		if( 1 == $deliveryStatus ) {
			if( preg_match('/^4/',phpversion()) > 0 ) {
				if( is_object($this->mailObjectHash[$mail_id])
					&& get_class($compare_member_mail) != get_class($this->mailObjectHash[$mail_id]) ) {
					$mailObject	= $this->mailObjectHash[$mail_id];
				} else {
					$targetMailObject	= spider_Controller::createObject('member_DaoMemberMail');
					if( $dbo->loadById( $targetMailObject, $mail_id ) ) {
						$this->mailObjectHash[$mail_id]	= $targetMailObject;
						$mailObject	= $this->mailObjectHash[$mail_id];
					} else {
						$deliveryStatus	= 202;
					}
				}
			} else {
				if( is_object($this->mailObjectHash[$mail_id])
					&& get_class($compare_member_mail) != get_class($this->mailObjectHash[$mail_id]) ) {
					$mailObject	= clone $this->mailObjectHash[$mail_id];
				} else {
					$targetMailObject	= spider_Controller::createObject('member_DaoMemberMail');
					if( $dbo->loadById( $targetMailObject, $mail_id ) ) {
						$this->mailObjectHash[$mail_id]	= $targetMailObject;
						$mailObject	= clone $this->mailObjectHash[$mail_id];
					} else {
						$deliveryStatus	= 202;
					}
				}
			}
		}
		
		// メールマガジン種別登録確認
		if( 1 == $deliveryStatus ) {
			$sql	= 'SELECT COUNT(*) FROM ' . TABLE_NAME_MEMBER_MAIL_REJECT_MEMBER
				. ' WHERE list_id='.$dbo->quote($mailObject->list_id)
				. ' AND member_id='.$dbo->quote($member_id)
			;
			$count	= $dbo->queryOne($sql);
			if(false === $count) {
				$deliveryStatus	= 205;
			} else if( $count > 0 ) {
				$deliveryStatus	= 103;
			}
		}
		
		// 送信対象会員の情報取得
		$deliveryAddress	= null;
		if( 1 == $deliveryStatus ) {
			$memberObject		= spider_Controller::createObject('member_DaoMember');
			if( $dbo->loadById( $memberObject, $member_id ) ) {
				if( $memberObject->status_flag > 200 ) {
					$deliveryStatus	= 102;
				} else {
					$deliveryAddress	= $memberObject->pc_mail;
					if( 'm' == $mailObject->target_type ) {
						$deliveryAddress	= $memberObject->mb_mail;
					}
					if( strlen(trim($deliveryAddress)) == 0 ) {
						$deliveryStatus	= 101;
					}
				}
			} else {
				$deliveryStatus	= 203;
			}
		}
			
		// メール送信処理
		if( 1 == $deliveryStatus ) {
			// ログ上の送信日時
			$deliveryDate	= date('Y-m-d H:i:s');
			// リターンメール自動処理なら一時的にmail_return_path書き換え
			if( preg_match('/^[sS][yY][sS][tT][eE][mM]\\_[aA][uU][tT][oO]$/', $mailObject->mail_return_path ) > 0
				|| preg_match('/^[sS][yY][sS][tT][eE][mM]\\_[rR][eE][cC][eE][iI][vV][eE]$/', $mailObject->mail_return_path ) > 0 ) {
				if( strlen($this->configuration_object->definitionHash['MEMBER_RETURN_MAIL_PREFIX']) > 0
					&& strlen($this->configuration_object->definitionHash['MEMBER_MAIL_DOMAIN']) > 0 ) {
					$mailObject->mail_return_path	= $this->configuration_object->definitionHash['MEMBER_RETURN_MAIL_PREFIX']
						.'-'.sprintf('%012d',$log_number)
						.'-'.sprintf('%010d',strtotime($deliveryDate))
						.'@'.$this->configuration_object->definitionHash['MEMBER_MAIL_DOMAIN'];
				} else {
					$mailObject->mail_return_path	= $mailObject->mail_from_address;
				}
			} else if( strlen(trim($mailObject->mail_return_path)) == 0 ) {
				$mailObject->mail_return_path	= $mailObject->mail_from_address;
			}
			// 置換ワードに会員情報を設定
			$replaceWordHash	= array();
			$replaceWordHash['member']	= $memberObject;
			// パッケージコネクタの確認処理をおこなう
			if( is_array($GLOBALS['member.existsConnectorHash'] ) ) {
				foreach( $GLOBALS['member.existsConnectorHash'] as $packageId => $connectorObject ) {
					$connectorObject->mailPreSend( $request, $mailObject );
				}
			}
			// メール送信実行
			$deliveryStatus	= $mailObject->sendMail( $request, $deliveryAddress, $replaceWordHash );
			if( $deliveryStatus == 1 ) {
				// 送信完了したら対応履歴にインサートする
				if( strlen($mailObject->correspond_status) > 0 ) {
					$correspondObject	= spider_Controller::createObject('member_DaoMemberCorrespond');
					$correspondObject->status_flag			= $mailObject->correspond_status;
					$correspondObject->member_id			= $memberObject->member_id;
					$correspondObject->correspond_type		= $mailObject->correspond_type;
					$correspondObject->correspond_method	= '0';
					$correspondObject->category_id			= $mailObject->correspond_category;
					$correspondObject->description			= 'システム配信['.$mailObject->mail_id.'] '.$mailObject->subject;
					$correspondObject->detail_note			= $mailObject->body;
					$correspondObject->start_date			= $mailObject->registered_date;
					$correspondObject->correspond_date		= date('Y-m-d H:i:s');
					$correspondObject->updated_date			= date('Y-m-d H:i:s');
					$correspondObject->registered_date		= date('Y-m-d H:i:s');
					$correspondObject->owner_id				= $mailObject->owner_id;
					$correspondObject->modifier_id			= $mailObject->modifier_id;
					if( !$dbo->setNextId( $correspondObject, 'MBAAAAAA{num:8}' ) ) {
						$request->addLocaledError('database2.error.setnextid',SPIDER_LOG_LEVEL_FATAL,array(''));
					} else {
						if( $dbo->insert($correspondObject) ){
						}
					}
				}
			}
		}
		// ログメッセージの生成
		switch ( $deliveryStatus ) {
			case 254:
				$logMessage	= 'Fail to load member package configuration or Can\'t log!';
				break;
			case 205:
				$logMessage	= 'Fail to load reject information!';
				break;
			case 203:
				$logMessage	= 'Fail to load member! ['.$member_id.']';
				break;
			case 202:
				$logMessage	= 'Fail to load member_mail! ['.$mail_id.']';
				break;
			case 103:
				$logMessage	= 'This member belong to reject list!';
				break;
			case 102:
				$logMessage	= 'This Member has left! ['.$member_id.']';
				break;
			case 101:
				$logMessage	= 'Target Mail Address is not registed!';
				break;
			case 1:
				$logMessage	= '';
				break;
			default:
				$logMessage	= 'Undefined Error!';
				break;
		}
		$msg	= $deliveryAddress.':'. $logMessage.':'.$deliveryStatus
			.':mail_id['.$mail_id.']:member_id['.$member_id.']'
		;
		$this->writeSentLog( $msg, $display_cli_message );
		
		// ログの状態変更
		$sql	= 'UPDATE '.TABLE_NAME_MEMBER_MAIL_LOG.' SET delivery_status='.$deliveryStatus
			.', delivery_log='.$dbo->quote($logMessage)
			.', delivery_date='.$dbo->quote($deliveryDate)
			.' WHERE log_number='.$log_number
		;
		$dbo->query($sql);
		
		return array(
			'delivery_status'		=> $deliveryStatus,
			'log_message'			=> $logMessage,
			'delivery_address'	=> $deliveryAddress,
			'message'				=> $msg,
		);
	}
	/**
	 * 送信ログファイルに記入します。
	 */
	function writeSentLog( $message, $display_cli_message=false ) {
		$log_file	= $this->getSentLogPath();
		if( false === $log_file ) {
			return false;
		} else {
			$log	= '['.date('Y-m-d H:i:s').']['.$this->mail_object_id.'] '.$message."\n";
			error_log($log, 3, $log_file);
			if( $display_cli_message ) {
				echo $log;
				ob_flush();
			}
			return true;
		}
	}
	/**
	 * 送信ログファイルパスを取得します
	 */
	function getSentLogPath() {
		$log_file			= DIR_PATH_LOG.DIRECTORY_SEPARATOR.'member';
		if( !file_exists($log_file) ) {
			if(@mkdir($log_file,0777)){
				@chmod($log_file,0777);
			} else {
				return false;
			}
		}
		$log_file			.= DIRECTORY_SEPARATOR.'mail';
		if( !file_exists($log_file) ) {
			if(@mkdir($log_file,0777)){
				@chmod($log_file,0777);
			} else {
				return false;
			}
		}
		$log_file			.= DIRECTORY_SEPARATOR.date('Y');
		if( !file_exists($log_file) ) {
			if(@mkdir($log_file,0777)){
				@chmod($log_file,0777);
			} else {
				return false;
			}
		}
		$log_file			.= DIRECTORY_SEPARATOR.date('m');
		if( !file_exists($log_file) ) {
			if(@mkdir($log_file,0777)){
				@chmod($log_file,0777);
			} else {
				return false;
			}
		}
		$log_file			.= DIRECTORY_SEPARATOR.date('d').'.log';
		if( !file_exists($log_file) ) {
			if( @touch( $log_file ) ) {
				@chmod( $log_file, 0666 );
				return $log_file;
			} else {
				return false;
			}
		} else {
			return $log_file;
		}
	}
	/**
	 * 配信プロセス起動のロックファイルを作成します
	 */
	function getDeliveryLock() {
		if( $this->canDelivering() ) {
			// 配信ロックファイルがない場合は作成する
			$lock_file	= $this->getDeliveryLockFilePath();
			if( false === $lock_file ) {
				// ロックファイルディレクトリが作成できないならエラー
				return false;
			} else {
				// ロックが取得可能な場合
				if( !file_exists($lock_file) ) {
					if( @touch( $lock_file ) ) {
						@chmod( $lock_file, 0666 );
						$fp	= fopen($lock_file,'w');
						if( $fp ) {
							if (flock($fp, LOCK_EX)) {
								fwrite( $fp, $this->mail_object_id );
								flock($fp, LOCK_UN);
							}
							fclose( $fp );
							return true;
						} else {
							return false;
						}
					} else {
						return false;
					}
				} else {
					// 本オブジェクトがかけたロックがすでに存在する
					@touch($lock_file);
					return true;
				}
			}
		} else {
			return false;
		}
	}
	/**
	 * 配信プロセスロックを解除します
	 */
	function releaseDeliveryLock() {
		if( $this->canDelivering() ) {
			$lock_file	= $this->getDeliveryLockFilePath();
			if( @unlink( $lock_file ) ) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}
	/**
	 * 配信開始可能か確認します
	 */
	function canDelivering() {
		$lock_file	= $this->getDeliveryLockFilePath();
		if( false === $lock_file ) {
			return false;
		} else {
			if( file_exists($lock_file) ) {
				if( time() - filemtime($lock_file) > 60 * 60 * 1 ) {
					// 一時間以上更新されていないロックなら実行中ではない
					@unlink( $lock_file );
					return true;
				} else {
					// 時間内のロックが存在する場合自分のロックか確認
					$oid	= trim(file_get_contents( $lock_file ) );
					if( trim($this->mail_object_id) == $oid ) {
						// 本オブジェクトがかけたロックなのでスタートOK
						@touch( $lock_file );
						return true;
					} else {
						return false;
					}
				}
			} else {
				// ロックがないので開始可能
				return true;
			}
		}
	}
	/**
	 * ロックファイルのパスを取得します。
	 */
	function getDeliveryLockFilePath() {
		$lock_file	= DIR_PATH_LOCK.DIRECTORY_SEPARATOR.'member';
		if( !file_exists($lock_file) ) {
			if(@mkdir($lock_file,0777)){
				@chmod($lock_file,0777);
			} else {
				return false;
			}
		}
		$lock_file	.= DIRECTORY_SEPARATOR.'mail.delivery.lock';
		return $lock_file;
	}
}
?>