WordPressのカスタム投稿タイプを作成する方法（ソースいじる編）

WordPress初心者の僕がカスタム投稿タイプについていろいろ調べました。
カスタム投稿タイプを作る方法は2つ。

 1. 自分で書く
 2. プラグインを使う

僕はコアのソースコードを書き換えるというのがそもそも好きではありません。
コアのバージョンが上がった時の対応とかがしんどいですし。
書き換えるのであれば継承して上から書くとかが理想です。
ただWordPressはそういった感じではないので、出来るのであればプラグインを使う方向で検討し、どうしても無理そうなら自分で書く。
自分で書く場合は、記述する範囲を最低限に留めることを心がけるのが良いのではないかなと思います。
今回は自分で書く方法についてのみ記述します。

# WordPressのライフサイクル
フレームワークやシステムを紐解くにはライフサイクルを理解しておくのが重要だと個人的に思います。
WordPressもどういった順にファイルが呼ばれるかどういった順に関数が呼ばれるかがわかればいろいろ見えてくるでしょう。
というわけで、調べてみたんですが情報がない。
なのでソースを追うわけですが、「うわぁぁぁ、しんど、無理」ってなりました。
（ソース追ってて思ったのはWordPressは関数ベースなんだなと。歴史詰まってるな〜って感じです。）
なんで一旦、要所だけ抑えます。

今回抑えておくべきは、以下の2つかなと思いました。

 * `functions.php` 
 * アクションフック

# `functions.php`
自分で書く場合は「記述する範囲を最低限に留める」ことを心がけたいです。
カスタム投稿タイプを作るなど、なにかコアに自分で処理を追記したい場合は`functions.php`に書くという情報が多いです。
ライフサイクルを細かく調べれていないですが、割とはじめの段階でこのファイルが呼ばれるのでしょう。
なので、出来る限り`functions.php`以外に記述を書かないとすることにします。
ワードプレスにはテーマが有るので、`wp-content/themes/<使うテーマ>/functions.php`に追記していきましょう。

# カスタム投稿タイプを作る
`functions.php`に書けば良いことがわかったので、実際にカスタム投稿タイプを作る記述をしてみます。
カスタム投稿タイプを作るには`register_post_type()`という関数を使うみたいです。
この関数の情報は以下

## 説明
投稿タイプを作成または変更します。 この `register_post_type()` は必ず `init` アクションの中から呼び出してください。 `init` より前に呼び出すと動作しないため、新規作成または変更した投稿タイプも正常に動作しません。
（`init` アクションについては後述）

## 使い方
```php
object register_post_type( string $post_type [ , mixed $args = array() ] )
```

## パラメータ
\$post_type
（文字列） （必須） 投稿タイプ（最大 20 文字、大文字や空白は禁止）。
初期値： なし

すでに予約されている投稿タイプ

 * post - 投稿
 * page - 固定ページ
 * attachment - 添付ファイル
 * revision - リビジョン
 * nav_menu_item - ナビゲーションメニュー

WordPress 関数の動作を妨害する文字列

 * action
 * order
 * theme

これらを\$post_typeに指定するのはNGです。
またここの文字列は一意にし衝突を避けたほうが良いので、接頭語をつけることが勧められています。

\$args
（配列） （オプション） 引数の配列。
初期値： なし

## 引数
第2引数\$argsについての詳細。一応全部書きます。

`label`
（文字列） （オプション） 投稿タイプを翻訳するための複数形の名前。
初期値： \$post_type

