フロントエンドから簡単に記事投稿できるフォーム(掲示板)作成方法

この記事では、WordPressの管理画面にアクセスせず、ユーザーがフロントエンドから記事を投稿できるフォームを作成する方法と、削除パスワードを使って安全に削除できる仕組みを紹介します。
ショートコード [frontend_post_form][frontend_post_list] を使えば、固定ページ内にフォームと投稿一覧を簡単に設置できます。

1 functions.php に追加するコード(パスワード対応)

注意: 必ず子テーマの functions.php に追加してください。


// -------------------------------------------
// フロントエンド投稿フォーム(削除パスワード必須版)
// -------------------------------------------
function my_frontend_post_form_with_password() {
    ob_start(); // 出力バッファ開始

    // 投稿成功メッセージ表示
    if ( isset($_GET['frontend_post']) && $_GET['frontend_post'] === 'success' ) {
        echo '<p style="color:green;">投稿が完了しました!承認されたら表示します。</p>';
    }

    // 投稿処理開始
    if ( isset($_POST['submit_post']) && check_admin_referer('frontend_post_action', 'frontend_post_nonce') ) {

        $title       = sanitize_text_field($_POST['post_title']); // ハンドルネームorタイトル取得
        $content     = wp_kses_post($_POST['post_content']); // 本文取得
        $category    = intval($_POST['post_category']); // カテゴリーID取得
        $delete_pass = sanitize_text_field($_POST['delete_pass']); // 削除パスワード取得

        // パスワード必須チェック
        if ( empty($delete_pass) ) {
            echo '<p style="color:red;">削除パスワードを入力してください。</p>';
        } else {

            // 投稿者のIP・ブラウザ情報を取得
            $user_ip = !empty($_SERVER['HTTP_CF_CONNECTING_IP']) ? sanitize_text_field($_SERVER['HTTP_CF_CONNECTING_IP'])
                     : (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? sanitize_text_field(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0])
                     : sanitize_text_field($_SERVER['REMOTE_ADDR']));
            $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field($_SERVER['HTTP_USER_AGENT']) : '不明';

            // 投稿データを登録
            $new_post = array(
                'post_title'    => $title,
                'post_content'  => $content,
                'post_status'   => 'pending', // 承認待ち
                'post_author'   => 0, // 非ログインでも投稿可
                'post_category' => array($category),
                'post_type'     => 'post'
            );

            $post_id = wp_insert_post($new_post); // 投稿をデータベースに登録

            if ($post_id && !is_wp_error($post_id)) {
                // 投稿メタデータを保存
                update_post_meta($post_id, '_user_ip', $user_ip);
                update_post_meta($post_id, '_user_agent', $user_agent);
                update_post_meta($post_id, '_delete_pass', $delete_pass);

                // 管理者に通知メール送信
                $admin_email = get_option('admin_email');
                $subject = '新しいフロントエンド投稿がありました';
                $message = "ハンドルネームorタイトル: $title\n";
                $message .= "削除パスワード: $delete_pass\n";
                $message .= "IPアドレス: $user_ip\n";
                $message .= "ブラウザ情報: $user_agent\n";
                $message .= "確認URL: " . get_edit_post_link($post_id) . "\n";
                wp_mail($admin_email, $subject, $message);

                // 投稿完了後にリダイレクトしてメッセージ表示
                wp_redirect( add_query_arg('frontend_post', 'success', get_permalink()) );
                exit;
            } else {
                echo '<p style="color:red;">投稿に失敗しました。</p>';
            }
        }
    }
    ?>

    <form id="frontend-post-form" method="post">
        <?php wp_nonce_field('frontend_post_action', 'frontend_post_nonce'); ?>

        <p>
            <label for="post-title">ハンドルネームorタイトル</label><br>
            <input type="text" id="post-title" name="post_title" required>
        </p>

        <p>
            <label for="post-content">本文</label><br>
            <?php wp_editor('', 'post-content', array('textarea_name' => 'post_content', 'media_buttons' => true, 'textarea_rows' => 10)); ?>
        </p>

        <p>
            <label for="post-category">カテゴリー</label><br>
            <select id="post-category" name="post_category" required>
                <option value="" disabled selected>カテゴリーを選択</option>
                <?php
                $categories = get_categories(array('orderby' => 'name', 'order' => 'ASC', 'hide_empty' => false));
                foreach ($categories as $cat) {
                    echo '<option value="' . esc_attr($cat->term_id) . '">' . esc_html($cat->name) . '</option>';
                }
                ?>
            </select>
        </p>

        <p>
            <label for="delete-pass">削除パスワード(必須)</label><br>
            <input type="password" id="delete-pass" name="delete_pass" maxlength="20" required>
        </p>

        <p><input type="submit" name="submit_post" value="投稿する"></p>
    </form>

    <?php
    return ob_get_clean();
}
add_shortcode('frontend_post_form', 'my_frontend_post_form_with_password');

