PHPで初歩的な「問い合わせフォーム」を作成する

WEBサイトには「問い合わせフォーム」がつきものです。

WEBサイトの管理者と連絡を取りたい場合。

「問い合わせフォーム」に名前やメールアドレス、用件などを入力してポチッと「送信」ボタンを押せば、入力内容がサイトの管理者に送信されます。

この「お問い合わせフォーム」。

WordPressの場合はプラグインで簡単に「問い合わせフォーム」を設置できますし、通常のWEBサイトでも無料で利用できるテンプレートを使うことで比較的簡単に導入することもできます。

でも、問い合わせフォームの仕組みを知るためにも、初歩的なお問い合わせフォームくらいは自分で作れる力は持っておきたいです。

ゆんつ
仕組みは知っておいたほうがいいですよね

そこで今日は、ごく初歩的な「問い合わせフォーム」を作ってみたいと思います。

初歩的な問い合わせフォームを作る

作成するのは次のようなお問い合わせフォームです。

  • フォームの項目は「名前」「メールアドレス」「用件」の3つ
  • フォームの入力内容のバリデーションはしない
  • 入力フォーム画面 → 確認画面 → 送信結果画面 の順に遷移する
  • 問い合わせフォームの入力内容を管理者宛てにメールする

こんな感じの初歩的なお問い合わせフォームを作りたいと思います。

form.php(入力フォーム画面)

イメージ

問い合わせフォーム
<?php
session_start();

//クリックジャッキングへの対策
header('X-Frame-Options: DENY');

//トークンの生成
$token = sha1(uniqid(rand(), true));

//トークンを$_SESSIONに格納し、それをキーとする
$_SESSION['key'] = $token;
?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>お問い合わせフォーム【デモ】</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  
<div id="contact">
  <div class="inner">
    <h2 class="contact-title">問い合わせフォーム</h2>
    <form class="contact-form" method="post" action="check.php">
      <ul class="contact-form-list">
        <li class="contact-form-item">
          <input type="text" name="name" placeholder="お名前" required>
        </li>
        <li class="contact-form-item">
          <input type="email" name="email" placeholder="メールアドレス" required>
        </li>
        <li class="contact-form-item">
          <textarea name="comment" placeholder="内容" required></textarea>
        </li>
        <li class="contact-form-item">
          <!-- 作成したトークンを次のページに引き継ぐ-->
          <input type="hidden" name="token" value="<?= $token ?>">
        </li>
        <li class="contact-form-item">
          <button class='submit' type="submit">確 認</button>
        </li>
      </ul>
    </form>
  </div><!-- /.inner -->
</div><!-- /#contact -->
  
</body>
</html>

まず最初にsessionを開始して、トークンを作成しそれを$_SESSIONのキーとして設定しています。

さらにトークンは「input type="hidden"」で次の「入力内容確認ページ」に引き継がれます。

「入力内容確認ページ」では、このトークンをきちんと持っているかを確認し、持っていない場合は以下のようなメッセージを表示してそこで処理を打ち切るようにしています。

トークンを引き継いでいくことによりフォームを経ずに確認画面に直接アクセスすることを防いでいるわけです。

またトークンとキーがあることで「クロスサイトリクエストフォージェリ(CSRF)」というセキュリティ上の問題に対する対策もしています。

さらに、トークンとキーは一番最後の工程の「送信完了」画面がリロードされた時に、入力内容を記載したメールが重複して送信されないようにするためにも利用します。

フォームのinput要素にはrequired属性を付けて入力必須にしてあります。

check.php(確認画面)

イメージ

入力内容確認画面
<?php
session_start();

//クリックジャッキングへの対策
header('X-Frame-Options: DENY');

//フォームを経ずにこのページに直接アクセスした場合は拒否する
if(!isset($_POST['token'])) {
  echo '不正なアクセスの可能性があります';
  exit;
}

//フォームに入力された値のエスケープ処理
function e($str) {
  return htmlspecialchars($str, ENT_QUOTES|ENT_HTML5, 'UTF-8');
}

//入力内容を$_SESSIONに格納する
$_SESSION['name'] = e($_POST['name']);
$_SESSION['email'] = e($_POST['email']);
$_SESSION['comment'] = e($_POST['comment']);
?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>入力内容の確認</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>

