LoginSignup
1
5

More than 3 years have passed since last update.

先頭固定表示を一覧ページでも実現する

Posted at

2行で

  • 一覧ページ等で先頭固定表示に対応させるのは意外と大変
  • pre_get_postsでのメインクエリ制御でなんとか実現したい

先頭固定表示とは?

標準で管理画面に実装されている機能で、指定した投稿を一番前に表示させたいために存在しています。
存在しているのですが、これにまともに向き合おうとすると結構大変です。

トップページとかの対応は楽

トップページに限らないのですが、メインクエリとは別のクエリを発行する箇所では、ちょっと意識するだけで対応可能です。
例えば、全記事から1件だけを雑に対応する場合は、こんな感じです。

front-page.php
$sticky = get_option('sticky_posts');
$disp_num = 3;
$rest_num = count($sticky) - $disp_num;
if($sticky){
    $args = [
        'posts_per_pages'   => 1,
        'post_in'           => $sticky,
    ];
    $the_query = new WP_Query( $args );
    if ($the_query->have_posts()) : while ($the_query->have_posts()) : $the_query->the_post();
        //ループ処理
    endwhile;
    endif;
    wp_reset_postdata();
}
//残りの件数分($rest_num)、処理を回す
( ignore_sticky_posts => 1  post__not_in は必要 

表示させたい箇所のみ、クエリを発行する場合は、上記の対応で可能です。
一方、カテゴリ一覧等で対応しようとすると、一気に面倒になります。

ページ分割している場合、指定した件数以上の表示になる

例えば1ページ5件表示で、先頭固定表示を2件設定した場合、
最初のページだけ7件出てきます。
すっきりしないです。

2件以上設定すると、重複して再度表示される

設定した投稿が先頭に出てくるのですが、本来ある箇所にも表示されてしまいます。
推測なのですが、先頭固定表示の想定が1件だけなのかもと思います。
(でもarrayで取得出来るので、想定はしているハズ・・・)

メインクエリで解決したい

WP_Queryをcategory.phpとかに書いて、2ページ目以降の対応とかも記述すれば実現出来ますが、ちょっともやっとしますね。何よりメインクエリを破棄して対応するので、無駄が発生します。
なんとかメインクエリで解決したい。
と思って、以下のような記述だと(おそらく)動くと思います。
pre_get_postsの対応です。

functions.php
//----------------------------------------------
// 出力件数設定
//----------------------------------------------
function change_posts_per_page($query) {
    /* 管理画面,メインクエリに干渉しないために必須 */
    if ( is_admin() || ! $query->is_main_query() ){
        return;
    }

    if ( $query->is_home() || $query->is_category()) {
        sticky_pre_get_posts($query);
        return;
    }
}
add_action( 'pre_get_posts', 'change_posts_per_page' );

functions.phpにまとめると長いので分割した場合

fixed_post.php
<?php
/**
* 先頭固定表示の対応、抽出して投稿IDを出力
*
* @param int 表示件数
* @param string カテゴリーを絞る場合は、カテゴリースラッグ
* @return array 投稿idの配列
*/
function show_top_page_post_ids(int $disp_num, $cat='')
{
    $sticky = get_option('sticky_posts');
    $args = [
         'posts_per_page' => $disp_num,
         'post__in'       => $sticky,
     ];
    if ($cat) {
        $args['category_name'] = $cat;
    }
    $q = get_posts($args);
    $post_ids = array_column($q, 'ID');//post_idだけを配列から取得

     //投稿の中で、先頭固定にマッチする投稿を検索
     $sticky_posts = [];
    if ($post_ids) {
        foreach ($post_ids as $post_s) {
            if (in_array($post_s, $sticky, true)) {
                $sticky_posts[] = $post_s;
            }
        }
    }
    $sticky_posts_num = count($sticky_posts);
     //固定表示件数が表示件数と同数か、0ならreturn
     if ($sticky_posts_num === $disp_num) {
         return $sticky_posts;
     }

     //先頭固定表示がない場合は通常処理のpost_idsをreturn
     if ($sticky_posts_num === 0) {
         $args = [
             'posts_per_page' => $disp_num,
         ];
         if ($cat) {
             $args['category_name'] = $cat;
         }
         $q = 0;
         $q = get_posts($args);
         if ($q) {
             $post_ids = array_column($q, 'ID');//post_idだけを配列から取得
             return $post_ids;
         }
     }

     //先頭固定表示が表示件数未満の場合に、post_idを混ぜて返す
     // $mix_post_num = $disp_num - $sticky_posts_num;
     $args = [
         'posts_per_page' => $disp_num,
         'post__not_in'   => $sticky_posts,
     ];
    if ($cat) {
        $args['category_name'] = $cat;
    }

    $q = get_posts($args);
    $post_ids = "";
    if ($q) {
        $post_ids = array_column($q, 'ID');//post_idだけを配列から取得
         $post_ids = array_merge($sticky_posts, $post_ids);//配列をマージ、先頭固定表示idを先頭に
         return $post_ids;
    }
}
/**
* 該当カテゴリーに先頭固定表示があるかを確認
*/
function is_exists_sticky_within_samecat(string $category_slug)
{
    $sticky_posts = get_option('sticky_posts');
    $args = [
        'include'    => $sticky_posts,
    ];
    $posts = get_posts($args);
    $flg = false;
    foreach ($posts as $post) {
        if (is_cat_match($post->ID, $category_slug)) {
            $flg = true;
        }
    }
    return $flg;
}
/**
* check category object
*/
function is_cat_match(int $post_id, string $category_slug)
{
    $cat_obj = get_the_terms($post_id, 'category');
    $flg = false;
    foreach ($cat_obj as $cat) {
        if ($cat->slug === $category_slug) {
            $flg = true;
        }
    }
    return $flg;
}

/**
* pre_get_posts対応
*/
function sticky_pre_get_posts($query)
{
    //先頭固定表示がなければスルー
    $post_num = get_option('posts_per_page');
    $sticky_posts = get_option('sticky_posts');
    if (!$sticky_posts) {
        return;
    }

    //該当するカテゴリーに先頭固定表示がなければスルー
    if (is_category()) {
        $cat_slug = get_queried_object()->slug;
        if (!is_exists_sticky_within_samecat($cat_slug)) {
            return;
        }
    }

    $query->set('ignore_sticky_posts', 1);
    $query->set('posts_per_page', $post_num);

    if (!is_paged()) {
        if (is_home()) {
            $post_ids = show_top_page_post_ids($post_num);
        }
        if (is_category()) {
            $post_ids = show_top_page_post_ids($post_num, $cat_slug);
        }
        $query->set('post__in', $post_ids);
        $query->set('orderby', 'post__in');
    } else {
        $sticky_count = count($sticky_posts);
        $paged = get_query_var('paged') ? get_query_var('paged') : 1;
        $offset_num = ($paged - 1) *  $post_num - $sticky_count;
        $query->set('offset', $offset_num);
        $query->set('paged', $paged);
        $query->set('post__not_in', $sticky_posts);
    }
}

最初のページだけ先頭固定表示を含む投稿を表示させて、
残りは先頭固定表示を除いた投稿を表示させる記述です。
とりあえずこれで動くのですが、ページネーションがうまく動かないです。
メインクエリを書き換えているので当然ですね。

そのため、ページネーションを以下のような対応にします。

pagination.php
<?php
/*
  cms_pagination
    投稿一覧のページング
*/
function cms_pagination($pages = '', $range = 2)
{
   global $paged;
  // 文言の変更はここから
  $paging_text['prev'] = '&lt;';
  $paging_text['next'] = '&gt;';
  $paging_text['newest'] = '最新';
  $paging_text['oldest'] = '先頭';
  // 文言の変更はここまで

  if (empty($paged)) {
      $paged = 1;
  }

    if (!isset($showitems)) {
        $showitems = 0;
    }

    if ($pages == '') {
        $pages = max_num_pages_with_sticky();
        if (!$pages) {
            $pages = 1;
        }
    }

    $content = '';
    if (1 != $pages) {
        ob_start();
        echo '<nav class="post-number">';
        if ($paged > 1 && $showitems < $pages) {
            echo '<a href="' . esc_url(get_pagenum_link($paged - 1)) . '">'.$paging_text['prev'].'</a>';
        }
    echo '<a href="'.esc_url(get_pagenum_link(1)).'">'.$paging_text['newest'].'</a>';
    if ($paged > 2 && $paged > $range+1 && $showitems < $pages) {
        echo '<a href="'.esc_url(get_pagenum_link(1)).'">&laquo;</a>';
    }

    for ($i = 1; $i <= $pages; $i++) {
        if (1 != $pages && (!($i >= $paged + $range + 1 || $i <= $paged - $range - 1) || $pages <= $showitems)) {
            echo ($paged == $i) ? '<span class="current">' . $i . '</span>' : '<a href="' . esc_url(get_pagenum_link($i)) . '" class="inactive">' . $i . '</a>';
       }
    }

    echo '<a href="'.esc_url(get_pagenum_link($pages)).'">'.$paging_text['oldest'].'</a>';
    if ($paged < $pages && $showitems < $pages) {
        echo '<a href="' . esc_url(get_pagenum_link($paged + 1)).'">'.$paging_text['next'].'</a>';
    }
        echo '</nav>';
        $content = ob_get_clean();
    }
    return $content;
}


/**
* 先頭固定表示を有効にした場合のために、
* pre_get_postsに影響されない最大ページ数を求める
*/
function max_num_pages_with_sticky()
{
    $post_num = get_option('posts_per_page');
    if(is_home()){
        $count_publish_posts = wp_count_posts('post')->publish;
        return ceil($count_publish_posts / $post_num);
    }
    if(is_category()){
        $count_term = get_queried_object()->count;
        return ceil($count_term / $post_num);
    }
    //上記以外はメインクエリの最大件数を返す
    global $wp_query;
    return $wp_query->max_num_pages;
}

ところどころコメントが適当だったりphpdocだったりするのは、ソースの継ぎ足しだったりするためです。
秘伝のソース大事。

カスタム投稿の対応は?

標準では対応せず、プラグインによる対応となります。
Seamless Sticky Custom Post Types
ただ、こちらのtracで議論されているように、みんなやりたいけど面倒、みたいな認識なんだろうと思います(適当)
先ほどのプラグインも5年間メンテされてないので利用は非推奨ですし、あまり積極的にサポートしない方が身のためかもです。

1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5