ポストありますね。カスタムポストありますね。この二つを関連づけて互いに補完し合う関係を作ろうと奮闘しました。
WordPressの基本は投稿(ポスト)で、このポストに紐付く形でタームとかポストメタがあります。大抵すべてがIDによって関連づけられますね。でもやりたいのはタイトル照合によるポスト同士の関連づけなんです。できるのか。力業でやりました。
ポストとカスタムポストのリレーション
何のこっちゃ。と思われますね。まず何をやりたかったのか説明します。
やりたいことは関連する事柄についての二つの動作です。ひとつはポストの中に出現するある言葉について、その言葉の詳細な記事を別途作成して繋げることです。
例えば食べ物について書いたポストの中に餃子って項目があったとして、ポストとは別の「餃子」というタイトルの記事を作って、それらを相互に繋げたいということですね。
もうひとつは逆に「餃子」の記事の中で「餃子」の項目を持つポストの記事一覧を作成することです。
タームでは駄目なのか
上記希望を真っ当に考えるとカスタムポストよりタクソノミーのタームとして登録するのが正しいとすぐにわかります。
タクソノミー”食べ物”、ターム”餃子”、これでほとんど希望通りのことができます。ポストから項目への関連もあるし、タームからポストの一覧も簡単に作れます。descriptionに何か書けばタームを記事みたいに見せることも出来ます。これで何が駄目なのか。
照合が1対1の単純な関係でなかったとき・・
タームでカバーしきれない壁がありまして、カスタムポストに行き着いたわけですがどういう壁かというとこういう壁です。
- タームのdescriptionだけでは収まらない … そのタームをタクソノミーで分類したりポストメタを付け加えたりしたかった(term_postmetaが最近利用できるようになりましたが)
- 照合の関係が1対1ではなかった … タクソノミー「食べ物」のターム「餃子」だけでなく、タクソノミー「中華料理」にも「餃子」、タクソノミー「ビールに最適」にも「餃子」と、そんなふうに複数のタクソノミーに同じターム名があり、ポストからどのタクソノミーのターム「餃子」を選んでもただ一つの「餃子」というページを表示したかったわけです。
多対1の関係ですね。こういうとき、どう考えると賢いのか全く判りませんでした。
WordPress ではタクソノミーとタームをポストIDで結びつけてリレーションの管理をしています。
「あるポストIDの記事に含まれるタクソノミーとタームのセット」というふうに、ポストID – タクソノミーとタームのセット – ターム という関連付けです。タームだけをポストと結びつけてタクソノミーを無視するということはできないんです。
タームにもIDがありますが、タクソノミーとのセットを前提としていて、別のタクソノミーだと同じターム名でも別のIDがつきます。つまり別もんです。これを同じ名前だからといって統合することなどできません。最初からタームに独立性などないのであります。
そこでカスタムポストの出番
そんなわけで、タームの代わりにカスタムポストを使うことにしたんです。
食べ物の例で言うと、カスタムポスト「食べ物」の各記事は「餃子」「うどん」「天かす」「鶏皮」と、こんなふうになります。
通常ポストにはポストメタ(タームでもいいんですけが)で「ビールに最適」や「好物」てな分類があるとしますね、記事の中に「ビールに最適:餃子」「好物:天かす, 餃子」と、仕込んでるとしましょう。「ビールに最適」の「餃子」でも「好物」の「餃子」でも、どっちでもクリックするとカスタムポスト「食べ物」の「餃子」ページに飛ばしてやりたいという、そういうことです。逆にカスタムポスト「食べ物」の「餃子」ページには、餃子に関する内容に加えて、「餃子」項目を含むポストの一覧を表示します。
こういうことをしたかったと、これが最終的に目標とする動きでした。
実際、具体的に
ちょっと食べ物とか餃子では話がしにくくなってきたし判りにくいこともありましょうし、ちゃんと具体的に書きましょう。実際には姉妹サイトMoviebooの人名に関する事柄についてのお話です。
ポストは映画の記事です。一つの記事は一つの映画タイトルです。記事に含まれるポストメタやタームは、映画に関する事柄で満ちています。製作国や公開年、そして特にクレジット表記についての部分が今回のリレーションにまつわる部分です。
映画のクレジットには「監督」「製作」「脚本」「出演」など、人名に関する項目がたくさんあります。タクソノミーやポストメタのkeyにあたりますが、それ以外にもっと大きく「人名」である点が話をややこしくします。
タクソノミー+タームで上手くいかないのは明白です。複数のタクソノミーに共通のタームがあり、タクソノミーと切り離したターム情報が必要だからです。これを実現するにはポスト扱いにするしかありません。そしてカスタムポスト「人々」を作って、人物の情報を個別記事としました。
どうやって繋げるか
ポストとカスタムポストをどうやって繋げるのか、あれこれ試行錯誤してきました。照合させるのが人名であることは決まっています。ポストに含まれる項目名とカスタムポストのタイトル名が同じこともほぼ決まっています。
ポストに含まれる項目名とカスタムポストのタイトルを比較してリンクを飛ばす
という、これに変わる良い方法を考え中ですが今はまだどうやっていいのかわかりません。本来なら項目名やタイトルではなくIDで繋げるのが一番いいのですが、それを簡単入力できる仕組みを作ることができず、やりかたも思いつきません。ですので無理矢理「項目名とタイトルの照合」路線を引きずっています。いい方法があれば教えてほしいのです。
かつてはURLつまりスラッグを元に繋げる方法を取っていました。URLの存在判定ができる関数なんかを使ったりして、ポストに含まれる項目名とカスタムポストのURL末尾を比較してURLが存在していればリンクを飛ばす、みたいなややこしいことをしていました。そこそこ上手く行っていたのですが、なんせスラッグが日本語表記だし、名前にアルファベットが含まれる時の整形の仕方とか、面倒なことも多くありました。上手く動かない例もありました。
カスタムポストのタイトルはポスト内の項目名と同じですが、スラッグを別のものにしたいということもあって、今はタイトルを照合してその後IDを取得、リンクはIDを使っています。
項目名の分類 – 影のタクソノミー「people」を作った
ポスト側に仕込む分類と項目についてです。具体的には「監督」「脚本」などクレジット分類です。同時に、それが人に関することであるなら「人名」というより大きな分類に含まれることになります。
単純に親子があるタクソノミーとして、人名 – 監督 みたいに登録しようかとも思いましたがそうはしていません。親子関係を作ったところで、同じ名前の子がいろんな親の下に多数ぶら下がってしまうからです。ターム数が爆発します。ですので、現在のところ重要な「監督」を除いて、ポストメタつまり単にカスタムフィールドとして設定しています。その代わり、影のタクソノミー「people」ってのを作って、人名を全部ぶち込んでいます。
これが準備と言えば準備です。ポストを編集するとき、忘れないように登場した人名をpeopeに全部ぶち入れますね。この作業、ずっと面倒でしたが最近とても楽なんです。その話はまたいずれ。
ポストからカスタムポストへのリンク
ポストに含まれる分類と項目(タームやメタバリュー)からカスタムポストへのリンクをこうしました。
クレジット表記がポスト内にあって、その中でカスタムポスト「person」の記事がもしあればリンクをつけるという案配です。
- カスタムフィールドとその項目があるかどうかをチェック
- あれば項目名とカスタムポストのタイトルを比較。同名タイトルのカスタムポストが存在すればその情報をゲット
- カスタムポストの情報のうち、IDを元にリンクを飛ばす
- カスタムポストが存在しないときは単に項目名を表示する
余計な装飾を取っ払ってわかりやすく書くとコードはこんな風になりました。content-single-post.phpテンプレートの中です。
<?php if(post_custom('producer')):?> <?php echo "製作:" ; $people = get_post_custom_values('producer'); //項目を仮保存 ?> <?php foreach ( (array)$people as $key => $person_title ) :?> <?php $page_data = get_page_by_title( $person_title, OBJECT, 'person' ); //カスタムポストの同名タイトルからデータを得ます $p_id = $page_data -> ID; //特にIDを取得しましょう ?> <?php if(isset($p_id)): ?> <!-- そのIDを持つ記事があればリンク付きで表示 --> <a href="/archives/<?php echo $p_id; ?>" rel="bookmark"><?php echo $person_title; ?></a> <?php else:?> <!-- 記事がなければ項目名だけを表示 --> <?php echo $person_title; ?> <?php endif;?> <?php endforeach; ?> <?php endif;?>
こんな感じで、これにdivやspanで表示をコントロールしたクレジット表記を作っています。具体的にはMoviebooの例えばこんなシングルページの上部クレジット欄なんかで確認出来ます。
少し以前まではこのリンク、項目名と等しいスラッグを持つURLでした。ちょっとの工夫でIDとなりました。これでカスタムポストのスラッグも自由につけられるようになりました。
カスタムポストからポストへのリンク
片やカスタムポストの記事です。こちらには人物についての事柄が書かれています。そして、ポストへのリンクというか、記事一覧を含ませています。
ポストで用意した影のタクソノミー「people」を使って一覧を作りました。
条件をつけて記事を取得するループです。こちらはpersonというカスタムポストを作りましたので content-single-person.phpというテンプレートの中に書いています。
<?php global $post; $cat = get_the_title(); // タイトルを覚えます $args = array( 'post_type' => array('post','posttype_diary'), // 抽出するポストタイプ 'numberposts' => '-1', //記事数に制限なし 'order' => 'DESC', //ソートですね 'orderby' => 'meta_value', //ソート項目はカスタムフィールドです 'meta_key' => 'pub_year', //この場合「年」でソートしようとしています 'tax_query' => array( 'relation' => 'AND', //タクソノミーの条件ですが、以下の両方を条件とします array( 'taxonomy' =>'people', // 影のタクソノミー「people」である 'field' => 'name', // people の nameを見るのである 'terms' => $cat, // それが$catつまりタイトルであると 'operator' => 'IN' // てな条件を含む ), array( // もうひとつのこの条件は、「監督」を別途リストとして作ってるのでtax監督を除外するというもの 'taxonomy' => 'director', 'field' => 'name', 'terms' => $cat, 'operator' => 'NOT IN' ) ) ); $myposts = get_posts($args); // 以上の抽出条件でもってポストをゲットする ?> <?php if($myposts): ?> <?php foreach($myposts as $post) : setup_postdata($post); ?> <!-- #ループ内容--> <?php endforeach; ?> <?php endif; ?>
これを仕込んだページは例えばMovieboo カスタムポストのこの記事なんかで確認出来ます。実際には「監督」というタクソノミーが別にあって、監督作品リストという別ループとセットで仕込んでいます。ですのでクエリに無関係っぽいのも入り込んでいますが、理屈は同じです。
まとめ
さてまとめるとこうでした。
やりたいこと
ポストの項目名(多)とカスタムポストのタイトル(1)を多対1のリレーションとして結びつけたい。
ポストの項目名とは、タクソノミーのタームまたはカスタムフィールドのメタバリューのことです。
例えばポストの中に「監督」という分類の「トム・マッカーシー」や「俳優」という分類の「トム・マッカーシー」があったとして、この複数の「トム・マッカーシー」から、ただ一つの「トム・マッカーシーの情報」ページにリンクを飛ばしたいということになります。
準備 – ポスト側
ポストメタでもタームでもどっちでもいいけど、ポストと紐付けた項目を入力、設定する。これは普通の分類を行う行為ですね。
このとき、分類をまとめた影のタクソノミーを設定しておきます。影のタクソノミーとは、分類をまたがった大きな分類を指します。表には出ませんがループを回したり、ポスト一覧を作る際に利用します。
分類が少なければ、phpに「監督または俳優または脚本」などという条件式をくっつければ済む話ですが、分類がもっと多い上に増える可能性もあるので、不動の「人名」という大きな分類を作ってすべての人名をそれに含めるようにしたわけです。
準備 – カスタムポスト側
ポスト内にある項目についての記事を作成、編集しておく。このときタイトルはポスト内の項目と同じにしておくこと。
こちらはさっきの例の続きで言うとカスタムポスト「Person」の「トム・マッカーシーの情報」を書いたポストのことです。項目名とタイトルをリレーションの照合に使いますから、項目名「人名」で使用した同じものをタイトルにします。例で言うと「トム・マッカーシー」です。
タイトルと項目名というあやふやなもので繋げますから、ちょっとした書き損じがあるだけで機能しません。それが弱点です。
ポストからカスタムポストへのリンク – content-single-post.php
項目とカスタムポストのタイトルを照合したループで存在をチェックし、存在していればIDを取得してリンク付きの表示を行う。
上記テンプレート内のコードです。
カスタムポストからポストの一覧を作りたいとき – content-single-“taxonomy”.php
影のタクソノミーを抽出条件にポストのループを作成する。
これも上記テンプレート内のコードになります。
通常ポストの「分類」と「項目」ですが、これをタクソノミーとタームにするのか、カスタムフィールドとメタバリューにするのかはお好みで。タクソノミーとしての扱いを他でも行うかどうかによりますね。
このようにして、ポストとカスタムポストの疑似リレーションを実現しました。何だか説明が難しく、上手く伝わったどうかわかりませんが。
もっとスマートで素敵な方法があればどうぞ教えてください。
[追記]
この記事の続編というか改というか、まとめ直した記事を書きました。
記事の内容非常に参考になりました!
ちょうど記事のリレーション方法を探していたところでした。
ちなみに、同じようなことができる(?)プラグインとして、toolset typesというものがあるようです。
管理者様が施したリレーションはこちらの方法と同じような感じでしょうか?
https://outlook.aptrust.net/toolset-jp-guide/associate-wordpress-post-types
コメントありがとうございます。わかりにくい記事ですが参考になったと言っていただいて光栄です。
プラグインがあるのですね、parentを使ってポストとポストをリレーションさせるということで、私はカスタムフィールドとポストを繋げていますが、目的は同じような感じですね。
私はこの投稿を書いたずっと後、FileMakerでwordpressを管理するようになって、そちらで繋げるようになりました。meta_value = term name = post_title という名前繋がりのリレーションを勝手に作って「リレーションのためだけの影のタクソノミー」を自動転記させて運用しています。