置顶文章参每页总数计算-【优化版】-zibll综合交流社区-zibll子比主题-WordPress主题模板-zibll子比主题

置顶文章参每页总数计算-【优化版】

上一次教程:置顶文章加入到显示文章数量内【更新】

功能介绍
为了让首页和归档页的列表展示更统一,我给 Zibll 增加了“置顶文章计入每页显示数量”的优化。开启后,置顶文章不再额外挤占列表总数,而是和普通文章一起遵循每页数量规则。比如每页设置 10 篇,那么无论是否有置顶,页面最终都只显示 10 篇,同时分页也会自动保持连续,避免重复或漏文。

使用说明

  1. 打开主题目录 wp-content/themes/zibll/
  2. 在该目录创建或编辑 func.php
  3. 将“置顶计数增强”代码完整粘贴到 func.php(文件开头保留 <?php)。
  4. 保存后刷新首页、分类页、标签页测试效果。
  5. 如有缓存插件/CDN,请清理缓存后再看前台结果。

生效范围

  • 前台主查询列表页(首页、归档页)。
  • 不影响后台、单篇文章页、Feed 页面。

效果说明

  • 置顶文章会参与每页总数计算。
  • 第一页显示“置顶 + 普通文章”后总数不超 posts_per_page
  • 后续分页自动补偿偏移量,分页数量与内容顺序更稳定。

代码

<?php
/**
 * Zibll 置顶计数增强
 *
 * 功能目标:
 * 1. 让置顶文章“占用”每页显示数量(例如每页 10 篇,包含置顶后总数仍为 10)。
 * 2. 首页第 1 页显示置顶 + 普通文章;后续分页自动补偿 offset,避免重复/漏文。
 * 3. 兼容 Zibll 现有“置顶角标”逻辑(通过 $GLOBALS['showed_sticky_posts'])。
 *
 * 使用方式:
 * - 本文件放在 /wp-content/themes/zibll/func.php 即可自动加载。
 * - 不依赖 Panda 的任何函数或配置项。
 */

if (!defined('ABSPATH')) {
    exit;
}

/**
 * 初始化:关闭 Zibll 默认置顶注入逻辑,改用本文件的“计入显示数量”方案。
 *
 * 为什么要关闭?
 * - Zibll 默认逻辑会把置顶“额外插入”列表,导致第一页总显示数可能超过 posts_per_page。
 * - 本方案要严格控制“每页总显示数”,因此需要替换默认逻辑。
 */
function zibll_sticky_count_bootstrap() {
    if (function_exists('zib_pre_main_query_sticky')) {
        remove_action('pre_get_posts', 'zib_pre_main_query_sticky', 999);
    }

    if (function_exists('zib_the_posts_sticky')) {
        remove_action('the_posts', 'zib_the_posts_sticky', 999);
        remove_filter('the_posts', 'zib_the_posts_sticky', 999);
    }
}
add_action('after_setup_theme', 'zibll_sticky_count_bootstrap', 99);

/**
 * 获取“原始候选置顶 ID”。
 *
 * 优先使用 Zibll 自带规则(zib_get_sticky_posts),可继承其“在哪些页面启用置顶”的配置;
 * 若该函数不存在,再回退到 WordPress 原生 sticky_posts。
 *
 * @param WP_Query $query 主查询对象
 * @return int[] 置顶文章 ID 列表(已去重、转 int)
 */
function zibll_get_raw_sticky_ids($query) {
    $sticky_ids = array();

    if (function_exists('zib_get_sticky_posts')) {
        $sticky_ids = (array) zib_get_sticky_posts($query);
    } else {
        $sticky_ids = (array) get_option('sticky_posts');
    }

    $sticky_ids = array_values(array_unique(array_filter(array_map('intval', $sticky_ids))));
    return $sticky_ids;
}

/**
 * 判断当前查询是否需要应用“置顶计入显示数量”。
 *
 * 仅作用于前台主查询,避免影响后台、单篇页、Feed 等非列表场景。
 *
 * @param WP_Query $query 查询对象
 * @return bool
 */
function zibll_sticky_count_should_apply($query) {
    if (is_admin() || !($query instanceof WP_Query) || !$query->is_main_query()) {
        return false;
    }

    if ($query->is_singular() || $query->is_feed() || $query->is_robots() || $query->is_favicon()) {
        return false;
    }

    // 仅在首页/归档页生效,避免影响搜索或其他自定义列表。
    if (!($query->is_home() || $query->is_archive())) {
        return false;
    }

    // 若其他代码明确要求忽略置顶,则直接退出。
    if ((int) $query->get('ignore_sticky_posts') === 1) {
        return false;
    }

    // 只处理包含 post 的查询(置顶机制本身针对 post 类型)。
    $post_type = $query->get('post_type');
    if (empty($post_type) || 'post' === $post_type || 'any' === $post_type) {
        return true;
    }
    if (is_array($post_type)) {
        return in_array('post', $post_type, true) || in_array('any', $post_type, true);
    }

    return false;
}

