(WordPress) ある投稿タイプの記事と別の投稿タイプの記事を紐付けるプラグイン

経緯

WordPress で、「ある投稿タイプの記事と別の投稿タイプの記事を紐付け」たくなりました。

しかし、そのままでは紐付けはできないので、次のように考えました。

  • カスタム投稿タイプ「SourcePost」、カスタム投稿タイプ「LinkedPost」、LinkedPostに紐付くカスタムタクソノミー「TaxSP」を予め作成しておく (Custom Post Type UIを利用等)
  • SourcePostの記事を公開・削除等した際にアクションフックで自動的にTaxSPに「名前: SourcePostの記事タイトル」「スラッグ: プレフィックス+SourcePostの記事の投稿ID+SourcePostの記事のスラッグ」という形でタームを生成・削除することでSourcePostとTaxSPの項目名・数を常に同期させる
  • LinkedPostでTaxSPの項目を選択、紐付けることで、TaxSPのスラッグからSourcePostの記事の情報を取得できるようにする
  • これにより、LinkedPostの記事からSourcePostの記事の情報(タイトルやリンク)を得て、リンクジャンプさせることができる

この仕様を実装するプラグインを作成しました。

成果物

プラグイン内でやっていること

  • 設定画面: SoucePostとTaxSPを紐付けする(両方ともラジオボタンでの選択)。また、他のタームとスラッグがバッティングすることを避けるためにプレフィックスも設定できるようにした
    • 投稿タイプ(Radio): SourcePostを選択
    • タクソノミー(Radio): TaxSPを選択
    • プレフィックス(Text): 各タームに付けられるプレフィックス
  • 一括生成画面:
    • SourcePostに既にある記事からTaxSP内にタームを一括生成するプログラム。主にテスト用の想定。そのため、制約を厳しめにしている。
    • 制約:
      • ソースとなる投稿タイプに記事が1つもないとエラー
      • 同期先のタクソノミーに1つでもタームが存在するとエラー
  • 処理: SourcePostの記事に変更がかかると、TaxSP内にタームを追加・更新・削除していく
    • SourcePostで記事を「公開(新規追加)」: TaxSPにタームを追加
    • SourcePostで記事を「更新」: TaxSP内に対応するタームが存在するか探し出し(スラッグで判定)、存在すればタームの情報を記事の更新内容で上書きする
    • SourcePostで記事を「ゴミ箱へ移動」: TaxSP内に対応するタームが存在するか探し出し(スラッグで判定)、タームが1つでもタクソノミーの属する先の投稿タイプの記事で使用されている(チェックが入っている)場合は何もしない。もし使用されていなければ、対応するタームを削除する

利用した機能は主に「メニュー(設定)画面」「サブメニュー画面」「アクションフック」。

挙動のサンプル

リンネアンサンサーラ・設定画面
リンネアンサンサーラ・設定画面

カスタム投稿とカスタムタクソノミーを紐付けと、各タームにつけるプレフィックスの設定

リンネアンサンサーラ・サンプル投稿
リンネアンサンサーラ・サンプル投稿

サンプル投稿がこうあったとして。

リンネアンサンサーラ・一括生成
リンネアンサンサーラ・一括生成

一括生成画面でチェックを付けて実行すると

リンネアンサンサーラで生成されたターム
リンネアンサンサーラで生成されたターム

紐付けたタクソノミーにタームが生成される。

リンネアンサンサーラで生成されたタームを紐付け先の投稿で選択
リンネアンサンサーラで生成されたタームを紐付け先の投稿で選択
リンネアンサンサーラで生成されたタームを紐付け先の投稿で選択
リンネアンサンサーラで生成されたタームを紐付け先の投稿で選択

嵌まった部分

サブメニュー画面にPOSTリクエストを投げる

一括生成画面では「POSTリクエストでシグナルを送り、シグナルが送られていたら処理を実行する」ということをやりたかったのですが、POSTリクエストの内容をデータベースに保存するわけではありません。

そのため、通常の設定画面でパラメータを wp_optionsテーブル に保存する

<form method="post" action="options.php">

action の値が異なるものに設定する必要がありました。

<form method="post" action="admin.php?page=<?= esc_attr( '<PLUGIN_SUBMENU_SETTINGS_PAGE>' ); ?>">

と、URLバーに表示されている自身のURL(GETパラメータも含む)を指定することで解決できました。

サブメニュー画面にPOSTリクエストを投げた際にアクションフックを発動させる

上述と同じつもりでアクションフックを設定しようとしました。

function hoge()
{
    echo "HOGEEEEEEEE";
}
add_action( 'admin_head-admin.php?page=PLUGIN_SUBMENU_SETTINGS_PAGE', 'hoge' );

……が、動かず。

アクションフックに指定する際はまた別のルールで定められている文字列を指定する必要がありました。

プラグインのアクションフックから外部クラスのメソッドを呼ぶ

プラグインの内部で処理ごとにクラスを分けて作成していたのですが、メインクラス→サブクラスのメソッドを呼ぶ必要が出てきました。

