PHP Logo PHP メールフォームの作り方

PHP を使った基本的なメールフォーム(コンタクトフォーム)の作り方についての覚書です。以下で取り扱っている例は確認画面なしのコンタクトフォームです。

入力された値の検証は基本的に PHP で行いますが HTML5 の機能を使ったものや jQuery を使ったものも掲載しています。

また、再読み込みによる二重送信の防止や自動返信の方法、PHPMailer を使ったメールの送信方法、reCAPTCHA v2/v3 を使ったスパム対策の実装方法についても記載しています。

関連ページ:コンタクトフォーム(お問い合わせページ)の作り方(確認画面あり)

更新日:2020年04月11日

作成日:2020年3月27日

基本的なコンタクトフォーム

一般的なコンタクトフォームは form 要素(HTML)を使って記述する入力フォームと PHP などのサーバ側の言語で入力値の検証やメールの送信処理などを記述するスクリプトで構成されています。

以下の例ではサーバ側の処理は PHP を使っています。

入力フォーム(HTML)
  • HTML の form 要素を使って記述
  • form 要素の action 属性に送信先の PHP ファイルを指定(自分自身へ送信することも可能)
  • method 属性で送信メソッド(POST や GET)を指定
  • HTML5 を使うと入力時や送信時に検証を行うことが可能

関連ページ:HTML フォームの設置

サーバ側の処理(PHP)
入力フォームから送信された値を受け取り以下のような処理を実行します
  • 入力された値を検証(正しい形式かや不正な値がないかなど)
  • 問題がなければ mb_send_mail() などを使ってメールを送信

関連ページ:PHP を使ったフォームの操作

jQuery(JavaScript)を使うと、入力時に値がサーバへ送信される前に値の検証を行うことができます。

関連ページ:jQuery フォームの操作や使い方

以下は1つのファイルで作成する入力画面のみのコンタクトフォームの作り方の例です。

HTML5 の機能を使ったフォーム

HTML5 の新しい type 属性やフォームの検証機能と PHP の filter_var() 関数を使って値を検証するコンタクトフォームです。

HTML5 を使っているので HTML5 に対応していないブラウザでは機能しません(caniuse.com)。

必須項目に入力して送信ボタンをクリックするとメールを送信し、問題なく送信できれば「送信成功」と同じページに表示し、何らかの理由で送信できなければ「送信失敗」と表示するだけです。

以下のサンプルでは実際にはメールは送信されません。

サンプルを別ページで開く

以下がコードの全文です。

contact.php
<?php 
//エスケープ処理やデータチェックを行う関数のファイルの読み込み
require '../libs/functions.php';

//POSTされたデータがあれば変数に格納、なければ NULL(変数の初期化)
$name = isset( $_POST[ 'name' ] ) ? $_POST[ 'name' ] : NULL;
$email = isset( $_POST[ 'email' ] ) ? $_POST[ 'email' ] : NULL;
$tel = isset( $_POST[ 'tel' ] ) ? $_POST[ 'tel' ] : NULL;
$subject = isset( $_POST[ 'subject' ] ) ? $_POST[ 'subject' ] : NULL;
$body = isset( $_POST[ 'body' ] ) ? $_POST[ 'body' ] : NULL;

//送信ボタンが押された場合の処理
if (isset($_POST['submitted'])) {

  //POSTされたデータに不正な値がないかを別途定義した checkInput() 関数で検証 
  $_POST = checkInput( $_POST ); 
  
  //filter_var を使って値をフィルタリング
  if(isset($_POST['name'])) {
    //スクリプトタグがあれば除去
    $name = filter_var($_POST['name'], FILTER_SANITIZE_STRING);
  }
  
  if(isset($_POST['email'])) {
    //全ての改行文字を削除
    $email = str_replace(array("\r", "\n", "%0a", "%0d"), '', $_POST['email']);
    //E-mail の形式にフィルタ
    $email = filter_var($email, FILTER_VALIDATE_EMAIL);
  }
  
  if(isset($_POST['tel'])) {
    //数値の形式にフィルタ(数字、+ 、- 記号 以外を除去)
    $tel = filter_var($_POST['tel'], FILTER_SANITIZE_NUMBER_INT);
  }
  
  if(isset($_POST['subject'])) {
    $subject = filter_var($_POST['subject'], FILTER_SANITIZE_STRING);
  }
  
  if(isset($_POST['body'])) {
    $body = filter_var($_POST['body'], FILTER_SANITIZE_STRING);
  }
  
  //POST でのリクエストの場合
  if ($_SERVER['REQUEST_METHOD']==='POST') {
  
    //メールアドレス等を記述したファイルの読み込み
    require '../libs/mailvars.php'; 

    //メール本文の組み立て。値は h() でエスケープ処理
    $mail_body = 'コンタクトページからのお問い合わせ' . "\n\n";
    $mail_body .=  "お名前: " .h($name) . "\n";
    $mail_body .=  "Email: " . h($email) . "\n"  ;
    $mail_body .=  "お電話番号: " . h($tel) . "\n\n" ;
    $mail_body .=  "<お問い合わせ内容>" . "\n" . h($body);

    //-------- sendmail を使ったメールの送信処理 ------------

    //メールの宛先(名前<メールアドレス> の形式)。値は mailvars.php に記載
    $mailTo = mb_encode_mimeheader(MAIL_TO_NAME) ."<" . MAIL_TO. ">";

    //Return-Pathに指定するメールアドレス
    $returnMail = MAIL_RETURN_PATH; //
    //mbstringの日本語設定
    mb_language( 'ja' );
    mb_internal_encoding( 'UTF-8' );

    // 送信者情報(From ヘッダー)の設定
    $header = "From: " . mb_encode_mimeheader($name) ."<" . $email. ">\n";
    $header .= "Cc: " . mb_encode_mimeheader(MAIL_CC_NAME) ."<" . MAIL_CC.">\n";
    $header .= "Bcc: <" . MAIL_BCC.">";

    //メールの送信結果を変数に代入 (サンプルなのでコメントアウト)
    if ( ini_get( 'safe_mode' ) ) {
      //セーフモードがOnの場合は第5引数が使えない
      $result = mb_send_mail( $mailTo, $subject, $mail_body, $header );
    } else {
      $result = mb_send_mail( $mailTo, $subject, $mail_body, $header, '-f' . $returnMail );
    }

    //メールが送信された場合の処理
    if ( $result ) {
      //空の配列を代入し、すべてのPOST変数を消去
      $_POST = array(); 

      //変数の値も初期化
      $name = '';
      $email = '';
      $tel = '';
      $subject = '';
      $body = '';
      
      //再読み込みによる二重送信の防止
      $params = '?result='. $result;
      $url = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://').$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; 
      header('Location:' . $url . $params);
      exit;
    } 
  }
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>コンタクトフォーム</title>
<link href="../bootstrap4/css/bootstrap.min.css" rel="stylesheet">
<link href="style.css" rel="stylesheet">
</head>
<body>
<div class="container">
  <h2 class="">お問い合わせフォーム</h2>
  <?php  if ( isset($_GET['result']) && $_GET['result'] ) : // 送信が成功した場合?>
  <h4>送信完了!</h4>
  <p>送信完了いたしました。</p>
  <hr>
  <?php elseif (isset($result) && !$result ): // 送信が失敗した場合? ?>
  <h4>送信失敗</h4>
  <p>申し訳ございませんが、送信に失敗しました。</p>
  <p>しばらくしてもう一度お試しになるか、メールにてご連絡ください。</p>
  <p>メール:<a href="mailto:info@example.com">Contact</a></p>
  <hr>
  <?php endif; ?>
  <p>以下のフォームからお問い合わせください。</p>
  <form id="form" method="post">
    <div class="form-group">
      <label for="name">お名前(必須)</label>
      <input type="text" class="form-control" id="name" name="name" placeholder="氏名" required value="<?php echo h($name); ?>">
    </div>
    <div class="form-group">
      <label for="email">Email(必須)</label>
      <input  type="email" class="form-control" id="email" name="email" placeholder="Email アドレス" required value="<?php echo h($email); ?>">
    </div>
    <div class="form-group">
      <label for="tel">お電話番号(半角英数字)</label>
      <input type="tel" class="form-control" id="tel" name="tel" value="<?php echo h($tel); ?>" placeholder="お電話番号(半角英数字でご入力ください)">
    </div>
    <div class="form-group">
      <label for="subject">件名(必須)</label>
      <input type="text" class="form-control" id="subject" name="subject" placeholder="件名" required value="<?php echo h($subject); ?>">
    </div>
    <div class="form-group">
      <label for="body">お問い合わせ内容(必須)</label>
      <textarea class="form-control" id="body" name="body" placeholder="お問い合わせ内容(1000文字まで)をお書きください" required rows="3"><?php echo h($body); ?></textarea>
    </div>
    <button name="submitted" type="submit" class="btn btn-primary">送信</button>
  </form>
</div>
</body>
</html>

PHP の部分では、値を検証する関数やエスケープ処理をする関数を記述した functions.php やメールの送信先の情報を記述した mailvars.php を require で読み込んでいます。

この例では libs というフォルダを作成して保存しています。

├── contact
│   ├── contact.php
│   └── style.css
└── libs
    ├── .htaccesss
    ├── functions.php
    └── mailvars.php

libs フォルダには外部からアクセスできないように以下のような .htaccesss を配置しています。

.htaccess
deny from all
functions.php
<?php
//エスケープ処理を行う関数
function h($var) {
  if(is_array($var)){
    //$varが配列の場合、h()関数をそれぞれの要素について呼び出す(再帰)
    return array_map('h', $var);
  }else{
    return htmlspecialchars($var, ENT_QUOTES, 'UTF-8');
  }
}

//入力値に不正なデータがないかなどをチェックする関数
function checkInput($var){
  if(is_array($var)){
    return array_map('checkInput', $var);
  }else{
    //NULLバイト攻撃対策
    if(preg_match('/\0/', $var)){  
      die('不正な入力です。');
    }
    //文字エンコードのチェック
    if(!mb_check_encoding($var, 'UTF-8')){ 
      die('不正な入力です。');
    }
    //改行、タブ以外の制御文字のチェック
    if(preg_match('/\A[\r\n\t[:^cntrl:]]*\z/u', $var) === 0){  
      die('不正な入力です。制御文字は使用できません。');
    }
    return $var;
  }
}
mailvars.php
<?php
//メールの宛先(To)のメールアドレス
define('MAIL_TO', "info@xxxxx.com");
//メールの宛先(To)の名前  
define('MAIL_TO_NAME', "宛先の名前 ");
//Cc の名前
define('MAIL_CC', 'xxxx@xxxxxx.com');
//Cc の名前
define('MAIL_CC_NAME', 'Cc宛先名');
//Bcc
define('MAIL_BCC', 'xxxxx@xxxxx.com');
//Return-Pathに指定するメールアドレス
define('MAIL_RETURN_PATH', 'info@xxxxxx.com');
//自動返信の返信先名前(自動返信を設定する場合)
define('AUTO_REPLY_NAME', '返信先名前');

また、以下の例では Bootstrap4 の CSS を読み込んで以下のようなスタイルを指定しています。

/* input 要素 */
#name, #email, #subject, #email_check, #tel {
  max-width:400px;
}
#body {
  max-width: 640px;
}
/* エラー表示 */
p.error, span.error {
  color: red;
}
/* フォーム要素(Bootstrap4 のスタイルを上書き) */
.form-control {
  border-radius: 0px;
  background-color: #fdfdfd;
  font-size: 14px;
}
.form-control:focus {
  border-color: #aadbe8;
  outline: 0;
  -webkit-box-shadow: inset 0 0px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.4);
  box-shadow: inset 0 0px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.4);
  background-color:#fff;
}
/* Google Chrome, Safari, Opera 15+, Android, iOS */
::-webkit-input-placeholder {
  font-size: 13px; 
}
/* Firefox 18- */
:-moz-placeholder {
  font-size: 13px; }
