独自formから記事を投稿するWPプラグイン【Outeside Postform】

wordpressには直接管理画面にアクセスしなくても記事を新規投稿・編集することができます。 自分で投稿用に完全オリジナル管理画面を制作したり、CSVやJSONなどのファイルを利用して一括で更新することが可能になります。

wp_insert_post( $post, $wp_error )を利用

worpdressの関数、wp_insert_postは引数を設定することで直接データベースに記事投稿や修正をすることができます。 関数リファレンス/wp insert post

$postに以下の引数を配列で設定することができます。

$my_post = array(
    'ID' => [ <投稿 ID> ] // 既存の投稿を更新する場合に指定。
    'post_content' => [ <文字列> ] // 投稿の全文。
    'post_name' => [ <文字列> ] // 投稿のスラッグ。
    'post_title' => [ <文字列> ] // 投稿のタイトル。
    'post_type' => [ 'post' | 'page' | 'link' | 'nav_menu_item' | カスタム投稿タイプ ] // 投稿タイプ。デフォルトは 'post'。
    'post_category' => [ array(<カテゴリー ID>, ...) ] // 投稿カテゴリー。デフォルトは空(カテゴリーなし)。
    'tags_input' => [ 'tag, tag, ...' | array ] // 投稿タグ。デフォルトは空(タグなし)。
);
wp_insert_post( $my_post, $wp_error ); 

post_title と post_contentは必須です。

$wp_errorはtrueにするとinsertに失敗したときのErrorオブジェクトを返すか返さないかの設定で、未設定の場合は0が返ってきます。未設定にしておいて問題ないでしょう。

独自の入力フォームからPOSTデータを利用してみる

入力フォームの設定。

<form action="[送信するURL]" method="POST">
    [入力フィールドの設定]
    <button type="submit">送信する</button>
</form>

nonceフィールドで認証の設定

フォームで入力したデータは正しく送信されたデータかチェックする必要があります。そのためのnonceフィールドを設定します。

<form action="[送信するURL]" method="POST">
    <?php wp_nonce_field('outside_postform', 'nonce_outside_postform'); ?>
    [入力フィールドの設定]
    <button type="submit">送信する</button>
</form>

通常のPHPだとcsrfの対策のため、ワンタイムトークンを生成してセッションとPOSTデータの一致を調べたり、リファラーを確認したりする必要がありますが、wordpressではwp_nonce_field()の関数を使うことでそのへんの処理を全部やってくれます。

設定したnonceフィールドのデータチェックします。チェエクにはwp_verify_nonce()を利用します。

$_POSTを直接wp_insert_postの引数に設定するのは気が引けるが、wp_insert_postには最初から無害化の処理がされるので、XSS(クロスサイト・スクリプティング)の対策は行う必要はありません。

wp_insert_post();実行後にリダイレクト

wp_insert_post();はインサートが完了しても、そのデータは消えません。POSTする場所は同一ページを利用しているため、リロードをしてしまうと重複して投稿されてしまう。

重複投稿を避けるためwp_insert_post()は現在のページにもう一度リダイレクトさせます。リダイレクトさせるURLに投稿が完了ステータスを付与してアラートで表示させます。

//インサートの実行
public function run_insert_post()
{
    if (is_user_logged_in()) {
        $nonce = $this->post_data['nonce_outside_postform'];
        if (wp_verify_nonce($nonce, 'outside_postform')) {
            $my_post = array(
                'post_status' => 'publish',
                'post_title' => $_POST['post_title'],
                'post_name' => $_POST['post_name'],
                'post_type' => $_POST['post_type'],
                'post_content' => $_POST['post_content'],
                'post_category' => $_POST['category'],
                'tags_input' => $this->insert_post_tag()
            );

            //IDがある場合投稿の編集
            if (isset($_POST['ID']) && get_post($_POST['ID'])) {
                $my_post['ID'] = $_POST['ID'];
            }
            $insert_status = wp_insert_post($my_post);
            if ($insert_status) {
                $mesg = '投稿完了';
            } else {
                $mesg = '投稿失敗';
            }

        } else {
            $mesg = '不正な投稿';
        }

        global $post;
        $this->this_post = $post;
        $url = add_query_arg(array('outside_postform_func_status' => $mesg), get_the_permalink($this->this_post->ID));
        wp_redirect($url);
        exit;
    }
}