<div id="check">
  <h1 class=check-title>入力内容の確認</h1>
  <p class=check-lead>以下の内容で送信してよろしいですか?</p>

  <ul class="check-list">
    <li class="check-item">お名前 : <?= $_SESSION['name']; ?></li>
    <li class="check-item">メールアドレス : <?=  $_SESSION['email']; ?></li>
    <li class="check-item">内容 : <?= $_SESSION['comment']; ?></li>
  </ul>
  
  <form class="contact-form" method="post" action="send_mail.php">
    <div>
      <!-- 入力フォームから送られてきたトークンを次のページに引き継ぐ -->
      <input type="hidden" name="token" value="<?= $_POST['token'] ?>">
      <ul class="btn-box">
        <li>
          <button type="button" class='back' onclick="history.back()">戻 る</button>
        </li>
        <li>
          <button type="submit" class='submit'>送 信</button>
        </li>
      </ul>
    </div>
  </form>
  
</div><!-- /#check -->

</body>
</html>

フォームからトークンを引き継がずにこのページにアクセスした場合にはメッセージを表示して処理を打ち切っています。

トークンを引き継いできた場合は、入力内容をhtmlspecialcharsを使ってエスケープ処理をしたのちに$_SESSIONに格納しています。

そしてフォームページから引き継がれたトークンをさらに次の「送信結果画面」に「input type="hidden"」で引き継ぐようにしています。

send_mail.php(送信結果画面)

イメージ

送信結果画面
<?php
session_start();

//クリックジャッキングへの対策
header('X-Frame-Options: DENY');

//フォームを経ずにこのページに直接アクセスした場合は拒否する
if(!isset($_POST['token'])) {
  echo '不正なアクセスの可能性があります';
  exit;
}

//キーとトークンが一致したら管理者に入力内容がメールで送られる
if($_SESSION['key'] === $_POST['token']) {
  $name = $_SESSION['name'];
  $email = $_SESSION['email'];
  $comment = $_SESSION['comment'];

  //メールの送り先
  $to = '送信先のメールアドレス';

  //メールの件名
  $subject = $name . 'さんからの入力フォームでの送信です';

  //メール本文
  $comment = '名前:' . $name . "\r\n\r\n" . 'メールアドレス:' . $email . "\r\n\r\n" . '内容:' . $comment;
  
  //メールヘッダー
  $header = 'From: ' . mb_encode_mimeheader($name). ' <' . $email. '>';
  
  //文字化け対策
  mb_language('ja');
  mb_internal_encoding('UTF-8');
  
  if(mb_send_mail($to, $subject, $comment, $header)) {

    //メールが送信出来たら$_SESSIONの値をクリア
    $_SESSION = array();

    //メールが送信出来たらセッションを破棄
    session_destroy();

    $message = '送信しました';
  } else {
    $message = '送信に失敗しました';
  }
} else {
  $message = 'キーとトークンが一致しません';
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>送信結果</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div id="result">
    <!-- 送信結果を表示 -->
    <h1><?= $message ?></h1>
    <div>
      <a href="form.php">元のページに戻る</a>
    </div>
  </div><!-- /#result -->
</body>
</html>

「入力内容確認画面」と同様に、フォームや確認画面を経ずに直接このページにアクセスしてきた場合にはメッセージを表示して処理を打ち切っています。

そしてキーとトークンが一致した場合にのみ「mb_send_mail」関数を利用して管理人にメールを送信します。

メールを送信したら$_SESSIONに格納されている様々な値をクリアします。

その後セッションを破棄。

セッションの値のクリアにより、フォーム画面で設定したキーがなくなります。

これでトークンとキーは一致しなくなり、この画面がリロードされたとしてもメールの送信が行われなくなります。

ゆんつ
二重送信の防止!

送信されるメールの内容は以下のような感じです。

送信されるメールのイメージ

メール送信に失敗した場合の画面は以下のような感じです。

送信失敗

キーとトークンが一致しない場合(ページをリロードした場合など)の画面は以下のような感じです。

キーとトークンの不一致

フォームのデモページを作成しました。

フォームのデモページ

デモページではメール送信部分の処理は削除し、メッセージだけが表示されるようになっています。

ですので「送信しました」と出ても何も送信されないようにしていますのでご安心ください。

ゆんつ
実際にメールが来たら困っちゃうからね

フォームのCSSはデモページを開発者ツールで開いて、ご確認いただければと思います。

まとめ

以上、初歩的な「問い合わせフォーム」の作り方について書いてみました。

ポイントは「トークン」と「キー」だと思います。

トークンとキーを使うことで「クロスサイトリクエストフォージェリ(CSRF)」と送信結果ページがリロードされた場合の2重送信の対策となります。

実際のサイトに問い合わせフォームを実装するには、さらに入力値のバリデーションなどが必要になってきます。

「問い合わせフォーム」の大まかな仕組みを理解するための参考になれば幸いです。

それでは、またー。