//-------------------------------------------
// 「レビュー待ちとして保存」を変更
//-------------------------------------------
add_filter('gettext', function($translated_text, $text, $domain) {
    if ($translated_text === 'レビュー待ちとして保存') {
        $translated_text = 'レビュー待ち・保存';
    }
    return $translated_text;
}, 20, 3);

// ---------------------------------------------
// フロントエンド用:削除パスワード入力付き投稿一覧
// ---------------------------------------------
function my_frontend_post_list_with_form() {
    ob_start(); // 出力バッファ開始

    // 投稿削除処理
    if ( isset($_POST['delete_post']) && isset($_POST['post_id']) && isset($_POST['delete_pass']) ) {
        $post_id = intval($_POST['post_id']); // 投稿IDを取得
        $input_pass = sanitize_text_field($_POST['delete_pass']); // 入力された削除パスワード
        $saved_pass = get_post_meta($post_id, '_delete_pass', true); // 保存された削除パスワードを取得

        // パスワード照合
        if ($input_pass && $saved_pass && $input_pass === $saved_pass) {
            wp_delete_post($post_id, true); // 投稿を完全削除
            echo '<p style="color:green;">投稿を削除しました。</p>';
        } else {
            echo '<p style="color:red;">削除パスワードが一致しません。</p>';
        }
    }

    // 投稿一覧取得
    $args = array(
        'post_type'      => 'post',
        'post_status'    => array('pending', 'publish'), // 承認待ち&公開済み
        'posts_per_page' => 10,
        'orderby'        => 'date',
        'order'          => 'DESC'
    );
    $query = new WP_Query($args);

    // 投稿がある場合
    if ($query->have_posts()) :
        echo '<ul class="frontend-post-list">';
        while ($query->have_posts()) : $query->the_post();
            $post_id = get_the_ID();
            $title = get_the_title();

            echo '<li>';
            echo '<strong>' . esc_html($title) . '</strong>';
            echo '<form method="post" style="display:inline-block; margin-left:10px;">';
            echo '<input type="hidden" name="post_id" value="' . esc_attr($post_id) . '">';
            echo '<input type="password" name="delete_pass" placeholder="削除パスワード" size="12" required>';
            echo '<input type="submit" name="delete_post" value="削除" class="delete-btn">';
            echo '</form>';
            echo '</li>';
        endwhile;
        echo '</ul>';
    else :
        echo '<p>投稿はありません。</p>';
    endif;

    wp_reset_postdata();
    return ob_get_clean();
}
add_shortcode('frontend_post_list', 'my_frontend_post_list_with_form');

2 汎用CSS


/* ---------------------------------------------- 
 * フロントエンドから簡単に記事投稿できるフォーム作成 
 * ---------------------------------------------- */

#frontend-post-form { /* フォーム全体のスタイル設定 */
    background-color: #f7f7f7; /* フォームの背景を薄いグレーに */
    border: 1px solid #ccc; /* 外枠に薄いグレーの線 */
    padding: 20px; /* 内側の余白 */
    margin-bottom: 20px; /* 下に余白を取る */
    border-radius: 5px; /* 角を丸く */
    max-width: 600px; /* 幅を最大600pxに制限 */
}

#frontend-post-form input[type="text"], /* ハンドルネームorタイトル入力欄 */
#frontend-post-form textarea, /* 本文入力欄 */
#frontend-post-form select { /* カテゴリー選択ボックス */
    width: 100%; /* 横幅いっぱいに広げる */
    padding: 8px; /* 内側の余白 */
    margin-bottom: 10px; /* 各入力欄の下に余白 */
    border: 1px solid #bbb; /* 枠線の色をグレーに */
    border-radius: 3px; /* 角を少し丸く */
    box-sizing: border-box; /* パディング含めて幅を計算 */
}

#frontend-post-form input[type="submit"] { /* 送信ボタン */
    background-color: #0073aa; /* WordPressの青色 */
    color: #fff; /* 文字色を白に */
    padding: 10px 20px; /* 内側余白(上下10px・左右20px) */
    border: none; /* 枠線を消す */
    border-radius: 3px; /* 少し角丸に */
    cursor: pointer; /* マウスカーソルを指マークに */
}

#frontend-post-form input[type="submit"]:hover { /* 送信ボタンのホバー時 */
    background-color: #005177; /* 少し濃い青に変化 */
}