ステータスからアラートを実行させてから元のページに遷移して完了となります。

//アラート後にページの遷移
public function run_alert()
{
    $link = get_the_permalink($this->this_post->ID);
    $insert_status = urldecode($this->get_data['outside_postform_func_status']);
    print <<< EOT
    <script>
        alert("{$insert_status}");
        location.href = "{$link}";
    </script>
EOT;
}

wp_insert_post()を使ったプラグインを制作

今までの内容を使ってプラグインを作ってみます。

プラグインにするための仕様をまとめてclass化

  • ショートコードを使ってどこのページでも外部投稿フォームを作れるようにする。
  • 現在のページにPOSTしてwp_insert_post()を動かす
  • 投稿結果のステータスをアラートで表示

プラグイン化する場合、実行タイミングよっては関数が動かない場合があります。actionフックの内容を確認して、適切なタイミングで実行させればエラーは起こりません。

public function __construct()
{
    $this->plugin_path = WP_PLUGIN_URL.'/'.str_replace(basename(__FILE__), "", plugin_basename(__FILE__));
    add_shortcode('outside_postform', array($this, 'outside_postform_func'));
    add_action('wp_enqueue_scripts', array($this, 'theme_name_scripts'));

    //POSTデータの取得
    $args = array(
        'nonce_outside_postform' => FILTER_SANITIZE_ENCODED
    );
    $this->post_data = filter_input_array(INPUT_POST, $args);
    if ($this->post_data['nonce_outside_postform']) {
        add_action('get_header', array($this, 'run_insert_post'));
    }

    //GETデータ取得
    $args = array(
        'outside_postform_func_status' => FILTER_SANITIZE_ENCODED
    );
    $this->get_data = filter_input_array(INPUT_GET, $args);
    if ($this->get_data['outside_postform_func_status']) {
        add_action('wp_head', array($this, 'run_alert'));
    }
}

ページのリロードでPOSTデータを連続投稿しないために、wp_insert_post()の後はリダイレクトをさせます。リダイレクトはget_headerのactionフックを利用することで。htmlより先にリダイレクトを実行できます。

投稿結果のステータスを表示させるアラートはjsを利用するため、wp_headのactionフックを利用します。そして、アラート表示後のパラメーターを最終的に取り除くためlocation.hrefでページを遷移させています。

<?php

/*
Plugin Name: Outeside Postform
Plugin URI:
Description: 外部フォームから投稿を可能にするプラグイン
Version: 1.0
Author: MATSUI KAZUKI
Author URI:
License:
*/

$outside_postform = new OutsidePostform();
class OutsidePostform
{
    private $plugin_path;
    private $post_data;
    private $get_data;
    private $this_post;


    public function __construct()
    {
        $this->plugin_path = WP_PLUGIN_URL.'/'.str_replace(basename(__FILE__), "", plugin_basename(__FILE__));
        add_shortcode('outside_postform', array($this, 'outside_postform_func'));
        add_action('wp_enqueue_scripts', array($this, 'theme_name_scripts'));
        add_filter( 'upload_mimes', array($this, 'custom_mime_types') );    

        //POSTデータの取得
        $args = array(
            'nonce_outside_postform' => FILTER_SANITIZE_ENCODED
        );
        $this->post_data = filter_input_array(INPUT_POST, $args);
        if ($this->post_data['nonce_outside_postform']) {
            add_action('get_header', array($this, 'run_insert_post'));
        }

        //GETデータ取得
        $args = array(
            'outside_postform_func_status' => FILTER_SANITIZE_ENCODED
        );
        $this->get_data = filter_input_array(INPUT_GET, $args);
        if ($this->get_data['outside_postform_func_status']) {
            add_action('wp_head', array($this, 'run_alert'));
        }
    }


    //csvを許可
    public function custom_mime_types( $mimes ) {
        $mimes['csv'] = 'text/csv';
        return $mimes;
    }