/* Firefox 19+ */
::-moz-placeholder {
  font-size: 13px; }
/* IE 10+ */
:-ms-input-placeholder {
  font-size: 13px; }
::placeholder{ 
  font-size: 13px;
}
textarea.form-control {
  height: 200px;
}
HTML

フォームの送信先はこのファイル自信なので、form 要素の action 属性は省略しています。またメソッドは POST メソッドを指定しています(16行目)。

4〜14行目は送信ボタンがクリックされてメール送信を実行した際に結果を表示します。

isset() で送信結果が定義されているかを判定して表示します(送信結果は送信ボタンをクリックしていない場合は定義されていないので表示されません)。

再読み込みによる二重送信を防止するため、送信が成功した場合は、$_GET['result']で送信結果(1 = true)を取得します。

送信が失敗した場合は変数 $result に送信結果(false)が入っています。

input 要素には入力する内容によって type 属性(例:type="email")を指定して入力時の検証をしています(HTML5 type 属性)。

また、必須項目には required 属性を指定して入力していないと送信できないようになっています(HTML5 Validation)。

HTML5 での検証は補助的なもので、PHP を使った検証が必要です。

contact.php HTML 部分抜粋
<body>
<div class="container">
  <h2 class="">お問い合わせフォーム</h2>
  <?php  if ( isset($_GET['result']) && $_GET['result'] ) : // 送信が成功した場合?>
  <h4>送信完了!</h4>
  <p>送信完了いたしました。</p>
  <hr>
  <?php elseif (isset($result) && !$result ): // 送信が失敗した場合 ?>
  <h4>送信失敗</h4>
  <p>申し訳ございませんが、送信に失敗しました。</p>
  <p>しばらくしてもう一度お試しになるか、メールにてご連絡ください。</p>
  <p>メール:<a href="mailto:info@example.com">Contact</a></p>
  <hr>
  <?php endif; ?>
  <p>以下のフォームからお問い合わせください。</p>
  <form id="form" method="post">
    <div class="form-group">
      <label for="name">お名前(必須)</label>
      <input type="text" class="form-control" id="name" name="name" placeholder="氏名" required value="<?php echo h($name); ?>">
    </div>
    <div class="form-group">
      <label for="email">Email(必須)</label>
      <input  type="email" class="form-control" id="email" name="email" placeholder="Email アドレス" required value="<?php echo h($email); ?>">
    </div>
    <div class="form-group">
      <label for="tel">お電話番号(半角英数字)</label>
      <input type="tel" class="form-control" id="tel" name="tel" value="<?php echo h($tel); ?>" placeholder="お電話番号(半角英数字でご入力ください)">
    </div>
    <div class="form-group">
      <label for="subject">件名(必須)</label>
      <input type="text" class="form-control" id="subject" name="subject" placeholder="件名" required value="<?php echo h($subject); ?>">
    </div>
    <div class="form-group">
      <label for="body">お問い合わせ内容(必須)</label>
      <textarea class="form-control" id="body" name="body" placeholder="お問い合わせ内容(1000文字まで)をお書きください" required rows="3"><?php echo h($body); ?></textarea>
    </div>
    <button name="submitted" type="submit" class="btn btn-primary">送信</button>
  </form>
</div>
</body>
PHP

6〜10行目ではフォームから送られたデータがあればその値を変数に代入し、まだ送られてきていなければ NULL で初期化しています。

送信ボタン(name="submitted")がクリックされてフォームに入力されたデータが送信されると、$_POST['submitted']に値が設定されるので、13行目以降の処理が実行されます。

16行目は functions.php に記述してある関数 checkInput() で送信された値を検証しています。

19〜42行目では PHP の関数 filter_var() で入力された値を検証(必要に応じて変換)しています。

45行目は、再読み込みによる二重送信防止のため、リクエストが POST かどうかを判定しています。

50〜55行目では送信するメールの本文を生成しています。フォームから受け取った値は、念の為 functions.php に記述してある h() 関数でエスケープ処理しています。

59〜79行目は mb_send_mail() を使ったメールの送信処理です。

mb_send_mail() は成功した場合に TRUE を、失敗した場合に FALSE を返すので、その値を変数 $result に代入して、フォームが送信されてページが再読み込みされた際に、HTML ではその値をもとにメール送信の結果を表示します。

問題なくメールが送信されれば、全ての $_POST 及び変数の値を消去しています(83〜91行目)。送信に失敗した場合は、それらの値は残しておいて再送信する際に利用できるようにしています。

contact.php PHP 部分抜粋
<?php 
//エスケープ処理やデータチェックを行う関数のファイルの読み込み
require '../libs/functions.php';

//POSTされたがあればデータを変数に格納(変数の初期化)
$name = isset( $_POST[ 'name' ] ) ? $_POST[ 'name' ] : NULL;
$email = isset( $_POST[ 'email' ] ) ? $_POST[ 'email' ] : NULL;
$tel = isset( $_POST[ 'tel' ] ) ? $_POST[ 'tel' ] : NULL;
$subject = isset( $_POST[ 'subject' ] ) ? $_POST[ 'subject' ] : NULL;
$body = isset( $_POST[ 'body' ] ) ? $_POST[ 'body' ] : NULL;

//送信ボタンが押された場合の処理
if (isset($_POST['submitted'])) {

  //POSTされたデータに不正な値がないかを検証 
  $_POST = checkInput( $_POST ); 
  
  //filter_var を使って値をフィルタリング
  if(isset($_POST['name'])) {
    //スクリプトタグがあれば除去
    $name = filter_var($_POST['name'], FILTER_SANITIZE_STRING);
  }
  
  if(isset($_POST['email'])) {
    //全ての改行文字を削除
    $email = str_replace(array("\r", "\n", "%0a", "%0d"), '', $_POST['email']);
    //E-mail の形式にフィルタ
    $email = filter_var($email, FILTER_VALIDATE_EMAIL);
  }
  
  if(isset($_POST['tel'])) {
    //数値の形式にフィルタ(数字、+ 、- 記号 以外を除去)
    $tel = filter_var($_POST['tel'], FILTER_SANITIZE_NUMBER_INT);
  }
  
  if(isset($_POST['subject'])) {
    $subject = filter_var($_POST['subject'], FILTER_SANITIZE_STRING);
  }
  
  if(isset($_POST['body'])) {
    $body = filter_var($_POST['body'], FILTER_SANITIZE_STRING);
  }
  
  //POST でのリクエストの場合
  if ($_SERVER['REQUEST_METHOD']==='POST') {
  
    //メールアドレス等を記述したファイルの読み込み
    require '../libs/mailvars.php'; 

    //メール本文の組み立て。値は h() でエスケープ処理
    $mail_body = 'コンタクトページからのお問い合わせ' . "\n\n";
    $mail_body .=  "お名前: " .h($name) . "\n";
    $mail_body .=  "Email: " . h($email) . "\n"  ;
    $mail_body .=  "お電話番号: " . h($tel) . "\n\n" ;
    $mail_body .=  "<お問い合わせ内容>" . "\n" . h($body);

    //--------sendmailを使ったメールの送信処理------------

    //メールの宛先(名前<メールアドレス> の形式)。値は mailvars.php に記載
    $mailTo = mb_encode_mimeheader(MAIL_TO_NAME) ."<" . MAIL_TO. ">";

    //Return-Pathに指定するメールアドレス
    $returnMail = MAIL_RETURN_PATH; //
    //mbstringの日本語設定
    mb_language( 'ja' );
    mb_internal_encoding( 'UTF-8' );

    // 送信者情報(From ヘッダー)の設定
    $header = "From: " . mb_encode_mimeheader($name) ."<" . $email. ">\n";
    $header .= "Cc: " . mb_encode_mimeheader(MAIL_CC_NAME) ."<" . MAIL_CC.">\n";
    $header .= "Bcc: <" . MAIL_BCC.">";

    //メールの送信結果を変数に代入 (サンプルなのでコメントアウト)
    if ( ini_get( 'safe_mode' ) ) {
      //セーフモードがOnの場合は第5引数が使えない
      $result = mb_send_mail( $mailTo, $subject, $mail_body, $header );
    } else {
      $result = mb_send_mail( $mailTo, $subject, $mail_body, $header, '-f' . $returnMail );
    }

    //メールが送信された場合の処理
    if ( $result ) {
      //空の配列を代入し、すべてのPOST変数を消去
      $_POST = array(); 

      //変数の値も初期化
      $name = '';
      $email = '';
      $tel = '';
      $subject = '';
      $body = '';
      
      //再読み込みによる二重送信の防止
      $params = '?result='. $result;
      $url = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://').$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; 
      header('Location:' . $url . $params);
      exit;
    } 
  }
}
?>

PHP でのフォームの検証方法などについては以下を御覧ください。

フォームの検証(バリデーション)

二重送信防止

送信ボタンをクリックして送信完了後に、ページを再読み込みすると「フォーム再送信の確認」メッセージが出て「続行」をクリックするとフォームの入力欄は空ですが、同じ内容が二重に送信されてしまいます。

これを回避する1つの方法は、送信完了後に header() 関数で Location ヘッダを使用して自分自身のページへリダイレクトさせます。

header() 関数は HTTP ヘッダを送信するための関数で、Location ヘッダに指定した URL にジャンプ(移動)させることができ、これは GET リクエストが使われます。

フォームの送信には POST リクエストを使うので、$_SERVER['REQUEST_METHOD']を使って POST でのリクエストの場合のみメールを送信するようにします。

そしてメール送信完了後(送信が成功した場合)に header() 関数で自分自身へリダイレクトします。 自分自身の URL は $_SERVER(環境変数)を使って組み立てています。

その際に、送信結果を表示するために必要な値 $result(送信結果の値)を GET リクエストのパラメータに付加します。