`labels`
（配列） （オプション） この投稿タイプのラベルの配列。デフォルトでは、投稿（post）の labels が階層なし投稿タイプに、固定ページ（page）の labels が階層あり投稿タイプに使用される。
初期値： 空の場合は、`label` の値が `name` へセットされ、`name` の値が `singular_name` へセットされる。

 * `name` - 投稿タイプの一般名、通常は複数形。省略すると \$post_type_object->label と同じ値になる。
 * `singular_name` - この投稿タイプのオブジェクト 1 個の名前（単数形）。デフォルトは 'name' の値。
 * `menu_name` - メニュー名のテキスト。メニュー項目の名前を決める文字列です。デフォルトは `name` の値。
 * `name_admin_bar` - 管理バーの「新規追加」ドロップダウンに入れる名前。デフォルトは、`singular_name` があればその値になり、無ければ `name` の値になる。
 * `all_items` - メニューの「すべての〜」に使うテキスト。デフォルトは `name` の値。
 * `add_new` - 「新規追加」のテキスト。デフォルトは階層あり／なしどちらの投稿タイプも "Add New"。この文字列を国際化対応にするには、gettext context を使って、投稿タイプをマッチさせてください。例： _x('Add New', 'product');
 * `add_new_item` - 「新規〜を追加」のテキスト。デフォルトは "Add New Post" または "Add New Page"。
 * `edit_item` - 「〜を編集」のテキスト。管理画面で、このラベルはカスタム投稿の編集パネルのメインヘッダーに表示されます。デフォルトは階層なしなら "Edit Post"、階層あり投稿タイプなら "Edit Page"。
 * `new_item` - 「新規〜」のテキスト。デフォルトは階層なしなら "New Post"、階層あり投稿タイプなら "New Page"。
 * `view_item` - 「〜を表示」のテキスト。デフォルトは "View Post" または "View Page"。
 * `search_items` - 「〜を検索」のテキスト。デフォルトは "Search Posts" または "Search Pages"。
 * `not_found` - 「〜が見つかりませんでした」のテキスト。デフォルトは "No posts found" または "No pages found"。
 * `not_found_in_trash` - 「ゴミ箱内に〜が見つかりませんでした」のテキスト。デフォルトは "No posts found in Trash" または "No pages found in Trash"。
 * `parent_item_colon` - 「親〜：」のテキスト。階層あり投稿タイプのときのみ使われる。デフォルトは "Parent Page"。

`description`
（文字列） （オプション） 投稿タイプの簡潔な説明。
初期値： ブランク

`public`
（真偽値） （オプション） 投稿タイプをパブリックにするかどうか。true の場合、管理画面とフロントエンド（ユーザー）の両方から利用可能。
初期値： false

 * `false` - 投稿タイプをパブリックにしない。他のところで明示的に用意しない限り、管理画面とフロントエンドのどちらからも使えない。
 * `true` - 投稿タイプをパブリックにする。フロントエンドと管理画面の両方から使えるように。

参考： exclude_from_search、publicly_queriable、show_ui および show_in_nav_menus のデフォルト値は public から継承されるが、デフォルトが設定された後はこの関係に依存せず各々が個別の機能を制御する。

`exclude_from_search`
（真偽値） （必須） この投稿タイプの投稿をフロントエンドの検索結果から除外するかどうか。
初期値： public 引数の反対の値

 * `true` - サイト/?s=検索対象文字列 の結果はこの投稿タイプの投稿を含まない。 
 * `false` - サイト/?s=検索対象文字列 の結果はこの投稿タイプの投稿を含む。

参考：タクソノミーのタームが関連付けられた投稿のリストを表示したければ、exclude_from_search を必ず false にセットする（例：サイト/?タクソノミーのスラッグ=タームのスラッグ または サイト/タクソノミーのスラッグ/タームのスラッグ を開く）。true にセットすると、タクソノミーアーカイブのページ（例：taxonomy.php）で WordPress は投稿を見つけられず、ページ送りすると 404 エラーが発生する…

`publicly_queryable`
（真偽値） （オプション） フロントエンドで post_type クエリが実行可能かどうか。これは parse_request() の一部として実行される。
初期値： public 引数の値

参考：次のようなクエリが影響を受ける（リライトによっても）。

 * `?post_type={投稿タイプのキー}`
 * `?{投稿タイプのキー}={投稿のスラッグ}`
 * `?{投稿タイプの query_var}={投稿のスラッグ}`

参考： FALSE にセットすると、カスタム投稿をプレビューも表示もできなくなる（404 エラーを返す）。