/* ---------------------------------------------------
 * 削除パスワードフォーム付き投稿一覧(スタイリッシュ版) 
 * --------------------------------------------------- */

.frontend-post-list { /* 投稿一覧全体のリスト */
  list-style: none; /* 行頭の・マークを消す */
  padding: 0; /* 内側余白をなくす */
  margin: 0; /* 外側余白をなくす */
  max-width: 600px; /* 最大幅を600pxに制限 */
}

.frontend-post-list li { /* 各投稿リスト項目の枠 */
  background: #fff; /* 背景を白に設定 */
  border: 1px solid #ddd; /* 薄いグレーの境界線 */
  border-radius: 8px; /* 角を丸くする */
  box-shadow: 0 2px 6px rgba(0,0,0,0.05); /* ほんのり影をつける */
  padding: 15px 20px; /* 内側余白 */
  margin-bottom: 15px; /* 各項目の間隔 */
  display: flex; /* フレックスレイアウトで縦並び */
  flex-direction: column; /* 縦方向に要素を配置 */
  transition: transform 0.2s ease, box-shadow 0.2s ease; /* ホバー時のアニメーション */
}

.frontend-post-list li:hover { /* ホバー時の視覚効果 */
  transform: translateY(-2px); /* 少し浮かせる */
  box-shadow: 0 4px 10px rgba(0,0,0,0.08); /* 影を強調 */
}

/* 投稿ハンドルネームorタイトル */
.frontend-post-list strong {
  font-size: 1.1em; /* 少し大きめの文字サイズ */
  color: #333; /* 濃いグレー文字 */
  margin-bottom: 10px; /* 下に余白を追加 */
}

/* パスワード+削除ボタンを横並びに配置 */
.frontend-post-list form {
  display: flex; /* 横並び */
  align-items: center; /* 縦位置を中央に揃える */
  gap: 10px; /* 要素間の隙間 */
}

/* パスワード入力欄 */
.frontend-post-list input[type="password"] {
  flex: 1; /* 入力欄を広げる */
  padding: 6px 10px; /* 内側余白を確保 */
  border: 1px solid #ccc; /* 薄いグレーの枠線 */
  border-radius: 5px; /* 角を丸く */
  transition: border-color 0.2s ease, box-shadow 0.2s ease; /* フォーカス時のアニメーション */
}

.frontend-post-list input[type="password"]:focus {
  border-color: #0073aa; /* フォーカス時の青色ライン */
  box-shadow: 0 0 3px rgba(0,115,170,0.4); /* 青い光を追加 */
  outline: none; /* ブラウザ標準の枠を消す */
}

/* 削除ボタン */
.frontend-post-list input.delete-btn {
  background: linear-gradient(135deg, #e53935, #b71c1c); /* 赤のグラデーション背景 */
  color: #fff; /* 白文字 */
  font-weight: bold; /* 太字にする */
  border: none; /* 枠線なし */
  border-radius: 5px; /* 角を丸く */
  margin-left:10px; /* 左に余白 */
  padding: 7px 14px; /* ボタン内の余白 */
  cursor: pointer; /* カーソルを指マークに変更 */
  transition: background 0.3s ease, transform 0.2s ease; /* ホバー時のアニメーション */
}

.frontend-post-list input.delete-btn:hover {
  background: linear-gradient(135deg, #f44336, #c62828); /* 明るめの赤に変化 */
  transform: scale(1.05); /* 少し拡大して浮かせる */
}

3 使い方

  • 投稿フォームショートコード → [frontend_post_form]
  • 投稿一覧+削除ボックスショートコード → [frontend_post_list]
  • 各投稿の右に「削除パスワード」入力欄+削除ボタンが表示されます。
  • 入力されたパスが一致した場合のみ削除が実行されます。

投稿フォームショートコードをウィジェット、記事内で表示する場合:


[frontend_post_form]
  

投稿一覧+削除ボックスショートコードをウィジェット、記事内で表示する場合:


[frontend_post_list]
  

4 特徴

  • IP共有環境でも安全(パスワードで本人確認)
  • URLにパスワードを残さない(セキュリティ向上)
  • ログイン不要で削除可能

5 注意点

  • 削除パスワードを忘れると削除できません。
  • IPではなく、投稿者が設定したパスワードで本人確認します。
  • 投稿ステータスは pending のままなので、管理者が承認するまで公開されません。

6 完成イメージ





投稿する

削除一覧

7 まとめ

ショートコード [frontend_post_form] を使えば、固定ページや任意の場所に簡単にフォームを設置できます。

カテゴリーは自動でワードプレスから取得します。

縦スクロールもあります。