[php]PHPMailerで添付ファイル付きのメールを送る

ざっくりとした流れの理解

やりたいこと

自作したメーラーはセキュリティやらメールの扱いやらが終わってるので突き詰めることに努力するくらいならPHPMailerを使う。
添付ファイルもやる。

添付ファイルの扱いを理解

  1. 一旦サーバーにデータをアップロードする。
  2. サーバーに上がったデータをメール添付する。
  3. メールを送信する。
  4. サーバーに上がったデータを削除する。

こんな感じになるのでちょっとめんどくさい。
要はアップローダーを作れという話。

入力ページを作る

前の記事をそのまんまやればいいんでないかな。
ファイルを選択してsubmitするように作る。

入力確認画面(ファイルのアップロード)

前項を受けて、添付ファイル用のディレクトリを作ってそこにアップするように組む。同階層に「temporary」って名前で作るとしたらこんな感じ。

<?php
$uploadDir = './temporary/';
$tempfile = $_FILES['file']['tmp_name'];
$filename = $_FILES['file']['name'];
if (is_uploaded_file($tempfile)) {
	if ( move_uploaded_file($tempfile , $uploadDir.$filename )) {
		echo $filename . "をアップロードしました。";
	} else {
		echo "ファイルをアップロードできません。";
	}
} else {
	echo "ファイルが選択されていません。";
}
?>
<form action="send.php" method="post">
	<input type="text" name="fileDir" value="<?php echo $uploadDir.$filename; ?>">
	<button type="submit">SUBMIT</button>
</form>

ファイルがpostされてるか確認して「move_uploaded_file」でアップロード処理。ディレクトリ込みでファイル名を指定してるところに注意。

ディレクトリ名込みのファイル名はメール添付とファイル削除に使うのでformでまたpostする。

メール送付・ファイル削除

前の記事のやつにちょっと足せばいいかんじ。

<?php
// HPMailer のクラスをグローバル名前空間(global namespace)にインポート
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

// HPMailer のインポート
require '../PHPMailer/src/PHPMailer.php';
require '../PHPMailer/src/SMTP.php';
require '../PHPMailer/src/Exception.php';
 
//エラーメッセージ用日本語言語ファイルを読み込む場合
require '../PHPMailer/language/phpmailer.lang-ja.php';
 
//言語、内部エンコーディングを指定
mb_language("japanese");
mb_internal_encoding("UTF-8");
 
// インスタンスを生成(引数に true を指定して例外 Exception を有効に)
$mail = new PHPMailer(true);
 
//日本語用設定
$mail->CharSet = "iso-2022-jp";
$mail->Encoding = "7bit";
 
//エラーメッセージ用言語ファイルを使用する場合に指定
$mail->setLanguage('ja', '../PHPMailer/language/');
 
