Amazon10/19まで!プライム感謝祭開催中! セール会場へ

SWELL│カテゴリーページも含めた人気記事ランキングの作り方

この記事はカテゴリーアーカイブ自身の閲覧数と投稿の閲覧数を混ぜる応用例です。

カテゴリー内の記事だけを人気順に並べたい場合とは異なります。

掲載コードは部分監査中で、集計・ページ送りを含む完成済みの汎用実装ではありません。

通常の累計カードは人気記事ランキングの基本例、ページ送りだけを知りたい場合はページネーション機能の実装方法を参照してください。

実装コードの一式は「SWELLでタクソノミーも含めた人気記事ランキングの作り方」にまとめています。先にコードを見たい場合は目次からそこへ進んでください。前半はページ送りとサイドバーの運用で分かったことです。

SWELLでタクソノミーも含めた人気記事ランキングウィジェットとページを作成してみました。

カテゴリーを含む人気記事一覧の過去の表示例
投稿記事や固定記事、カテゴリー、タグごとのPVカウントや、関連した人気記事ランキングの表示イメージ。
  • 投稿記事、固定記事、カテゴリー、タグ、それぞれにPVをカウント
  • 表示中のカテゴリー・タグに応じてランキング候補を選ぶ

という感じです。

PHPは検証環境の子テーマfunctions.phpまたはPHPスニペットへ、CSSは追加CSSへ設置します。

旧版と重ねて登録せず、停止時は追加したフック・ショートコード・CSSを外してください。

タームのPVはPHPが動くアクセスだけで加算されるため、ページキャッシュ下ではアクセス解析のPVと一致しません。

目次

著者

WEB制作をしているデジタルノマド
WordPressのカスタマイズが好きで、色々と自作しています。

WordPressのカスタマイズに困ったらご相談ください!

ページ送りの不具合と確認方法

2026年10月2日の実HTTP検証で、2ページ目の戻るリンクに現在のページ番号が重なり、最初のページへ戻れない不具合を確認しました。

ページ送りのbaseには未エスケープのURLを渡し、最終的なリンク生成をWordPressのpaginate_linksに任せる形へ修正しました。

スマホでページ送りが本文と重なる表示も、専用クラス内の折り返しと余白を設定して修正しました。

PC1280pxとスマホ390pxでEnterによるページ送りを確認し、最初のページと2ページ目で候補が分かれることを確認しました。

同じ隔離環境でカテゴリー一覧とタグ一覧をHTTPから開き、各タームのPVが1増えることを確認しました。

WP RocketなどがPHPを実行せずキャッシュを返す場合のPV加算や、大規模データの処理性能は未検証です。

サイドバーの「もっと見る」は、自分で公開したランキング固定ページのURLに合わせてから、対象カテゴリーと対象外ページの両方で確認してください。

人気記事ランキングの2ページ目と修正したページ送り
2026年10月2日撮影、WordPress 7.1.2・SWELL 2.19.0の隔離環境。

サイドバーと「もっと見る」の固定ページをつなぐ

スクロールできます
配置・設定指定する内容確認する結果
サイドバー[swell_popular_posts limit="3"]人気記事3件と「もっと見る」を表示する
全体ランキングの固定ページスラッグをpopularにする分類条件のない表示では/popular/へ移動する
カテゴリーのランキング固定ページ最上位カテゴリーのスラッグがwordpressならwordpress-popularそのカテゴリー内の「もっと見る」から固定ページへ移動する
カテゴリーのランキング本文[swell_popular_posts_inline limit="12"]を置き、固定ページにも対象カテゴリーを設定する分類内の記事と対象アーカイブを候補にする

「もっと見る」のURLはコード内で組み立てるため、リンク先の固定ページを先に公開します。

掲載例と別のURLを使う場合は、サイドバー用コードの$more_linkを公開先に合わせて変更してください。

子カテゴリーの画面からも最上位カテゴリー用の固定ページへ移動するため、一覧の分類範囲をどこまで広げるかを先に決めます。

固定ページに分類を付けない場合は、本文用ショートコードが全体ランキングとして動くので、リンク元と同じ範囲になるとは限りません。

掲載コードの分類条件がない場合、サイドバー用はタグも候補に含めますが、本文用は投稿・固定ページとカテゴリーを候補にします。

このため、全体ランキングの「もっと見る」へ移動しても、同じ順位になるとは限りません。

2026年10月3日の隔離環境では、PCサイドバーの3件と移動先の上位3件が同じ順になることを確認しました。

従来のウィジェット画面でテキストウィジェットを保存し、サイドバーの表示件数を確認しました。

ブロックウィジェットの保存画面、導入先のページキャッシュ、大規模データの性能は未検証です。

サイドバーのもっと見るから開いたカテゴリーのランキング固定ページ
サイドバーから移動したランキング固定ページ(2026/10/03撮影、隔離環境、PC1280px)

テキストウィジェットで表示件数を保存する

サイドバーのコードを登録したら、外観のウィジェット画面で「共通サイドバー」にテキストウィジェットを追加します。

従来の編集画面ではコード入力モードを開き、[swell_popular_posts limit="3"]を入力して保存します。

この手順は掲載した独自ショートコードを呼び出すものです。