//POST でのリクエストの場合のみ以下を実行
if ($_SERVER['REQUEST_METHOD']==='POST') {

・・・メールの送信処理・・・ 

//メール送信が成功した場合の処理
if ( $result ) {
  
  ・・・

  //GET リクエストに追加するパラメータ
  $params = '?result='. $result;
  //自分自身の URL
  $url = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://').$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; 
  header('Location:' . $url . $params);
  exit;
}

HTML では送信が成功した場合は $_GET['result'] に 1(true)が入っているので、「送信完了!」と表示します。

送信が失敗した場合は、リダイレクトは行われず $result に false が入っているので「送信失敗」と表示します。

<?php  if ( isset($_GET['result']) && $_GET['result'] ) : // 送信が成功した場合?>
<h4>送信完了!</h4>
<p>送信完了いたしました。</p>
<hr>
<?php elseif (isset($result) && !$result ): // 送信が失敗した場合 ?>
<h4>送信失敗</h4>
<p>申し訳ございませんが、送信に失敗しました。</p>
<p>しばらくしてもう一度お試しになるか、メールにてご連絡ください。</p>
<p>メール:<a href="mailto:info@example.com">Contact</a></p>
<hr>
<?php endif; ?>
完了ページを表示

送信が完了(成功)した場合には、別途用意した完了ページを表示する例です。

サンプルを別ページで開く

この例では以下のようなシンプルな完了ページ(complete.php)を別途用意します。実際にはご案内やリンク、画像などを表示することが考えられます。

complete.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>完了画面</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<div class="container">
  <h1 class="">完了画面</h1>
  <h2>送信完了いたしました。</h2>
  <p>ありがとうございました。</p>
</div>
</body>
</html>

コンタクトフォームの PHP の部分は、前述の例とほぼ同じですが、送信が成功した場合の二重送信防止の部分で自分自身へのリダイレクトを完了ページへのリダイレクトに変更します。

contact1.php PHP 抜粋
//メールが送信された場合の処理
if ( $result ) {
  //空の配列を代入し、すべてのPOST変数を消去
  $_POST = array(); 

  //変数の値も初期化
  $name = '';
  $email = '';
  $tel = '';
  $subject = '';
  $body = '';

  //完了ページ(complete.php)へリダイレクト
  $url = 'complete.php'; 
  header('Location:' . $url );
  exit;
} 

HTML 部分は送信が成功した場合の部分が不要になります。

contact1.php HTML 抜粋
<div class="container">
  <h2 class="">お問い合わせフォーム</h2>
  <?php if (isset($result) && !$result ): // 送信が失敗した場合 ?>
  <h4>送信失敗</h4>
  <p>申し訳ございませんが、送信に失敗しました。</p>
  <p>しばらくしてもう一度お試しになるか、メールにてご連絡ください。</p>
  <p>メール:<a href="mailto:info@example.com">Contact</a></p>
  <hr>
  <?php endif; ?>
  <p>以下のフォームからお問い合わせください。</p>
  <form id="form" method="post">
  ・・・以下省略・・・
  
完了ページに入力内容を表示

以下は完了ページにフォームへの入力内容を表示する例です。

但し、完了ページへのリダイレクトは GET メソッドを使用するので、入力内容が URL に表示されてしまうので、内容によりこの方法を使うかどうかを検討する必要があります。

サンプルを別ページで開く

前述の例との違いは、送信が成功した場合の処理で、変数の値を初期化する前にそれぞれの値をパラメータ用の変数($params)に格納します。

[注意]これらの情報はパラメータとして URL に表示されます。

以下では全ての情報をパラメータに渡していますが、例えば名前や電話番号、メールアドレスは除外するなどを検討(配慮)する必要があります。

//メールが送信された場合の処理
if ( $result ) {
  //空の配列を代入し、すべてのPOST変数を消去
  $_POST = array(); 
  
  //リダイレクトの URL に付加するパラメータ用の変数
  $params = '?';
  $params .= 'name='. h($name);
  $params .= '&email='. h($email);
  $params .= '&tel='. h($tel);
  $params .= '&subject='. h($subject);
  $params .= '&body='. h($body);

  //変数の値も初期化
  $name = '';
  $email = '';
  $tel = '';
  $subject = '';
  $body = '';

  //完了画面の URL にパラメータを付加してリダイレクト
  $url = 'complete_2.php'; 
  header('Location:' . $url . $params);
  exit;
} 

全コードを別ページで開く

完了ページでは、パラメータから値を取得して表示します。

complete_2.php
<body>
<div class="container">
  <h2 class="">完了画面</h2>
  <h4>送信完了いたしました。</h4>
  <p>この度は、お問い合わせ頂き誠にありがとうございます。</p>
  <p>以下の内容でお問い合わせを受け付けました。</p>
  <div class="table-responsive">
    <table class="table table-bordered  table-hover">
      <tr>
        <th>お名前</th>
        <td><?php 
          if ( isset($_GET['name']) && $_GET['name'] ) echo $_GET['name'];
          ?></td>
      </tr>
      <tr>
        <th>メールアドレス</th>
        <td><?php 
          if ( isset($_GET['email']) && $_GET['email'] ) echo $_GET['email']; 
          ?></td>
      </tr>
      <tr>
        <th>お電話番号</th>
        <td><?php 
          if ( isset($_GET['tel']) && $_GET['tel'] ) echo $_GET['tel']; 
          ?></td>
      </tr>
      <tr>
        <th>件名</th>
        <td><?php 
          if ( isset($_GET['subject']) && $_GET['subject'] ) echo $_GET['subject'];
          ?></td>
      </tr>
      <tr>
        <th>お問い合わせ内容</th>
        <td><?php 
          if ( isset($_GET['body']) && $_GET['body'] ) echo $_GET['body']; 
          ?></td>
      </tr>
    </table>
  </div>
</div>
</body>

HTML5 の検証機能を使わない例

以下のコンタクトフォームも前述の例と同様1つの PHP ファイルで作成していて、入力画面に送信結果を表示する例です。

こちらは HTML5 の機能を使用していないので、HTML5 未対応のブラウザでも機能します。

検証は PHP を使って行っています。

以下のサンプルでは実際にはメールは送信されません。

サンプルを別ページで開く

前述の例とフォームの内容はほぼ同じですが、この例では E-mail の確認用の入力を追加しています。

以下がコードの全文です。

contact2.php
<?php 
//エスケープ処理やデータチェックを行う関数のファイルの読み込み
require '../libs/functions.php';

//POSTされたデータを変数に格納
$name = isset( $_POST[ 'name' ] ) ? $_POST[ 'name' ] : NULL;
$email = isset( $_POST[ 'email' ] ) ? $_POST[ 'email' ] : NULL;
$email_check = isset( $_POST[ 'email_check' ] ) ? $_POST[ 'email_check' ] : NULL;
$tel = isset( $_POST[ 'tel' ] ) ? $_POST[ 'tel' ] : NULL;
$subject = isset( $_POST[ 'subject' ] ) ? $_POST[ 'subject' ] : NULL;
$body = isset( $_POST[ 'body' ] ) ? $_POST[ 'body' ] : NULL;

//POSTされたデータを整形(前後にあるホワイトスペースを削除)
$name = trim( $name );
$email = trim( $email );
$email_check = trim( $email_check );
$tel = trim( $tel );
$subject = trim( $subject );
$body = trim( $body );

if (isset($_POST['submitted'])) {

  //POSTされたデータをチェック  
  $_POST = checkInput( $_POST ); 

  //エラーメッセージを保存する配列の初期化
  $error = array();
  
  //値の検証
  if ( $name == '' ) {
    $error['name'] = '*お名前は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,30}\z/u', $name ) == 0 ) {
    $error['name'] = '*お名前は30文字以内でお願いします。';
  }
  if ( $email == '' ) {
    $error['email'] = '*メールアドレスは必須です。';
  } else { //メールアドレスを正規表現でチェック
    $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/uiD';
    if ( !preg_match( $pattern, $email ) ) {
      $error['email'] = '*メールアドレスの形式が正しくありません。';
    }
  }
  if ( $email_check == '' ) {
    $error['email_check'] = '*確認用メールアドレスは必須です。';
  } else { //メールアドレスを正規表現でチェック
    if ( $email_check !== $email ) {
      $error['email_check'] = '*メールアドレスが一致しません。';
    }
  }
  if ( preg_match( '/\A[[:^cntrl:]]{0,30}\z/u', $tel ) == 0 ) {
    $error['tel'] = '*電話番号は30文字以内でお願いします。';
  }
  if ( $tel != '' && preg_match( '/\A\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}\z/u', $tel ) == 0 ) {
    $error['tel_format'] = '*電話番号の形式が正しくありません。';
  }
  if ( $subject == '' ) {
    $error['subject'] = '*件名は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,100}\z/u', $subject ) == 0 ) {
    $error['subject'] = '*件名は100文字以内でお願いします。';
  }
  if ( $body == '' ) {
    $error['body'] = '*内容は必須項目です。';
    //制御文字(タブ、復帰、改行を除く)でないことと文字数をチェック
  } else if ( preg_match( '/\A[\r\n\t[:^cntrl:]]{1,1050}\z/u', $body ) == 0 ) {
    $error['body'] = '*内容は1000文字以内でお願いします。';
  }
  
  //エラーがなく且つ POST でのリクエストの場合
  if (empty($error) && $_SERVER['REQUEST_METHOD']==='POST') {
    //メールアドレス等を記述したファイルの読み込み
    require '../libs/mailvars.php'; 

    //メール本文の組み立て
    $mail_body = 'コンタクトページからのお問い合わせ' . "\n\n";
    $mail_body .=  "お名前: " .h($name) . "\n";
    $mail_body .=  "Email: " . h($email) . "\n"  ;
    $mail_body .=  "お電話番号: " . h($tel) . "\n\n" ;
    $mail_body .=  "<お問い合わせ内容>" . "\n" . h($body);

    //--------sendmail------------

    //メールの宛先(名前<メールアドレス> の形式)。値は mailvars.php に記載
    $mailTo = mb_encode_mimeheader(MAIL_TO_NAME) ."<" . MAIL_TO. ">";

    //Return-Pathに指定するメールアドレス
    $returnMail = MAIL_RETURN_PATH; //
    //mbstringの日本語設定
    mb_language( 'ja' );
    mb_internal_encoding( 'UTF-8' );

    // 送信者情報(From ヘッダー)の設定
    $header = "From: " . mb_encode_mimeheader($name) ."<" . $email. ">\n";
    $header .= "Cc: " . mb_encode_mimeheader(MAIL_CC_NAME) ."<" . MAIL_CC.">\n";
    $header .= "Bcc: <" . MAIL_BCC.">";

    //メールの送信
    //セーフモードがOnの場合は第5引数が使えない
    if ( ini_get( 'safe_mode' ) ) {
      $result = mb_send_mail( $mailTo, $subject, $mail_body, $header );
    } else {
      $result = mb_send_mail( $mailTo, $subject, $mail_body, $header, '-f' . $returnMail );
    }
    
    //メール送信の結果判定
    if ( $result ) {
      $_POST = array(); //空の配列を代入し、すべてのPOST変数を消去
      //変数の値も初期化
      $name = '';
      $email = '';
      $email_check = '';
      $tel = '';
      $subject = '';
      $body = '';
      
      //再読み込みによる二重送信の防止
      $params = '?result='. $result;
      $url = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://').$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; 
      header('Location:' . $url . $params);
      exit;
    } 
  }

}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>コンタクトフォーム</title>
<link href="../../../../plugins/bootstrap4/css/bootstrap.min.css" rel="stylesheet">
<link href="style.css" rel="stylesheet">
</head>
<body>
<div class="container">
  <h2 class="">お問い合わせフォーム</h2>
  <?php  if ( isset($_GET['result']) && $_GET['result'] ) : // 送信が成功した場合?>
  <h4>送信完了!</h4>
  <p>送信完了いたしました。</p>
  <hr>
  <?php elseif (isset($result) && !$result ): // 送信が失敗した場合 ?>
  <h4>送信失敗</h4>
  <p>申し訳ございませんが、送信に失敗しました。</p>
  <p>しばらくしてもう一度お試しになるか、メールにてご連絡ください。</p>
  <p>メール:<a href="mailto:info@example.com">Contact</a></p>
  <hr>
  <?php endif; ?>
  <p>以下のフォームからお問い合わせください。</p>
  <form id="form" method="post">
    <div class="form-group">
      <label for="name">お名前(必須) 
        <span class="error"><?php if ( isset( $error['name'] ) ) echo h( $error['name'] ); ?></span>
      </label>
      <input type="text" class="form-control" id="name" name="name" placeholder="氏名" value="<?php echo h($name); ?>">
    </div>
    <div class="form-group">
      <label for="email">Email(必須) 
        <span class="error"><?php if ( isset( $error['email'] ) ) echo h( $error['email'] ); ?></span>
      </label>
      <input type="text" class="form-control" id="email" name="email" placeholder="Email アドレス" value="<?php echo h($email); ?>">
    </div>
    <div class="form-group">
      <label for="email_check">Email(確認用 必須) 
        <span class="error"><?php if ( isset( $error['email_check'] ) ) echo h( $error['email_check'] ); ?></span>
      </label>
      <input type="text" class="form-control" id="email_check" name="email_check" placeholder="Email アドレス(確認のためもう一度ご入力ください。)" value="<?php echo h($email_check); ?>">
    </div>
    <div class="form-group">
      <label for="tel">お電話番号(半角英数字) 
        <span class="error"><?php if ( isset( $error['tel'] ) ) echo h( $error['tel'] ); ?></span>
        <span class="error"><?php if ( isset( $error['tel_format'] ) ) echo '<br>'. h( $error['tel_format'] ); ?></span>
      </label>
      <input type="text" class="form-control" id="tel" name="tel" value="<?php echo h($tel); ?>" placeholder="お電話番号(半角英数字でご入力ください)">
    </div>
    <div class="form-group">
      <label for="subject">件名(必須) 
        <span class="error"><?php if ( isset( $error['subject'] ) ) echo h( $error['subject'] ); ?></span> 
      </label>
      <input type="text" class="form-control" id="subject" name="subject" placeholder="件名" value="<?php echo h($subject); ?>">
    </div>
    <div class="form-group">
      <label for="body">お問い合わせ内容(必須) 
        <span class="error"><?php if ( isset( $error['body'] ) ) echo h( $error['body'] ); ?></span>
      </label>
      <textarea class="form-control" id="body" name="body" placeholder="お問い合わせ内容(1000文字まで)をお書きください" rows="3"><?php echo h($body); ?></textarea>
    </div>
    <button name="submitted" type="submit" class="btn btn-primary">送信</button>
  </form>
