自分用メール送信クラスに添付ファイル送信機能をつけた。

以前の記事←で作成したメール送信クラスに添付ファイル送信機能をつけてみた。

使える全メソッドは下部で解説。
簡単な使い方は関数上部のコメントアウトを参照されたし。

<?php
/**
 * sendmailを用いたシンプルなメール送信用クラス
 * 単純に送信したい場合は以下の通り
 * ※to, cc, bcc, のいずれか一つの指定は必須
 * 
 * $mail = new simpleMailTransmission();
 * 		
 * $res = $mail
 * ->to('to@local.host') // 必要に応じて
 * ->cc('cc@local.host') // 必要に応じて
 * ->Bcc('bcc@local.host') // 必要に応じて
 * ->attachments('/path/to/file.ext') // 添付ファイル設定
 * ->from('from@local.host') // 必須
 * ->subject('タイトルを指定')
 * ->send('本文を指定');
 * 
 * ※添付ファイルの詳細設定は、attachments()関数上部のコメントアウトを参照
 * 
 */
class simpleMailTransmission {
	
	// 送信先
	protected $to = array();
	
	// メールタイトル
	protected $subject = '';
	
	// メール本文
	protected $message = '';
	
	// ヘッダー情報
	protected $header = '';
	
	// 送信先一時格納変数(複数可)
	protected $_to = array();
	
	// 送信元設定
	protected $_from = array();
	
	// Cc格納変数
	protected $_cc = array();

	// Bcc格納変数
	protected $_bcc = array();

	// ヘッダー情報一時格納変数
	protected $_header = '';
	
	// テンプレートファイルパス
	protected $_template = '';
	
	// テンプレートに渡す変数
	protected $_viewVars = array();
	
	// 添付ファイル
	protected $_attachments = array();

	// 添付ファイル送信用バウンダリヘッダ
	protected $_boundary = null;
	
	// 言語設定
	protected $_lang = 'ja';
	
	// エンコーディング
	protected $_encoding = 'UTF-8';
	
	// キャラセット
	protected $_charset = 'ISO-2022-JP';
	
	// 送信時文字エンコード
	protected $_transferEncoding = '7bit';
	
	// 送信ヘッダー内「X-Mailer」設定
	protected $_xMailer = 'GEKIOKOPUNPUNMARU';
	
	/**
	 * 送信先のセット
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return multitype:|mailTerminal
	 */
	public function to($email = null, $name = null) {
		if($email === null) {
			return $this->_to;
		}
		return $this->_setEmail('_to', $email, $name);
	}
	
	/**
	 * 送信先の追加
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	public function addTo($email, $name = null) {
		return $this->_addEmail('_to', $email, $name);
	}

	/**
	 * Ccのセット
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return multitype:|mailTerminal
	 */
	public function cc($email = null, $name = null) {
		if($email === null) {
			return $this->_cc;
		}
		return $this->_setEmail('_cc', $email, $name);
	}
	
	/**
	 * Ccの追加
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	public function addCc($email, $name = null) {
		return $this->_addEmail('_cc', $email, $name);
	}
	
	/**
	 * Bccのセット
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return multitype:|mailTerminal
	 */
	public function bcc($email = null, $name = null) {
		if($email === null) {
			return $this->_bcc;
		}
		return $this->_setEmail('_bcc', $email, $name);
	}
	
	/**
	 * Bccの追加
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	public function addBcc($email, $name = null) {
		return $this->_addEmail('_bcc', $email, $name);
	}
	
	/**
	 * セット用メソッド(配列可)
	 * @param unknown_type $varName
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	protected function _setEmail($varName, $email, $name) {
		if(is_array($email)) {
			$list = array();
			foreach($email as $key => $value) {
				if(is_int($key)) {
					$key = $value;
				}
				if(self::email($key)) {
					$list[$key] = $value;
				}
			}
			$this->{$varName} = $list;
			return $this;
		}
		if(self::email($email)) {
			if($name === null) {
				$name = $email;
			}
			$this->{$varName} = array($email => $name);
		}
		return $this;
	}
	
	/**
	 * 追加用メソッド(配列可)
	 * @param unknown_type $varName
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	protected function _addEmail($varName, $email, $name) {
		if(is_array($email)) {
			$list = array();
			foreach($email as $key => $value) {
				if(is_int($key)) {
					$key = $value;
				}
				if(self::email($key)) {
					$list[$key] = $value;
				}
			}
			$this->{$varName} = array_merge($this->{$varName}, $list);
			return $this;
		}
		if(self::email($email)) {
			if($name === null) {
				$name = $email;
			}
			$this->{$varName}[$email] = $name;
		}
		return $this;
	}
	
	/**
	 * 送信元の設定
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return multitype:|mailTerminal
	 */
	public function from($email = null, $name = null) {
		if($email === null) {
			return $this->_from;
		}
		return $this->_setEmailSingle('_from', $email, $name);
	}
	