SWELL標準の「人気記事」ウィジェットとは設定項目と抽出処理が異なるため、テキストウィジェット側で件数を指定してください。

保存後に編集画面を開き直し、ショートコードが残っていることを確認します。

投稿・固定ページでは、掲載コードのmy_get_current_slugs()が取得したカテゴリーの先頭1件を分類条件に使います。

複数カテゴリーをまとめて指定する処理や、SEOプラグインのメインカテゴリーを優先する処理は含まれていません。

投稿・固定ページに付いたタグは分類条件に使わず、カテゴリーを取得できなければ全体ランキングになります。

タグ別の絞り込みは、タグアーカイブを開いた場合に使います。

隔離環境の従来ウィジェット画面でlimit="3"を保存すると3件が表示されました。

次にlimit="2"へ変更して保存すると、同じ分類の上位2件だけが表示されました。

写真は2件指定の実サイドバーです。

戻す場合は、変更前に控えたショートコードと件数へ戻して保存します。

表示が変わらないときは、ウィジェットの保存内容と表示している分類を先に照合し、その後に対象ページのキャッシュを確認してください。

WP Rocketを有効にした構成とスマホ用サイドバーは、今回の追加確認に含めていません。

2件指定で保存した実サイドバーの人気記事
2件指定で保存した実サイドバーの人気記事。2026年10月3日、隔離したWordPress 7.1.2 / SWELL 2.19.0で撮影。

分類別ランキングが別のページでも残る場合

スクロールできます
表示条件実際のサイドバー出力
カテゴリーAをキャッシュ有効で開き、その後Bを開くBのページにもAのランキングが残る
Bのページでサイドバーキャッシュを使わないBのランキングへ切り替わる
次の追加PHPを入れてB、Aの記事を開く各ページの分類に対応するランキングを出力する
独自ランキングのショートコードが未登録追加PHPはキャッシュ設定を変更しない

このランキングは、表示中の記事・カテゴリー・タグに応じて内容を変えます。

SWELL 2.19.0の共有サイドバーキャッシュでは分類IDを分けて保存しないため、最初に生成したランキングを別の分類でも表示することがあります。

実際のテキストウィジェットとサイドバーで、カテゴリーAからBへ切り替えて再現しました。

分類連動のランキングを共通サイドバーに置くサイトでは、次のPHPを掲載済みのランキングPHPと同じ貼付先へ追加してください。

子テーマのfunctions.phpのPHPタグ内、または一箇所のPHPスニペットへ登録します。

元のランキング関数は置き換えません。

追加処理は、この独自ランキングが登録されているサイトの投稿・固定ページ・アーカイブで、SWELLのサイドバーキャッシュを使わないようにします。

データベースに保存された設定値は変えません。

これらのページではサイドバーの生成回数が増えるため、分類連動のカードを使うサイトに限って適用してください。

ページキャッシュ製品やCDNが返すHTMLは別の処理です。

導入先では異なる分類の2ページでランキングと「もっと見る」のリンク先を照合します。

WP Rocketの実構成での動作は今回の検証に含みません。

追加対策だけを戻すには、次の関数とadd_action行を取り除きます。

ランキング本体・ショートコード・CSSは残せます。

分類別ランキングを共通サイドバーに残す間は、共有キャッシュを再び使うと混在が再発する可能性があります。

// 分類に連動する独自ランキングを共通サイドバーへ置くサイト用。
function waza_context_ranking_sidebar_cache() {
    if ( class_exists( 'SWELL_Theme' )
        && function_exists( 'my_get_current_slugs' )
        && shortcode_exists( 'swell_popular_posts' )
        && ( is_single() || is_page() || is_archive() ) ) {
        SWELL_Theme::set_setting( 'cache_sidebar', false );
    }
}
add_action( 'wp', 'waza_context_ranking_sidebar_cache', 20 );

SWELLでタクソノミーも含めた人気記事ランキングの作り方

  • 投稿・固定ページに対するSWELLのPVと、カテゴリー・タグに保存したPVをランキングに使う
  • 管理画面の表示調整
  • 関連ごとの人気記事になるように表示条件の共通関数を作成
  • 公開済みの通常投稿・固定ページと、対象範囲のカテゴリー・タグを取得してPV順に並べる
  • ショートコードで表示

という感じです。

コードだけで完結しますが、かなりコードが長いです。

固定記事でカテゴリーやタグを紐づけ

固定記事にカテゴリーやタグをつけられるようにします。

function add_categories_tags_to_pages() {
    register_taxonomy_for_object_type('category', 'page'); // カテゴリーを固定ページに追加
    register_taxonomy_for_object_type('post_tag', 'page'); // タグを固定ページに追加
}
add_action('init', 'add_categories_tags_to_pages');

タグ・カテゴリーのPVカウント

/**
 * 1) タグ・カテゴリーのPVカウント
 * タグ・カテゴリーアーカイブにアクセスした際、対象タームのメタキー(SWELL_CT_KEY または swell_pv_ct)の値を+1します。
 */
