ACFの投稿オブジェクトで紐づけられた側の投稿に、紐づけた側の投稿を表示
こんにちは。LINICAエンジニアの橋本です。
気づいたら前回の投稿から結構日が経ってしまってました。
なにやらタイトルがややこしいですが・・今回もWordPressのお話です。
ACFの投稿オブジェクトを使って、他のカスタム投稿の内容を表示する、というのはよくやることだと思いますが、今回はその反対をやってみました。
例えば、
ニュース
製品
という2つのカスタム投稿タイプがある場合に、ニュース側に投稿オブジェクトを追加し、ニュースごとに関連する製品を紐づけられるようにします。
この実装は非常に楽にできます。
今回はこの逆をやります。つまり、ニュースに製品が紐づけられたら、製品側でも紐づいたニュース記事を自動的に表示する、という実装です。
今回は、投稿オブジェクトで選べる投稿が一つの場合と、複数選択が可能な場合の2パターンをご紹介します。(クラシックエディタでの制作です。)
投稿オブジェクトの作成
せっかくなので、投稿オブジェクトを作成するところからはじめます。
フィールドグループを新規追加します。
フィールドタイプ:「投稿オブジェクト」
フィールドラベル:関連製品
フィールド名 :related_product
投稿タイプでフィルター:製品
としました。
複数選択はひとまず選択せずにおきます。
ニュースにフィールドグループを紐付け、変更内容を保存します。
ニュースを新規作成すると、このように関連製品が選べるようになるので、関連する製品を選び、更新します。
(カテゴリーは事前にACFで追加しておいたものです。)
次に、single-news.phpを編集します。
以下のようになりました。
<main>
<div class="l-container p-post">
<h1 class="c-title">ニュース</h1>
<h2 class="c-title --md"><?php the_title(); ?></h2>
<div class="p-post__date-and-cat">
<p class="c-date"><?php echo the_date(); ?></p>
<p class="c-tag">
<?php
$terms = get_the_terms($post->ID, 'news-cat');
if(!empty($terms)) {
foreach($terms as $term) {
echo '<a href="' . esc_url(get_term_link($term)) . '">' . $term->name . '</a>';
}
}
?>
</p>
</div>
<div>
<div class="p-wysiwyg">
<?php the_content(); ?>
</div>
<?php if ( $products_card = get_field( 'related_product' ) ) :
$product_id = $products_card->ID;
?>
<div class="p-post__card-area">
<h2 class="c-title --sm">関連製品</h2>
<div class="p-post__card-group">
<article class="p-card">
<a href="<?php echo get_the_permalink($product_id); ?>">
<div class="p-card__img-area">
<img src="<?php echo get_the_post_thumbnail_url($product_id); ?>" alt="">
</div>
</a>
<h3 class="p-card__title c-title"><?php echo get_the_title($product_id); ?></h3>
<div class="p-card__text-area">
<?php echo get_the_excerpt($product_id); ?>
</div>
</article>
</div>
</div>
<?php endif; ?>
</div>
</div>
</main>
重要なのは関連製品の部分のコードです。抜き出すと以下の部分です。
<?php if ( $products_card = get_field( 'related_product' ) ) :
$product_id = $products_card->ID;
?>
<div class="p-post__card-area">
<h2 class="c-title --sm">関連製品</h2>
<div class="p-post__card-group">
<article class="p-card">
<a href="<?php echo get_the_permalink($product_id); ?>">
<div class="p-card__img-area">
<img src="<?php echo get_the_post_thumbnail_url($product_id); ?>" alt="">
</div>
</a>
<h3 class="p-card__title c-title"><?php echo get_the_title($product_id); ?></h3>
<div class="p-card__text-area">
<?php echo get_the_excerpt($product_id); ?>
</div>
</article>
</div>
</div>
<?php endif; ?>
関連製品(related_product)に登録がある場合のみ、このエリアが表示されます。投稿のIDを$product_idという変数に格納し、その後、タイトルやサムネの表示に使っています。
上記のコードでこのような表示になったかと思います。(同じ見た目にするにはもちろんCSSを書く必要があります。)
ここまでは、通常よくやる実装かと思います。
逆をやってみる(単数選択編)
では、次に製品側でニュースの情報を表示できるように実装します。
<main>
<div class="l-container p-post">
<h1 class="c-title">製品情報</h1>
<h2 class="c-title --md"><?php the_title(); ?></h2>
<div class="p-post__date-and-cat">
<p class="c-date"><?php echo get_the_date(); ?></p>
<ul class="c-tag">
<?php
$terms = get_the_terms($post->ID, 'products-cat');
if(!empty($terms)) {
foreach($terms as $term):
echo '<li><a href="' . esc_url(get_term_link($term)) . '">' . $term->name . '</a></li>';
endforeach;
}
?>
</ul>
</div>
<div>
<img src="<?php echo get_the_post_thumbnail_url(); ?>" alt="">
<div class="p-wysiwyg">
<?php the_content(); ?>
</div>
</div>
</div>
</main>
製品詳細のページは、こんな感じです。
単にタイトルや投稿日、カテゴリ、サムネとテキスト(the_content())が表示されています。
当然ながら、製品Aが紐づけられているニュース記事(「製品紹介に製品Aを追加しました。」)は表示されていません。
the_content()を囲っているdivの下に、以下のように追加します。
<?php
$args = array(
'post_type' => 'news',
'post_status' => 'publish',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'related_product',
'value' => $post_id,
'compare' => 'like',
),
),
);
$query = new WP_Query($args);
if ($query->have_posts()): ?>
<h2 class="c-title --sm">この製品に関連するニュース</h2>
<?php
while ( $query -> have_posts() ): $query -> the_post();
?>
<div>
<div class="p-products__card-area">
<article class="p-products-card">
<a href="<?php echo get_the_permalink(); ?>">
<p class="c-date"><?php echo the_date(); ?></p>
<h3 class="p-products-card__title"><?php echo get_the_title(); ?></h3>
</a>
</article>
</div>
</div>
<?php endwhile; endif; ?>
重要なのは以下の部分です。
$args = array(
'post_type' => 'news',
'post_status' => 'publish',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'related_product',
'value' => $post_id,
'compare' => 'like',
),
),
);
$query = new WP_Query($args);
この部分で、カスタム投稿タイプ「ニュース(news)」の投稿の中で、「関連製品(related_product)」のIDが現在表示している投稿IDと一致するものを、$argsに入れています。
この記述により、上記のように製品側にもニュースが表示されるようになりました。
逆をやってみる(複数選択編)
次に、紐づけるオブジェクトが複数の場合です。
ACFの投稿オブジェクトを複数選択に変更しておきます。
ニュースの該当のページを見てみると、エラーになりました。
これは複数選択にしたことで、表示方法が変わるためです。
$products_card = get_field( 'related_product' );
var_dump($products_card);
とすると、以下のように表示されました。
複数選択にしたことで、配列に格納されたようです。
というわけで、配列の中をループで回しましょう。
関連製品の部分を以下のように書き換えます。
<?php if ( $products_cards = get_field( 'related_product' ) ) :
$products_cards = get_field( 'related_product' );
?>
<div class="p-post__card-area">
<h2 class="c-title --sm">関連製品</h2>
<div class="p-post__card-group">
<?php foreach ($products_cards as $products_card) {
$product_id = $products_card->ID;
?>
<article class="p-card">
<a href="<?php echo get_the_permalink($product_id); ?>">
<div class="p-card__img-area">
<img src="<?php echo get_the_post_thumbnail_url($product_id); ?>" alt="">
</div>
</a>
<h3 class="p-card__title c-title"><?php echo get_the_title($product_id); ?></h3>
<div class="p-card__text-area">
<?php echo get_the_excerpt($product_id); ?>
</div>
</article>
<?php } ?>
</div>
</div>
<?php endif; ?>
foreachで、$products_cardsの配列をループさせ、製品の投稿IDを取得、表示しています。
これで、ニュース詳細は先ほどの表示に戻りました。
次に、製品側です。
実は先ほどのコードのままでも表示は崩れていないのですが、meta_queryのvalue部分を以下のように変更する必要があります。
'value' => serialize(strval($post->ID)),
理由は、複数選択になったことにより、related_productのデータがシリアライズされたことにあります。
単数選択の場合は、以下のような値だったデータが、
複数選択にすると、以下のように変わりました。(DBのwp_postmetaテーブルを見ると確認できます。)
このような構造になると、単純に
'value' => $post->ID,
としていると、
例えば$post->IDが5のとき、この文字列から5を探すことになるので、5文字のID(10000)が登録されていた場合も、s:5:"10000"となり、一致してしまいます。
そのため、value側もシリアライズして、絞り込む必要があります。
シリアライズすることで、valueも
s:2:"12";
のようになるので、文字数に関係なく、IDを探しにいけるというわけです。
meta_queryでの絞り込みといまだに仲良く慣れていないので、ここまでくるのに結構な時間を溶かしましたが、とりあえず、
投稿オブジェクトは複数選択の場合、シリアライズされて保存されている
というのを覚えて帰っていただければ幸いです。
もし機会があったら、試してみてください。