WordPressで管理画面以外から投稿させる機能を作る

Jun 27th (Wed)2012
文字数 10,155文字
所要時間 およそ25
経過時間 299日経過
最終更新日時 2012 年 6 月 27 日
カテゴリー プログラミング
タグ ,
フィードバック 9件

「wp_insert_postという関数でユーザーに投稿させることができるらしいですが、どうすればいいですか」という質問を受けたのですが、その関数だけでは投稿することができません。質問者さんはおそらくWordPress でゲストに記事投稿させるフォームが作れる wp_insert_post() 関数が便利すぎてぎっくり腰になる件とかを見て「へー、そんなに簡単に作れるんだ」と思ったんでしょうが、この記事の作者さんはCakePHPとかでWebサイトを作ったりしているスキルセットの持ち主なので、「便利」とか「簡単」とか思うポイントがちょっと違うと思います。「SQL書かないで関数一発で保存できるなんて超便利」というぐらいじゃないでしょうか。「何も考えずコピペしたらSNS作れた嬉しい」という意味ではありません。

ぼくが声を大にして言いたいのは、フォームも作らないとダメですよということです。データを保存する機能がわかっても、ユーザーからデータを取得するフォームがなければ投稿なんて作れないでしょう。要はTwitterのあの窓です。

twitterのシンプルな投稿フォーム
twitterのシンプルな投稿フォーム

今回は掲示板を例にして、このフォームの作り方から説明しつつ、チュートリアルビデオとソースコードを300円で売りつけるという社会実験へと至る華麗な流れを見せたいと思います。

サマリー

オライリー本などには「この記事の対象読者」という前段が必ずあるので、真似して書いてみます。

この記事の内容

WordPressをブログツールではなく、CMS的に使っている場合、「ユーザーが自由に投稿できたらいいな」と思うことは多々あります。基本的にはそういうことをするプラグインはすでにあります(Post from site)が、あえて自分で投稿する画面を作る方法を紹介します。

この記事の対象読者

WordPressでサイトを制作したことがある方を対象にしています。WordPressのインストール方法やテーマカスタマイズの基本については説明しません。

WordPressというのは単なるブログシステムとして使うこともできますが、Webサービスの土台として活用することもできます。この記事はそのファーストステップです。

目の前にやらなければいけないことがあって、とりあえずそれを解消したいという方にとっては遠回りな方法ですが、実現したいWebサービスがあって、それはいま自分にはできそうもないのでそのとっかかりを掴みたいという方を対象にしています。

この記事で解決できること

WordPressは多くの人に使われているツールですが、その管理画面を一目見て使いこなすことはまだ難しいです。Twitterのようにシンプルな投稿画面にすると多くのコンテンツが集まるというのはわかるでしょう。

やだ、私の管理画面ボタン多過ぎ…!?
やだ、私の管理画面ボタン多過ぎ…!?

そういえば、Greeの田中社長は楽天勤務時代、先輩に「おまえはコメントすら書けないユーザーが世の中の大多数だということをわかってない」と言われて衝撃を受けたそうです。

それでは、説明します。

ステップ1. 仕様の確認

コードを書く前にまず仕様を確認しましょう。

  • 登録しているユーザーは誰でも投稿できる
  • 投稿できるのはカスタム投稿タイプのthreadsである
  • 投稿を行うのは固定ページの「スレッドを作成する(create-thread)」である
  • 投稿する内容は投稿タイトル、投稿本文、カテゴリー、参考URLである
  • 参考URLはカスタムフィールドで実装する

イメージとしては、はてな人力検索のようなコンテンツです。

ステップ2. カスタム投稿タイプの作成

まずはカスタム投稿タイプを作成しましょう。名前はthreadsです。これに関してはCustom Post UIなどのプラグインを利用してください。

ステップ3. 固定ページのテンプレートを作る

固定ページで作成するテンプレートを作ります。名前はpage-create-thread.phpなどにして、中身をこんな感じにします。

<?php
/**
  * Template Name: スレッド投稿フォーム
  */
 get_header(); ?>
これは投稿フォームです
<?php get_footer(); ?>

ステップ4. 投稿用の固定ページを作る