function count_taxonomy_page_view() {
    if ( is_category() || is_tag() ) {
        $term = get_queried_object();
        if ( isset( $term->term_id ) ) {
            // テーマ側でSWELL_CT_KEYが定義されていればその値、なければ 'swell_pv_ct' を使用
            $meta_key = defined( 'SWELL_CT_KEY' ) ? SWELL_CT_KEY : 'swell_pv_ct';
            $pv = (int) get_term_meta( $term->term_id, $meta_key, true );
            $pv++;
            update_term_meta( $term->term_id, $meta_key, $pv );
        }
    }
}
add_action( 'wp', 'count_taxonomy_page_view' );


/**
 * 2) 管理画面のカテゴリー一覧に「PV」カラムを追加
 * カラムID を swell_pv_ct として最後に追加
 */
function add_category_pv_column( $columns ) {
    $columns['swell_pv_ct'] = 'PV';
    return $columns;
}
add_filter( 'manage_edit-category_columns', 'add_category_pv_column' );

/**
 * 2b) 管理画面のタグ一覧に「PV」カラムを追加
 */
function add_tag_pv_column( $columns ) {
    $columns['swell_pv_ct'] = 'PV';
    return $columns;
}
add_filter( 'manage_edit-post_tag_columns', 'add_tag_pv_column' );


/**
 * 3) カテゴリー一覧のカラムにPV値を表示
 */
function show_category_pv_column( $content, $column_name, $term_id ) {
    if ( 'swell_pv_ct' === $column_name ) {
        $meta_key = defined( 'SWELL_CT_KEY' ) ? SWELL_CT_KEY : 'swell_pv_ct';
        $pv = get_term_meta( $term_id, $meta_key, true );
        return (string) max(0, (int) $pv);
    }
    return $content;
}
add_filter( 'manage_category_custom_column', 'show_category_pv_column', 10, 3 );

/**
 * 3b) タグ一覧のカラムにPV値を表示
 */
function show_tag_pv_column( $content, $column_name, $term_id ) {
    if ( 'swell_pv_ct' === $column_name ) {
        $meta_key = defined( 'SWELL_CT_KEY' ) ? SWELL_CT_KEY : 'swell_pv_ct';
        $pv = get_term_meta( $term_id, $meta_key, true );
        return (string) max(0, (int) $pv);
    }
    return $content;
}
add_filter( 'manage_post_tag_custom_column', 'show_tag_pv_column', 10, 3 );


/**
 * 4) 管理画面のカテゴリー一覧のソート可能カラムを登録
 */
function sortable_category_columns( $columns ) {
    $columns['swell_pv_ct'] = 'swell_pv_ct';
    return $columns;
}
add_filter( 'manage_edit-category_sortable_columns', 'sortable_category_columns' );

/**
 * 4b) 管理画面のタグ一覧のソート可能カラムを登録
 */
function sortable_tag_columns( $columns ) {
    $columns['swell_pv_ct'] = 'swell_pv_ct';
    return $columns;
}
add_filter( 'manage_edit-post_tag_sortable_columns', 'sortable_tag_columns' );


/**
 * 5) pre_get_terms フックで、orderby=swell_pv_ct の場合にタームメタを使った数値ソートを設定
 */
function my_sort_taxonomy_by_swell_pv_ct( $term_query ) {
    // 管理画面以外では処理しない
    if ( ! is_admin() ) {
        return;
    }
    // WP_Term_Query の query_vars から取得
    $orderby  = isset( $term_query->query_vars['orderby'] )  ? $term_query->query_vars['orderby']  : '';
    $taxonomy = isset( $term_query->query_vars['taxonomy'] ) ? $term_query->query_vars['taxonomy'] : '';
    
    // 対象タクソノミーが category または post_tag、かつ orderby が swell_pv_ct の場合
    if ( !empty( (array) $taxonomy ) && !array_diff( (array) $taxonomy, array( 'category', 'post_tag' ) ) && 'swell_pv_ct' === $orderby ) {
        $meta_key = defined( 'SWELL_CT_KEY' ) ? SWELL_CT_KEY : 'swell_pv_ct';
        $term_query->query_vars['meta_key']  = $meta_key;
        $term_query->query_vars['orderby']   = 'meta_value_num';
        $term_query->query_vars['meta_type'] = 'NUMERIC';
        $term_query->meta_query->parse_query_vars($term_query->query_vars);
    }
}
add_action( 'pre_get_terms', 'my_sort_taxonomy_by_swell_pv_ct' );

旧版の「全投稿にPVを加算するコード」と「URL指定でカテゴリーPVを全消去するコード」は削除しました。

admin_initへの登録は管理画面を開くたびに加算され、1回限りの処理ではありませんでした。

また、URLだけで変更するリセット処理には権限・nonceの確認がありませんでした。

ランキング表示にこれらの処理は不要です。

旧版を設置済みの場合は、my_increase_all_posts_pvとreset_category_swell_ct_key_on_requestを登録するコードを停止してください。

実データの値は変更せず、動作テストには独立した検証環境のテスト投稿・タームを使います。

過去の加算分はこの修正で自動復元できません。

管理画面のPV欄は数値を返すフィルターで表示します。

PV順に並べ替えたときは、閲覧数メタデータが保存済みのカテゴリー・タグだけが対象です。

未計測の項目が見えなくなったら名前順に戻してください。

データが削除されたわけではありません。