	/**
	 * 単一の値であることを保障するためのセット関数
	 * @param unknown_type $varName
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	protected function _setEmailSingle($varName, $email, $name) {
		$current = $this->{$varName};
		$this->_setEmail($varName, $email, $name);
		if(count($this->{$varName}) !== 1) {
			$this->{$varName} = $current;
		}
		return $this;
	}
	
	/**
	 * メールタイトルのセット
	 * @param unknown_type $subject
	 * @return string|mailTerminal
	 */
	public function subject($subject = null) {
		if($subject === null) {
			return $this->subject;
		}
		$this->subject = (string)$subject;
		return $this;
	}
	
	/**
	 * テンプレートファイルパスの指定
	 * @param unknown_type $template
	 * @return string|mailTerminal
	 */
	public function template($template = false) {
		if($template === false) {
			return $this->_template;
		}
		$this->_template = $template;
		return $this;
	}
	
	/**
	 * テンプレートファイルに渡す変数を設定
	 * @param unknown_type $viewVars
	 * @return multitype:|mailTerminal
	 */
	public function viewVars($viewVars = null) {
		if($viewVars === null) {
			return $this->_viewVars;
		}
		$this->_viewVars = array_merge($this->_viewVars, (array)$viewVars);
		return $this;
	}
	
	/**
	 * 添付ファイルの設定
	 * 
	 * $mail->attachments('/path/to/file.ext');
	 * 
	 * $mail->attachments(array('customName.ext' => '/path/to/file.ext'));
	 * 
	 * $mail->attachments(array('customName.ext' => array(
	 *		'file' => '/path/to/file.ext',
	 *		'mimetype' => 'image/jpg',
	 *		'contentId' => 'qwerty',
	 *		'contentDisposition' => false
	 * ));
	 * 
	 */
	public function attachments($attachments = null) {
		if($attachments === null) {
			return $this->_attachments;
		}
		$attach = array();
		foreach((array)$attachments as $name => $fileInfo) {
			if(!is_array($fileInfo)) {
				$fileInfo = array('file' => $fileInfo);
			}
			if(empty($fileInfo['file'])) {
				return 'File not specified.';
			}
			$fileInfo['file'] = realpath($fileInfo['file']);
			if($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
				return 'File not found.';
			}
			if(is_int($name)) {
				$name = basename($fileInfo['file']);
			}
			if(!isset($fileInfo['mimetype'])) {
				$fileInfo['mimetype'] = 'application/octet-stream';
			}
			$attach[$name] = $fileInfo;
		}
		$this->_attachments = $attach;
		return $this;
	}
	
	/**
	 * 添付ファイルの追加
	 * @param unknown_type $attachments
	 * @return simpleMailTransmission
	 */
	public function addAttachments($attachments) {
		$current = $this->_attachments;
		$this->attachments($attachments);
		$this->_attachments = array_merge($current, $this->_attachments);
		return $this;
	}
	
	/**
	 * ヘッダーバウンダリの生成
	 * ※要添付ファイル
	 */
	protected function _createBoundary() {
		if (!empty($this->_attachments)) {
			$this->_boundary = md5(uniqid(time()));
		}
	}
	
	/**
	 * 添付ファイルがある場合、
	 * メッセージにバウンダリーを挿入
	 * @return string
	 */
	protected function _addBoundaries() {

		$this->_createBoundary();
		$msg = '';
	
		$contentIds = array_filter((array)self::_extract($this->_attachments, '{s}.contentId'));
		$hasInlineAttachments = count($contentIds) > 0;
		$hasAttachments = !empty($this->_attachments);
		
		$boundary = $relBoundary = $textBoundary = $this->_boundary;
	
		if ($hasInlineAttachments) {
			$msg .= '--'.$boundary."\n";
			$msg .= 'Content-Type: multipart/related; boundary="rel-'.$boundary.'"'."\n\n";
			$relBoundary = $textBoundary = 'rel-'.$boundary;
		}
		if (isset($this->message)) {
			if ($textBoundary !== $boundary || $hasAttachments) {
				$msg .= '--'.$textBoundary."\n";
				$msg .= 'Content-Type: text/plain; charset='.$this->_charset."\n";
				$msg .= 'Content-Transfer-Encoding: '.$this->_transferEncoding."\n\n";
			}
			$msg .= $this->message."\n\n";
		}
		if ($hasInlineAttachments) {
			$attachments = $this->_attachInlineFiles($relBoundary);
			$msg .= $attachments."\n";
			$msg .= '--'.$relBoundary.'--'."\n\n";
		}
		if ($hasAttachments) {
			$attachments = $this->_attachFiles($boundary);
			$msg .= $attachments."\n";
		}
		if($hasAttachments) {
			$msg .= '--'.$boundary.'--'."\n\n";
		}
		return $msg;
	}
	
