【WordPress】特定の記事を先頭に固定し、ページネーション対応する

電脳備忘録

本記事のソースコードの利用によって生じた損害について、当方は一切の責任を負いません。ご自身の判断と責任のもとで参照・ご利用ください。

WordPressでブログやサイトを運営していると、「特定の記事を常にリストの先頭に表示させたい」というニーズが出てくることがあります。例えば、最新のキャンペーン情報、重要なアナウンス、あるいは注目のコンテンツなどです。

通常のWordPressの表示では、記事は日付順に並び、ページネーションによって古い記事へと遷移していきます。しかし、特定の記事を固定し、かつその記事を除いた形でページネーションも正しく機能させるのは、少し工夫が必要です。

今回は、WordPressのWP_Queryとpaginate_linksを駆使し、以下の要件を実現しました。

  • 1ページあたり6件(または任意の件数)の記事を表示
  • 指定したPost IDの記事を常に1件目に表示
  • それ以降は、固定記事を除く最新の記事をページネーションで表示

WP_Queryのoffsetパラメータは非常に便利ですが、offsetとpagedを同時に使うとページネーションがずれるというWordPressの既知の挙動があります。このため、複数のWP_Queryを組み合わせ、ページネーションの総数を手動で正確に計算するなどの対応が必要になります。

実装するPHPコードの概要

今回ご紹介するPHPコードは、主に以下の2つのWP_Queryを組み合わせて表示を実現します。

  1. 固定記事用クエリ: 1ページ目のみ、指定したPost IDの記事(例: post=28)を1件取得します。
  2. メイン記事用クエリ: 固定記事を除外した全ての公開記事から、現在のページに応じた記事をページネーションを考慮して取得します。

そして、paginate_links関数が正確なページ数を表示するように、固定記事を除いた全記事の総数を別途取得し、それを基に総ページ数を計算します。

PHPコード例

以下に、上記のロジックを実装したPHPコードを示します。このコードは、WordPressのテーマファイル(例: archive.phpやカスタムテンプレートファイルなど)に直接記述して使用することを想定しています。

<?php
// 現在のページ番号を取得
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

// 固定したい記事のIDと、1ページあたりの表示件数を定義
$sticky_post_id = 28;
$posts_per_page_global = 6; // 1ページに表示したい全体の記事数(固定記事含む)
?>