</div>
</body>
</html>
HTML

こちらも前述の例と同じく HTML5 で記述していますが、HTML5 の機能は利用していないので、HTML5 未対応のブラウザでも作動します。

前述の例とはフォームの入力項目の label 要素の部分に PHP の検証でエラーがあった場合に表示する記述を追加しています。

以下は $error['name'] が設定されていれば、別途 functions.php で定義した h() 関数で値をエスケープ処理してエラーの内容を表示する例です。

<?php if ( isset( $error['name'] ) ) echo h( $error['name'] ); ?>

また、HTML5 の検証機能は使用しないので input 要素の type 属性は type="text" を指定し、required 属性は指定していません。

この例では、検証は PHP のみで実施しています。

contact2.php HTML 一部抜粋
<form id="form" method="post">
  <div class="form-group">
    <label for="name">お名前(必須) 
      <span class="error"><?php if ( isset( $error['name'] ) ) echo h( $error['name'] ); ?></span>
    </label>
    <input type="text" class="form-control" id="name" name="name" placeholder="氏名" value="<?php echo h($name); ?>">
  </div>
  <div class="form-group">
    <label for="email">Email(必須) 
      <span class="error"><?php if ( isset( $error['email'] ) ) echo h( $error['email'] ); ?></span>
    </label>
    <input type="text" class="form-control" id="email" name="email" placeholder="Email アドレス" value="<?php echo h($email); ?>">
  </div>
  
  ・・・中略・・・
  
  <div class="form-group">
    <label for="body">お問い合わせ内容(必須) 
      <span class="error"><?php if ( isset( $error['body'] ) ) echo h( $error['body'] ); ?></span>
    </label>
    <textarea class="form-control" id="body" name="body" placeholder="お問い合わせ内容(1000文字まで)をお書きください" rows="3"><?php echo h($body); ?></textarea>
  </div>
  <button name="submitted" type="submit" class="btn btn-primary">送信</button>
</form>
PHP

14〜19行目は、念の為入力された値の前後に空白文字があれば削除しています(省略可能)。

前述の例と同様、送信ボタン(name="submitted")がクリックされてフォームに入力されたデータが送信されると、$_POST['submitted']に値が設定され、処理が実行されます。

PHP の検証では、エラーメッセージを保存する配列 $error を用意して、それぞれの項目で問題があった場合は、エラーメッセージを $error に保存します。

この例では HTML5 の required 属性で必須項目のチェックをしていないので、それぞれの値が空でないかも検証しています。

また、この例では filter_var() は使わず、各項目を正規表現 preg_match() を使って検証しています。

関連項目:フォームの検証(バリデーション)

メールの送信部分は前述の例と同じで、メールの本文を生成して mb_send_mail() を使ってメールを送信しています。

contact2.php PHP 部分の抜粋
<?php 
//エスケープ処理やデータチェックを行う関数のファイルの読み込み
require '../libs/functions.php';

//POSTされたデータを変数に格納
$name = isset( $_POST[ 'name' ] ) ? $_POST[ 'name' ] : NULL;
$email = isset( $_POST[ 'email' ] ) ? $_POST[ 'email' ] : NULL;
$email_check = isset( $_POST[ 'email_check' ] ) ? $_POST[ 'email_check' ] : NULL;
$tel = isset( $_POST[ 'tel' ] ) ? $_POST[ 'tel' ] : NULL;
$subject = isset( $_POST[ 'subject' ] ) ? $_POST[ 'subject' ] : NULL;
$body = isset( $_POST[ 'body' ] ) ? $_POST[ 'body' ] : NULL;

//POSTされたデータを整形(前後にあるホワイトスペースを削除)
$name = trim( $name );
$email = trim( $email );
$email_check = trim( $email_check );
$tel = trim( $tel );
$subject = trim( $subject );
$body = trim( $body );