`show_ui`
（真偽値） （オプション） この投稿タイプを管理するデフォルト UI を生成するかどうか。
初期値： public 引数の値

 * `false` - この投稿タイプのユーザーインターフェースを表示しない
 * `true` - （管理パネルで）この投稿タイプのユーザーインターフェースを表示する

参考：投稿や固定ページのようなビルトイン（WordPress に最初から組み込まれている）投稿タイプは、意図的に false に設定されている。

`show_in_nav_menus`
（真偽値） （オプション） ナビゲーションメニューでこの投稿タイプが選択可能かどうか。
初期値： public 引数の値

`show_in_menu`
（真偽値|文字列） （オプション） 管理画面にこの投稿タイプを表示するかどうか。表示するには show_ui が true でなければならない。
初期値： show_ui 引数の値

 * `false` - 管理画面に表示しない
 * `true` - 管理画面のトップレベルのメニューとして表示する
 * `メニューの文字列` - 'tools.php' や 'edit.php?post_type=page' のようなトップレベルのページを指定すると、この投稿タイプをそのサブメニューに配置する。

参考： あるプラグインが作成するメニューページのサブメニューとなるように 'メニューの文字列' を指定すると、この項目はサブメニューの最初の項目になると共に、トップレベルのリンク先を置き換える。そうしたくない場合は、プラグインが admin_menu にメニュー表示アクションをフックする add_action を実行するとき、優先度に 9 以下を指定する必要がある。
参考： この引数は show_ui の値をデフォルトとするが、それ自体のデフォルト値である public に基づく（投稿タイプがパブリックに利用可能である場合には管理画面に表示する）と考えるのが最もわかりやすい。少なくとも、これはビルトインの投稿タイプである post と page に該当する。

`show_in_admin_bar`
（真偽値） （オプション） この投稿タイプを WordPress の管理バーから使えるようにするかどうか。
初期値： show_in_menu 引数の値

`menu_position`
（整数） （オプション） この投稿タイプが表示されるメニューの位置。表示するには show_in_menu が true でなければならない。
初期値： null - デフォルトは「コメントの下」

 * 5 - 投稿の下
 * 10 - メディアの下
 * 15 - リンクの下
 * 20 - 固定ページの下
 * 25 - コメントの下
 * 60 - 最初の区切りの下（コメントの下に区切りがある）
 * 65 - プラグインの下
 * 70 - ユーザーの下
 * 75 - ツールの下
 * 80 - 設定の下
 * 100 - 二つ目の区切りの下（設定の下に区切りがある）

`menu_icon`
（文字列） （オプション） このメニューで使用するアイコンの URL、または Dashicons のアイコンの名前。
初期値： null - デフォルトは投稿アイコン
用例

 * `'dashicons-video-alt'` （Dashicons のビデオアイコンを使う）
 * `'get_template_directory_uri() . "images/cutom-posttype-icon.png"'` （現在のテーマに含まれているイメージを使う）

`capability_type`
（文字列|配列） （オプション） 閲覧／編集／削除の権限を構築する文字列。権限を構築するベースにこの引数を使う場合、配列を指定すると複数形の名前を変更できる。例えば array('story', 'stories') を指定すると、最初の要素が単数形の権限、二番目が複数形の権限として使われる。これは、配列を指定しない場合に自動生成される名前（"storys" になる筈）の代わりになる。'capabilities' パラメータによって明示的にセットされなければ、この 'capability_type' パラメータが権限構築のベースとして使われる。これを有効にするには `map_meta_cap` を true にする必要がある。
初期値： "post"
使用できる権限タイプの幾つか（たぶん網羅したリストではない）：

 * post （デフォルト）
 * page

これらのビルトイン（組み込み済み）タイプは使用できない：

 * attachment
 * mediapage