<main>
    <div class="entry-list-wrapper">
        <ul class="entry-list">
            <?php
            // ★ 1. 1ページ目のみ、固定記事(ID 28)を表示するクエリ ★
            // 2ページ目以降では、このセクションはスキップされる。
            if ($paged == 1) :
                $sticky_args = array(
                    'p' => $sticky_post_id, // 特定の記事IDで取得
                    'post_type' => 'post',
                    'post_status' => 'publish',
                    'posts_per_page' => 1, // 1件のみ取得
                );
                $sticky_query = new WP_Query($sticky_args);

                if ($sticky_query->have_posts()) :
                    while ($sticky_query->have_posts()) : $sticky_query->the_post();
            ?>
            <li>
                <a href="<?php the_permalink(); ?>">
                    //記事タイトルなどを表示
                </a>
            </li>
            <?php
                    endwhile;
                    wp_reset_postdata(); // 固定記事クエリの投稿データをリセット
                endif;
            endif; // End of if ($paged == 1)


            // ★ 2. 残りの記事(固定記事を除く)を取得するメインクエリ ★

            // メインクエリのオフセットを計算
            $offset_for_main_query = 0;
            if ($paged == 1) {
                // 1ページ目の場合、固定記事1件を除いた残りの5件を表示するため、
                // メインクエリはオフセットなしで、最初の非固定記事から取得を開始する。
                // ただし、表示件数は5件にする。
                $posts_per_page_for_main_query = $posts_per_page_global - 1; // 5件
            } else {
                // 2ページ目以降の場合、1ページ目に表示された記事数(固定1件 + 非固定5件 = 計6件)分をスキップする。
                // その後、各ページで6件ずつ表示する。
                // オフセットは、1ページ目の非固定記事5件分 + (現在のページ番号 - 2) * 6件
                // 例: paged=2の場合: offset = 5 + (2-2)*6 = 5
                // 例: paged=3の場合: offset = 5 + (3-2)*6 = 11
                $offset_for_main_query = ($posts_per_page_global - 1) + (($paged - 2) * $posts_per_page_global);
                $posts_per_page_for_main_query = $posts_per_page_global; // 2ページ目以降は6件表示
            }

            $main_args = array(
                'post_type' => 'post',
                'post_status' => 'publish',
                'posts_per_page' => $posts_per_page_for_main_query, // 1ページ目と2ページ目以降で表示件数を調整
                'post__not_in' => array($sticky_post_id), // 固定記事IDを必ず除外
                'orderby' => 'date', // 日付でソート
                'order' => 'DESC', // 降順
                'offset' => $offset_for_main_query, // 計算されたオフセットを適用
                'paged' => 0, // offsetを使用するためpagedは0にする (WP_Queryのoffset挙動対策)
                               // もしくは、offsetを常に0にし、post__not_inで対応し、posts_per_pageを調整
                               // しかし、それではページネーションがずれるため、offsetを使うのが一般的。
                               // ここでは、offsetがpagedを上書きするWPの挙動を考慮し、pagedを0としてoffsetで制御。
                'no_found_rows' => true, // paginate_linksのtotalを自前で計算するため、found_postsを無効化
            );
            
            $main_query = new WP_Query($main_args);

            if ($main_query->have_posts()) :
                while ($main_query->have_posts()) : $main_query->the_post();
            ?>
            <li>
               <a href="<?php the_permalink(); ?>">
                   //記事タイトルなどを表示
               </a>
            </li>
            <?php
                endwhile;
                wp_reset_postdata(); // 投稿データをリセット
            endif;
            ?>
        </ul>
    </div><!--/.entry-list-wrapper-->

    <div class="pager-nav-wrapper">
        <div class="pager-nav">
            <?php
            // ページャーは、固定記事を除いた総記事数に基づいて総ページ数を生成する必要がある。

            // まず、固定記事を除いた全記事の総数を取得する
            $total_posts_without_sticky_args = array(
                'post_type' => 'post',
                'post_status' => 'publish',
                'post__not_in' => array($sticky_post_id), // 特定の記事IDを除外
                'posts_per_page' => -1, // 全件取得
                'fields' => 'ids', // IDのみ取得して軽量化
            );
            $total_posts_without_sticky_query = new WP_Query($total_posts_without_sticky_args);
            $total_non_sticky_posts_count = $total_posts_without_sticky_query->found_posts;
            wp_reset_postdata(); // クエリのリセット

            // 総ページ数を計算
            // 1ページ目は固定記事1件 + 非固定記事 (posts_per_page_global - 1) = 5件
            // 2ページ目以降は非固定記事 posts_per_page_global = 6件
            
            // 非固定記事が1ページ目で収まる(5件以下)場合
            if ($total_non_sticky_posts_count <= ($posts_per_page_global - 1)) {
                $max_num_pages = 1;
            } else {
                // 1ページ目の非固定記事数(5件)を超えた残りの非固定記事数
                $remaining_posts_after_first_page = $total_non_sticky_posts_count - ($posts_per_page_global - 1);
                // 残りの記事を2ページ目以降の1ページあたりの表示件数(6件)で割って、追加のページ数を計算
                $additional_pages = ceil($remaining_posts_after_first_page / $posts_per_page_global);
                // 総ページ数 = 1ページ目 + 追加のページ数
                $max_num_pages = 1 + $additional_pages;
            }

            // paginate_links の引数を設定
            global $wp_rewrite;
            $paginate_base = get_pagenum_link(1);
            if (strpos($paginate_base, '?') || !$wp_rewrite->using_permalinks()) {
                $paginate_format = '';
                $paginate_base = add_query_arg('paged', '%#%');
            } else {
                $paginate_format = (substr($paginate_base, -1, 1) == '/' ? '' : '/') .
                    user_trailingslashit('page/%#%/', 'paged');
                $paginate_base .= '%_%';
            }

            echo paginate_links(array(
                'base' => $paginate_base,
                'format' => $paginate_format,
                'total' => $max_num_pages, // 計算した総ページ数を指定
                'mid_size' => 3, // ページャーの表示数 (例: 1 2 ... 5 6 7 ... 10)
                'current' => ($paged ? $paged : 1),
                'prev_text' => '←',
                'next_text' => '→',
            ));
            ?>
        </div>
    </div><!--/.pager-nav-wrapper-->
</main>

なお、posts_per_page_globalの件数を変更(例: 6件から9件)しても意図した通りに動作することを確認済みです。

0%