【wordpressプラグイン】投稿をCSVでインポート・エクスポート可能に!

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'      => [ <文字列&gt; ] // 投稿のスラッグ。
    'post_title'     => [ <文字列> ] // 投稿のタイトル。
    'post_status'    => [ 'draft' | 'publish' | 'pending'| 'future' | 'private' | 登録済みカスタムステータス ] // 公開ステータス。デフォルトは 'draft'。
    'post_type'      => [ 'post' | 'page' | 'link' | 'nav_menu_item' | カスタム投稿タイプ ] // 投稿タイプ。デフォルトは 'post'。
    'post_parent'    => [ <投稿 ID> ] // 親投稿の ID。デフォルトは 0。
    'post_excerpt'   => [ <文字列> ] // 投稿の抜粋。
    'post_category'  => [ array(<カテゴリー ID>, ...) ] // 投稿カテゴリー。デフォルトは空(カテゴリーなし)。
    'tags_input'     => [ 'tag, tag, ...' | array ] // 投稿タグ。デフォルトは空(タグなし)。
    'tax_input'      => [ array( <タクソノミー> => array | string, ...) ] // カスタムタクソノミーとターム。デフォルトは空。
);
wp_insert_post( $my_post, $wp_error );

よく使うとしたらこれらの値だと思いますが、他の内容も設定可能です。気になる人は公式サイトへ。

post_titleとpost_contentは必須なので。

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

独自の入力フォームから記事の投稿

入力フォームの設定。

<form action="[送信するURL]" method="POST">
    <input name="add_title" type="text" value="" placeholder="記事のタイトル">
    <textarea name="add_content" placeholder="記事の内容"></textarea>
    <button type="submit">送信する</button>
</form>

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

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

<form action="[送信するURL]" method="POST">
    <?php wp_nonce_field('my_insert_post', '_wpnonce_my_insert_post'); ?>
    <input name="add_title" type="text" value="" placeholder="記事のタイトル">
    <textarea name="add_content" placeholder="記事の内容"></textarea>
    <button type="submit">送信する</button>
</form>

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

設定したnonceフィールドのデータチェックします。チェエクにはwp_verify_nonce()を利用します。 $_POSTを直接wp_insert_postの引数に設定するのは気が引けるが、wp_insert_postには最初から無害化の処理がされるので、XSS(クロスサイト・スクリプティング)のセキュリティ対策は行う必要がありません。

応用カスタマイズでCSVを使った一括投稿も可能!

入力フォームを利用せずCSVやJSONを利用して一括投稿することもできます。

これでスクレイピングして取得したデータで無限にページを生成していくようなシステムも作成できそうですね。 ポータルサイトを管理する時に重宝します。

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

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

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

  • 管理画面にインポートとエクスポートの設定ページを追加
  • CSVをメディアにアップロードして利用する
  • 新規追加・編集の両方をできるようにする
  • エクスポートはすぐエクセルに貼り付けられうようにtableで出力
  • カスタム投稿タイプも選択してエクスポートできるようにする

今回の制作ではここまでの機能ですが、今後少しづつ機能追加してバージョンアップさせていこうとおもいます。

ダウンロードはこちら
GitHub

設定ページを追加

1ページでまとめても良かったのですが、サブページも追加してインポートとエクスポートを分けます。

//パス
private $path;
private $full_path;
//GETデータ
private $get_data;
//エクスポート
private $export_html;
private $export_post_data;
private $key_items = array('ID', 'post_title', 'post_name', 'post_content', 'post_category', 'tags_input', 'post_type');
private $export_csv_file;
//CSV取得
private $csv_update_array = array();


public function __construct()
{
    add_action('admin_menu', array($this, 'csv_free_menu'));
}

public function csv_free_menu()
{
    add_menu_page('Csv Free Posts', 'Csv Free Posts', 'manage_options', 'csv_free_posts', array($this, 'csv_free_posts'), 'dashicons-welcome-learn-more', 80);
    add_submenu_page('csv_free_posts', 'エクスポート', 'エクスポート', 'manage_options', 'csv_free_posts_sub_export', array($this, 'csv_free_posts_sub_export'));
}

