スパムとハッキングにさようなら!WordPressでの安全なお問い合わせフォーム

スパムとハッキングにさようなら!WordPressでの安全なお問い合わせフォーム セキュリティ

WordPressのお問い合わせフォームは、スパムの対象になりやすいです。
それに加え、お問い合わせフォーム用のプラグインには脆弱性がしばしば見つかります。

実際、プラグインの脆弱性を突かれたハッキングもよく発生します。

個人的には、スパムが嫌ですぐにお問い合わせフォームを閉じました。
そのため、X(旧Twitter)を経由して問い合わせが来ることがあります。

ただ、DMは解放していません。
よって、投稿へのリプという形で問い合わせが来るという感じです。

このこと自体、非常にハードルが上がっています。
だから、お問い合わせを復活させたいとはずっと思っていました。

お問い合わせフォームの復活

この度、約3年振りぐらいにお問い合わせフォームを復活させました。

それは、スパム問題は大丈夫と判断したからです。
自作のフォームなら、そうそう簡単にスパムに侵略されないはずなので。

侵略されたら、その都度対応はしていくつもりです。
自作のため、何とでもできますからね。

あとは、SMTPを利用しないで済む方法が見つかったのも大きいです。
GASを使えば、個人でSMTPを用意しないで済みます。

SMTPによるメール送信は、今後ますます厳しくなっていくはずです。

また、求められるセキュリティレベルもどんどんと高くなります。
それなら、メール送信はGoogleさんに任せようということです。

お問い合わせフォームの構成

WordPressの固定ページを利用しています。
WordPressのソースは、できる限りで触りたくありません。

固定ページにHTMLタグを入力しています。
そして、カスタムCSSとカスタムJavaScriptを利用する形になります。

ここまではWordPressの管理画面だけで済みます。
ただ、ここから先がハードルが少しあがります。

JavaScriptから非同期でPHPにアクセスしています。
そのPHP上でGASにアクセスとなります。

セキュリティに問題なければ、JavaScriptからGASへ直接もありなんですけどね。
それはマズイので、PHPを間に入れているということです。

HTML

<form id="contact-form" method="post">
  <div>
    <label for="name">名前:</label>
    <input type="text" id="name" name="name" required>
  </div>
  <div>
    <label for="email">メールアドレス:</label>
    <input type="email" id="email" name="email" required>
  </div>
  <div>
    <label for="message">メッセージ:</label>
    <textarea id="message" name="message" required></textarea>
  </div>
  <div>
    <button type="submit">送 信</button>
  </div>
</form>

<!-- 確認画面のモーダル(初期状態では非表示) -->
<div id="confirmModal" style="display:none;">
  <p><strong>名前:</strong> <span id="confirmName"></span></p>
  <p><strong>メールアドレス:</strong> <span id="confirmEmail"></span></p>
  <p><strong>メッセージ:</strong></p>
  <p style="white-space: pre-line;" id="confirmMessage"></p>
  <button id="confirmSubmit">確 定</button>
  <div id="loadingSpinner" class="spinner"></div>
  <button id="goBack">戻 る</button>
</div>

<!-- 成功メッセージ表示用の要素 -->
<div id="successMessage" style="display: none; padding: 20px; background-color: #4CAF50; color: white; text-align: center; border-radius: 5px;">
  お問い合わせを受け付けました。
</div>

カスタムCSS

/* ボタンのスタイル */
button {
    background-color: #4CAF50; /* 緑色の背景 */
    color: white; /* 白色のテキスト */
    padding: 15px 32px; /* 上下15px、左右32pxのパディング */
    text-align: center; /* テキストを中央揃え */
    text-decoration: none; /* テキストの装飾をなしに */
    display: inline-block; /* インラインブロック要素として表示 */
    font-size: 16px; /* フォントサイズ */
    margin: 4px 2px; /* 上下4px、左右2pxのマージン */
    cursor: pointer; /* カーソルをポインターに */
    border: none; /* 境界線なし */
    border-radius: 5px; /* 角を丸くする */
}

/* ボタンのホバー時のスタイル */
button:hover {
    background-color: #45a049; /* ホバー時の背景色を少し暗く */
}

/* フェードインエフェクト */
.fade-in {
    animation: fadeIn 1s;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* 確認画面モーダルのスタイル */
#confirmModal {
    background-color: #f5f5f5;
    border: 1px solid #ccc;
    padding: 20px;
    max-width: 90%;
    margin: 0 auto;
}

#confirmModal p {
    margin: 10px 0;
}

#confirmModal strong {
    font-weight: bold;
}

