端くれプログラマの備忘録 PHP [PHP] トラブルに強い問い合わせフォームを作る

[PHP] トラブルに強い問い合わせフォームを作る

前回のエントリで問い合わせフォームのトラブルについて書いた。トラブルフリーな問い合わせフォームは無いけれど、トラブルの影響を最小限に食い止めるための方策はいろいろある。すなわち「問い合わせをもらっていたのに知らなかった」という状況を避ける対処法。僕が気をつけているのは以下のこと。

1. mb_send_mail()の戻り値は必ずチェック

mb_send_mail()はmail()のラッパクラスで、mail()はphp.iniでパス指定されたsendmailコマンドを呼んでいる。sendmailコマンドがリクエストを正しく受け入れると正常ステータスを返すのだけど、実際にメールを送信するのはMTAの役割なので、メールが送られたかどうかをステータスから判断することはできない。だけど、少なくともリクエストは正しく受け入れられないとメールは送られないので、関数の戻り値は必ずチェックしてエラーはユーザに通知する。ユーザに「エラーで送られなかったんだ」と知らしめるために。

2. メール送り先は複数メールアドレスで、異なるドメインも含めて。

受信メールサーバーの不具合や、メール受信者のミスなどを想定し、メールの控えを複数のメールアドレスに送信しておく。全てのメールアドレスが同じドメインだとサーバー不具合で全不通になるので、異なるドメインのメールアドレスも混ぜておく。たとえばGmailとか。

3. バウンスメールを喪失しないようにenvelop senderを必ず指定。

受信メールサーバーの不具合などでメールがバウンスしてきたときに確実に通知を受け取れるように、sendmailコマンドの -f オプションを使ってenvelop senderを必ず指定する。もし指定しないのなら、どのメールアドレスがenvelop senderになるのかホスティングプロバイダのデフォルト設定を確認した上で、そのメールアドレスへの着信を常時チェックするようにする。

4. フォーム投稿をサーバー上のログファイルに記録して頻繁に目を通す。

メールは信頼できないものと割り切って、全ての投稿をサーバー上のログファイルに欠かさず記録する。たとえ不具合によりメールが一切機能しなくても、ログを見れば問い合わせがあったことと問い合わせ内容を知ることができる。ログファイルはウェブサーバーから見れないところに置くこと、破損しないように排他制御を行うこと、確実に書き込むために書き込みエラー時にはリトライを行うこと、そして必ず頻繁に管理者が目を通すこと、など基本的な点は遵守すること。僕は管理しているサイトのアクセスログを毎日朝一で閲覧するので、そのときにこのログにも必ず目を通すようにしている。

スクリプト例

<?php
// Contact form script
 
define('MAIL_TO', 'yyyy@example.com');
define('BCC', 'zzzz@example.com,abc@sample.com');
define('MAIL_SUBJ', 'Site ABC contact');
define('RETURN_PATH', 'xxxx@example.com');
define('LOG_FILE', '/xxxx/xxxx/xxxx/contact.log');
 
function sanitize($data)
{
    $charset = 'UTF-8';
    $original_charset = 'auto';
 
    foreach ($data as $key => $value) {
        // Encode the text.
        mb_internal_encoding($charset);
        $value = mb_convert_encoding($value, $charset, $original_charset);
        // Remove HTML tags.
        $value = strip_tags($value);
        // Alphanumrcic:hankaku, blank:hankaku, Katakana:zenkaku.
        $value = mb_convert_kana($value, "asK", $charset);
        // Remove the leading/tailing spaces.
        $value = trim($value);
 
        $data[$key] = $value;
    }
    return $data;
}
 
function logging($name, $email, $message, $result)
{
    $ip = @$_SERVER['REMOTE_ADDR'];
    if ($ip) {
        $remoteHost = gethostbyaddr($ip);
    } else {
        $remoteHost = $ip;
    }
 
    date_default_timezone_set("America/Los_Angeles");
 
    $data = "Date: " . date('Y/m/d H:i:s T') . "\n" .
        "Host: " . $remoteHost . "\n" .
        "Name: " . $name . "\n" .
        "Email: " . $email . "\n" .
        "Result: " . $result . "\n" .
        $message . "\n-----\n";
 
    $file = LOG_FILE;
    $fh = fopen($file, 'a');
    for ($i = 0; $i < 5; $i++) { // retry loop
        if (flock($fh, LOCK_EX)) {
            fwrite($fh, $data);
            fflush($fh);
            flock($fh, LOCK_UN);
            break; // success
        }
        sleep(1); // wait small period
    }
    fclose($fh);
    // TODO: notify if the logging failed
}
 
