プラグインなしでWordPressにカスタム投稿タイプを実装したい

## まえがき
wordpressはあまり触らないので、誤り、不足、余計な記述などあるかもしれません。
また、シンプルにするため本記事ではタクソノミーなどは扱いません。

## 目的
「info」というカスタム投稿タイプを実装し、wordpressのコントロールパネル上でinfo投稿を作成することを目的とする。
その際各投稿のURLは、`https://{domain}/wp/info/{slug}`となるようにする。

## 構成
プロジェクト名を`wp`とし、themesディレクトリ直下にオリジナルのテーマとして`mytheme`ディレクトリを作る。

最低限必要なファイルとして、`themes`直下に`index.php`、`style.css`、`functions.php`を作っておく。
また、投稿の一覧ページ（`https://{domain}/wp/info`）を表示するための`archive-info.php`、各投稿ページを表示するための`single-info.php`を後々作る。

カスタム投稿タイプの実装部分functions.phpに書くとコードが大きくなるので`mytheme/includes/info/custom-post-type.php`に書いていく。
ただ、このディレクトリ構成はChatGPTが教えてくれたものなので良いのか悪いのかはよくわかってない。

## コード全体
`mytheme/includes/info/custom-post-type.php`
```php:custom-post-type.php
<?php 
    function create_custom_post_type_info() {
        $args = array(
            'labels' => array(
                'name' => 'info',
                'singular_name' => 'info',
                'add_new' => '新規投稿を追加',
                'edit_item' => '編集',
                'view_item' => '投稿を表示',
                'search_items' => '投稿を検索',
                'not_found' => '投稿は見つかりませんでした',
                'not_found_in_trash' => 'ゴミ箱に投稿はありません',
            ),
            'public' => true,
            'show_in_rest' => true,
            'has_archive' => true,
            'rewrite' => array(
                'slug' => 'info',
                'with_front' => false, 
                'hierarchical' => true,
            ),
            'supports' => array(
                'title', 
                'editor', 
                'custom-fields',
            ),
        );

        register_post_type( 'info', $args );
    }
    add_action( 'init', 'create_custom_post_type_info' );
?>
```

`mytheme/functions.php`
```php:functions.php
<?php 
require_once plugin_dir_path(__FILE__) . '/includes/info/custom-post-type.php';
?>
```

<details><summary>テンプレートファイル</summary>

`mytheme/archive-info.php`
```php:archive-info.php

<?php 
get_header();
?>

<h2>info一覧</h2>

<?php
$args = array(
    'post_type' => 'info',
    'posts_per_page' => -1,
    'post_status' => 'publish',
    'orderby' => 'date',
    'order' => 'ASC',
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) : 
?>
<ul>
<?php 
while( $query->have_posts() ) : $query->the_post();
    $url = esc_url( home_url( '/' ) . 'info/' . get_post_field( 'post_name' ) );
?>
    <li><a href="<?php echo( $url ); ?>">
        <?php echo( get_the_title() ); ?>
    </a></li>
<?php 
endwhile;
endif;
?>

</ul>

<?php
wp_reset_postdata();

get_footer();
?>
```

`mytheme/single-info.php`
```php:single-info.php
<?php get_header(); ?>

<h1>info</h1>

<div>
    <?php 
    global $post;
    
    if ( !empty($post) ) :
    ?>
        <div>
            <h2><?php echo( $post->post_title ); ?></h2>
            <small><?php echo( $post->post_date ); ?></small>

            <div>
                <?php print_r( $post->post_content ); ?>
            </div>
        </div>
    <?php 
    endif;
    ?>
</div>

<?php get_footer(); ?>
```

</details>

## 実装

### 投稿タイプの登録
`add_action()`で`init`に`register_post_type()`を追加する。

```php:custom-post-type.php
<?php 
    function create_custom_post_type_info() {
        $args = array(
            // 省略
        );

        register_post_type( 'info', $args );
    }
    add_action( 'init', 'create_custom_post_type_info' );
?>
```

`register_post_type()`の第一引数には投稿タイプ名（ここでは'info'）、第二引数には投稿タイプの情報を定義するための連想配列を渡す。