投稿のランキングとタームのランキングは別の集計です。

人気記事ウィジェット(共通関数)

/**
 * =====================
 * 1) 共通関数
 * =====================
 */

/**
 * get_swell_popular_posts()
 * WP_Query を用いて「PV >= 0」かつ特定カテゴリー/タグに絞った人気記事を取得する。
 * limit, paged, category_slug, tag_slug を受け取る。
 */
function get_swell_popular_posts( $limit = 5, $paged = 1, $category_slug = '', $tag_slug = '' ) {
    $meta_key = defined( 'SWELL_CT_KEY' ) ? SWELL_CT_KEY : 'swell_pv_ct';

    $tax_query = [];
    if ( ! empty( $category_slug ) ) {
        $category = get_category_by_slug( $category_slug );
        if ( ! $category ) return null;
        if ( $category ) {
            // 現在のカテゴリーのID
            $terms = [ $category->term_id ];
            // 子カテゴリーの場合、親カテゴリーのIDも追加
            if ( $category->parent > 0 ) {
                $terms[] = $category->parent;
            }
            $tax_query[] = [
                'taxonomy'         => 'category',
                'field'            => 'term_id',
                'terms'            => $terms,
                'include_children' => true,
            ];
        }
    }
    if ( ! empty( $tag_slug ) ) {
        $tag = get_term_by( 'slug', $tag_slug, 'post_tag' );
        if ( ! $tag || is_wp_error( $tag ) ) return null;
        if ( $tag ) {
            $tax_query[] = [
                'taxonomy' => 'post_tag',
                'field'    => 'term_id',
                'terms'    => $tag->term_id,
            ];
        }
    }

    $args = [
        'post_type'           => ['post', 'page'],
        'posts_per_page'      => $limit,
        'paged'               => $paged,
        'meta_key'            => $meta_key,
        'orderby'             => ['meta_value_num' => 'DESC', 'ID' => 'ASC'],
        'order'               => 'DESC',
        'post_status'         => 'publish',
        'has_password'        => false,
        'ignore_sticky_posts' => 1,
        'meta_query'          => [
            [
                'key'     => $meta_key,
                'value'   => 0,
                'compare' => '>=',
                'type'    => 'NUMERIC',
            ],
        ],
    ];

    if ( ! empty( $tax_query ) ) {
        $args['tax_query'] = $tax_query;
    }

    $query = new WP_Query( $args );
    return ( $query->have_posts() ) ? $query : null;
}


/**
 * 親カテゴリ(1階層上)を返す
 * 親が無ければ false
 */
function my_get_parent_category( $term ) {
    if ( ! $term instanceof WP_Term ) return false;
    if ( 0 === $term->parent ) {
        return false;
    }
    $parent = get_term( $term->parent, 'category' );
    return ( $parent && ! is_wp_error( $parent ) ) ? $parent : false;
}

/**
 * 最上位カテゴリを返す
 * 親が無ければ自身を返す
 */
function my_get_top_category( $term ) {
    if ( ! $term instanceof WP_Term ) return null;
    $ancestors = get_ancestors( $term->term_id, 'category' );
    if ( empty($ancestors) ) {
        // 親なし → 自分
        return $term;
    }
    $top_id   = end( $ancestors );
    $top_term = get_term( $top_id, 'category' );
    return ( $top_term && ! is_wp_error($top_term) ) ? $top_term : $term;
}

/**
 * 指定カテゴリの「子孫カテゴリ」をすべて取得
 * get_term_children() は階層をすべて取得するため、多階層下の子カテゴリも含まれる
 */
function my_get_all_child_categories( $term_id ) {
    $child_ids = get_term_children( $term_id, 'category' );
    if ( empty($child_ids) || is_wp_error($child_ids) ) {
        return [];
    }
    $child_terms = get_terms([
        'taxonomy'   => 'category',
        'include'    => $child_ids,
        'hide_empty' => false,
    ]);
    if ( ! $child_terms || is_wp_error($child_terms) ) {
        return [];
    }
    return $child_terms;
}


/** 現在のタグだけを、正のPVがある場合にランキングへ加える。 */
function my_build_tag_items( $slug ) {
    $term = get_term_by( 'slug', $slug, 'post_tag' );
    if ( ! $term || is_wp_error( $term ) ) { return []; }
    $key = defined( 'SWELL_CT_KEY' ) ? SWELL_CT_KEY : 'swell_pv_ct';
    $pv = (int) get_term_meta( $term->term_id, $key, true );
    $url = get_term_link( $term );
    if ( $pv <= 0 || is_wp_error( $url ) ) { return []; }
    return [[ 'type' => 'tag', 'title' => $term->name . 'のタグ記事一覧', 'link' => $url, 'pv' => $pv ]];
}

2つめです

function get_swell_category_h1_by_id($term_id) {
    $term = get_term($term_id, 'category');
    if (!$term || is_wp_error($term)) {
        return '';
    }
    $custom_title = get_term_meta($term_id, 'swell_term_meta_ttl', true);
    return is_string($custom_title) && $custom_title !== '' ? $custom_title : $term->name;
}

/**
 * 現在のページから「カテゴリースラッグ / タグスラッグ」を取得
 */
