上一次教程:置顶文章加入到显示文章数量内【更新】
功能介绍
为了让首页和归档页的列表展示更统一,我给 Zibll 增加了“置顶文章计入每页显示数量”的优化。开启后,置顶文章不再额外挤占列表总数,而是和普通文章一起遵循每页数量规则。比如每页设置 10 篇,那么无论是否有置顶,页面最终都只显示 10 篇,同时分页也会自动保持连续,避免重复或漏文。
使用说明
- 打开主题目录 wp-content/themes/zibll/。
- 在该目录创建或编辑 func.php。
- 将“置顶计数增强”代码完整粘贴到 func.php(文件开头保留 <?php)。
- 保存后刷新首页、分类页、标签页测试效果。
- 如有缓存插件/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');