/**
 * 在“当前查询条件”下,筛出实际可展示的置顶文章 ID。
 *
 * 说明:
 * - sticky_posts 只表示“站点被标记为置顶”的文章;
 * - 但当前分类/标签/自定义 taxonomy 查询下,不一定全部可见;
 * - 所以这里做一次轻量子查询,仅保留当前查询条件下可命中的置顶文章。
 *
 * @param WP_Query $query 主查询对象
 * @return int[] 当前列表场景中真正可显示的置顶 ID
 */
function zibll_get_query_sticky_ids($query) {
    $sticky_ids = zibll_get_raw_sticky_ids($query);
    if (empty($sticky_ids)) {
        return array();
    }

    $args = $query->query_vars;

    // 清理会影响子查询准确性的参数。
    unset($args['post__in'], $args['post__not_in'], $args['offset'], $args['paged']);

    // 清理本功能写入的内部参数,避免递归污染。
    unset(
        $args['zibll_sticky_count_enabled'],
        $args['zibll_original_posts_per_page'],
        $args['zibll_first_page_normal_posts'],
        $args['zibll_sticky_posts']
    );

    $args['post__in']                = $sticky_ids;
    $args['posts_per_page']          = -1;
    $args['ignore_sticky_posts']     = 1;
    $args['no_found_rows']           = true;
    $args['fields']                  = 'ids';
    $args['post_status']             = 'publish';
    $args['cache_results']           = false;
    $args['update_post_meta_cache']  = false;
    $args['update_post_term_cache']  = false;

    $sticky_query = new WP_Query($args);
    $matched_ids  = array_map('intval', (array) $sticky_query->posts);

    // 保持原置顶顺序,同时只保留匹配到当前查询条件的文章。
    return array_values(array_unique(array_intersect($sticky_ids, $matched_ids)));
}

/**
 * pre_get_posts:改写主查询参数,实现“置顶占位计数”。
 *
 * 核心思路:
 * - 把置顶文章先从主查询中排除(post__not_in + ignore_sticky_posts=1);
 * - 首页第 1 页只查“普通文章数量 = posts_per_page - 置顶数”;
 * - 第 2 页起通过 offset 补偿第 1 页少查的普通文章,确保分页连续且不重复。
 *
 * @param WP_Query $query
 * @return void
 */
function zibll_modify_sticky_count_query($query) {
    if (!zibll_sticky_count_should_apply($query)) {
        return;
    }

    $sticky_ids   = zibll_get_query_sticky_ids($query);
    $sticky_count = count($sticky_ids);
    if ($sticky_count < 1) {
        return;
    }

    $posts_per_page = (int) $query->get('posts_per_page');
    if ($posts_per_page < 1) {
        $posts_per_page = (int) get_option('posts_per_page');
    }
    if ($posts_per_page < 1) {
        return;
    }

    // 第一页中“普通文章”应占位数量;可能为 0(当置顶数 >= 每页数)。
    $first_page_normal_posts = max(0, $posts_per_page - $sticky_count);

    // WP_Query 的 posts_per_page 不能用 0,这里最少查 1 条以维持 found_posts 统计稳定。
    $first_page_query_posts = max(1, $first_page_normal_posts);

    // 写入内部标记,供后续 the_posts / wp 阶段使用。
    $query->set('zibll_sticky_count_enabled', 1);
    $query->set('zibll_original_posts_per_page', $posts_per_page);
    $query->set('zibll_first_page_normal_posts', $first_page_normal_posts);
    $query->set('zibll_sticky_posts', $sticky_ids);

    // 禁用 WP 默认置顶行为(我们将手动合并置顶)。
    $query->set('ignore_sticky_posts', 1);

    // 从主查询排除置顶,防止后续手动合并时重复。
    $post_not_in = array_map('intval', (array) $query->get('post__not_in'));
    $query->set('post__not_in', array_values(array_unique(array_merge($post_not_in, $sticky_ids))));

    // 第 1 页:只查“应显示的普通文章数量”。
    if (!$query->is_paged()) {
        $query->set('posts_per_page', $first_page_query_posts);
        return;
    }

    // 第 2 页起:补偿 offset,跳过第 1 页已经展示过的普通文章。
    $paged  = max(1, (int) $query->get('paged'));
    $offset = $first_page_normal_posts + (($paged - 2) * $posts_per_page);
    $query->set('offset', max(0, $offset));
}
add_action('pre_get_posts', 'zibll_modify_sticky_count_query');