try {
  //サーバの設定
  $mail->SMTPDebug = SMTP::DEBUG_SERVER;  // デバグの出力を有効に(テスト環境での検証用)
  $mail->isSMTP();   // SMTP を使用
  $mail->Host       = 'mail.example.com';  // SMTP サーバーを指定
  $mail->SMTPAuth   = true;   // SMTP authentication を有効に
  $mail->Username   = 'info@example.com';  // SMTP ユーザ名
  $mail->Password   = 'password';  // SMTP パスワード
  $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;  // 暗号化を有効に
  $mail->Port       = 465;  // TCP ポートを指定
 
  //受信者設定 
  //※名前などに日本語を使う場合は文字エンコーディングを変換
  //差出人アドレス, 差出人名
  $mail->setFrom('sender@example.com', mb_encode_mimeheader('差出人名'));  
  //受信者アドレス, 受信者名(受信者名はオプション)
  $mail->addAddress('someone@xxxx.com', mb_encode_mimeheader("受信者名")); 
  //追加の受信者(受信者名は省略可能なのでここでは省略)
  $mail->addAddress('someone@gmail.com'); 
  //返信用アドレス(差出人以外に別途指定する場合)
  $mail->addReplyTo('info@example.com', mb_encode_mimeheader("お問い合わせ")); 
  //Cc 受信者の指定
  $mail->addCC('foo@example.com'); 

//Attachments
if(!empty($_POST['fileDir'])) {
	$mail->addAttachment($_POST['fileDir']); 
}

  //コンテンツ設定
  $mail->isHTML(true);   // HTML形式を指定
  //メール表題(文字エンコーディングを変換)
  $mail->Subject = mb_encode_mimeheader('日本語メールタイトル'); 
  //HTML形式の本文(文字エンコーディングを変換)
  $mail->Body  = mb_convert_encoding('メッセージ <b>BOLD</b>',"JIS","UTF-8");  
  //テキスト形式の本文(文字エンコーディングを変換)
  $mail->AltBody = mb_convert_encoding('テキストメッセージ',"JIS","UTF-8"); 
 
  $mail->send();  //送信
  echo 'Message has been sent';
} catch (Exception $e) {
  //エラー(例外:Exception)が発生した場合
  echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

//添付ファイル削除
if(!empty($_POST['fileDir'])) {
	unlink($_POST['fileDir']);
	echo $_POST['fileDir'].'を削除しました';
}
?>

前の記事をベースに「ファイルがあるなら添付する」「ファイルがあるなら送信後に削除する」をそれぞれ書き込んである。

動作はこんな感じで、これをベースに項目をいじったりセキュリティをアレコレしておしまい。

導入時の注意

やっぱり気になるのは同じ名前のファイルを同時に送信されたらどうなるのっていうところ。同名だった場合は上書きされちゃうので非常にまずい。ユニークなんちゃらとかランダムなんちゃらを生成してファイル名に足したらいいんじゃないかな。送る際に文字列がそのままにならないようにまたリネームして。

これでいう確認画面で操作をやめられたら一時保存用のディレクトリにファイルが残りっぱなしになるのも難点。アップロードで終わってるからどうしようもないんで、古いやつは消すみたいなのをやるしかないのかなって感じがある。

ファイル名について

尚、全角でファイル名が付けられていた場合、エンコードが変わるので添付ファイルの名前がぶっ壊れる。
めんどくさいならこちらでリネームしちゃえばいい。

$mail->addAttachment($fileDir,'image.jpg');

添付ファイルの名称を変更したければ、例えばこんな感じでやれる。添付時のファイル名を後に追記すればいい。
拡張子まで書かないと拡張子なしのファイルになっちゃうのと、全部「image.jpg」でええんじゃ!ってとこで思考停止しちゃうと他の拡張子だったら非常にまずいから気をつけるように。

全角ファイル名対応版

添付ファイルのファイル名が全角だった場合文字化けする。
前項の感じでファイル名をこっちで半角英数にしちゃえば回避できるけど拡張子判別のなんちゃらもめんどくさいし、拡張子くっついてるしファイル名をそのまま使いたいじゃんっていう。

<?php
// HPMailer のクラスをグローバル名前空間(global namespace)にインポート
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

// HPMailer のインポート
require '../PHPMailer/src/PHPMailer.php';
require '../PHPMailer/src/SMTP.php';
require '../PHPMailer/src/Exception.php';
 
//エラーメッセージ用日本語言語ファイルを読み込む場合
require '../PHPMailer/language/phpmailer.lang-ja.php';
 
//言語、内部エンコーディングを指定
mb_language("japanese");
mb_internal_encoding("UTF-8");
 
// インスタンスを生成(引数に true を指定して例外 Exception を有効に)
$mail = new PHPMailer(true);
 
//日本語用設定
$mail->CharSet = "UTF-8";
$mail->Encoding = "base64";
 
//エラーメッセージ用言語ファイルを使用する場合に指定
$mail->setLanguage('ja', '../PHPMailer/language/');
 
try {
  //サーバの設定
  $mail->SMTPDebug = SMTP::DEBUG_SERVER;  // デバグの出力を有効に(テスト環境での検証用)
  $mail->isSMTP();   // SMTP を使用
  $mail->Host       = 'mail.example.com';  // SMTP サーバーを指定
  $mail->SMTPAuth   = true;   // SMTP authentication を有効に
  $mail->Username   = 'info@example.com';  // SMTP ユーザ名
  $mail->Password   = 'password';  // SMTP パスワード
  $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;  // 暗号化を有効に
  $mail->Port       = 465;  // TCP ポートを指定
 
  //受信者設定 
  //差出人アドレス, 差出人名
  $mail->setFrom('sender@example.com', '差出人名');  
  //受信者アドレス, 受信者名(受信者名はオプション)
  $mail->addAddress('someone@xxxx.com', '受信者名'); 
  //追加の受信者(受信者名は省略可能なのでここでは省略)
  $mail->addAddress('someone@gmail.com'); 
  //返信用アドレス(差出人以外に別途指定する場合)
  $mail->addReplyTo('info@example.com', '問い合わせ');
  //Cc 受信者の指定
  $mail->addCC('foo@example.com'); 

//Attachments
if(!empty($_POST['fileDir'])) {
	$mail->addAttachment($_POST['fileDir']); 
}

  //コンテンツ設定
  $mail->isHTML(true);   // HTML形式を指定
  //メール表題
  $mail->Subject = '日本語メールタイトル'; 
  //HTML形式の本文
  $mail->Body  = 'メッセージ <b>BOLD</b>';  
  //テキスト形式の本文
  $mail->AltBody = 'テキストメッセージ'; 
 
  $mail->send();  //送信
  echo 'Message has been sent';
} catch (Exception $e) {
  //エラー(例外:Exception)が発生した場合
  echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

//添付ファイル削除
if(!empty($_POST['fileDir'])) {
	unlink($_POST['fileDir']);
	echo $_POST['fileDir'].'を削除しました';
}
?>

探り探りやったので書式が本当に正しいかは謎だけどとりあえず。
htmlのmetaで宣言してるそっちが優先されるんじゃね?っていう安易な考えから、内部エンコーディングを全て「UTF-8」「base64」で統一。そうすると件名とか本文とか各所をエンコードする必要がないから直接内容をぶっこむ。ファイル名は多分、これらのエンコードを通さずに直接ぶっこんだからコードが違っちゃって文字化けしてたんじゃねーのってことで、それもUTF-8だろうと高をくくって触らずにそのまま。

使ってみたら文字化けしないで送れた。

多分、逆にファイル名の書式をどうのこうのすることもできたんじゃないかなって思ったりもしたんだけど、そうすれば追加するだけで済んだんじゃないのって思うんだけど、ソースがスッキリしたから結果オーライかなって。

ユニーク名でアップロードする安心感

よっぽど運が悪くなきゃぴったり同じタイミングで、しかも同じファイル名で送ってくるなんてことはないと思うんだけどね。一応被ってファイルが上書きされるのを回避する手段を組み立てる。

要するにファイル名がかぶらなければ上書きされないんだから、ファイル名にランダム文字数をぶっこんじゃえばいいじゃんっていう処置。

高まればもっと良いやりようはあるんだろうけど、とりあえずって感じでやっていく。ベースは上の方に書いてあるやつで、変更とか追加箇所を挙げていく。

入力ページ

<?php
if(!isset($_POST['uniqId'])){
    $uniqId = substr(str_shuffle('1234567890abcdefghijklmnopqrstuvwxyz'), 0, 36);
}
?>
<input type="hidden" name="uniqId" value="<?php echo $uniqId; ?>">

とにかくユニークIDを生成する。uniqid()でもなんでも作り方はあるので、とにかくはファイル名として成立する種類・文字数の範囲で作る。
ブラウザバックじゃなくてsubmitで擬似的に「戻る」をやる場合のために、すでに登録があったら再生成しないようにifとかで制限はしておく。

確認ページ

<?php
if(isset($_FILES['file'])){
    $uploadDir = './temporary/';
    $tempfile = $_FILES['file']['tmp_name']; // 一時ファイル名
    $filename = $_FILES['file']['name']; // 本来のファイル名
    $upFileName = $_POST['uniqId'].$_FILES['file']['name']; //アップロード用ファイル名
    if (is_uploaded_file($tempfile)) {
        if ( move_uploaded_file($tempfile , $uploadDir.$upFileName )) {
            echo $filename . "をアップロードしました。";
        } else {
            echo "ファイルをアップロードできません。";
        }
    } else {
        echo "ファイルが選択されていません。";
    }
}
?>
	<input type="text" name="fileName" value="<?php echo $filename; ?>">
	<input type="text" name="fileDir" value="<?php echo $uploadDir.$upFileName; ?>">

生成した文字列とファイル名をくっつけてアップロードする。
リネームのために元のファイル名を一緒にPOSTする。

送信ページ

// PHPMailer添付
if(!empty($_POST['fileDir'])) {
	$mail->addAttachment($_POST['fileDir'],$_POST['fileName']); 
}

送付するファイルの指定とファイル名の指定。
まあこんな感じですね。
やることはいっぱいあるけどやってることは難しい話じゃない。

コメント

タイトルとURLをコピーしました