//送信ボタンが押された場合の処理
if (isset($_POST['submitted'])) {

  //POSTされたデータをチェック  
  $_POST = checkInput( $_POST ); 

  //エラーメッセージを保存する配列の初期化
  $error = array();
  
  //値の検証
  if ( $name == '' ) {
    $error['name'] = '*お名前は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,30}\z/u', $name ) == 0 ) {
    $error['name'] = '*お名前は30文字以内でお願いします。';
  }
  if ( $email == '' ) {
    $error['email'] = '*メールアドレスは必須です。';
  } else { //メールアドレスを正規表現でチェック
    $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/uiD';
    if ( !preg_match( $pattern, $email ) ) {
      $error['email'] = '*メールアドレスの形式が正しくありません。';
    }
  }
  if ( $email_check == '' ) {
    $error['email_check'] = '*確認用メールアドレスは必須です。';
  } else { //メールアドレスが一致するかをチェック
    if ( $email_check !== $email ) {
      $error['email_check'] = '*メールアドレスが一致しません。';
    }
  }
  if ( preg_match( '/\A[[:^cntrl:]]{0,30}\z/u', $tel ) == 0 ) {
    $error['tel'] = '*電話番号は30文字以内でお願いします。';
  }
  if ( $tel != '' && preg_match( '/\A\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}\z/u', $tel ) == 0 ) {
    $error['tel_format'] = '*電話番号の形式が正しくありません。';
  }
  if ( $subject == '' ) {
    $error['subject'] = '*件名は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,100}\z/u', $subject ) == 0 ) {
    $error['subject'] = '*件名は100文字以内でお願いします。';
  }
  if ( $body == '' ) {
    $error['body'] = '*内容は必須項目です。';
    //制御文字(タブ、復帰、改行を除く)でないことと文字数をチェック
  } else if ( preg_match( '/\A[\r\n\t[:^cntrl:]]{1,1050}\z/u', $body ) == 0 ) {
    $error['body'] = '*内容は1000文字以内でお願いします。';
  }
  
  //エラーがなく且つ POST でのリクエストの場合
  if (empty($error) && $_SERVER['REQUEST_METHOD']==='POST') {
    //メールアドレス等を記述したファイルの読み込み
    require '../libs/mailvars.php'; 

    //メール本文の組み立て
    $mail_body = 'コンタクトページからのお問い合わせ' . "\n\n";
    $mail_body .=  "お名前: " .h($name) . "\n";
    $mail_body .=  "Email: " . h($email) . "\n"  ;
    $mail_body .=  "お電話番号: " . h($tel) . "\n\n" ;
    $mail_body .=  "<お問い合わせ内容>" . "\n" . h($body);
    
    ・・・以下省略・・・
jQuery の検証を追加

PHP の検証の場合、データを一度サーバに送信してから処理されるため、環境によっては検証結果の表示に時間がかかり、使い勝手が良くありません。

jQuery を使って検証することで、データの送信前に入力のエラーなどをユーザに伝えることができます。

jQuery の検証では PHP による検証と同等かそれ以上の検証を行います(jQuery の検証を通過したのに、その後また PHP の検証を行う必要がないように)。

また、jQuery の検証は JavaScript がオフになっていると機能しないので、jQeury の検証を実装しても PHP の検証は必要です。

以下のサンプルでは実際にはメールは送信されません。

サンプルを別ページで開く

コードの全文を別ページで開く

jQuery の検証を追加する場合、検証の対象となる要素にクラス名を付与して、それをもとに jQuery で検証を行います。

例えば、以下の input 要素の場合、validate max50 required の3つのクラスは検証用のクラスで、それぞれ「検証対象の要素であること」「最大文字数が50文字」「必須項目」であることを意味しています。

<input type="text" class="form-control validate max50 required" id="name" name="name" placeholder="氏名" value="<?php echo h($name); ?>">

関連項目:jQuery フォームのバリデーション(検証)

contact3.php HTML の一部抜粋と jQuery
<form id="form" method="post">
    <div class="form-group">
      <label for="name">お名前(必須) 
        <span class="error"><?php if ( isset( $error['name'] ) ) echo h( $error['name'] ); ?></span>
      </label>
      <input type="text" class="form-control validate max50 required" id="name" name="name" placeholder="氏名" value="<?php echo h($name); ?>">
    </div>
    <div class="form-group">
      <label for="email">Email(必須) 
        <span class="error"><?php if ( isset( $error['email'] ) ) echo h( $error['email'] ); ?></span>
      </label>
      <input type="text" class="form-control validate mail required" id="email" name="email" placeholder="Email アドレス" value="<?php echo h($email); ?>">
    </div>
    <div class="form-group">
      <label for="email_check">Email(確認用 必須) 
        <span class="error"><?php if ( isset( $error['email_check'] ) ) echo h( $error['email_check'] ); ?></span>
      </label>
      <input type="text" class="form-control validate email_check required" id="email_check" name="email_check" placeholder="Email アドレス(確認のためもう一度ご入力ください。)" value="<?php echo h($email_check); ?>">
    </div>
    <div class="form-group">
      <label for="tel">お電話番号(半角英数字) 
        <span class="error"><?php if ( isset( $error['tel'] ) ) echo h( $error['tel'] ); ?></span>
        <span class="error"><?php if ( isset( $error['tel_format'] ) ) echo '<br>'. h( $error['tel_format'] ); ?></span>
      </label>
      <input type="text" class="validate max30 tel form-control" id="tel" name="tel" value="<?php echo h($tel); ?>" placeholder="お電話番号(半角英数字でご入力ください)">
    </div>
    <div class="form-group">
      <label for="subject">件名(必須) 
        <span class="error"><?php if ( isset( $error['subject'] ) ) echo h( $error['subject'] ); ?></span> 
      </label>
      <input type="text" class="form-control validate max100 required" id="subject" name="subject" placeholder="件名" value="<?php echo h($subject); ?>">
    </div>
    <div class="form-group">
      <label for="body">お問い合わせ内容(必須) 
        <span class="error"><?php if ( isset( $error['body'] ) ) echo h( $error['body'] ); ?></span>
      </label>
      <span id="count"> </span>/1000
      <textarea class="form-control validate max1000 required" id="body" name="body" placeholder="お問い合わせ内容(1000文字まで)をお書きください" rows="3"><?php echo h($body); ?></textarea>
    </div>
    <button name="submitted" type="submit" class="btn btn-primary">送信</button>
  </form>
</div>

<!-- jQuery の読み込み -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> 
<script>
jQuery(function($){
  
  function show_error(message, this$) {
    text = this$.parent().find('label').text() + message;
    this$.parent().append("<p class='error'>" + text + "</p>")
  }
  
  $("form#form").submit(function(){  
    //エラー表示の初期化
    $("p.error").remove();
    $("div").removeClass("error");
    var text = "";
    $("#errorDispaly").remove();
    
    //メールアドレスの検証
    var email =  $.trim($("#email").val());
    if(email && !(/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/gi).test(email)){
      $("#email").after("<p class='error'>メールアドレスの形式が異なります</p>")
    }
    //確認用メールアドレスの検証
    var email_check =  $.trim($("#email_check").val());
    if(email_check && email_check != $.trim($("input[name="+$("#email_check").attr("name").replace(/^(.+)_check$/, "$1")+"]").val())){
      show_error("が一致しません", $("#email_check"));
    }
    //電話番号の検証
    var tel = $.trim($("#tel").val());
    if(tel && !(/^\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}$/gi).test(tel)){
      $("#tel").after("<p class='error'>電話番号の形式が異なります(半角英数字でご入力ください)</p>")
    }
    
    //1行テキスト入力フォームとテキストエリアの検証
    $(":text,textarea").filter(".validate").each(function(){
      
      $(this).filter(".required").each(function(){
        if($(this).val()==""){
          show_error(" は必須項目です", $(this));
        }
      })
        
      //文字数の検証
      $(this).filter(".max30").each(function(){
        if($(this).val().length > 30){
          show_error(" は30文字以内です", $(this));
        }
      })
      $(this).filter(".max50").each(function(){
        if($(this).val().length > 50){
          show_error(" は50文字以内です", $(this));
        }
      })
      //文字数の検証
      $(this).filter(".max100").each(function(){
        if($(this).val().length > 100){
          show_error(" は100文字以内です", $(this));
        }
      })
      //文字数の検証
      $(this).filter(".max1000").each(function(){
        if($(this).val().length > 1000){
          show_error(" は1000文字以内でお願いします", $(this));
        }
      })
    })
    //error クラスの追加の処理
    if($("p.error").length > 0){
      $("p.error").parent().addClass("error");
      $('html,body').animate({ scrollTop: $("p.error:first").offset().top-180 }, 'slow');
      return false;
    }
  }) //ここまでが submit イベントを使った検証
  
  //テキストエリアに入力された文字数を表示
  $("textarea").on('keydown keyup change', function() {
    var count = $(this).val().length;
    $("#count").text(count);
    if(count > 1000) {
      $("#count").css({color: 'red', fontWeight: 'bold'});
    }else{
      $("#count").css({color: '#333', fontWeight: 'normal'});
    }
  });
})
</script>
自動返信

コンタクトフォームから送信したユーザーに自動返信メールを送る機能を追加する例です。

110〜143行目でフォームの送信が成功した場合に、自動返信メールを送信するようにしています。

また、フォームでのお問い合わせを実行した時刻を日本時間で取得して表示するようにしています。

自動返信の送信先はユーザのアドレス($email)を指定し、From や Reply-To に指定する名前は mailvars.php に記述してあります。

146〜149行目では、リダイレクト先の自身の URL パラメータに自動返信の送信結果($result2)を追加しています。

contact4.php PHP 部分抜粋
<?php 
//エスケープ処理やデータチェックを行う関数のファイルの読み込み
require '../libs/functions.php';

//お問い合わせ日時を日本時間に
date_default_timezone_set('Asia/Tokyo'); 

//POSTされたデータを変数に格納
$name = isset( $_POST[ 'name' ] ) ? $_POST[ 'name' ] : NULL;
$email = isset( $_POST[ 'email' ] ) ? $_POST[ 'email' ] : NULL;
$email_check = isset( $_POST[ 'email_check' ] ) ? $_POST[ 'email_check' ] : NULL;
$tel = isset( $_POST[ 'tel' ] ) ? $_POST[ 'tel' ] : NULL;
$subject = isset( $_POST[ 'subject' ] ) ? $_POST[ 'subject' ] : NULL;
$body = isset( $_POST[ 'body' ] ) ? $_POST[ 'body' ] : NULL;

//POSTされたデータを整形(前後にあるホワイトスペースを削除)
$name = trim( $name );
$email = trim( $email );
$email_check = trim( $email_check );
$tel = trim( $tel );
$subject = trim( $subject );
$body = trim( $body );

if (isset($_POST['submitted'])) {

  //POSTされたデータをチェック  
  $_POST = checkInput( $_POST ); 

  //エラーメッセージを保存する配列の初期化
  $error = array();
  
  //値の検証
  if ( $name == '' ) {
    $error['name'] = '*お名前は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,30}\z/u', $name ) == 0 ) {
    $error['name'] = '*お名前は30文字以内でお願いします。';
  }
  if ( $email == '' ) {
    $error['email'] = '*メールアドレスは必須です。';
  } else { //メールアドレスを正規表現でチェック
    $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/uiD';
    if ( !preg_match( $pattern, $email ) ) {
      $error['email'] = '*メールアドレスの形式が正しくありません。';
    }
  }
  if ( $email_check == '' ) {
    $error['email_check'] = '*確認用メールアドレスは必須です。';
  } else { //メールアドレスを正規表現でチェック
    if ( $email_check !== $email ) {
      $error['email_check'] = '*メールアドレスが一致しません。';
    }
  }
  if ( preg_match( '/\A[[:^cntrl:]]{0,30}\z/u', $tel ) == 0 ) {
    $error['tel'] = '*電話番号は30文字以内でお願いします。';
  }
  if ( $tel != '' && preg_match( '/\A\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}\z/u', $tel ) == 0 ) {
    $error['tel_format'] = '*電話番号の形式が正しくありません。';
  }
  if ( $subject == '' ) {
    $error['subject'] = '*件名は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,100}\z/u', $subject ) == 0 ) {
    $error['subject'] = '*件名は100文字以内でお願いします。';
  }
  if ( $body == '' ) {
    $error['body'] = '*内容は必須項目です。';
    //制御文字(タブ、復帰、改行を除く)でないことと文字数をチェック
  } else if ( preg_match( '/\A[\r\n\t[:^cntrl:]]{1,1050}\z/u', $body ) == 0 ) {
    $error['body'] = '*内容は1000文字以内でお願いします。';
  }
  
  //エラーがなく且つ POST でのリクエストの場合
  if (empty($error) && $_SERVER['REQUEST_METHOD']==='POST') {
    //メールアドレス等を記述したファイルの読み込み
    require '../libs/mailvars.php'; 

    //メール本文の組み立て
    $mail_body = 'コンタクトページからのお問い合わせ' . "\n\n";
    $mail_body .=  date("Y年m月d日 D H時i分") . "\n\n";
    $mail_body .=  "お名前: " .h($name) . "\n";
    $mail_body .=  "Email: " . h($email) . "\n"  ;
    $mail_body .=  "お電話番号: " . h($tel) . "\n\n" ;
    $mail_body .=  "<お問い合わせ内容>" . "\n" . h($body);

    //--------sendmail------------

    //メールの宛先(名前<メールアドレス> の形式)。値は mailvars.php に記載
    $mailTo = mb_encode_mimeheader(MAIL_TO_NAME) ."<" . MAIL_TO. ">";

    //Return-Pathに指定するメールアドレス
    $returnMail = MAIL_RETURN_PATH; //
    //mbstringの日本語設定
    mb_language( 'ja' );
    mb_internal_encoding( 'UTF-8' );

    // 送信者情報(From ヘッダー)の設定
    $header = "From: " . mb_encode_mimeheader($name) ."<" . $email. ">\n";
    $header .= "Cc: " . mb_encode_mimeheader(MAIL_CC_NAME) ."<" . MAIL_CC.">\n";
    $header .= "Bcc: <" . MAIL_BCC.">";

    //メールの送信
    //セーフモードがOnの場合は第5引数が使えない
    if ( ini_get( 'safe_mode' ) ) {
      $result = mb_send_mail( $mailTo, $subject, $mail_body, $header );
    } else {
      $result = mb_send_mail( $mailTo, $subject, $mail_body, $header, '-f' . $returnMail );
    }
    
    //メール送信の結果判定
    if ( $result ) {
      
      //自動返信メール
      //ヘッダー情報
      $ar_header = "MIME-Version: 1.0\n";
      $ar_header .= "From: " . mb_encode_mimeheader( AUTO_REPLY_NAME ) . " <" . MAIL_TO . ">\n";
      $ar_header .= "Reply-To: " . mb_encode_mimeheader( AUTO_REPLY_NAME ) . " <" . MAIL_TO . ">\n";
      //件名
      $ar_subject = 'お問い合わせ自動返信メール';
      //本文
      $ar_body = $name." 様\n\n";
      $ar_body .= "この度は、お問い合わせ頂き誠にありがとうございます。" . "\n\n";
      $ar_body .= "下記の内容でお問い合わせを受け付けました。\n\n";
      $ar_body .= "お問い合わせ日時:" . date("Y-m-d H:i") . "\n";
      $ar_body .= "お名前:" . $name . "\n";
      $ar_body .= "メールアドレス:" . $email . "\n";
      $ar_body .= "お電話番号: " . $tel . "\n\n" ;
      $ar_body .="<お問い合わせ内容>" . "\n" . $body;

      if ( ini_get( 'safe_mode' ) ) {
        $result2 = mb_send_mail( $email, $ar_subject, $ar_body , $ar_header  );
      } else {
        $result2 = mb_send_mail( $email, $ar_subject, $ar_body , $ar_header , '-f' . $returnMail );
      }
      
      $_POST = array(); //空の配列を代入し、すべてのPOST変数を消去
      //変数の値も初期化
      $name = '';
      $email = '';
      $email_check = '';
      $tel = '';
      $subject = '';
      $body = '';
      
      //再読み込みによる二重送信の防止
      $params = '?result='. $result .'&result2=' . $result2;
      $url = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://').$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; 
      header('Location:' . $url . $params);
      exit;
    } 
  }
}
?>