同じクラス内のメソッドをアクションフックから呼ぶときは

    public funtion fuga () {
    }
    // 略
    public funtion __construct (){
        add_action( 'before_post_content', [ $this, 'fuga' ] );
    }

こういう形で良いですが、別クラスの場合は

    // Hoge.php
    public funtion fuga () {
    }

    // main.php
    public funtion __construct (){
        add_action( 'before_post_content', [ new Hoge(), 'fuga' ] );
    }

こんなイメージ。クラスの指定を自身 ($this) から他クラスのインスタンスに書き換えているだけですね。

タームを生成する

タームの生成は テンプレートタグwp_insert_term() を使用する。タクソノミー内にタームがなければ生成、ある場合は既存のタームの情報を返す、とのことなので更新は別途処理 (wp_update_term()) が必要。

スラッグ

スラッグに日本語が混じっているとサニタイズ(パーセントエンコード)される。これに対応するために sanitize_title() を使用。

タームをスラッグから取得する

今回、タームを生成した後にそのタームのIDは全く関知していないので、 get_term() は利用できない。

更新・削除の際は「このタームを操作したい」と指定する必要があるので、他の方法でタームの情報を取得する必要がある。

そこで、 get_term_by() を利用した。

get_post(), get_term_by() 等の戻り値の型を指定

get_post()get_term_by() 等で得られる記事やタームの情報の戻り値の型を指定するために、 WordPress 側で定数が用意されている。 OBJECT, ARRAY_A, ARRAY_N

var_dump() の内容を文字列として出力したい

デバッグ中に実際に get_posts() で得られる記事オブジェクトの配列の中身を知りたくなったのですが、そのままだとHTMLタグが出力されてしまうので、そのまま表示させると画面の表示が崩れてしまいます。

それを回避するためにバッファリングを使用しました。

var_dump() の内容をログファイルとして出力

今度は記事を公開・更新した際のアクションフックがきちんと発動しているか確認するために get_posts() で得られる記事オブジェクトの配列や get_term_by() で得られるタームオブジェクトの配列の中身を知りたくなったのですが、ブロックエディタで var_dump() したらより表示がひどいことになりそうだったのでログファイルに逃がすことにしました。

記事更新時のアクションフック

公開されている記事を更新すると publish_<POST_TYPE> のアクションフックも発動するので、わざわざ <POST_TYPE>_updated のアクションフックを設定する必要はない (下書きなどの状態も含めるならば必要そうですが)。

記事削除とゴミ箱に入れるアクションフック

delete_<POST_TYPE> は記事を本当に削除するときに発動する。ゴミ箱は trash_<POST_TYPE>

ゴミ箱に入れた記事のスラッグ

ゴミ箱に入れると記事のスラッグの末尾に __trashed と付く。「ゴミ箱に入れたら、その記事のスラッグと同じタームを削除」という判定を書いていても、 __trashed の有無で不一致を起こす。

$term = get_term_by(
    'slug',
    preg_replace('/(__trashed)$/i', '', sanitize_title($prefix . '-' . $post_ID . '-' . $post->post_name)),
    $taxonomy,
    OBJECT
);

例えばこんな形で __trashed を除去してからマッチングさせること。

explode() の制限

PHPの explode() は第三引数で区切り文字による分割の回数を制限できる。

$termStrs = explode('-', $term->slug, 3); // - で区切るのは3個まで

例えばスラッグで prefix-42-hoge-piyo-fuga のようなスラッグが設定されていた場合、 prefix, 42, hoge-piyo-fuga という風に分けたい。

そこで、第三引数に3つまで、ということで3を指定することでこの挙動を実現した。

wp-env のデータベースコンテナの情報

Host: 127.0.0.1
Username: root
Database: wordpress
Password: password

DB周りの情報はこうなっている模様。なお、DBコンテナのポート番号は起動の度に変わる。

参考

Dashicon

アクションフック一覧

記事関係のアクションフック

post_updated

trash_post

サブメニューを作る

add_options_page はデフォルトの「設定」メニューのサブメニューとして作成する。

投稿タイプを取得

応用

タクソノミーを取得

options.php

スラッグ

小文字の半角英数字とハイフン。

__trashed のスラッグ

wp-env

DBポート

DB情報

mappings

開発中のプラグインのプロジェクトディレクトリをWPイメージ内にマッピングすることは可能

通知バーのクラス

updated で更新、処理完了。 error でエラー。

投稿タイプ

get_posts

get_post

Term オブジェクトとプロパティ

タームの使用件数

$term->count

タクソノミーにタームを追加

wp_insert_term と wp_create_term と wp_insert_category

近い役割のものたち。

  • wp_insert_term(): タクソノミー全般、新規作成が可能 (既に指定タームが存在する場合は既存タームの情報を返す)
  • wp_create_term(): タクソノミー全般、新規作成のみ可能 (更新は不可)
  • wp_insert_category(): カテゴリー専用、新規作成が可能

という違いということでOK?

wp_update_term

wp_delete_term

未使用

CSV Importer

get_terms

get_term

get_term_by

プラグインのアクションフックから外部クラスのメソッドを呼ぶ

sanitize_title

PHP explode

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。