$thanks = false;
$errors = '';
 
$msgs = array();
$msgs['w_name'] = 'Please enter your name.';
$msgs['w_email'] = 'Please enter your email.';
$msgs['w_email2'] = 'Please enter a valid email.';
$msgs['w_message'] = 'Please enter your message';
$msgs['failed'] = 'Sending message failed.';
 
if ($_POST['submit']) {
    $_POST = sanitize($_POST);
 
    if (!isset($_POST['name']) || $_POST['name'] == '')
        $errors = $msgs['w_name'] . '<br />';
    if (!isset($_POST['email']) || $_POST['email'] == '')
        $errors = $msgs['w_email'] . '<br />';
    if (!isset($_POST['message']) || $_POST['message'] == '')
        $errors = $msg['w_message'];
 
    if ($errors == '') {
        mb_language("ja");
 
        $name = $_POST['name'];
        $email = $_POST['email'];
        $message = $_POST['message'];
 
        $to = MAIL_TO;
        $subject = MAIL_SUBJ;
        $body = $message . "\n";
 
        $subject = mb_convert_encoding($subject, "ISO-2022-JP", "AUTO");
        $subject = mb_encode_mimeheader($subject);
 
        $headers = "MIME-Version: 1.0 \n" ;
        $headers .= "From: "
            . mb_encode_mimeheader(mb_convert_encoding($name, "ISO-2022-JP", "AUTO"))
            . " <".$email."> \n";
        $headers .= "Bcc: " . BCC . "\n";
        $headers .= "Reply-To: "
            . mb_encode_mimeheader(mb_convert_encoding($name, "ISO-2022-JP", "AUTO"))
            . " <".$email."> \n";
 
        $parameters = "-f " . RETURN_PATH;
 
        if (mb_send_mail($to, $subject, $body, $headers, $parameters)) {
            $thanks = true;
        } else {
            $errors = $msgs['failed'];
        }
        logging($name, $email, $message, ($thanks ? "Success" : "Failed"));
    }
}
?>
 
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link href="style.css" rel="stylesheet" media="all" type="text/css" />
</head>
<body>
 
<?php if ($thanks) { ?>
    <div align="center">Thank you for your message. We wll get back to you ASAP.</div>
 
<?php } else { ?>
    <!-- Validation script -->
    <script Language="JavaScript">
    <!--
    function Validation(theForm) {
        if (theForm.name.value === "") {
            alert("<?php echo $msgs['w_name']; ?>");
            theForm.name.focus();
            return false;
        }
        if (theForm.email.value === "") {
            alert("<?php echo $msgs['w_email']; ?>");
            theForm.email.focus();
            return false;
        }
        if ((theForm.email.value.indexOf('@', 0) === -1) || (theForm.email.value.length < 5)) {
            alert("<?php echo $msgs['w_name2']; ?>");
            theForm.email.focus();
            return false;
        }
        if (theForm.message.value === "") {
            alert("<?php echo $msgs['w_message']; ?>");
            theForm.message.focus();
            return false;
        }
        return true;
    }
    //-->
    </script>
 
    <div align="center">Please feel free to contact us!</div>
 
    <!-- Error Message -->
    <?php if ($errors != '') echo '<p>'.$errors.'</p>'; ?>
 
    <!--- Form --->
    <form name="form1" method="post"
        action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>"
        onSubmit="return Validation(this);">
    <fieldset>
    <label>Your name:</label>
    <input type="text" name="name" size="20" value="<?php echo $_POST['name']; ?>" /><br />
    <label>E-mail address:</label>
    <input type="text" name="email" size="20" value="<?php echo $_POST['email']; ?>" /><br />
    <label>Message:</label>
    <textarea name="message" cols="25" rows="15"><?php echo $_POST['message']; ?></textarea>
    <br />
    <input name="submit" value="Send!" type="submit" />
    </fieldset>
    </form>
 
<?php } ?>
 
</body>
</html>