HTML ではフォームの送信結果の部分に、自動返信の送信結果 $_GET['result2'] を取得してその値による表示を追加しています。

ユーザへ自動送信した宛先アドレスも $_GET を使えば「自動返信メールを xxxx@xxxx.com へお送りいたしました」のように表示できますが、URL のパラメータに表示されてしまうので、あえて表示しないようにしています。

その他の部分は同じです。

自動返信メールが送信できなかった場合のメッセージなどは、環境に合わせてもう少し工夫する必要があるかと思います。

contact4.php HTML 抜粋
<?php  if ( isset($_GET['result']) && $_GET['result'] ) : ?>
  <h4>送信完了!</h4>
  <p>送信完了いたしました。</p>
  <?php  if ( isset($_GET['result2']) && $_GET['result2'] ) : ?>
    <p>確認の自動返信メールをお送りいたしました。</p>
  <?php elseif (isset($_GET['result2']) && !$_GET['result2']): ?>
    <p>確認の自動返信メールが送信できませんでした。</p>
  <?php endif; ?>
  <hr>
<?php elseif (isset($result) && !$result ): ?>
  <h4>送信失敗</h4>
  <p>申し訳ございませんが、送信に失敗しました。</p>
  <p>しばらくしてもう一度お試しになるか、メールにてご連絡ください。</p>
  <p>メール:<a href="mailto:info@example.com">Contact</a></p>
  <hr>
<?php endif; ?>

コードの全文を別ページで開く

PHPMailer を使う

添付ファイルなどを送信する必要がある場合は mb_send_mail() の代わりに PHPMailer を使うと簡単かも知れません。但し、ライブラリを更新するなどの必要があるので実際に使用する場合は検討が必要です。

関連ページ:PHPMailer の使い方

以下は mb_send_mail() の代わりに PHPMailer を使ってメールを送信する場合の PHP の例です。

4〜6行目は PHPMailer を使うのに必要な記述です。use は if ブロックなどブロック内のスコープではエラーになるので先頭に記述しています。

メールの送信処理以外は前述の例と同じですが、PHPMailer を使うにはメールアカウントのパスワードなどを設定する必要があるのでそれらの情報は別途 phpmailvars.php というファイルに記述して読み込んでいます(96行目)。

メールの送信処理では PHPMailer のインスタンスを生成し(103行目)、そのプロパティやメソッドを使って送信処理のための記述をします。自動返信の処理では別途新たにインスタンスを生成する必要があります(148行目)。

111行目や151行目のコメントアウトを外すと、デバグの結果が全て出力されます(メインテナンス用)。また、141行目や186行目のコメントアウトを外すと送信ができなかった際に PHPMailer によるエラーメッセージが表示されます。

<?php 
//PHPMailer 名前空間の使用(ファイル内の一番外側のスコープで行う)
//ブロック内のスコープではインポートできない
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

//PHPMailer の読み込み  Load Composer's autoloader
require '../php_mailer/vendor/autoload.php';

//エスケープ処理やデータチェックを行う関数のファイルの読み込み
require '../libs/functions.php';

//お問い合わせ日時を日本時間に
date_default_timezone_set('Asia/Tokyo'); 

//POSTされたデータを変数に格納
$name = isset( $_POST[ 'name' ] ) ? $_POST[ 'name' ] : NULL;
$email = isset( $_POST[ 'email' ] ) ? $_POST[ 'email' ] : NULL;
$email_check = isset( $_POST[ 'email_check' ] ) ? $_POST[ 'email_check' ] : NULL;
$tel = isset( $_POST[ 'tel' ] ) ? $_POST[ 'tel' ] : NULL;
$subject = isset( $_POST[ 'subject' ] ) ? $_POST[ 'subject' ] : NULL;
$body = isset( $_POST[ 'body' ] ) ? $_POST[ 'body' ] : NULL;

//POSTされたデータを整形(前後にあるホワイトスペースを削除)
$name = trim( $name );
$email = trim( $email );
$email_check = trim( $email_check );
$tel = trim( $tel );
$subject = trim( $subject );
$body = trim( $body );

if (isset($_POST['submitted'])) {

  //POSTされたデータをチェック  
  $_POST = checkInput( $_POST ); 

  //エラーメッセージを保存する配列の初期化
  $error = array();
  
  //値の検証
  if ( $name == '' ) {
    $error['name'] = '*お名前は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,30}\z/u', $name ) == 0 ) {
    $error['name'] = '*お名前は30文字以内でお願いします。';
  }
  if ( $email == '' ) {
    $error['email'] = '*メールアドレスは必須です。';
  } else { //メールアドレスを正規表現でチェック
    $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/uiD';
    if ( !preg_match( $pattern, $email ) ) {
      $error['email'] = '*メールアドレスの形式が正しくありません。';
    }
  }
  if ( $email_check == '' ) {
    $error['email_check'] = '*確認用メールアドレスは必須です。';
  } else { //メールアドレスを正規表現でチェック
    if ( $email_check !== $email ) {
      $error['email_check'] = '*メールアドレスが一致しません。';
    }
  }
  if ( preg_match( '/\A[[:^cntrl:]]{0,30}\z/u', $tel ) == 0 ) {
    $error['tel'] = '*電話番号は30文字以内でお願いします。';
  }
  if ( $tel != '' && preg_match( '/\A\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}\z/u', $tel ) == 0 ) {
    $error['tel_format'] = '*電話番号の形式が正しくありません。';
  }
  if ( $subject == '' ) {
    $error['subject'] = '*件名は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,100}\z/u', $subject ) == 0 ) {
    $error['subject'] = '*件名は100文字以内でお願いします。';
  }
  if ( $body == '' ) {
    $error['body'] = '*内容は必須項目です。';
    //制御文字(タブ、復帰、改行を除く)でないことと文字数をチェック
  } else if ( preg_match( '/\A[\r\n\t[:^cntrl:]]{1,1050}\z/u', $body ) == 0 ) {
    $error['body'] = '*内容は1000文字以内でお願いします。';
  }
  
  //エラーがなく且つ POST でのリクエストの場合
  if (empty($error) && $_SERVER['REQUEST_METHOD']==='POST') {
    
    //メール本文の組み立て
    $mail_body = 'コンタクトページからのお問い合わせ' . "\n\n";
    $mail_body .=  date("Y年m月d日 D H時i分") . "\n\n";
    $mail_body .=  "お名前: " .h($name) . "\n";
    $mail_body .=  "Email: " . h($email) . "\n"  ;
    $mail_body .=  "お電話番号: " . h($tel) . "\n\n" ;
    $mail_body .=  "<お問い合わせ内容>" . "\n" . h($body);

    //-------- PHPMailer を使ったメールの送信------------
    
    //メールアカウント情報(パスワード等)の読み込み PHPMailer用
    require '../libs/phpmailvars.php';

    //mbstring の日本語設定
    mb_language( "japanese" );
    mb_internal_encoding( "UTF-8" );

    //PHPMailer のインスタンスを生成 (引数に true を指定して例外 Exception を有効に)
    $mail = new PHPMailer( true );

    //送信結果の真偽値の初期化
    $result = false;
    $result2 = false;

    try {
      //サーバ設定
      //$mail->SMTPDebug = SMTP::DEBUG_SERVER; // デバグの出力を有効に(テスト環境検証用)
      $mail->isSMTP(); // SMTP を使用
      $mail->Host = MAIL_HOST; // SMTP サーバーを指定(phpmailvars.phpで定義)
      $mail->SMTPAuth = true; // SMTP authentication を有効に
      $mail->Username = MAIL_USER; // SMTP ユーザ名(phpmailvars.phpで定義)
      $mail->Password = MAIL_PASSWORD; // SMTP パスワード(phpmailvars.phpで定義)
      $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // TLS を有効に
      $mail->Port = 587; // TCP ポートを指定(TLS の場合のポート番号)

      //日本語用
      $mail->CharSet = "iso-2022-jp";
      $mail->Encoding = "7bit";

      //Recipients
      $mail->setFrom( $email, mb_encode_mimeheader( $name ) ); //差出人アドレス, 差出人名 
      $mail->AddAddress(SEND_TO, mb_encode_mimeheader(SEND_TO_NAME)); //送信先アドレス・宛先名(phpmailvars.phpで定義)
      $mail->AddBcc( BCC ); //Bcc アドレス(phpmailvars.phpで定義) 

      $mail->isHTML( false ); // Set email format to plain text
      $mail->Subject = mb_encode_mimeheader( $subject ); //件名
      $mail->WordWrap = 70; //70 文字で改行(好みで)

      $mail->Body = mb_convert_encoding( $mail_body, "JIS", "UTF-8" );
      //$mail->AltBody = mb_convert_encoding($mail_body,"JIS","UTF-8"); //テキスト表示の本文

      //メール送信の結果(真偽値)を $result に代入
      $result = $mail->send();

    } catch ( Exception $e ) {
      //PHPMailer のエラーメッセージ(例外が発生した場合)
      //echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
    }

    //メール送信の結果判定
    if ( $result ) {

      //自動返信メール
      $autoresponder = new PHPMailer( true );
      try {
        //サーバ設定
        //$autoresponder->SMTPDebug = SMTP::DEBUG_SERVER; // デバグの出力を有効に
        $autoresponder->isSMTP(); // SMTP を使用
        $autoresponder->Host = MAIL_HOST; // SMTP サーバーを指定(phpmailvars.phpで定義)
        $autoresponder->SMTPAuth = true; // SMTP authentication を有効に
        $autoresponder->Username = MAIL_USER; // SMTP ユーザ名(phpmailvars.phpで定義)
        $autoresponder->Password = MAIL_PASSWORD; // SMTP パスワード(phpmailvars.phpで定義)
        $autoresponder->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; //TLS を有効に
        $autoresponder->Port = 587; // TCP ポートを指定 

        //日本語用
        $autoresponder->CharSet = "iso-2022-jp";
        $autoresponder->Encoding = "7bit";

        //Recipients
        $autoresponder->setFrom( AR_SEND_FROM, mb_encode_mimeheader( AR_SEND_FROM_NAME ) ); //差出人アドレス, 差出人名 
        $autoresponder->AddAddress( $email, mb_encode_mimeheader( $name ) ); //送信先・宛先(phpmailvars.phpで定義) 

        $autoresponder->isHTML( false ); // Set email format to plain text
        $autoresponder->Subject = mb_encode_mimeheader( "自動返信メール" ); //件名
        //返信用アドレス(差出人以外に別途指定する場合)
        $autoresponder->addReplyTo( MAIL_USER, mb_encode_mimeheader("お問い合わせ")); 
        $autoresponder->WordWrap = 70; //70 文字で改行(好みで)
        $ar_body = $name." 様\n\n";
        $ar_body .= "この度は、お問い合わせ頂き誠にありがとうございます。" . "\n\n";
        $ar_body .= "下記の内容でお問い合わせを受け付けました。\n\n";
        $ar_body .= "お問い合わせ日時:" . date("Y-m-d H:i") . "\n";
        $ar_body .= "お名前:" . $name . "\n";
        $ar_body .= "メールアドレス:" . $email . "\n";
        $ar_body .= "お電話番号: " . $tel . "\n\n" ;
        $ar_body .="<お問い合わせ内容>" . "\n" . $body;
        $autoresponder->Body = mb_convert_encoding( $ar_body, "JIS", "UTF-8" );
        //自動送信メールの送信結果(真偽値)を result2 に代入
        $result2 = $autoresponder->send();
      } catch ( Exception $e ) {
        //PHPMailer のエラーメッセージ
        //echo "Auto Response Message could not be sent. Mailer Error: {$autoresponder->ErrorInfo}";
      }
      
      //空の配列を代入し、すべてのPOST変数を消去
      $_POST = array(); 
      //変数の値も初期化
      $name = '';
      $email = '';
      $email_check = '';
      $tel = '';
      $subject = '';
      $body = '';
      
      //再読み込みによる二重送信の防止
      $params = '?result='. $result .'&result2=' . $result2;
      $url = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://').$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; 
      header('Location:' . $url . $params);
      exit;
    } 
  }
}
?>