function my_get_current_slugs() {
    if ( is_category() || is_tag() ) {
        $term = get_queried_object();
        if ( ! $term instanceof WP_Term ) { return ['', '']; }
        return is_category() ? [$term->slug, ''] : ['', $term->slug];
    }
    if ( is_singular( ['post', 'page'] ) ) {
        $cats = get_the_terms( get_queried_object_id(), 'category' );
        if ( ! empty( $cats ) && ! is_wp_error( $cats ) ) {
            return [$cats[0]->slug, ''];
        }
    }
    return ['', ''];
}

/**
 * ランキング用に「カテゴリ自身 + 親(1階層) + 子孫全部」を配列化し、PV>0なら追加
 */
function my_build_category_items( WP_Term $term ) {
    $meta_key = defined('SWELL_CT_KEY') ? SWELL_CT_KEY : 'swell_pv_ct';
    $list     = [];

    // 1) 自分
    $pv_self = (int)get_term_meta($term->term_id, $meta_key, true);
    // PVが正のカテゴリーだけランキングに含める
    if ( $pv_self > 0 ) {
        $list[] = [
            'type'  => 'category',
            'title' => get_swell_category_h1_by_id($term->term_id), // 現在のカテゴリのH1
            'link'  => get_term_link($term),
            'pv'    => $pv_self,
        ];
    }

    // 2) 親(1階層)
    $parent = my_get_parent_category($term);
    if ( $parent ) {
        $pv_parent = (int)get_term_meta($parent->term_id, $meta_key, true);
        if ( $pv_parent > 0 ) {
            $list[] = [
                'type'  => 'category',
                'title' => get_swell_category_h1_by_id($parent->term_id), // 親カテゴリのH1
                'link'  => get_term_link($parent),
                'pv'    => $pv_parent,
            ];
        }
    }

    // 3) 子孫(すべて)
    $children = my_get_all_child_categories($term->term_id);
    if ( $children ) {
        foreach ( $children as $child ) {
            $child_pv = (int)get_term_meta($child->term_id, $meta_key, true);
            if ( $child_pv > 0 ) {
                $list[] = [
                    'type'  => 'category',
                    'title' => get_swell_category_h1_by_id($child->term_id), // 子カテゴリのH1
                    'link'  => get_term_link($child),
                    'pv'    => $child_pv,
                ];
            }
        }
    }

    return $list;
}

/**
 * ランキング配列(投稿やカテゴリなど)を PV降順でソートし、上位 $limit 件だけ返す
 */
function my_sort_and_slice( array $ranking_list, $limit ) {
    usort($ranking_list, function($a,$b){
        return $b['pv'] - $a['pv'];
    });
    return array_slice($ranking_list, 0, $limit);
}

人気記事ウィジェット(ショートコード)

本文用

/**
 * 本文用の人気記事リスト(ページネーション付き)
 * カテゴリがある場合 → そのカテゴリ(親+子)を取得
 * カテゴリが無い場合 → 全てのカテゴリを取得
 */