    //ショートコード
    public function outside_postform_func()
    {
        $form = '';
        if (is_user_logged_in()) {
            $nonce = wp_nonce_field('outside_postform', 'nonce_outside_postform', true, false);
            $action_url = $_SERVER['REQUEST_URI'];

            //投稿タイプ
            $select_option = '';
            $args = array(
                'public' => true,
            );
            $post_types = get_post_types($args, 'names');
            unset($post_types['attachment']);
            foreach ($post_types as $v) {
                $select_option .= '<option>' . $v . '</option>';
            }
            unset($v);

            //カテゴリー、タグ
            $cats_li = $this->get_post_category_terms('category');
            $tags_li = $this->get_post_category_terms('post_tag');

            //formタグ
            $form = <<< EOT
            <form id="outside_postform_func" action="{$action_url}" method="POST">
                {$nonce}
                <input type="number" name="ID" value="" placeholder="ID"><span class="sub">※修正の場合はIDを入力する</span>
                <input type="text" name="post_title" value="" placeholder="ここにタイトルを入力">
                <input type="text" name="post_name" value="" placeholder="スラッグ">
                <span class="heading">投稿タイプ</span>
                <select name="post_type">{$select_option}</select>
                <span class="heading">カテゴリー</span>
                {$cats_li}
                <span class="heading">タグ</span>
                {$tags_li}
                <textarea name="post_content"></textarea>
                <button class="button button-large button-primary" type="submit">送信する</button>
            </form>
EOT;
        }
        return $form;
    }


    //termsのリスト取得
    private function get_post_category_terms($taxonomies)
    {
        $checkbox_li = '<ul class="terms_li">';
        $args = array(
            'hide_empty' => false,
        );
        $cats = get_terms($taxonomies, $args);
        foreach ($cats as $v) {
            $checkbox_li .= <<< EOT
                <li>
                    <label><input type="checkbox" name="{$taxonomies}[]" value="{$v->term_id}">{$v->name}</label>
                </li>
EOT;
        }
        unset($v);
        $checkbox_li .= '</ul>';
        return $checkbox_li;
    }


    //外部ファイル読み込み
    public function theme_name_scripts()
    {
        wp_enqueue_style('style-name', $this->plugin_path . 'assets/css/style.css');
    }


    //インサートの実行
    public function run_insert_post()
    {
        if (is_user_logged_in()) {
            $nonce = $this->post_data['nonce_outside_postform'];
            if (wp_verify_nonce($nonce, 'outside_postform')) {
                $my_post = array(
                    'post_status' => 'publish',
                    'post_title' => $_POST['post_title'],
                    'post_name' => $_POST['post_name'],
                    'post_type' => $_POST['post_type'],
                    'post_content' => $_POST['post_content'],
                    'post_category' => $_POST['category'],
                    'tags_input' => $this->insert_post_tag()
                );

                //IDがある場合投稿の編集
                if (isset($_POST['ID']) && get_post($_POST['ID'])) {
                    $my_post['ID'] = $_POST['ID'];
                }
                $insert_status = wp_insert_post($my_post);
                if ($insert_status) {
                    $mesg = '投稿完了';
                } else {
                    $mesg = '投稿失敗';
                }

            } else {
                $mesg = '不正な投稿';
            }

            global $post;
            $this->this_post = $post;
            $url = add_query_arg(array('outside_postform_func_status' => $mesg), get_the_permalink($this->this_post->ID));
            wp_redirect($url);
            exit;
        }
    }


    //タグの配列を文字列に変更
    private function insert_post_tag() {
        $tag_array = array();
        if(is_array($_POST['post_tag'])){
            foreach($_POST['post_tag'] as $v) {
                $tag_data = get_tag($v);
                $tag_array[] = $tag_data->name;
            }
            unset($v);
        }
        return implode(",", $tag_array);
    }


    //アラート後にページの遷移
    public function run_alert()
    {
        $link = get_the_permalink($this->this_post->ID);
        $insert_status = urldecode($this->get_data['outside_postform_func_status']);
        print <<< EOT
        <script>
            alert("{$insert_status}");
            location.href = "{$link}";
        </script>
EOT;
    }
}

ショートコードを貼り付ければフォームが表示されます。

正社員という奴隷制度に中指を立てるWebエンジニアです。PHPが得意。繋がれた鎖を断ち切るために、自由を取り戻すために、プログラミングスキルを磨く日々です。プログラミングと個人でもできるビジネスについて、情報発信しています。

詳しくはこちら