以下は PHPMailer を使用するためのアカウント情報などを記述したファイルの例です。パスワードなどを記述してあるので、パブリックからアクセスできない場所に保存するか .htaccess などで外部からアクセスできないようにします。

phpmailvars.php
<?php
//SMTP サーバー(サーバーの場合:localhost でも可)
define('MAIL_HOST', 'mail.xxxxxx.com');

//PHPMailer を使って送信するための E-mail アカウント
define('MAIL_USER', 'xxxxx@xxxxxxx.com');

//パスワード
define('MAIL_PASSWORD', 'xxxxxxxxxx');

//送信先
define('SEND_TO', 'xxxx@xxxxxx.com');

//送信先の名前
define('SEND_TO_NAME', '送信先名前');

//Bcc アドレス
define('BCC', 'xxxx@xxxxxx.com');

//自動返信をする場合の送信元アドレス
define('AR_SEND_FROM', 'xxxx@xxxxxxxx.com');

//自動返信をする場合の送信元名前
define('AR_SEND_FROM_NAME', '自動返信送信元名前');

コードの全文を別ページで開く

reCAPTCHA v2 を使う

Google が提供するキャプチャ認証システムの reCAPTCHA v2 を実装する例です。以下は自動返信の例に reCAPTCHA v2 を実装しています。

関連ページ:Google reCAPTCHA の使い方(v2/v3)

以下のサンプルでは実際にはメールは送信されません。

サンプルを別ページで開く

コードの全文を別ページで開く

HTML

HTML では、reCAPTCHA の認証が通らなかった場合にエラーを表示する領域(4〜6行目)を追加します。

そして reCAPTCHA のウィジェットを表示する id 属性(id="recaptcha")を指定した div 要素を送信ボタンの前に配置します(41行目)。ウィジェットを表示する JavaScript ではこの要素の id を指定します。

HTML 抜粋
<body>
<div class="container">
  <h2 class="">お問い合わせフォーム</h2>
  <?php if ( isset( $error['recaptcha'] ) ): ?>
  <p><span class="error"><?php echo h( $error['recaptcha'] ); ?></span></p>
  <?php endif; ?>
  <?php  if ( isset($_GET['result']) && $_GET['result'] ) : ?>
  <h4>送信完了!</h4>
  <p>送信完了いたしました。</p>
  <?php  if ( isset($_GET['result2']) && $_GET['result2'] ) : ?>
  <p>確認の自動返信メールをお送りいたしました。</p>
  <?php elseif (isset($_GET['result2']) && !$_GET['result2']): ?>
  <p>確認の自動返信メールが送信できませんでした。</p>
  <?php endif; ?>
  <hr>
  <?php elseif (isset($result) && !$result ): ?>
  <h4>送信失敗</h4>
  <p>申し訳ございませんが、送信に失敗しました。</p>
  <p>しばらくしてもう一度お試しになるか、メールにてご連絡ください。</p>
  <p>メール:<a href="mailto:info@example.com">Contact</a></p>
  <hr>
  <?php endif; ?>
  <p>以下のフォームからお問い合わせください。</p>
  <form id="form" method="post">
    <div class="form-group">
      <label for="name">お名前(必須) 
        <span class="error"><?php if ( isset( $error['name'] ) ) echo h( $error['name'] ); ?></span>
      </label>
      <input type="text" class="form-control validate max50 required" id="name" name="name" placeholder="氏名" value="<?php echo h($name); ?>">
    </div>
    
    ・・・中略・・・
    
    <div class="form-group">
      <label for="body">お問い合わせ内容(必須) 
        <span class="error"><?php if ( isset( $error['body'] ) ) echo h( $error['body'] ); ?></span>
      </label>
      <span id="count"> </span>/1000
      <textarea class="form-control validate max1000 required" id="body" name="body" placeholder="お問い合わせ内容(1000文字まで)をお書きください" rows="3"><?php echo h($body); ?></textarea>
    </div>
    <div id="recaptcha"></div><!-- reCAPTCHA を表示する要素 -->
    <button id="send" name="submitted" type="submit" class="btn btn-primary">送信</button>
  </form>
</div>
JavaScript

script タグを使ってウィジェットを表示する JavaScript を記述します。

grecaptcha.render() メソッドではウィジェットを表示する要素の id(recaptcha)とサイトキーなどの必要なオプションやコールバック関数名を指定します。

そしてオプションで指定したコールバック関数を定義します。

ウィジェットを表示するための API の読み込みでは grecaptcha.render() を使ってウィジェットを表示するのでパラメータ onload=onloadCallback&render=explicit を指定しています(71行目)。

<script>
  var onloadCallback = function() {
    //ウィジェットを表示するメソッド
    grecaptcha.render('recaptcha', {
      'sitekey' : "<?php echo $siteKey; ?>",  //サイトキー
      'callback' : verifyCallback,  //コールバック関数の名前
      'expired-callback' : expiredCallback  //コールバック関数の名前
    });
  };
  //チェックを入れて成功した場合に呼び出されるコールバック関数
  var verifyCallback = function(response) { 
    //ウィジェットの要素に verified クラスを設定
    document.getElementById('recaptcha').className = "verified";
    //エラーメッセージを表示する要素
    var recaptchaError = document.getElementById('recaptcha_error');
    if(recaptchaError != null) {
       //エラーメッセージを表示する要素が存在すれば削除
       recaptchaError.parentNode.removeChild(recaptchaError);
    } 
  };
  //期限切れの場合に呼び出されるコールバック関数
  var expiredCallback = function() {
    //verified クラスを削除
    document.getElementById('recaptcha').classList.remove('verified');
  };
</script> 
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script> 
jQuery

jQuery ではウィジェットにチェックを入れずに送信した場合にエラーを表示する検証を追加しています。

