はじめに

WordPressのMW WP FORMからシンプルなPHP工房のメールフォームに移行する過程で、様々なカスタマイズを行いました。このブログでは、その主要なポイントや遭遇した問題、そして解決策をご紹介します。PHP工房のフォームは軽量でシンプルですが、適切なカスタマイズによって高機能なフォームに変身させることができます。

目次

  1. PHP工房フォームの基本構造
  2. WordPressテーマとの統合
  3. 郵便番号による住所自動入力機能の実装
  4. メール内容のカスタマイズ
  5. フォームレイアウトの最適化
  6. JavaScript によるリアルタイムバリデーション
  7. 文字化け問題の解決方法
  8. 変数管理の一元化
  9. セッション管理とセキュリティ対策
  10. 動作検証とトラブルシューティング

1. PHP工房フォームの基本構造

PHP工房のメールフォームは、主に3つのファイルから構成されています:

  • index.php - フォームの入力画面
  • mail.php - フォーム処理と送信確認画面・完了画面の表示
  • confirm/thanks/index.php - 送信完了画面(追加)

最初にこれらのファイルの基本的な役割を理解することが重要です。mail.phpは送信処理だけでなく、確認画面や完了画面の表示も担当しています。特に$confirmDsp$jumpPageの設定によって動作が変わるため、全体の流れを把握しておくことが大切です。

2. WordPressテーマとの統合

WordPressテーマと統合する際の主なポイント:

//変数定義
$tophomeurl = 'https://www.example.jp/site';
$topthemeurl = 'https://www.example.jp/site/wp-content/themes/my-theme';
$commonsurl = 'https://www.example.jp/site/wp-content/themes/my-theme/commons';

//ヘルパー関数
function e($string) {
return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}

//インクルード
include($_SERVER['DOCUMENT_ROOT'] . '/site/form-thesis/head-plain.php');
include($_SERVER['DOCUMENT_ROOT'] . '/site/form-thesis/header-formUc7F.php');
include($_SERVER['DOCUMENT_ROOT'] . '/site/form-thesis/footer-formUc7F.php');

WordPress のショートコードを使わずに、静的な変数と独自のヘルパー関数を定義することで、テーマのヘッダーやフッターを適切に読み込むことができます。本番環境ではパスを絶対パスで指定するのが安全です。

3. 郵便番号による住所自動入力機能の実装

郵便番号から住所を自動入力する機能は、ユーザビリティを大きく向上させます:

//郵便番号から住所自動入力のJavaScript
window.addEventListener('DOMContentLoaded', function() {
const postcodeInput = document.getElementById('php-client-postcode');

if (postcodeInput) {
let typingTimer;
const doneTypingInterval = 100; //入力が止まってから処理を開始するまでの時間(ミリ秒)

postcodeInput.addEventListener('input', function() {
clearTimeout(typingTimer);

const postcode = this.value.replace(/[^\d]/g, '');

if (postcode.length === 7) {
typingTimer = setTimeout(function() {
fetchAddress(postcode);
}, doneTypingInterval);
}
});

function fetchAddress(postcode) {
fetch(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=${postcode}`)
.then(response => response.json())
.then(data => {
if (data.results) {
const addressData = data.results[0];
//都道府県のセレクトボックスを更新
const prefectureSelect = document.getElementById('php-client-address1');
const prefectureName = addressData.address1;

for (let i = 0; i < prefectureSelect.options.length; i++) {
if (prefectureSelect.options[i].text === prefectureName) {
prefectureSelect.selectedIndex = i;
break;
}
}

//市区町村・番地のテキストエリアに入力
const addressTextarea = document.getElementById('php-client-address2');
addressTextarea.value = addressData.address2 + addressData.address3;
}
})
.catch(error => {
console.error('住所取得エラー:', error);
});
}
}
});

このコードのポイント:

  • DOMContentLoadedイベントを使用して、ページ読み込み完了後に処理を実行
  • 入力が完了する前(7桁入力時点)で住所検索を開始
  • 連続入力中の無駄なAPI呼び出しを防ぐためのタイマー実装
  • 郵便番号APIを使用して都道府県と市区町村を自動入力
  • エラーハンドリングを適切に実装

4. メール内容のカスタマイズ

デフォルトのメール内容をカスタマイズするために、専用の関数を作成:

function customMailToAdmin($arr, $subject, $mailFooterDsp, $mailSignature, $encode, $confirmDsp) {
global $my_sitename;

//送信日時のフォーマット
$send_datetime = date("Y/m/d (D) H:i:s", time());

//ユーザーエージェント
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';

//管理者宛メール本文
$adminBody = "【{$my_sitename}】お問い合わせがありました。\n\n";
$adminBody .= "---------------------------------------\n";
$adminBody .= "送信日時:{$send_datetime}\n";
$adminBody .= "[お申込内容]\n";
$adminBody .= "お問い合わせ\n\n";
$adminBody .= "[お客様情報]\n";

//各フィールドの値を取得して設定
$name = isset($arr['お名前']) ? $arr['お名前'] : '';
$furigana = isset($arr['フリガナ']) ? $arr['フリガナ'] : '';
$institute = isset($arr['会社名・団体名']) ? $arr['会社名・団体名'] : '';
//他のフィールドも同様に設定

//メール本文に情報を追加
$adminBody .= "【お名前】  :{$name}\n";
$adminBody .= "【フリガナ】 :{$furigana}\n";
$adminBody .= "【会社名・団体名】:{$institute}\n";
//他のフィールドも同様に追加

return mb_convert_encoding($adminBody, "JIS", $encode);
}

これにより、送信されるメールの内容とフォーマットを自由に変更できます。会社名や住所などの変数を使って柔軟なテンプレートが作成可能です。特にisset()でのチェックを入れることで、未入力項目によるエラーも防止できます。

5. フォームレイアウトの最適化

フォームのレイアウトで特に注意が必要だったのはfieldset要素の扱いです:

.content-form fieldset {
display: contents;
}

.form__innerzone,
.form__innerzoneom {
margin-bottom: 2rem;
width: 100%;
}

.formTable {
width: 100%;
border-collapse: separate;
border-spacing: 0 1rem;
}

.formTable th {
width: 30%;
text-align: left;
vertical-align: top;
padding-top: 0.5rem;
}

fieldset要素は通常、コンテンツの幅を制御するのが難しい特殊な要素ですが、display: contentsを指定することでレイアウトの問題を解決できました。

また、確認画面と入力画面で同じHTMLレイアウト構造を維持することで、CSSでの見た目の切り替えがスムーズになります。テーブルレイアウトの適切な設定も重要です。

6. JavaScript によるリアルタイムバリデーション

サーバーサイドでのバリデーションに加えて、JavaScriptによるリアルタイムバリデーションを追加:

document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form');

form.addEventListener('submit', function(event) {
event.preventDefault();
clearAllErrors();
const isValid = validateRequiredFields();
if (isValid) {
form.submit();
}
});

function validateRequiredFields() {
let isValid = true;

//必須テキストフィールドのチェック
const requiredTextInputs = form.querySelectorAll('input[type="text"][required], textarea[required]');
requiredTextInputs.forEach(input => {
if (!input.value.trim()) {
showError(input, '※未入力です。');
isValid = false;
}
});

//セレクトボックスのチェック
const requiredSelects = form.querySelectorAll('select[required]');
requiredSelects.forEach(select => {
if (!select.value) {
showError(select, '※未選択です。');
isValid = false;
}
});

//チェックボックスのチェック
const policyCheckbox = document.getElementById('php-client-policy');
if (policyCheckbox && !policyCheckbox.checked) {
showError(policyCheckbox, '※個人情報保護方針をご一読いただき、同意をお願いいたします。');
isValid = false;
}

return isValid;
}

//エラー表示用関数
function showError(element, message) {
clearError(element);
const errorElement = document.createElement('span');
errorElement.className = 'error-message';
errorElement.style.color = '#C20017';
errorElement.style.display = 'block';
errorElement.style.fontSize = '93%';
errorElement.style.fontWeight = '500';
errorElement.style.marginTop = '5px';
errorElement.textContent = message;

if (element.id === 'php-client-policy') {
const parentCell = element.closest('td');
if (parentCell) parentCell.appendChild(errorElement);
} else {
element.parentNode.insertBefore(errorElement, element.nextSibling);
}

element.style.borderColor = '#e74c3c';
}

//その他のヘルパー関数...
});

このコードにより、ページ遷移なしで入力エラーを即座にフィードバックできるようになりました。エラーメッセージも「※未入力です。」「※未選択です。」と簡潔に統一し、視覚的にも明確にフィードバックします。

7. 文字化け問題の解決方法

文字化けに悩まされた際の解決策:

<?php 
//ヘッダー送信
header("Content-Type:text/html;charset=utf-8");
?>
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8">
<!-- その他のメタタグやCSS -->
</head>

PHPファイルの先頭に文字コード宣言を追加することで、日本語の文字化け問題を解決しました。また、すべてのファイルのエンコーディングをUTF-8に統一することも重要です。

8. 変数管理の一元化

複数のファイルで利用する変数を一元管理するために、専用の変数ファイルを作成:

<?php
//ROOT PATH
$root_path = $_SERVER['DOCUMENT_ROOT'];
$root_themepath = $_SERVER['DOCUMENT_ROOT'] . '/site/wp-content/themes/my-theme';

//Site URL
$tophomeurl = 'https://www.example.jp/site';
$topthemeurl = 'https://www.example.jp/site/wp-content/themes/my-theme';
$commonsurl = 'https://www.example.jp/site/wp-content/themes/my-theme/commons';

//会社情報
$my_name = '株式会社かんぱに';
$my_postcode = '123-4567';
$my_address1 = '東京都';
$my_address2 = '〇〇区〇〇町1-2-3';
$my_tel = '03-1234-5678';
$my_email = 'info@example.jp';
$my_sitename = '株式会社かんぱに';

//送信先メールアドレス(To:)
$my_recipients = "info@example.jp";
//フォームのメールアドレス入力箇所のname属性の値
$Email = "Email";

//エスケープするためのヘルパー関数
if (!function_exists('e')) {
function e($string) { return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); }
}
?>

この変数ファイルを各PHPファイルの先頭で読み込むことで、変数管理を一元化できます。特に会社情報や設定値が複数ファイルで使われる場合に有効です。

また、同じヘルパー関数が複数回定義されるのを防ぐために、if (!function_exists('e'))による条件分岐を追加しています。

9. セッション管理とセキュリティ対策

フォームのセキュリティを向上させるための対策:

<?php
//バッファリング開始
ob_start();

//ヘッダー送信
header("Content-Type:text/html;charset=utf-8");

//フォーム用変数の読み込み
include($_SERVER['DOCUMENT_ROOT'] . '/site/form-thesis/form-variables.php');

//セッション設定
$useToken = 1; //CSRF対策を有効化
$confirmDsp = 1; //確認画面表示を有効化

//トークンチェック用のセッションスタート(変数定義後)
session_name('PHPMAILFORMSYSTEM');
session_start();
?>

セッションを使ったCSRF対策の実装でポイントとなるのは:

  1. 変数定義の前にバッファリングを開始
  2. 変数定義後にセッションを開始
  3. トークンチェックの実装

特に重要なのは、変数が定義される前にセッション関連の処理を行わないことです。また、送信時のトークンチェックを強化して、不正なリクエストを防止します:

//トークンチェック(CSRF対策)
if ($useToken == 1 && $confirmDsp == 1) {
if (!isset($_POST['mailform_token']) || empty($_SESSION['mailform_token']) ||
($_SESSION['mailform_token'] !== $_POST['mailform_token'])) {
exit('ページ遷移が不正です - トークンが一致しません');
}
//トークン破棄
if (isset($_SESSION['mailform_token'])) unset($_SESSION['mailform_token']);
if (isset($_POST['mailform_token'])) unset($_POST['mailform_token']);
}

10. 動作検証とトラブルシューティング


フォームの実装後に行うべき動作検証とトラブルシューティングのポイント:

デバッグモードの有効化

//デバッグ用
error_reporting(E_ALL);
ini_set('display_errors', 1);

開発・修正中は上記コードでエラー表示を有効にし、問題点を素早く特定できるようにします。本番リリース前には必ず無効化しましょう。

変数スコープに関する問題

PHP工房のメールフォームでは、変数のスコープに関する問題が発生しがちです。特に注意すべき点は:

  1. 変数が定義される前に使用されていないか
  2. インクルードされるファイル間で変数がオーバーライドされていないか
  3. 条件分岐によって変数定義がスキップされていないか

変数を使用する前にisset()でチェックし、必要に応じてデフォルト値を設定するのがベストプラクティスです。

インクルードのタイミング

ヘッダー、フッター、変数ファイルのインクルードは適切なタイミングで行う必要があります。特に以下の点に注意:

  1. 変数ファイルは、その変数を使用する前にインクルード
  2. ヘッダーのインクルードは、HTMLの開始部分
  3. フッターのインクルードは、HTMLの終了部分

インクルードのタイミングによってはHTML構造が崩れたり、「headers already sent」エラーが発生したりする可能性があります。

まとめ

PHP工房のメールフォームは、適切なカスタマイズを施すことで、WordPress環境でもシームレスに動作する高機能なフォームになります。特に重要なポイントは:

  1. 一貫したHTML構造とCSS設計
  2. JavaScriptによるリアルタイムバリデーション
  3. 郵便番号による住所自動入力で使いやすさの向上
  4. メール内容のカスタマイズによる情報の整理
  5. 文字エンコーディングの適切な設定
  6. 変数管理の一元化による保守性の向上
  7. セッション管理とセキュリティ対策
  8. 適切なデバッグとトラブルシューティング

これらのポイントに注意することで、PHP工房のシンプルなフォームから、見た目も機能も充実した高品質なお問い合わせフォームを構築できます。

参考までに、今回実装した完全なソースコードはGitHubにて公開予定です。(※架空のリンクです)