`capabilities`
（配列） （オプション） この投稿タイプの権限の配列。
初期値： capability_type を使って構築される
デフォルトでは、権限の配列に 7 種類のキーを入れることができる：

 * edit_post, read_post, それと delete_post - これら 3 つはメタ権限であり、通常はコンテキストに応じて、対応する基本権限に変換される。コンテキストは例えば、編集／閲覧／削除される投稿（post）と、チェックされるユーザーまたは権限グループ（role）で決まる。つまり、これらの権限は通常、ユーザーや権限グループに対して直接に許可されるものではない。
 * edit_posts - この投稿タイプのオブジェクトの編集可否を制御する。
 * edit_others_posts - この投稿タイプの、他のユーザーが所有するオブジェクトの編集可否を制御する。投稿タイプが投稿者をサポートしなければ、これは edit_posts と同様に振る舞う。
 * publish_posts - この投稿タイプのオブジェクトの公開可否を制御する。
 * read_private_posts - プライベートなオブジェクトの閲覧可否を制御する。

参考：後の 4 つは基本権限であり、WordPress コアのいろいろな箇所でチェックされる。
さらに 7 種類の基本権限があるが、それらは map_meta_cap() を除く WordPress コアでは直接参照されない。map_meta_cap() は、最初に説明した 3 つのメタ権限を受け取り、コンテキストによってユーザーや権限グループに対してチェックされる一つ以上の基本権限へ変換する。下記の権限は map_meta_cap() の中でのみ使われる。そのため、下記の権限は 'map_meta_cap' 引数を true にして（デフォルトは false）投稿タイプを登録したときにデフォルトで割り当てられる。

 * read - この投稿タイプのオブジェクトの閲覧可否を制御する。
 * delete_posts - この投稿タイプのオブジェクトの削除可否を制御する。
 * delete_private_posts - プライベートなオブジェクトの削除可否を制御する。
 * delete_published_posts - 公開されているオブジェクトの削除可否を制御する。
 * delete_others_posts - 他のユーザーが所有するオブジェクトの削除可否を制御する。投稿タイプが投稿者をサポートしなければ、これは delete_posts と同様に振る舞う。
 * edit_private_posts - プライベートなオブジェクトの編集可否を制御する。
 * edit_published_posts - 公開されているオブジェクトの編集可否を制御する。

`map_meta_cap`
（真偽値） （オプション） WordPress が持つデフォルトのメタ権限処理を使用するかどうか。
初期値： null
参考：false を指定すると admin 権限グループのユーザーは投稿タイプを編集できなくなります。その場合は edit_post 権限をすべての権限グループ（投稿タイプを追加または編集する）へ追加しなければなりません。

`hierarchical`
（真偽値） （オプション） この投稿タイプが階層を持つ（例：固定ページ）かどうか。true の場合、親を指定できるようになる。編集ページに親を選択するボックスを表示するために、'supports' パラメータに 'page-attributes' を含めなければならない。
初期値： false
参考：このパラメータは元々固定ページのために用意された。もし多数のエントリーを作る（ざっと 100 を超えるような）つもりなら、メモリー不足の問題が発生するだろう。このパラメータを true にすると、その投稿タイプの管理画面を表示するときに、WordPress はその投稿タイプのすべてのエントリーをすべてのメタデータと一緒に取得するから。

`supports`
（配列|真偽値） （オプション） add_post_type_support()/en を直接呼び出すエイリアス。バージョン 3.5 以降では、配列の代わりに真偽値 false を指定することによりデフォルトの動作（title と editor）を止めることができる。
初期値： title と editor

 * 'title' （タイトル）
 * 'editor' （内容の編集）
 * 'author' （作成者）
 * 'thumbnail' （アイキャッチ画像。現在のテーマが post-thumbnails をサポートしていること）
 * 'excerpt' （抜粋）
 * 'trackbacks' （トラックバック送信）
 * 'custom-fields' （カスタムフィールド）
 * 'comments' （コメントの他、編集画面にコメント数のバルーンを表示する）
 * 'revisions' （リビジョンを保存する）
 * 'page-attributes' （メニューの順序。「親〜」オプションを表示するために hierarchical が true であること）
 * 'post-formats' （投稿のフォーマットを追加。投稿フォーマットを参照）