投稿用の固定ページを作成します。タイトルは「スレッドを作成する」で、スラッグはcreate-threadにします。本文はなにも書かなくて構いません。

ページテンプレートは先ほど作成した「スレッド投稿フォーム」にしてください。ページを表示し、「これは投稿フォームです」と表示されることを確認してください。

ステップ5. フォームを作る

先ほど作成したpage-create-thread.phpにフォームを書きます。URLはテキストフィールドで、カテゴリーはドロップダウンで表示します。

<?php
 /**
  * Template Name: スレッド投稿フォーム
  */
 get_header(); ?>
<form action="<?php the_permalink();?>" method="post">
    <table class="form-table">
    <tbody>
        <tr>
            <th><label for="title">タイトル</label></th>
            <td><input id="title" type="text" name="title" value="" /></td>
        </tr>
        <tr>
            <th><label for="content">本文</label></th>
            <td><textarea id="content" name="content"></textarea></td>
        </tr>
        <tr>
            <th><label>カテゴリー</label></th>
            <td><?php wp_dropdown_categories(); ?></td>
        </tr>
        <tr>
            <th><label for="url">参考URL</label></th>
            <td><input id="url" type="text" name="url" value="" /></td>
        </tr>
    </tbody>
    </table>
    <p class="submit">
        <input type="submit" value="投稿する" />
    </p>
</form>
<?php get_footer(); ?>

このフォームは同じURLに戻るフォームです。これだけだと何も起きません。

ステップ6. フォームの投稿内容を確認する

フォームができたら、そのデータを受け取る場所を作ります。テーマフォルダにfunctionsというフォルダを作成し、その中にcreate-thread.phpというファイルを作成してください。これをfunctions.phpから読み込みます。

//投稿用ファイルを読み込む
get_template_part('functions/create-thread');

読み込んだら、create-thread.phpにこんな感じでフックを書きます。

/**
 * テンプレートが読み込まれる直前で実行される
 */
function _my_create_thread(){
    //POSTデータを表示して終了
    var_dump($_POST);
    die();
}
add_action('template_redirect', '_my_create_thread');

template_redirectというアクションフックは、ページが読み込まれてテンプレートを取得する直前に実行されるフックです。ここで$_POST(フォームが投げるデータ)を表示しています。

これだけだとすべてのページが表示されなくなってしまうので、if文で囲みます。

/**
 * テンプレートが読み込まれる直前で実行される
 */
function _my_create_thread(){
    if(is_page('create-thread') && !empty($_POST)){
        //POSTデータを表示して終了
        var_dump($_POST);
        die();
    }
}
add_action('template_redirect', '_my_create_thread');

どうでしょう、「スレッド投稿フォーム」でポストしたときだけvar_dumpが実行されているのがわかりましたでしょうか。

ステップ7. nonceを設定する

こうしたフォームというのはPOSTメソッドを使っているだけなので、よそのページからフォームを飛ばしても受け付けてしまいます。「スレッド投稿フォーム」以外からのPOSTを受け付けてしまうと、予期せぬエラーが発生しますし、CSRF脆弱性を生むことになります。

これを防ぐために、WordPressではnonceという仕組みを採用しています。24時間限り有効なトークンを発行して、その有効性を確かめることで特定のフォームから来たであろうことを保証します。

このための関数はwp_verify_nonceです。先ほどのフォーム内に書きましょう。

<?php
 /**
  * Template Name: スレッド投稿フォーム
  */
 get_header(); ?>
    <form action="<?php the_permalink();?>" method="post">
        <?php wp_nonce_field('create_thread'); ?>

これでフォームを見ると、2つのinput[type=hidden]ができていることがわかります。_wpnonceというinputタグにある値がnonceですね。16進数のわけのわからない値になっていると思います。

先ほど作ったtemplate_redirectフックにおいて、必ずこのnonceをチェックし、合っている場合だけ処理を進めます。