//追加ページ
public function csv_free_posts()
{
    //追加ページの内容
}

//追加サブページ
public function csv_free_posts_sub_export()
{
    //追加サブページの内容
}

更新ページでメディアアップローダーの呼び出し

CSVの保存場所はメディアなのでメディアアップローダーを呼び出します。

wordpressのメディアアップローダー本体のwp_enqueue_media();と制御するためのjsを読み込むために、admin_enqueue_scriptsのactionフックを利用します。

public function __construct()
{
    add_action('admin_menu', array($this, 'csv_free_menu'));
    add_action('admin_enqueue_scripts', array($this, 'my_scripts_method'));
}
//jsの読み込み
public function my_scripts_method()
{
    wp_enqueue_media();
    wp_enqueue_script('my_admin_script', $this->path.'/assets/js/functions.js', array('jquery'), '', true);
    if (function_exists('wp_add_inline_script')) {
        $content_url = content_url();
        $tag = <<< EOT
        var content_path = '$content_url';
        add_csv();
        execution_button();
EOT;
        wp_add_inline_script('my_admin_script', $tag, 'after');
    }
}

メディアからCSVを選択するとinputのvalue値に選択したCSVのパスが入るようにします。

//メディァアップローダーの起動
function add_csv() {
	jQuery(function(){
		
		//メディアアップロードの起動
	    var upload_add_csv;
	    jQuery('.js-add-csv').on('click',function(e) {
		    var _this = jQuery(this);
	        e.preventDefault();
	        if (upload_add_csv) {
	            upload_add_csv.open();
	            return;
	        }

	        //メディアアップローダーの設定
			upload_add_csv = wp.media({
	            title: "CSVの選択",
	            library: { type: "text" },
	            button: { text: "CSVの選択" },
	            multiple: false
            });
			
	        //画像選択後の処理
	        upload_add_csv.on("select", function() {
	            var images = upload_add_csv.state().get("selection");
	            images.each(function(file){ 
					var filePath = file.attributes.url.replace(content_path, '');
					update_inpiut(filePath, _this);
	            });
			});
			upload_add_csv.open();
	    });
	});
}

//inputに選択したCSVのpathを入れる
function update_inpiut(filePath, _this) {
	_this.prev('input[name="import"]').val(filePath);
}

値を現在のページに送信

メディアから設定した値を現在のページに送信します。もともとのページがパラメーター付きなので。GETで送ります。

nonceと現在のページのパラメーターもセットしたフォームがこちら。

<form method="get" action="<?php print $_SERVER['SCRIPT_NAME']; ?>">
	<table class="form-table">
		<tbody>
			<tr>
				<th scope="row">CSVのアップロード</th>
				<td>
					<input name="import" type="text" value="" class="regular-text" readonly="">
					<span class="button js-add-csv">CSVを追加</span>
				</td>
			</tr>
		</tbody>
	</table>
	<?php wp_nonce_field('nonce_csv_free_posts', '_wpnonce_csv_free_posts'); ?>
	<input type="hidden" name="page" value="csv_free_posts">
	<p class="submit">
		<button type="submit" id="update_url" class="button-primary">更新する</button>
		<img class="load" style="display: none;" src="<?php print $this->path; ?>/data/images/icon_loader.gif">
	</p>
</form>

GETで受け取ったデータCSVを取得

CSVはエクセル等で簡単に修正できますが、JSONの用に階層化ができていません。

PHPで扱う場合混乱しないように連想配列に変換すると良いでしょう。keyにはIDを利用しています。そのまま使ってると、あとで絶対混乱するので変換しています。

そして、CSVに記載されているIDがちゃんと正しいかどうかのチェックもここで行っています。get_post();にIDを入れて存在するIDかどうかを調べ、なかった場合エラーIDとして連想配列のkeyとして保存しています。