参考：アイキャッチ画像を使用できるカスタム投稿タイプを使うときは、テーマがアイキャッチ画像をサポートしているか、または add_theme_support() を呼び出しているかチェックすること。

`register_meta_box_cb`
（callback） （オプション） 編集フォームのメタボックスをセットアップするのに呼び出すコールバック関数を指定する（関数名を文字列で指定）。コールバック関数は 1 つの引数 \$post （現在編集中の投稿の WP_Post オブジェクト）を受け取る。コールバックの中では remove_meta_box()/en と add_meta_box() を呼び出す。
初期値： なし

`taxonomies`
（配列） （オプション） この投稿タイプで使用する、登録されたタクソノミーの配列（category や post_tag など）。register_taxonomy_for_object_type() を直接呼び出す代わりに使用可能。タクソノミー（カスタム分類）は register_taxonomy() で登録する必要がある。
初期値： タクソノミー無し

`has_archive`
（真偽値|文字列） （オプション） この投稿タイプのアーカイブを有効にする。デフォルトでは、アーカイブのスラッグとして \$post_type が使われる。
初期値： false
参考：リライトが有効になっている時は、適切なリライトルールが生成される。rewrite オプションを指定して、使用されるスラッグを変更することもできる。

`permalink_epmask`
（文字列） （オプション） リライト用 endpoint ビットマスクのデフォルト値。詳細は Trac チケット 12605 および Make WordPress Plugins ブログの endpoint を要約した投稿を参照。
初期値： EP_PERMALINK
参考：バージョン 3.4 から、この引数は rewrite の 'ep_mask' 引数によって置き換えられました。

`rewrite`
（真偽値|配列） （オプション） この投稿タイプのパーマリンクのリライト方法を変更する。リライトを避けるには false を指定する。
初期値： true - \$post_type をスラッグに使う
\$args 配列

 * 'slug' => 文字列 パーマリンク構造のスラッグを変更。デフォルトは \$post_type の値。翻訳可能であること。
 * 'with_front' => 真偽値 Should the permalink structure be prepended with the front base. （例：パーマリンク構造が /blog/ である場合、false ならリンクは /news/、true なら /blog/news/ になる。）デフォルトは true
 * 'feeds' => 真偽値 この投稿タイプについてフィードのパーマリンク構造を作成する。デフォルトは has_archive 引数の値
 * 'pages' => 真偽値 パーマリンク構造をページ送りに対応させる。デフォルトは true
 * 'ep_mask' => 定数 バージョン 3.4 以降 この投稿タイプに endpoint マスクを割り当てる。詳しくは Trac チケット 19275 および Make WordPress Plugins ブログの endpoint を要約した投稿を参照。
   - これを指定せず permalink_epmask がセットされていると、permalink_epmask の値が使われる。
   - これを指定せず permalink_epmask もセットされていなければ、デフォルトの EP_PERMALINK になる。

参考：プラグインの内部で投稿タイプを登録する場合は、有効化と無効化のフック（下記の「有効化するときリライトルールをフラッシュする」を参照）の中で flush_rewrite_rules() を呼び出すこと。もし flush_rewrite_rules() /enを使わない場合は、カスタム投稿タイプが正しいパーマリンク構造を表示するために、管理画面の 設定 > パーマリンク設定 を開いてパーマリンク構造を更新（変更を保存）する必要がある。

`query_var`
（真偽値|文字列） （オプション） この投稿に使用する query_var キーの名前。
初期値： true - $post_type の値

 * `false` - query_var キーを使用しない。URL 形式のクエリ /?{query_var}={投稿のスラッグ} では表示できない。
 * `文字列` - /?{この引数で指定した文字列}={投稿のスラッグ} で意図したとおり表示される。