	/**
	 * コンテンツIDを持つ添付ファイルの追加
	 * @param unknown_type $boundary
	 * @return multitype:string unknown
	 */
	protected function _attachInlineFiles($boundary) {
		$msg = '';
		foreach($this->_attachments as $filename => $fileInfo) {
			if(empty($fileInfo['contentId'])) continue;
			$data = $this->_readFile($fileInfo['file']);
			$msg .= '--'.$boundary."\n";
			$msg .= 'Content-Type: '.$fileInfo['mimetype']."\n";
			$msg .= 'Content-Transfer-Encoding: base64'."\n";
			$msg .= 'Content-ID: <'.$fileInfo['contentId'].'>'."\n";
			$msg .= 'Content-Disposition: inline; filename="'.$filename.'"'."\n\n";
			$msg .= $data."\n\n";
		}
		return $msg;
	}
	
	/**
	 * コンテンツIDを持たない添付ファイルの追加
	 * @param unknown_type $boundary
	 * @return multitype:string
	 */
	protected function _attachFiles($boundary) {
		$msg = '';
		foreach($this->_attachments as $filename => $fileInfo) {
			if (!empty($fileInfo['contentId'])) continue;
			$data = $this->_readFile($fileInfo['file']);
	
			$msg .= '--'.$boundary."\n";
			$msg .= 'Content-Type: '.$fileInfo['mimetype']."\n";
			$msg .= 'Content-Transfer-Encoding: base64'."\n";
			if (!isset($fileInfo['contentDisposition']) || $fileInfo['contentDisposition']) {
				$msg .= 'Content-Disposition: attachment; filename="'.$filename.'"'."\n\n";
			}
			$msg .= $data."\n\n";
		}
		return $msg;
	}
	
	/**
	 * 添付するファイルの読み込み(エンコード)
	 * @param unknown_type $path
	 * @return string
	 */
	protected function _readFile($path) {
		return chunk_split(base64_encode(file_get_contents($path)));
	}
	
	/**
	 * メールアドレスのバリデーション設定
	 * @param unknown_type $check
	 * @return boolean
	 */
	protected function email($check) {
		$hostName = '(?:[_a-z0-9][-_a-z0-9]*\.)*(?:[a-z0-9][-a-z0-9]{0,62})\.(?:(?:[a-z]{2}\.)?[a-z]{2,})';
		$regex = '/^[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@'.$hostName.'$/i';
		return self::_check($check, $regex);
	}
	
	/**
	 * メールの送信開始トリガー
	 * @param unknown_type $plainMessage
	 * @return string|Ambigous <boolean, multitype:boolean >
	 */
	public function send($plainMessage = null) {
		if(empty($this->_from)) {
			return 'From is not specified.';
		}
		if(empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) {
			return 'You need to specify at least one destination for to, cc or bcc.';
		}
		if(!file_exists($this->_template)) {
			$this->message = self::mce($plainMessage);
		} else {
			$this->message = self::mce($this->createMessage());
		}
		if(!empty($this->_attachments)) $this->message = $this->_addBoundaries();
		
		$this->header = $this->createHeader();
		$this->to = $this->createTo();

		return self::_send();
	}
	
	/**
	 * 送信処理
	 * @return boolean|multitype:boolean
	 */
	protected function _send() {
		mb_language($this->_lang);
		mb_internal_encoding($this->_encoding);
		
		if(!empty($this->_attachments)) return self::_sendWithAttachments();
		
		if(empty($this->to)) {
			return mb_send_mail(null, $this->subject, $this->message, $this->header);
		} else {
			$_errors = array();
			foreach($this->to as $to) {
				if(!mb_send_mail($to, $this->subject, $this->message, $this->header)) $_errors[$to] = false;
			}
			if(empty($_errors)) return true;
			return $_errors;
		}
	}
	
	protected function _sendWithAttachments() {
		$this->message = str_replace("\n", "\r\n", $this->message);
		$this->header = str_replace("\n", "\r\n", $this->header);
		if(empty($this->to)) {
			return mail(null, self::mem($this->subject), $this->message, $this->header);
		} else {
			$_errors = array();
			foreach($this->to as $to) {
				if(!mail($to, self::mem($this->subject), $this->message, $this->header)) $_errors[$to] = false;
			}
			if(empty($_errors)) return true;
			return $_errors;
		}
	}
	