grecaptcha.render() メソッドに指定したコールバック関数 verifyCallback ではチェックが入った場合にウィジェットの要素(#recaptcha)に verified というクラスを付与するのでそれを利用します。

//ウィジェットの要素(#recaptcha)に .verified がない場合
if(!$('#recaptcha').hasClass('verified')) {
  //エラーメッセージの要素を追加
  $('#recaptcha').append("<p class='error' id='recaptcha_error'>チェックを入れてください</p>");
}   
PHP

reCAPTCHA のサイトキーやシークレットキーは別ファイル(この例では recaptchavars.php)に保存して、外部からアクセスできないようにしておきます。

他のメール関連の情報を記述してあるファイル(この例では mailvars.php)にこれらの情報を記述しておくこともできます。

recaptchavars.php
<?php
// reCAPTCHA v2 サイトキー
define('V2_SITEKEY', 'xxxxxxxxxxxxxxxxxxxxxxx');
// reCAPTCHA v2 シークレットキー
define('V2_SECRETKEY', 'xxxxxxxxxxxxxxxxxxxxxxx');

上記ファイルを読み込み、サイトキーとシークレットキーの値を変数に代入しておきます(6〜10行目)。

19〜45行目が reCAPTCHA の認証部分で検証結果(真偽値)を変数 $result_status に代入します。

認証に失敗した場合は検証結果に false が入っているので、その場合はエラーメッセージの配列($error)にエラーを追加します(51〜53行目)。

検証結果が false の場合、$error は空ではないので66行目の判定によりメールは送信されません。

php 抜粋
<?php 
//エスケープ処理やデータチェックを行う関数のファイルの読み込み
require '../libs/functions.php';

//reCAPTCHA 
require '../libs/recaptchavars.php';
// reCAPTCHA サイトキー
$siteKey = V2_SITEKEY;
// reCAPTCHA シークレットキー
$secretKey = V2_SECRETKEY;

・・・中略・・・

if (isset($_POST['submitted'])) {

  //POSTされたデータをチェック  
  $_POST = checkInput( $_POST ); 
  
  if ( isset( $_POST[ 'g-recaptcha-response' ] ) ) {
    //cURL セッションを初期化
    $ch = curl_init();
    // curl_setopt() により転送時のオプションを設定
    //API の URL の指定
    curl_setopt($ch, CURLOPT_URL,"https://www.google.com/recaptcha/api/siteverify");
    //HTTP POST メソッドを使う
    curl_setopt($ch, CURLOPT_POST, true );
    //API パラメータの指定
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
      'secret' => $secretKey, 
      'response' => $_POST[ 'g-recaptcha-response' ]
    )));
    //curl_execの返り値を文字列にする
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    //転送を実行してレスポンスを $json に格納
    $json = curl_exec($ch);
    //セッションを終了
    curl_close($ch);

    //レスポンスの $json(JSON形式)をデコード
    $rc_result = json_decode( $json );

    //reCAPTCHA 検証結果の真偽値を変数に代入
    $result_status = $rc_result->success ;

  }

  //エラーメッセージを保存する配列の初期化
  $error = array();
  
  //reCAPTCHA の検証結果が false の(認証失敗)場合
  if(!$result_status) {
    $error['recaptcha'] = 'reCAPTCHA 検証が失敗しました。・・・';
  }
  
  //値の検証
  if ( $name == '' ) {
    $error['name'] = '*お名前は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,30}\z/u', $name ) == 0 ) {
    $error['name'] = '*お名前は30文字以内でお願いします。';
  }
  
  ・・・中略・・・
  
  //エラーがなく且つ POST でのリクエストの場合
  if (empty($error) && $_SERVER['REQUEST_METHOD']==='POST') {
    //メールアドレス等を記述したファイルの読み込み
    require '../libs/mailvars.php'; 

    //メール本文の組み立て
    $mail_body = 'コンタクトページからのお問い合わせ' . "\n\n";
    $mail_body .=  date("Y年m月d日 D H時i分") . "\n\n";
    $mail_body .=  "お名前: " .h($name) . "\n";
    $mail_body .=  "Email: " . h($email) . "\n"  ;
    $mail_body .=  "お電話番号: " . h($tel) . "\n\n" ;
    $mail_body .=  "<お問い合わせ内容>" . "\n" . h($body);

    //--------sendmail------------

    //メールの宛先(名前<メールアドレス> の形式)。値は mailvars.php に記載
    $mailTo = mb_encode_mimeheader(MAIL_TO_NAME) ."<" . MAIL_TO. ">";
    
    ・・・以下省略・・・

  }
}
?>
reCAPTCHA v3 を使う

reCAPTCHA v3 を実装する例です。以下は自動返信の例に reCAPTCHA v3 を実装しています。

以下のサンプルでは実際にはメールは送信されません。

サンプルを別ページで開く

コードの全文を別ページで開く

HTML

HTML では、reCAPTCHA の認証が通らなかった場合にエラーを表示する領域(19〜21行目)を追加します(通常エラーになることはほとんどないと思います)。

3〜16行目は reCAPTCHA の動作確認用の表示の領域で、実際のコンタクトフォームでは記述しません。この部分には reCAPTCHA のレスポンスの内容を表示しています。

<body>
<div class="container">
  <!-- ここから reCAPTCHA 検証結果確認用(テスト用)  -->
  <div style="color:blue; margin: 30px 0;">
    <!-- reCAPTCHA 検証を通過した場合  -->
    <?php if (isset($_GET['success'])) echo 'success: '.$_GET['success'].'<br>' ?>
    <?php if (isset($_GET['action'])) echo 'action: '.$_GET['action'].'<br>' ?>
    <?php if (isset($_GET['score'])) echo 'score: '.$_GET['score'].'<br>' ?>
  </div>
  <div style="color: red; margin: 30px 0;">
    <!-- reCAPTCHA 検証を失敗した場合  -->
    <?php if (isset($rc_result->success)) echo 'success: '.$rc_result->success.'<br>' ?>
    <?php if (isset($rc_result->action)) echo 'action: '.$rc_result->action.'<br>' ?>
    <?php if (isset( $rc_result->score)) echo 'score: '.$rc_result->score.'<br>' ?>
  </div>
  <!-- ここまで reCAPTCHA 検証結果確認用(テスト用) -->
  
  <h2>お問い合わせフォーム</h2>
  <?php if (isset( $error['recaptcha'] ) ): ?>
  <p><span class="error"><?php echo h( $error['recaptcha'] ); ?></span></p>
  <?php endif; ?>
JavaScript/jQuery

reCAPTCHA を表示するためサイトキーをパラメータに指定した API を読み込みます。

<script src="https://www.google.com/recaptcha/api.js?render=<?php echo $siteKey; ?>"></script>

この例では jQuery を使ってフォームを送信した際にトークンを取得するようにしています。このためトークンの有効期限は送信ボタンをクリックしてから約2分間になります。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script><!-- jQuery の読み込み -->  
<script>
jQuery(function($){
  ・・・中略・・・
  //フォーム要素に submit イベントを設定
  $("form#form").submit(function(event){  
    //変数 that に $("form#form")を代入
    var that = $(this);
    //エラー表示の初期化
    $("p.error").remove();
    $("div").removeClass("error");
    var text = "";
    $("#errorDispaly").remove();
    
    //メールアドレスの検証(エラーがあれば p.error を追加)
    var email =  $.trim($("#email").val());
    if(email && !(/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/gi).test(email)){
      $("#email").after("<p class='error'>メールアドレスの形式が異なります</p>")
    }
    
    ・・・中略(その他の入力項目の検証)・・・
    
    //error クラスの追加の処理
    if($("p.error").length > 0){
      $("p.error").parent().addClass("error");
      $('html,body').animate({ scrollTop: $("p.error:first").offset().top-180 }, 'slow');
      //エラーがあれば中断
      return false;
    } else {  //エラーがなければ reCAPTCHA のトークンを取得
      //デフォルトの動作(送信)を停止
      event.preventDefault();
      var action_name = 'contact'; //アクション名 
      grecaptcha.ready(function() {
        grecaptcha.execute('<?php echo $siteKey; ?>', { action: action_name }).then(function(token) {
          //input 要素を生成して値にトークンを設定
          that.prepend('<input type="hidden" name="g-recaptcha-response" value="' + token + '">');
          //input 要素を生成して値にアクション名を設定
          that.prepend('<input type="hidden" name="action" value="' + action_name + '">');
          //unbind で一度 submit のイベントハンドラを削除してから submit() を実行
          that.unbind('submit').submit(); 
        });;
      });
    }
  }) 
  ・・・以下省略・・・
})
</script>
PHP

他の例では、送信ボタンがクリックされ $_POST['submitted']に値が設定されたら処理を実行するようにしていますが、この場合は reCAPTCHA のトークンの値($_POST['g-recaptcha-response'])が取得できていれば処理を実行します(25行目)。

そしてトークンとアクション名が取得できれば API を使ってトークンを検証します(31〜63行目)。

v2 とは異なりレスポンスの success の値だけでは判定できないので、success が true でアクション名が一致し、スコアが 0.5 以上の場合に合格(検証通過)としています。スコアの値を変更することで、判定の厳しさの度合いを設定することができます(56行目)。

この例では検証の判定結果を変数 $result_status に真偽値で格納し、$result_status が false の場合は、エラーとして $error['recaptcha'] にエラーメッセージを設定しています。

84行目の条件文でエラーがなければ送信処理を実行するようにしています。

<?php 
//エスケープ処理やデータチェックを行う関数のファイルの読み込み
require '../libs/functions.php';

//reCAPTCHA 
require '../libs/recaptchavars.php';
// reCAPTCHA サイトキー
$siteKey = V3_SITEKEY;
// reCAPTCHA シークレットキー
$secretKey = V3_SECRETKEY;

・・・中略・・・

//POST 送信された reCAPTCHA トークンがあれば $token に格納、なければ $token は NULL で初期化
$token = isset( $_POST[ 'g-recaptcha-response' ] ) ? $_POST[ 'g-recaptcha-response' ] : NULL;
//POST 送信された reCAPTCHA アクション名あれば $action に格納、なければ $action は NULL で初期化 
$action = isset( $_POST[ 'action' ] ) ? $_POST[ 'action' ] : NULL;

・・・中略・・・

//reCAPTCHA の検証を通過したかどうかの真偽値(初期値 false を設定)
$result_status = false;

//トークンが設定されていれば以下を実行
if (isset($_POST['g-recaptcha-response'])) {

  //POSTされたデータをチェック  
  $_POST = checkInput( $_POST ); 
  
  // トークンとアクション名が取得できれば API を使ってトークンを検証
  if ( $token && $action) {

    //cURL セッションを初期化(API のレスポンスの取得)
    $ch = curl_init();
    // curl_setopt() により転送時のオプションを設定
    //URL の指定
    curl_setopt($ch, CURLOPT_URL,"https://www.google.com/recaptcha/api/siteverify");
    //HTTP POST メソッドを使う
    curl_setopt($ch, CURLOPT_POST, true );
    //API パラメータの指定
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
      'secret' => $secretKey, // シークレットキー
      'response' => $token // トークン
    )));
    //curl_execの返り値を文字列にする
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    //転送を実行してレスポンスを $api_response に格納
    $api_response = curl_exec($ch);
    //セッションを終了
    curl_close($ch);

    //レスポンスの $json(JSON形式)をデコード
    $rc_result = json_decode( $api_response );

    //判定
    if ( $rc_result->success && $rc_result->action === $action && $rc_result->score >= 0.5) {
      //success が true でアクション名が一致し、スコアが 0.5 以上の場合は合格
      $result_status = true;
    } else {
      // 上記以外の場合は 不合格
      $result_status = false;
    }
  }
  
  //エラーメッセージを保存する配列の初期化
  $error = array();
  
  //reCAPTCHA 検証(判定結果 $result_status が false ならエラーを表示)
  if(!$result_status) {
    $error['recaptcha'] = 'reCAPTCHA 検証が失敗しました。申し訳ございませんがメールまたは電話にてご連絡ください。';
  }
  
  //値の検証
  if ( $name == '' ) {
    $error['name'] = '*お名前は必須項目です。';
    //制御文字でないことと文字数をチェック
  } else if ( preg_match( '/\A[[:^cntrl:]]{1,30}\z/u', $name ) == 0 ) {
    $error['name'] = '*お名前は30文字以内でお願いします。';
  }
  
  ・・・中略・・・
  
  //エラーがなく且つ POST でのリクエストの場合は
  if (empty($error) && $_SERVER['REQUEST_METHOD']==='POST') {
    
    //メールの送信処理を実行
    
    ・・・以下省略・・・

reCAPTCHA の使い方については以下を御覧ください。

関連ページ:Google reCAPTCHA の使い方(v2/v3)