参考： 'publicly_queryable' パラメータが false のとき、この query_var パラメータは無効。query_var パラメータを指定するとカスタム投稿タイプのクエリ変数が WordPress の query_var 配列に追加され、認識されるようになる。この配列にないクエリ変数は WordPress によって取り除かれる。
true を指定すると、カスタム投稿タイプ（例：book）を次の形式でリクエストできる： example.com/?book=life-of-pi
true ではなく文字列を指定すると（例：'publication'）次の形式でリクエストできる： example.com/?publication=life-of-pi

`can_export`
（真偽値） （オプション） この投稿タイプをエクスポート可能かどうか。
初期値： true

# 実際にやってみる
`register_post_type()`を使い、上記の引数の意味を理解すればもう出来たも同然！
今回は「Qiitaの投稿」という名前でカスタム投稿タイプを作成してみます。
`functions.php`の一番下に（どこでも良いけどわかりやすくするために下）以下を追記します。

```php:functions.php
register_post_type(
  'oreore_qiita',
  [
    'label' => 'Qiitaの投稿',
    'public' => true,
  ]
);
```
これで管理画面の左メニューに「Qiitaの投稿」と出てきます。
![管理画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128044/75b19fae-1702-1e3c-2da8-3537dfee5aa9.png)
他、第2引数の設定次第でまだまだカスタマイズが可能です。
ここまででカスタム投稿タイプを作ることが出来ましたが、このやり方はWordPressのお作法的によくなさそうです。

# アクションフック
`add_action()`を使うことで、WordPressが用意するアクションや自分で作ったアクションが動く際に、このフックで登録した関数を呼び出すことが出来ます。
用意されているアクションの中に`init`があります。
このアクションで`register_post_type()` を登録してやるとお作法的に良いみたいです。
`add_action()`関数の情報は以下

## 説明
特定のアクションに関数をフックします。

## 使い方
```php
bool add_action( $hook, $function_to_add, $priority, $accepted_args );
```

## パラメータ
\$hook
（文字列） （必須） \$function_to_add がフックされるアクション名。アクションフック名一に覧ついては プラグイン API/アクションフック一覧 を参照。テーマまたはプラグインファイル内のアクション名も指定できる。または特別なタグ "all" を使えば、すべてのフックで関数が呼び出される。
初期値： なし

\$function_to_add
（コールバック） （必須） フックする関数名。注: 'callback' タイプとして PHP ドキュメンテーションに掲載されている文字列形式構文のみが有効
初期値： なし

\$priority
（整数） （オプション） 特定のアクションに関連づけられている関数が実行される優先順序を指定する。少ない数であれば早く実行され、同じ数の優先度である関数はアクションに追加された順序で実行される。
初期値： 10

\$accepted_args
（整数） （オプション） フックした関数が受け入れられる引数の数。WordPress 1.5.1 以降では、フックした関数は、対応する do_action() あるいは apply_filters() が実行される時に、余分に引数を取ることができます。例えば、アクション comment_id_not_found は、これにフックする関数に、リクエストされたコメントの ID を渡すことができます。
初期値： 1

## アクション一覧
たくさんアクションが有るみたいです。以下が参考になります。
[プラグイン API/アクションフック一覧](https://wpdocs.osdn.jp/%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3_API/%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%95%E3%83%83%E3%82%AF%E4%B8%80%E8%A6%A7)

# 最終形態
`add_action()`で`init`時に`register_post_type()`を使う形にするのが理想なので、

```php:functions.php
function oreore_register_post_type_qiita() {
  register_post_type(
    'oreore_qiita',
    [
      'label' => 'Qiitaの投稿',
      'public' => true,
    ]
  );
}

add_action('init', 'oreore_register_post_type_qiita');
```

# まとめ
WordPressのカスタム投稿タイプをソースをいじる方法で作成しました。
`functions.php`とアクションフックを抑えて、ドキュメントを呼んでいけば良さそうです。

# 参考
 * [関数リファレンス/register post type](https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/register_post_type)
 * [関数リファレンス/add action](https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/add_action)
 * [プラグイン API/アクションフック一覧](https://wpdocs.osdn.jp/%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3_API/%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%95%E3%83%83%E3%82%AF%E4%B8%80%E8%A6%A7)