function swell_popular_posts_inline_shortcode( $atts ) {
    $previous_post = $GLOBALS['post'] ?? null;
    $atts = shortcode_atts([
        'limit' => 12,
    ], $atts, 'swell_popular_posts_inline' );

    $paged = isset($_GET['waza_rank_page']) && is_scalar($_GET['waza_rank_page'])
        ? max(1, absint(wp_unslash($_GET['waza_rank_page']))) : 1;
    $limit = max(1, min(24, (int) $atts['limit']));

    // 1) 現在のスラッグを取得
    list($category_slug, $tag_slug) = my_get_current_slugs();

    // 2) 投稿(WP_Query)
    //   ※ 'include_children'=>true が効いているので、$category_slug があればその子孫投稿を含む
	$query = get_swell_popular_posts( -1, 1, $category_slug, $tag_slug );


    // 3) 投稿を配列化
    $ranking_list = [];
    $meta_key     = defined('SWELL_CT_KEY') ? SWELL_CT_KEY : 'swell_pv_ct';

    while ( $query && $query->have_posts() ) {
        $query->the_post();
        $ranking_list[] = [
            'type'  => 'post',
            'title' => get_the_title(),
            'link'  => get_permalink(),
            'pv'    => (int) get_post_meta( get_the_ID(), $meta_key, true ),
        ];
    }
    wp_reset_postdata();
    $GLOBALS['post'] = $previous_post;
    if ( $previous_post instanceof WP_Post ) {
        setup_postdata( $previous_post );
    }

    // 4) カテゴリ追加
    if ( $category_slug ) {
        // ========== カテゴリーがある場合 ==========
        $current_term = get_term_by('slug', $category_slug, 'category');
        if ( $current_term && ! is_wp_error($current_term) ) {
            // 親・子・自分をまとめて配列化
            $cat_items   = my_build_category_items( $current_term ); 
            $ranking_list = array_merge($ranking_list, $cat_items);
        }
    } elseif ( $tag_slug ) {
        $ranking_list = array_merge( $ranking_list, my_build_tag_items( $tag_slug ) );
    } else {
        // ========== カテゴリーが無い(= 全体ランキング)場合 ==========
        // 「すべてのカテゴリ」を取得し、PV>0 のみリストに入れる
        $all_cats = get_terms([
            'taxonomy'   => 'category',
            'hide_empty' => false, // 投稿数0でも取得
        ]);
        if ( ! is_wp_error($all_cats) && ! empty($all_cats) ) {
            foreach ( $all_cats as $cat ) {
                $cat_pv = (int) get_term_meta( $cat->term_id, $meta_key, true );
                if ( $cat_pv > 0 ) {
                    $ranking_list[] = [
                        'type'  => 'category',
                        'title' => $cat->name . 'のカテゴリー記事一覧',
                        'link'  => get_term_link($cat),
                        'pv'    => $cat_pv,
                    ];
                }
            }
        }
    }

    // 5) ソート & limit
    //    (同じ処理。関数化してもOK)
	usort($ranking_list, function($a, $b) {
		return ($b['pv'] <=> $a['pv']) ?: strcmp((string) $a['link'], (string) $b['link']);
	});
	$total_items = count($ranking_list);
	// $limit is validated above.
	$total_pages = max(1, (int) ceil($total_items / $limit));
	$paged = min($paged, $total_pages);
	$offset      = ($paged - 1) * $limit;
	$ranking_list = array_slice($ranking_list, $offset, $limit);


    // 6) HTML 出力
    ob_start(); 
    ?>
    <div class="c-widget widget_swell_popular_posts custom">
        <ul class="p-postList -type-list -w-ranking">
            <?php foreach ( $ranking_list as $item ) : ?>
                <li class="p-postList__item">
                    <a href="<?php echo esc_url($item['link']); ?>" class="p-postList__link">
                        <div class="p-postList__body">
                            <div class="p-postList__title">
                                <?php
                                    $title      = $item['title'];
                                    echo esc_html($title);
                                ?>
                            </div>
                        </div>
                    </a>
                </li>
            <?php endforeach; ?>
        </ul>
    </div>
    <?php

    // 投稿とカテゴリーを結合した件数からページ送りを作る。
    if ($total_pages > 1) {
        $base = add_query_arg('waza_rank_page', 999999999, remove_query_arg('waza_rank_page', get_pagenum_link(1, false)));
        echo '<nav class="waza-rank-pagination" aria-label="人気記事のページ送り">';
        echo wp_kses_post(paginate_links([
            'base' => str_replace('999999999', '%#%', $base),
            'format' => '',
            'current' => $paged,
            'total' => $total_pages,
            'type' => 'list',
        ]));
        echo '</nav>';
    }

    return ob_get_clean();
}
add_shortcode('swell_popular_posts_inline', 'swell_popular_posts_inline_shortcode');

サイドバー用