#confirmModal button {
    background-color: #007bff;
    color: white;
    border: none;
    padding: 10px 20px;
    cursor: pointer;
}

#confirmModal button:hover {
    background-color: #0056b3;
}

/* 名前とメールアドレス入力フィールドのスタイル */
#name,
#email {
    width: 100%;
    padding: 10px;
    margin-bottom: 10px;
    font-size: 1rem;
}

/* ローディングスピナーのスタイル */
.spinner {
    border: 4px solid rgba(0, 0, 0, 0.3);
    border-radius: 50%;
    border-top: 4px solid #007bff;
    width: 40px;
    height: 40px;
    animation: spin 2s linear infinite;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: none; /* 初期状態では非表示 */
}

@keyframes spin {
    0% { transform: translate(-50%, -50%) rotate(0deg); }
    100% { transform: translate(-50%, -50%) rotate(360deg); }
}

カスタムJavaScript

document.getElementById('contact-form').addEventListener('submit', handleFormSubmit);

function handleFormSubmit(event) {
  event.preventDefault(); // 通常の送信を阻止
  const name = document.getElementById('name').value;
  const email = document.getElementById('email').value;
  const message = document.getElementById('message').value;

  if (!validateFormData(name, email, message)) {
    return;
  }

  setConfirmationData(name, email, message);
  toggleDisplay('contact-form', false);
  toggleDisplay('confirmModal', true);

  document.getElementById('confirmSubmit').onclick = handleConfirmSubmit;
  document.getElementById('goBack').onclick = handleGoBack;
}

function validateFormData(name, email, message) {
  if (name === '') {
    alert('名前を入力してください。');
    return false;
  }
  if (!email.match(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/)) {
    alert('有効なメールアドレスを入力してください。');
    return false;
  }
  if (message === '') {
    alert('メッセージを入力してください。');
    return false;
  }
  return true;
}

function setConfirmationData(name, email, message) {
  document.getElementById('confirmName').textContent = name;
  document.getElementById('confirmEmail').textContent = email;
  document.getElementById('confirmMessage').textContent = message;
}

function toggleDisplay(elementId, show) {
  document.getElementById(elementId).style.display = show ? 'block' : 'none';
}

function handleConfirmSubmit() {
  const submitButton = document.getElementById('confirmSubmit');
  submitButton.disabled = true;
  submitButton.textContent = '送信中...';
  toggleDisplay('loadingSpinner', true);

  const formData = {
    name: document.getElementById('confirmName').textContent,
    email: document.getElementById('confirmEmail').textContent,
    message: document.getElementById('confirmMessage').textContent
  };

  sendFormData(formData)
    .then(response => {
      if (response.success) {
        toggleDisplay('successMessage', true);
        toggleDisplay('confirmModal', false);
        window.scrollTo(0, 0);
      } else {
        alert('エラーが発生しました。\n' + response.error);
      }
    })
    .catch(() => alert('メールの送信に失敗しました。'))
    .finally(() => {
      submitButton.disabled = false;
      submitButton.textContent = '確定';
      toggleDisplay('loadingSpinner', false);
    });
}

function handleGoBack() {
  toggleDisplay('contact-form', true);
  toggleDisplay('confirmModal', false);
}

function sendFormData(formData) {
  return fetch('/sv/api/send_mail.php', {
    method: 'POST',
    headers: {'Content-Type': 'application/json;charset=UTF-8'},
    body: JSON.stringify(formData)
  })
  .then(response => {
    if (!response.ok) {
      throw new Error('ネットワークレスポンスが不正です。');
    }
    return response.json();
  })
  .catch(error => {
    throw new Error('リクエスト中にエラーが発生しました: ' + error.message);
  });
}

PHP(send_mail.php)に関しては、非公開としておきます。
さすがに手の内を晒すのは、マズイと思いますので。

でも、60行前後のソースのため複雑なことはありません。
このPHPから、GASのプログラムにアクセスしています。
GASのプログラムは、上記記事内で紹介したそのままです。

そして、アクセスと言っても単なるHTTPリクエストです。
curlも使っていません。

それでも、send_mail.phpの中身を知りたい場合は下記から問い合わせてください。

メールで情報を返信します。

なお、この自作のお問い合わせフォームは2時間で制作しました。
コードは、ほぼChatGPTに書かせました。

要件入力、検証、レビューを繰り返しただけです。
それだけでお問い合わせフォームが、簡単に作れます。

なお、嘘のような本当の話ですが、早速問い合わせがありました。
復活から24時間も経っていないのに、仕事の依頼です。

復活させた甲斐があると言うもんです。

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