参考：[register_post_type()](https://developer.wordpress.org/reference/functions/register_post_type/)

### 重要なパラメーター

1\. `public: bool`
コントロールパネルで操作するためにはtrueにする必要がある。
デフォルトではfalseなので注意が必要。

2\. `show_in_rest: bool`
投稿タイプをREST APIに含めるかどうかを決める。
ブロックエディタを使う場合には`true`にする必要がある。falseだと強制敵にクラシックエディタになる。
デフォルトではおそらくfalse（ドキュメントには記載がない）。

3\. `has_archive: bool|string`
アーカイブを持つかどうか決める。trueだと指定のスラッグでアーカイブをもつ。stringを渡した場合はその文字列がスラッグとなる。今回はアーカイブが欲しいのでtrueを渡す。
デフォルトではfalse。

4\. `supports: array|false`
投稿タイプが持つ機能を定義する。
例えば、デフォルトだと'title'と'editor'が含まれるので、タイトルと本文の編集機能を利用できる。ただし、'editor'を含めると自動保存機能も有効になる。
本記事では触れないが、カスタムフィールドを使いたいので'custom-fields'をいれる。（いれなくても一応使えるっちゃ使える）
このパラメーターにはtrueは渡さない。

<details><summary>その他のオプション</summary>

1\. comments
投稿タイプにコメント機能を追加。
コメント数が管理画面に表示され、投稿にコメントを許可できる。

2\. revisions
投稿の履歴を保存。
投稿を更新したときに、以前のバージョンに戻れる機能。複数回の編集を管理できるようになる。

3\. trackbacks
トラックバックをサポート。他のサイトが投稿をリンクした場合、その通知を受け取れる。

4\. author
投稿の作成者情報を表示・編集可能にする。

5\. excerpt
投稿の抜粋を入力可能にする。

6\. page-attributes
ページの属性を設定する。投稿に親子関係を設定したり、表示順序を指定する場合に使る。

7\. thumbnail
投稿のアイキャッチ画像を設定可能にする。投稿編集画面に「アイキャッチ画像」設定欄が追加される。

8\. post-formats
投稿タイプに投稿フォーマットをサポートする。標準、ギャラリー、動画、画像などの形式を選択可能にする。

</details>

### labelsパラメーター

要素が多すぎる。本記事内のコードには最低限の記述はあると思う。
[ドキュメント](https://developer.wordpress.org/reference/functions/get_post_type_labels/)を見て必要なものを適宜追加していけば良い。

<details><summary>deepLの翻訳</summary>

- **name** - 一般的な投稿タイプの名前。同じで $post_type_object->label で上書きされます。デフォルトは 'Posts' / 'Pages' です。
- **singular_name** - この投稿タイプのオブジェクトの名前。デフォルトは 'Post' / 'Page' です。
- **add_new** - 新しい項目を追加するためのラベル。デフォルトは 'Add New' / 'Add New' です。
- **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' です。
- **view_items** - 投稿タイプのアーカイブを表示するためのラベル。デフォルトは 'View Posts' / 'View Pages' です。
- **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:' です。
- **all_items** - サブメニューリンクのすべての項目を示すラベル。デフォルトは 'All Posts' / 'All Pages' です。
- **archives** - ナビゲーションメニューのアーカイブを示すラベル。デフォルトは 'Post Archives' / 'Page Archives' です。
- **attributes** - 属性メタボックスのラベル。デフォルトは 'Post Attributes' / 'Page Attributes' です。
- **insert_into_item** - メディアフレームボタンのラベル。デフォルトは 'Insert into post' / 'Insert into page' です。
- **uploaded_to_this_item** - メディアフレームフィルターのラベル。デフォルトは 'Uploaded to this post' / 'Uploaded to this page' です。
- **featured_image** - フィーチャー画像のメタボックスタイトルのラベル。デフォルトは 'Featured image' です。
- **set_featured_image** - フィーチャー画像を設定するためのラベル。デフォルトは 'Set featured image' です。
- **remove_featured_image** - フィーチャー画像を削除するためのラベル。デフォルトは 'Remove featured image' です。
- **use_featured_image** - フィーチャー画像を使うためのメディアフレーム内のラベル。デフォルトは 'Use as featured image' です。
- **menu_name** - メニュー名のラベル。デフォルトは name と同じ。
- **filter_items_list** - テーブルビューの非表示見出しのラベル。デフォルトは 'Filter posts list' / 'Filter pages list' です。
- **filter_by_date** - リストテーブルの日付フィルタのラベル。デフォルトは 'Filter by date' です。
- **items_list_navigation** - テーブルのページネーションの非表示見出しのラベル。デフォルトは 'Posts list navigation' / 'Pages list navigation' です。
- **items_list** - テーブルの非表示見出しのラベル。デフォルトは 'Posts list' / 'Pages list' です。
- **item_published** - アイテムが公開されたときに使われるラベル。デフォルトは 'Post published. / ページが公開されました。
- **item_published_privately** - アイテムが非公開で公開されている場合に使用するラベル。
デフォルトは 'Post published privately. / ページは非公開で公開されます。
- **item_reverted_to_draft** - アイテムが下書きに切り替えられたときに使用するラベル。
デフォルトは 'Post reverted to draft.' / 'Page reverted to draft. / ページが下書きに戻されました。
- **item_trashed** - アイテムがゴミ箱に移動したときに使われるラベル。デフォルトは 'Post trashed. / ページがゴミ箱に捨てられました。
- **item_scheduled** - アイテムが公開予定になったときに使用するラベル。デフォルトは 'Post scheduled. / ページが予定されています。
- **item_updated** - アイテムが更新されたときに使用するラベル。デフォルトは 'Post updated. / ページが更新されました。
- **item_link** - ナビゲーションリンクブロックのバリエーションのタイトル。デフォルトは 'Post Link' / 'Page Link' です。
- **item_link_description** - ナビゲーションリンクブロックのバリエーションの説明。デフォルトは 'A link to a post. / ページへのリンク' です。

</details>

## 実装ファイルの読み込み

`myteme/functions.php`内で`require_once`で実装を書いたファイルを読み込むことで反映できる。
```php:functions.php
<?php 
require_once plugin_dir_path(__FILE__) . '/includes/info/custom-post-type.php';
?>
```


## コントロールパネルでの操作

設定 > パーマリンクで「変更を保存」をクリックして更新しないといけない。この時、特にほかの操作をする必要はない。
これをしないと正しいURLにアクセスしてもページが見つからない。

あとはデフォルトの投稿機能同様にして投稿を追加していく。

## あとがき
やってみて、全然プラグイン使えばいいと思った。