/**
 * the_posts(第一页):把置顶文章插回列表头部,并确保总数不超过原 posts_per_page。
 *
 * @param WP_Post[] $posts
 * @param WP_Query  $query
 * @return WP_Post[]
 */
function zibll_merge_sticky_posts_into_first_page($posts, $query) {
    if (!($query instanceof WP_Query) || !(int) $query->get('zibll_sticky_count_enabled') || $query->is_paged()) {
        return $posts;
    }

    $sticky_ids = array_values(array_unique(array_filter(array_map('intval', (array) $query->get('zibll_sticky_posts')))));
    if (empty($sticky_ids)) {
        return $posts;
    }

    $posts_per_page = (int) $query->get('zibll_original_posts_per_page');
    if ($posts_per_page < 1) {
        return $posts;
    }

    $sticky_posts = get_posts(array(
        'post__in'               => $sticky_ids,
        'orderby'                => 'post__in',
        'post_type'              => 'post',
        'post_status'            => 'publish',
        'posts_per_page'         => count($sticky_ids),
        'ignore_sticky_posts'    => 1,
        'no_found_rows'          => true,
        'suppress_filters'       => false,
        'cache_results'          => false,
        'update_post_meta_cache' => false,
        'update_post_term_cache' => false,
    ));

    if (empty($sticky_posts)) {
        return $posts;
    }

    // 兼容 Zibll 原生“置顶角标”判断函数 zib_is_sticky()。
    $GLOBALS['showed_sticky_posts'] = $sticky_ids;

    // 先放置顶,再放普通文章;按 ID 去重。
    $merged = array();
    foreach ($sticky_posts as $post) {
        $merged[$post->ID] = $post;
    }
    foreach ((array) $posts as $post) {
        if (!isset($merged[$post->ID])) {
            $merged[$post->ID] = $post;
        }
    }

    $posts = array_slice(array_values($merged), 0, $posts_per_page);
    $query->post_count = count($posts);

    return $posts;
}
add_filter('the_posts', 'zibll_merge_sticky_posts_into_first_page', 10, 2);

/**
 * the_posts(全分页):兜底去重,避免插件/主题其他过滤器导致重复文章。
 *
 * @param WP_Post[] $posts
 * @param WP_Query  $query
 * @return WP_Post[]
 */
function zibll_dedupe_sticky_count_posts($posts, $query) {
    if (!($query instanceof WP_Query) || !(int) $query->get('zibll_sticky_count_enabled')) {
        return $posts;
    }

    $unique = array();
    foreach ((array) $posts as $post) {
        if (!isset($post->ID)) {
            continue;
        }
        $id = (int) $post->ID;
        if (!isset($unique[$id])) {
            $unique[$id] = $post;
        }
    }

    $posts = array_values($unique);
    $query->post_count = count($posts);

    return $posts;
}
add_filter('the_posts', 'zibll_dedupe_sticky_count_posts', 20, 2);

/**
 * 修正分页总页数(max_num_pages)。
 *
 * 原因:
 * - 主查询中的 found_posts 统计的是“非置顶文章总数”(因为我们在 pre_get_posts 里排除了置顶);
 * - 但第一页已被置顶占位,因此剩余普通文章分页要按“第一页普通文章已消耗量”重新计算。
 */
function zibll_adjust_sticky_count_pagination() {
    if (is_admin()) {
        return;
    }

    global $wp_query;
    if (!($wp_query instanceof WP_Query) || !$wp_query->is_main_query() || !(int) $wp_query->get('zibll_sticky_count_enabled')) {
        return;
    }

    $posts_per_page          = (int) $wp_query->get('zibll_original_posts_per_page');
    $first_page_normal_posts = (int) $wp_query->get('zibll_first_page_normal_posts');
    if ($posts_per_page < 1) {
        return;
    }

    $non_sticky_posts = max(0, (int) $wp_query->found_posts);
    $remaining_posts  = max(0, $non_sticky_posts - $first_page_normal_posts);
    $total_pages      = 1 + (int) ceil($remaining_posts / $posts_per_page);

    $wp_query->max_num_pages = max(1, $total_pages);
}
add_action('wp', 'zibll_adjust_sticky_count_pagination');
请登录后发表评论