あまり使用する機会が少なかったWordPressのSticky Posts(先頭固定表示)ですが、先日Sticky Postsを使用しているクライアントの案件に携わることになりました。
先頭固定表示と聞くと、普通の query_posts
のループでチェックを入れた投稿が勝手に先頭に表示されるようにWordPressが良い感じにしくれるのかと思っていましたが、そうではなかったようです。
WordPressのリファレンスを見ても、Sticky Postsがない場合は、通常の投稿がループに表示されますが、Sticky Postsがあると先頭固定表示の投稿だけになってしまうような方法しか掲載されていません。Googleなどで調べてみても、一度Sticky Postsのループを実行した後、先頭固定表示の投稿を除外して再度ループを実行すると言うコードが煩雑になってしまいがちなものが多く、なんとかコードをスリムにできないか、いろいろと試してみました。
と言うことで、今回は、Sticky Postsがなければ通常の投稿が、先頭固定表示対象があった場合は、それらを通常渡航の先頭に表示させるという当たり前の表示を少ないコードで実装できる方法を備忘録も兼ねてご紹介したいと思います。
Sticky Postsの先頭固定をWordPressは良い感じに表示してはくれない
リファレンスを見ると、以下のようなコードが掲載されています。
$args = array(
‘posts_per_page’ => 1,
‘post__in’ => get_option( ‘sticky_posts’ ),
‘ignore_sticky_posts’ => 1
);
$query = new WP_Query( $args );
これは、Sticky Postsがなければ、通常投稿が表示され、Sticky Postsがあったら、それが表示されるわけですが、上記のように表示件数が1件の場合は、問題ないのですが、5件表示で先頭固定表示対象が1件だった場合、先頭固定表示の1件しか表示されないようになってしまいます。
よく想定されるパターンとしては、5件表示で先頭固定表示対象が1件だった場合は、残り4件は通常投稿が表示されるものかと思います。
なので、公式のリファレンスに掲載の方ほでは問題ありとなってしまいます。
$wpdbを使用して独自のSQLでSticky Postsと通常投稿を取得する。
WordPressで用意されている query_posts
や WP_Query
では、先頭固定表示を含む投稿を表示させることはできないので、$wpdbを使用して独自のSQLを実行します。
SELECT
p1.ID
FROM
wp_posts as p1
WHERE
p1.post_type = ‘post’
AND
p1.post_status IN (‘publish’)
ORDER BY p1.post_date DESC
LIMIT 5
普通に投稿(ID)を取得するSQLは、上記のようになると思いますが、このままだとSticky Postsを上位に固定表示させることができません。
そこで、ORDER BY field
を使用して、順序を制御します。Sticky Postsの投稿IDが、1,2,3 だった場合は、 ORDER BY field(p1.ID, 1,2,3)
のようにします。そうすると、ここで指定した先頭固定表示の投稿ID群が最初に来るようにソートされます。それ以外は、投稿日順というよくあるパターンで投稿を取得することができます。
なので、先ほど最終的なSQLは、以下のようになります。 これで、Sticky Postsがあれば、上位に固定表示できるようになります。
SELECT
p1.ID
FROM
wp_posts as p1
WHERE
p1.post_type = ‘post’
AND
p1.post_status IN (‘publish’)
ORDER BY field(p1.ID, 1,2,3) DESC, p1.post_date DESC
LIMIT 5
function としてテンプレートから実行できるようにする。
先ほどのSQLをfunctionとしてテンプレートから実行できるようにします。また、カスタム投稿タイプなどを考慮して実行時に指定できるようにしておくと汎用性が増すかと思います。
複数の投稿タイプを跨いで取得するなど、他のケースも考えられますが、これをベースに少しカスタマイズしてもらえれば応用ができるかと思います。
/**
* 固定表示含む投稿取得ファンクション
*
* @param string $post_type
* @param array $sticky
* @param integer $limit
* @return array
*/
function get_post_with_sticky(string $post_type, array $sticky, int $limit){
global $wpdb;
if(!(bool)$post_type) $post_type = ‘post’;
if(!(bool)$limit) $limit = 1;
if(!(bool)$sticky) $sticky = [‘null’];
$sticky = implode(‘,’,$sticky);
$post_status = [‘publish’];
if(current_user_can( ‘administrator’ )) $post_status[] = ‘private’;
$post_status = implode(“’,’”,$post_status);
$sql = “SELECT
p1.ID
FROM
{$wpdb->posts} as p1
WHERE
p1.post_type = ‘{$post_type}’
AND
p1.post_status IN (‘{$post_status}’)
ORDER BY field(p1.ID, {$sticky}) DESC, p1.post_date DESC
LIMIT {$limit}
";
$result = $wpdb->get_results($sql);
return $result;
}
テンプレートで投稿を表示する
先ほどのfunctionをテンプレートから実行して投稿を表示させたいと思いおます。
WordPress標準の query_posts
などでは、while
を使用しますが、foreach
を使用して投稿を表示させます。
これは、ただの1例ですが、テンプレートでは、以下のようにして投稿を表示させることができます。
<?php $postdata = get_post_with_sticky(‘post’, get_option(‘sticky_posts’), 5); ?>
<?php if((bool)$postdata): foreach($postdata as $data): global $post; $post = get_post( $data->ID ); setup_postdata($post); ?>
<article class=“post”>
<p class=“post__title”>
<a href=“<?php the_permalink(); ?>”><?php the_title(); ?></a>
</p>
<time datetime=“<?php the_time(‘Y-m-d’); ?>” class=“post__time”><?php the_time(‘Y.m.d D’); ?></time>
</article>
<?php endforeach; endif; wp_reset_postdata(); ?>
独自のSQLで取得した投稿なので、 global $post;
$post = get_post( $data->ID );
このあたりの記述が必要になってきます。
また、 setup_postdata($post);
この記述をすることで、WordPress標準の the_title()
や the_permalink()
などのテンプレートタグを使用することが可能になります。
いかがでしたでしょうか?テンプレート側のコードは、かなりシンプルになったかと思います。WordPressは、独自にSQLを実行することで、標準ではできないようなことも意外と簡単に実現できたりするので、ぜひ色々と試してみてください。