function _my_create_thread(){
    if(is_page('create-thread') && isset($_POST['_wpnonce]) && wp_verify_nonce($_POST['_wpnonce'], 'create_thread')){
        //POSTデータを表示して終了
        var_dump($_POST);
        die();
    }
}

動くかどうか確かめてください。OKですね。

ステップ8. ログインしているユーザーにのみフォームを表示

ログインしていないユーザーにはそもそもフォームを表示せず、「ログインしてください」というリンクをだしておく必要があります。このためフォーム全体をif文でくくります。

<?php
 /**
  * Template Name: スレッド投稿フォーム
  */
 get_header(); ?>
<?php if(is_user_logged_in()): /*ログイン確認*/ ?>
    <form action="<?php the_permalink();?>" method="post">
        <!-- 中略 -->
    </form>
<?php else: ?>
    <p><a href="<?php echo wp_login_url(get_permalink()); ?>">ログイン</a>してください。</p>
<?php endif; ?>
<?php get_footer(); ?>

フォームだけではなく、POSTをうけつける部分もログイン判別を条件として追加しておきます。

function _my_create_thread(){
    if(
        is_page('create-thread')
            &&
        is_user_logged_in()
            &&
        isset($_POST['_wpnonce])
            &&
        wp_verify_nonce($_POST['_wpnonce'], 'create_thread')
    ){
        //POSTデータを表示して終了
        var_dump($_POST);
        die();
    }
}

ステップ9. データを保存する

データの保存自体はwp_insert_postで実現できます。配列にpost_titleなどの形で渡すことで保存できます。2番目の引数にtrueを渡すと、失敗した場合にWP_Errorが返ってきますので、エラーメッセージも利用できます。注意点としては、カテゴリーは必ず配列で渡し、個々の値は整数にキャストすることです。整数以外は受け付けないというのはPHPだと珍しい仕様ですね。

//_my_create_threadのif内
$id = wp_insert_post(array(
	'post_title' => (string)$_POST['title'],
	'post_content' => (string)$_POST['content'],
	'post_status' => 'publish',
	'post_author' => get_current_user_id(),
	'post_type' => 'threads',
	'post_category' => array(intval($_POST['cat']))
), true);
//データの挿入に成功していたら移動
if(!is_wp_error($id)){
	//カスタムフィールドurl_rel(参考URL)を追加
	update_post_meta($id, 'url_ref', $_POST['url']);
	//ページを移動
	header('Location: '.get_permalink($id));
	die();
}

完成したら、動くかどうか確認してみましょう。こうした「誰でも投稿できるフォーム」でHTMLをうけつけることは少ない(Githubぐらい?)ですが、今のところはノーチェックで保存します。出力するときにhtmlspecialcharsなどでエスケープすることにしてください。

ステップ10. エラーメッージを表示

これまでは上手くいった場合だけを想定していますが、タイトル空っぽの投稿が入ったりしたら困りますね。なので、バリデーションを入れ、ダメだった場合はメッセージを表示します。メッセージ格納用のグローバル変数を用意して、エラーが発生するたびにメッセージを追加します。

//create-thread.phpの上の方に書く
global $create_thread_error;
$create_thread_error = array();

それでは、実際にバリデーションを行ってみましょう。

function _my_create_thread(){
    global $create_thread_error;
    if(
        is_page('create-thread')
            &&
        is_user_logged_in()
            &&
        isset($_POST['_wpnonce])
            &&
        wp_verify_nonce($_POST['_wpnonce'], 'create_thread')
    ){
        //ここまできたらnonceはOK
	if(!isset($_POST['title']) || empty($_POST['title'])){
		$create_thread_error[] = 'タイトルが空白です';
	}
	if(!isset($_POST['content']) || empty($_POST['content'])){
		$create_thread_error[] = '本文が空です';
	}
	if(!isset($_POST['url']) || empty($_POST['url']) || !preg_match('/^(https?)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)$/', $_POST['url'])){
		$create_thread_error[] = '参考URLは必ず入力してください';
	}
	//エラーが無かったら投稿を追加
	if(empty($create_thread_error)){
		$id = wp_insert_post(array(
			'post_title' => (string)$_POST['title'],
			'post_content' => (string)$_POST['content'],
			'post_status' => 'publish',
			'post_author' => get_current_user_id(),
			'post_type' => 'threads',
			'post_category' => array(intval($_POST['cat']))
		), true);
		//データの挿入に成功していたら移動
		if(!is_wp_error($id)){
			//カスタムフィールド追加
			update_post_meta($id, 'url_ref', $_POST['url']);
			//ページを移動
			header('Location: '.get_permalink($id));
			die();
		}else{
			$create_thread_error[] = 'エラーが発生しました'.$id->get_error_message();
		}
	}
    }
}

あとはエラーメッセージを表示する関数をcreate-thread.php内で定義します。

/**
 * スレッド作成画面でエラーがあれば表示
 * @global array $create_thread_error
 */
function show_thread_error(){
	global $create_thread_error;
	if(!empty($create_thread_error)){
		echo '<div id="error">';
		echo implode('<br />', $create_thread_error);
		echo '</div>';
	}
}

フォームの上の方に、エラーメッセージを表示しましょう。

<?php
 /**
  * Template Name: スレッド投稿フォーム
  */
 get_header(); ?>
<?php if(is_user_logged_in()): /*ログイン確認*/ ?>
    <?php show_thread_error(); ?>
    <form action="<?php the_permalink();?>" method="post">

これでエラーがある場合はリダイレクトされず、エラーメッセージを表示するようになります。おめでとうございます。

応用編はこんな風にしてはいかが?

これまでのチュートリアルで、「フォームを用意し、そこから取得したデータをWordPressに保存する」ということはできたと思うので、こんな関数を使って色々やってみてください。

  • 匿名ユーザーを作成して、ゲストには匿名ユーザーとして投稿させる。recaptchaなどと組み合せれば、ボットからの襲撃をうけても安心!
  • update_user_metaやdelete_user_metaなどを使って、ユーザープロフィール編集画面をカスタマイズ

他にも色々あります。

おまけ:知っておきたいセキュリティ

今回の通りに作ると<script>タグを埋め込まれてXSS脆弱性を生んでしまう可能性があるので、セキュリティに関しては色々と勉強してみてください。以下の本がよくまとまっています。

PHPサイバーテロの技法―攻撃と防御の実際 [書籍]

PHPサイバーテロの技法―攻撃と防御の実際

価格: ¥ 1,890

著者: GIJOE

出版社: ソシム

出版日: 2005-11

商品カテゴリー: 単行本

ページ数: 239

ISBN: 4883374718


ワンモアシング

このチュートリアルで実際に稼働させながらコードを書いている様子を収めたビデオ(1時間)とソースコード一式を300円で販売中です。記事を読んでも理解できなかったという方は購入してみてください。サンプルビデオも上げておきます。

この投稿は¥300で販売しています。 購入するとダウンロードリストのファイルをダウンロードできるようになります。

いますぐ購入

対応端末

未確認 Kindle 2 Kindle 2
未確認 Kindle 3 Kindle 3
未確認 iPad iPad
未確認 iPhone・iPod Touch iPhone・iPod Touch
確認済 PC PC
未確認 Androdi 携帯 Androdi 携帯
未確認 kobo Touch kobo Touch

ここに掲載されていないものでも表示できる場合があります。端末は管理人が購入し次第検証いたします。
端末追加のご要望についてはお問い合わせよりご連絡ください。

ダウンロード

zip ZIP
テーマ Twenty Thirteen (2012 年 6 月 27 日)

Twenty Elevenの子テーマとして動きます。登録済みユーザーが投稿できるフォームが作成できます。

対応端末:PC
利用不可 7KB 購入後ダウンロード可能
mov MOV
コーディング動画 (2012 年 6 月 27 日)

コーディングの様子を収めた1時間のmovファイルです。QuickTime Playerで見ることができます。解像度は960 x 600です。

対応端末:PC
利用不可 270.5MB 購入後ダウンロード可能

[PR]

  • Pingback: nginx + PHP-FPMで巨大なファイルをダウンロードさせる | 高橋文樹.com

  • Pingback: ボクでもwebサービスつくれるかもと思わせてくれるWordPressプラグイン | ムラッチドットコム

  • Pingback: WordPressのダッシュボード以外から投稿できる会員制サイト作成。 – ダウンアンダー:Wordpress・XOOPS・CMSはおまかせ。東京都練馬区のデザイン事務所 | 印刷・ホームページなど、販売促進

  • Pingback: ほんとうは怖いWP Super Cacheの話 | 高橋文樹.com

  • Pingback: あずみ.net » [Bookmark]WordPressで管理画面以外から投稿させる機能を作る | 高橋文樹.com

  • yama

    求めていた機能でしたので、大変勉強になります。
    利用について、わからなかったのでご質問ですが、カスタム投稿でカテゴリの取得が出来なかったので、以下のようにしてみましたが、管理画面からでしか、適応されていないので、試行錯誤しております。

    page-thread.phpには、で、カスタム投稿のカテゴリに当てて、表示箇所には、ID, ‘threads_cat’, ”, ‘ , ‘, ” ); ?>
    で、タクソノミーのカテゴリを表示するようにしていますが、やはり管理画面からでしか反されないようです。ご教唆いただけると助かります。

    • http://takahashifumiki.com 高橋文樹

      > 管理画面からでしか、適応されていない

      この部分の意味がわかりません。管理画面で確認するとカテゴリーが反映されているが、ブログのページを確認するとカテゴリーが出ていないということでしょうか。

      保存ができないのか、表示ができないのかがよくわかりません。

      あと、「ご教唆」と書かれていますが、「教唆」は「教えてそそのかすこと」を意味し、犯罪等とセットで使われる言葉ですので(ex. 殺人教唆)、こういう場合は「ご教示」や「ご教授」と書くのが普通です。

  • yama

    すいません。大変失礼しました。ご教示いただけると助かります。

    ワードプレスの管理画面で確認すると、手動で管理画面よりカテゴリーを追加登録すると反映されます(当然ですが・・・。)が、page-thread.phpのフォームを経由すると「タイトル」「参考URL」「本文」のみ反映されて、カテゴリーは反映されないので、ブログページにも反映されていないということです。

    「カテゴリの保存」ができれば表示はタクソノミーを割り当てれば良いのではと考えているのですが、「カテゴリーの保存」方法が難しくて困っている次第です。

    推奨のプラグイン「custom-post-type-ui」を使わず、カスタム投稿で、カテゴリを作っているのが原因なのかも?と考えていますが、悩んだ所、問題点を解消できない為、お伺いした次第です。

    • http://takahashifumiki.com 高橋文樹

      サンプルのソースはデフォルトのカテゴリーを割り当てています。

      どうやらカスタム分類 threads_cat を利用しているようなので、割り当て方が通常のカテゴリーとは異なります。wp_isert_postに渡すのではなく、次の関数を使います。

      wp_set_object_terms

      詳しくはソースに同梱されているbulletin-board.phpの _hametuha_thread_add という関数内の処理を見てください。タクソノミーの割り当て方が違っているはずです。

      これ以上詳しく教えるのは大変なので、これぐらいでご勘弁を。

関連投稿

文学フリマ

文学フリマ

4/14(日)大阪、4/28(日)幕張メッセの両方参加します! 来てね!

WordPress本

WordPress本

僕が寄稿したWordPres本が出ました

好きな言葉

どいつもこいつも不味いツラだ — 尾崎紅葉, 出典不明

最新電子書籍

  • 北千住ソシアルクラブ表紙

    北千住ソシアルクラブ ¥100

    北千住でひきこもっていた作家志望の男が同居人に追い出されて、ついに外に出た。荒川…

  • ここにいるよ

    ここにいるよ ¥500

    彼女にはとても素敵な名前があった——ある女性への追想から始まる手記。男が一人の人…

  • cover

    ハムスターに水を ¥500

    大学卒業後、すべてを失って栃木で塾講師として働く「ぼく」。一緒に暮らしている寛美…

  • 泣きながら、ポークソテー表紙

    泣きながら、ポークソテー ¥0

    私達は雨もりの修繕をするように生きてきました。ひっそりと生きる夫婦の、束の間の幸…

    Free
  • cover

    東京守護天使 ¥300

    「ああ、ペニー、この世に小説家なんて一人もいなければよかったのにね! それだけで…

高橋先生の処女作

高橋先生の処女作

2001年幻冬舎NET学生文学大賞受賞作です。

Web制作やります

Web制作やります

Web制作のご依頼は株式会社破滅派へ

不定期メルマガ

高橋文樹.comでは、不定期でニュースレターを配信しています。滅多に送らないので是非購読してください。

高橋文樹.comではプライバシーポリシーに準じて登録情報を取り扱います。