	/**
	 * 追加ヘッダーの生成
	 * @return string
	 */
	protected function createHeader() {
		foreach($this->_from as $email => $name) $this->_header .= 'From: '.self::mem($name).' <'.$email.'>'."\n";
		if(!empty($this->_cc)) {
			$this->_header .= 'Cc: ';
			foreach($this->_cc as $email => $name) {
				$this->_header .= self::mem($name).' <'.$email.'>'.",";
			}
			$this->_header = self::trimLastChar($this->_header)."\n";
		}
		if(!empty($this->_bcc)) {
			$this->_header .= 'Bcc: ';
			foreach($this->_bcc as $email => $name) {
				$this->_header .= self::mem($name).' <'.$email.'>'.",";
			}
			$this->_header = self::trimLastChar($this->_header)."\n";
		}
		$this->_header .= 'X-Mailer: '.$this->_xMailer."\n";
		if(!empty($this->_attachments)) {
			$this->_header .= 'MIME-Version: 1.0'."\n";
			$this->_header .= 'Content-Type: multipart/mixed; boundary='.$this->_boundary."\n";
			$this->_header .= 'Content-Transfer-Encoding: '.$this->_transferEncoding."\n";
		}
		return $this->_header;
	}
	
	/**
	 * 送信先ヘッダーの生成
	 * @return multitype:string
	 */
	protected function createTo() {
		$t = array();
		if(!empty($this->_to)) {
			foreach($this->_to as $email => $name) {
				$t[] = self::mem($name).' <'.$email.'>';
			}
		}
		return $t;
	}
	
	/**
	 * テンプレートより本文の生成
	 * @return Ambigous <string, mixed>
	 */
	protected function createMessage() {
		$b = '';
		$tmp = file_get_contents($this->_template);
		if($tmp) {
			$varArray = array();
			if(!empty($this->_viewVars)) {
				foreach($this->_viewVars as $varName => $value) $varArray['{%'.$varName.'%}'] = $value;
			}
			$b = str_replace(array_keys($varArray) ,array_values($varArray), $tmp);
		}
		return $b;
	}
	
	/**
	 * バリデーションの実行
	 * @param unknown_type $check
	 * @param unknown_type $regex
	 * @return boolean
	 */
	protected function _check($check, $regex) {
		if(preg_match($regex, $check)) return true;
		return false;
	}
	
	protected function mem($var) {
		$internalEncoding = function_exists('mb_internal_encoding');
		if($internalEncoding) {
			$restore = mb_internal_encoding();
			mb_internal_encoding($this->_encoding);
		}
		$return = mb_encode_mimeheader($var, $this->_encoding, 'B');
		if($internalEncoding) mb_internal_encoding($restore);
		return $return;
	}
	
	protected function mce($var) {
		return mb_convert_encoding($var, $this->_charset);
	}
	
	protected function trimLastChar($var) {
		return substr($var, 0, -1);
	}
	
	/**
	 * 配列の中から指定した文字列を抽出する
	 * 
	 * - `{n}` Matches any numeric key, or integer.
	 * - `{s}` Matches any string key.
	 * 
	 * @param array $data
	 * @param unknown_type $path
	 * @return multitype:|array|Ambigous <multitype:multitype: , multitype:unknown >
	 */
	protected function _extract(array $data, $path) {
		if(empty($path) || !preg_match('/[{]/', $path)) return $data;
		$tokens = explode('.', $path);
		$_key = '__set_item__';
		$context = array($_key => array($data));
		foreach($tokens as $token) {
			$next = array();
			foreach($context[$_key] as $item) {
				foreach((array)$item as $k => $v) {	
					if(self::_matchToken($k, $token)) {
						$next[] = $v;
					}
				}
			}	
			$context = array($_key => $next);
		}
		return $context[$_key];
	}
	
	/**
	 * トークンに対するキーチェック
	 * @param unknown_type $key
	 * @param unknown_type $token
	 * @return boolean
	 */
	protected function _matchToken($key, $token) {
		if($token === '{n}') return is_numeric($key);
		if($token === '{s}') return is_string($key);
		return ($key === $token);
	}
}

用意した全メソッドの使い方は以下の通り。

$mail = new simpleMailTransmission();
$res = $mail
->to('to@local.host')
->addTo('addTo@local.host')
->addTo('addTo2@local.host')
->cc('cc@local.host')
->addCc('addCc@local.host')
->addCc('addCc2@local.host')
->bcc('bcc@local.host')
->addBcc('addBcc@local.host')
->addBcc('addBcc2@local.host')
->from('from@local.host')
->subject('mailSubjectHere.')
->template('/path/to/template.ext')
->viewVars($params)
->attachments('/path/to/file1.ext')
->addAttachments('/path/to/file2.ext')
->addAttachments('/path/to/file3.ext')
->send();

var_dump($res);

複数のTo、Cc、Bcc、添付ファイルの送信に対応。
テンプレートの文法、変数の渡し方は前回の記事を参照されたし。