function swell_popular_posts_shortcode( $atts ) {
    $previous_post = $GLOBALS['post'] ?? null;
    $atts = shortcode_atts([
        'limit' => 5,
    ], $atts, 'swell_popular_posts' );

    $atts['limit'] = max(1, min(24, (int) $atts['limit']));

    // 1) 現在の category_slug, tag_slug を取得
    list($category_slug, $tag_slug) = my_get_current_slugs();

    // ===============================
    // (A) まず投稿ランキングを取得
    // ===============================
    // もしカテゴリが空の場合、get_swell_popular_posts(...) に category_slug='' かつ tag_slug=''
    // → 全投稿を対象とした人気記事
    $query = get_swell_popular_posts( (int)$atts['limit'], 1, $category_slug, $tag_slug );


    // WP_Query結果を配列化
    $ranking_list = [];
    $meta_key     = defined('SWELL_CT_KEY') ? SWELL_CT_KEY : 'swell_pv_ct';

    while ( $query && $query->have_posts() ) {
        $query->the_post();
        $ranking_list[] = [
            'type'  => 'post',
            'title' => get_the_title(),
            'link'  => get_permalink(),
            'pv'    => (int)get_post_meta( get_the_ID(), $meta_key, true ),
        ];
    }
    wp_reset_postdata();
    $GLOBALS['post'] = $previous_post;
    if ( $previous_post instanceof WP_Post ) {
        setup_postdata( $previous_post );
    }

    // ===============================
    // (B) カテゴリ or 全カテゴリ/タグ を追加
    // ===============================
    if ( $category_slug ) {
        // ---------------------------
        // カテゴリあり → 親・子も追加
        // ---------------------------
        $term = get_term_by('slug', $category_slug, 'category');
        if ( $term && ! is_wp_error($term) ) {
            // 自身 + 親(1階層) + 子孫カテゴリ
            $cat_items    = my_build_category_items( $term );
            $ranking_list = array_merge( $ranking_list, $cat_items );
        }

    } elseif ( $tag_slug ) {
        $ranking_list = array_merge( $ranking_list, my_build_tag_items( $tag_slug ) );
    } else {
        // ---------------------------
        // カテゴリが無い → 全体ランキング
        // → すべてのカテゴリ + すべてのタグ を混在させる
        // ---------------------------

        // 1) 全てのカテゴリ
        $all_cats = get_terms([
            'taxonomy'   => 'category',
            'hide_empty' => false,
        ]);
        if ( ! is_wp_error($all_cats) && ! empty($all_cats) ) {
            foreach ( $all_cats as $cat ) {
                $cat_pv = (int) get_term_meta( $cat->term_id, $meta_key, true );
                if ( $cat_pv > 0 ) {
                    $ranking_list[] = [
                        'type'  => 'category',
                        'title' => $cat->name . 'のカテゴリー記事一覧',
                        'link'  => get_term_link($cat),
                        'pv'    => $cat_pv,
                    ];
                }
            }
        }

        // 2) 全てのタグ (必要なら)
        // 「カテゴリに関連していない→全タグも含めたい」という要望なら加える
        $all_tags = get_terms([
            'taxonomy'   => 'post_tag',
            'hide_empty' => false,
        ]);
        if ( ! is_wp_error($all_tags) && ! empty($all_tags) ) {
            foreach ( $all_tags as $tag ) {
                $tag_pv = (int) get_term_meta( $tag->term_id, $meta_key, true );
                if ( $tag_pv > 0 ) {
                    $ranking_list[] = [
                        'type'  => 'tag',
                        'title' => $tag->name . 'のタグ記事一覧',
                        'link'  => get_term_link($tag),
                        'pv'    => $tag_pv,
                    ];
                }
            }
        }
    }

    // ===============================
    // (C) PV降順でソート → 上位 limit
    // ===============================
    usort($ranking_list, function($a,$b){
        return ($b['pv'] <=> $a['pv']) ?: strcmp((string) $a['link'], (string) $b['link']);
    });
    $ranking_list = array_slice($ranking_list, 0, (int)$atts['limit']);

    if ( ! $ranking_list ) return '';

    // ===============================
    // (D) もっと見るボタン
    // ===============================
    $more_link = home_url('/popular/');
    if ( $category_slug ) {
        $cur_term = get_term_by('slug', $category_slug, 'category');
        if ( $cur_term && ! is_wp_error($cur_term) ) {
            $root = my_get_top_category($cur_term);
            if ( $root ) {
                $more_link = home_url( '/'. $root->slug . '-popular/' );
            }
        }
    }

    // ===============================
    // (E) HTML出力
    // ===============================
    ob_start();
    ?>
    <div class="c-widget widget_swell_popular_posts custom">
        <div class="c-widget__title -side">人気記事</div>

        <ul class="p-postList -type-list -w-ranking">
            <?php foreach ( $ranking_list as $item ) : ?>
                <li class="p-postList__item">
                    <a href="<?php echo esc_url($item['link']); ?>" class="p-postList__link">
                        <div class="p-postList__body">
                            <div class="p-postList__title">
                                <?php
                                    $title      = $item['title'];
                                    echo esc_html($title);
                                ?>
                            </div>
                        </div>
                    </a>
                </li>
            <?php endforeach; ?>
        </ul>

        <div class="p-popular__more" style="text-align:right; margin-top:10px;">
            <a href="<?php echo esc_url($more_link); ?>">もっと見る</a>
        </div>
    </div>
    <?php

    return ob_get_clean();
}
add_shortcode('swell_popular_posts', 'swell_popular_posts_shortcode');

CSS

.widget_swell_popular_posts.custom .-type-list .p-postList__body {
    width: 100%;
}

.widget_swell_popular_posts.custom .p-postList__item {
    padding-left: 2.5em;
}

.widget_swell_popular_posts.custom  .p-postList__item::after {
  content: "";
  display: block;
  width: 100%;
  height: 1px;
  background: #ccc;
  position: absolute;
  bottom	: -12px;
	left: 0;	
}

.widget_swell_popular_posts.custom .p-postList .p-postList__title {
    line-height: 1.8;
}