結構ごちゃごちゃしてしまいましたが、いい方法がこれしか思いつきませんでした……。

    //csvの取得
    public function get_csv_array()
    {
        $csv_key = array();
        $csv_val = array();
        if (file_exists($this->update_csv_path)) {
            $fp = new SplFileObject($this->update_csv_path);
            $fp->setFlags(SplFileObject::READ_CSV);
            $c = 0;
            foreach ($fp as $line) {
                if ($c == 0) {
                    $csv_key = $line;
                } else {
                    $csv_val[] = $line;
                }
                $c ++;
            }
            unset($line);

            $c = 0;
            foreach ($csv_val as $value) {
                $key_c = 0;
                foreach ($csv_key as $key) {
                    if ($key == 'ID') {
                        $error_id = 1;

                        //IDの有無・正誤のチェック
                        if ($value[$key_c]) {
                            $post_data = get_post($value[$key_c]);
                        } else {
                            $post_data = false;
                        }

                        if ($post_data == false) {
                            $value[$key_c] = 'error';
                        }
                    }
                    $this->csv_update_array[$c][$key] = $value[$key_c];
                    $key_c ++;
                }
                unset($key);
                $c ++;
            }
            unset($value);
        }
    }

wp_insert_post();の実行

変換した連想配列を使ってwp_insert_post();を実行していきます。

カテゴリーやタグはCSVに影響がでなさそうなセミコロン区切りの文字列になっていますが、insertするときは配列に戻します。

さらに、正しく投稿されたかのチェックをもして完了です。

最終的にリダイレクトしてパラーメターを取り除けば二重で更新されることもないでしょう。

    //投稿のアップデート
    private function update_post()
    {
        $nonce = $_REQUEST['_wpnonce_csv_free_posts'];
        $nonce_check = wp_verify_nonce($nonce, 'nonce_csv_free_posts');
        if (!$nonce_check) {
            $this->alert_message = '不正な投稿を検知しました。';
        } else {
            $c = 0;
            $resurt = array();
            foreach ($this->csv_update_array as $v) {

                //カテゴリー
                if (!isset($v['post_category'])) {
                    $v['post_category'] = '';
                }
                $v['post_category'] = explode(';', $v['post_category']);

                //タグ
                if (!isset($v['tags_input'])) {
                    $v['tags_input'] = '';
                }
                $v['tags_input'] = explode(';', $v['tags_input']);

                $my_post = array(
                    'post_title' => $v['post_title'],
                    'post_name' => $v['post_name'],
                    'post_content' => $v['post_content'],
                    'post_status' => 'publish',
                    'post_category' => $v['post_category'],
                    'tags_input' => $v['tags_input'],
                    'post_type' => $v['post_type'],
                );

                //idがerrorじゃなければ投稿の編集
                if ($v['ID'] != 'error') {
                    $my_post['ID'] = $v['ID'];
                }
                $resurt[] = wp_insert_post($my_post);
            }
            unset($k, $v);
        
            $error_count = array_count_values($resurt);
            if (isset($error_count[0])) {
                $this->alert_message = $error_count[0] . '件の更新エラーがありました。';
            } else {
                $this->alert_message = '更新が完了しました。';
            }
        }

        //リダイレクトしてアラートの実行
        $url = add_query_arg(array('page'=>'csv_free_posts', 'alert_message'=>$this->alert_message), $_SERVER['SCRIPT_NAME']);
        wp_redirect($url);
        exit;
    }

アラートを表示

アラート実行ページにリダイレクトされ、更新のステータスがjsのalertで表示されます。

さらに、アラート用のパラメーターも削除したURLに遷移させて更新は完了となります。

    //アラート
    public function csv_free_posts()
    {
        if ($this->get_data['alert_message']) {
            $redirect = remove_query_arg('alert_message');
            print <<< EOT
            <script>
                alert("{$this->get_data[alert_message]}");
                location.href="{$redirect}";
            </script>
EOT;
        }
        include 'include/update.php';
    }

無料プラグインダウンロード

ダウンロードはこちら
GitHub

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

詳しくはこちら