/* ランキング専用ページ送り。本文のリスト装飾を打ち消します。 */
.waza-rank-pagination ul.page-numbers{display:flex;flex-wrap:wrap;gap:.5rem;list-style:none;padding:0;margin:1.5rem 0}
.waza-rank-pagination ul.page-numbers>li{margin:0!important;padding:0;list-style:none}
.waza-rank-pagination ul.page-numbers>li::before{content:none}
.waza-rank-pagination a.page-numbers,
.waza-rank-pagination span.page-numbers{display:inline-flex;align-items:center;justify-content:center;position:static;width:auto;min-width:2.5em;min-height:2.5em;padding:.4em .7em;margin:0!important;line-height:1.3;background:var(--color_gray,#eee);color:var(--color_text,#222)}
.waza-rank-pagination span.current{background:var(--color_main,#176aa6);color:#fff}
.waza-rank-pagination a:focus-visible{outline:3px solid #176aa6;outline-offset:3px}

以上です。

旧版のコードは、GitHubのMOTOKI-LLC/WAZA-codeにもまとめています。

本文用のページ送りは、投稿とカテゴリーを先に結合・並べ替えし、その結果を分割します。

URLのwaza_rank_pageでページを指定し、1ページは1〜24件です。

範囲外の番号は最終ページへ丸め、最初のページへ戻るリンクを表示します。

候補を全件取得する小規模サイト向けの例で、同一ページの本文用ランキングはページ番号を共有します。

サイドバー用のlimitは1〜24件に制限し、0や負数でも1件として扱います。

投稿が取得できなくてもPVのあるカテゴリーがあれば候補に残ることは、2026年10月1日の実WordPressで確認しました。

2026年10月3日には、実SWELLのサイドバーから「もっと見る」をEnterで開き、公開済みのランキング固定ページへ移動するところまで確認しました。

タグアーカイブでは、そのタグが付いた投稿と、現在のタグ自身だけを候補にします。

無関係なカテゴリー・タグは混ぜません。

カテゴリーのページは従来どおり、現在のカテゴリー・親・子孫のアーカイブを候補にします。

投稿側の条件に親カテゴリーが含まれる場合は、親配下の兄弟カテゴリーの記事も対象になります。

今回の修正では、投稿候補からパスワード保護記事を除外し、カテゴリー・タグのアーカイブ候補はPVが正のものに揃えました。

PVが保存済みで0の投稿は候補に残し、PVメタがない投稿は取得しません。

タグのPVが0または未計測なら、タグ自身を候補へ加えません。

投稿候補は通常投稿と固定ページに限定します。

旧版ではカテゴリーなどを指定しない全体ランキングから固定ページが漏れる状態を確認したため、post_typeを明示しました。

任意のカスタム投稿タイプを自動で含める実装ではありません。

個別記事の絞り込みは主クエリの投稿を基準にします。

別の投稿ループ内でショートコードを呼び出しても、表示後は呼び出し前の投稿へ戻します。

同じPVの候補はリンク先の順で並べ、本文のページ送りで順序が変わらないようにします。

候補の絞り込みと投稿状態の復帰に加え、実HTTPのタームPV加算、PC・スマホの本文ページ送り、PCのサイドバーから固定ページへの移動を確認しています。

よくある質問:SWELLでカテゴリーページも含めた人気記事ランキングを作るとき

この記事のランキングは、どんなものですか?

記事では、カテゴリーアーカイブ自身の閲覧数と投稿の閲覧数を混ぜる応用例で、カテゴリー内の記事だけを人気順に並べたい場合とは異なります。投稿記事、固定記事、カテゴリー、タグのそれぞれにPVをカウントし、表示中のカテゴリー・タグに応じてランキング候補を選びます。

掲載コードは、そのまま使える完成品ですか?

記事では、掲載コードは部分監査中で、集計・ページ送りを含む完成済みの汎用実装ではありません。PHPは検証環境の子テーマfunctions.phpまたはPHPスニペットへ、CSSは追加CSSへ設置します。旧版と重ねて登録せず、停止時は追加したフック・ショートコード・CSSを外します。

ページキャッシュを使っていると、PVはアクセス解析と一致しますか?

一致しません。記事では、タームのPVはPHPが動くアクセスだけで加算されるため、ページキャッシュ下ではアクセス解析のPVと一致しないとしています。

サイドバーのランキングは、どう設定しますか?

記事では、サイドバーのコードを登録したら、外観のウィジェット画面で「共通サイドバー」にテキストウィジェットを追加し、従来の編集画面でコード入力モードを開いてショートコードを入力して保存します。SWELL標準の「人気記事」ウィジェットとは設定項目と抽出処理が異なるため、テキストウィジェット側で件数を指定します。

サイドバーの「もっと見る」と、移動先のランキングは同じ順位になりますか?

同じになるとは限りません。記事では、掲載コードの分類条件がない場合、サイドバー用はタグも候補に含めますが、本文用は投稿・固定ページとカテゴリーを候補にします。「もっと見る」のURLはコード内で組み立てるため、リンク先の固定ページを先に公開します。

カテゴリーを切り替えても、前のカテゴリーのランキングが残るのはなぜですか?

記事では、SWELL 2.19.0の共有サイドバーキャッシュは分類IDを分けて保存しないため、最初に生成したランキングを別の分類でも表示することがあると説明しています。分類連動のランキングを共通サイドバーに置くサイトでは、追加PHPでサイドバーキャッシュを使わないようにします。ただし、サイドバーの生成回数が増えるため、分類連動のカードを使うサイトに限って適用します。

ページ送りは、どう動きますか?

記事では、本文用のページ送りは、投稿とカテゴリーを先に結合・並べ替えし、その結果を分割します。URLのwaza_rank_pageでページを指定し、1ページは1〜24件です。範囲外の番号は最終ページへ丸めます。候補を全件取得する小規模サイト向けの例で、サイドバー用の件数は1〜24件に制限されます。

どこまで検証されていますか?

記事では、2026年10月の隔離環境(WordPress 7.1.2・SWELL 2.19.0)で、従来のウィジェット画面でテキストウィジェットを保存してサイドバーの件数を確認し、「もっと見る」からランキング固定ページへ移動できることを確認しています。一方、ブロックウィジェットの保存画面、導入先のページキャッシュ、大規模データの性能、WP Rocketを有効にした構成とスマホ用サイドバーは未検証です。

  • URLをコピーしました!

実装を任せたい方へ

SWELLのカスタマイズ代行

記事のコードをそのまま使うのが不安な場合や、サイトに合わせた調整が必要な場合は当社が実装します。1時間 ¥8,000〜(税込)。テーマ更新に強い書き方で納品します。

WAZAの有料記事のサブスクリプションも開始しました。

サービス

Service

WordPressサイトのカスタマイズのサービスに関心がありましたら、ぜひ詳細をご覧ください。

目次