子比主题开发文档
使用指南Codestar Framework主题扩展在线部署AI 功能推荐插件赞助打赏

论坛小工具与侧栏

蒸馏子比主题论坛 widgets 模块的侧栏注册、页面挂载、版块小工具、帖子列表、话题列表、Ajax 加载和 Tab 切换写法。

模块边界

论坛小工具不是通用文章小工具的简单复用。它依赖论坛模块的对象、查询、权限和前端列表协议,入口在:

inc/functions/bbs/widgets/widgets.php
inc/functions/bbs/widgets/widgets-plate.php
inc/functions/bbs/widgets/widgets-posts.php
inc/functions/bbs/widgets/widgets-term.php
inc/functions/bbs/widgets/widgets-other.php

widgets.php 负责引入子文件和注册论坛页面侧栏;widgets-plate.php 负责版块信息、版主和版块列表;widgets-posts.php 负责帖子列表和多 Tab 帖子列表;widgets-term.php 负责话题列表。

论坛小工具只有在论坛模块开启后才会随 inc/functions/bbs/bbs.php 加载。扩展时不要假设 zib_bbs()plateforum_post 或论坛模板 Hook 在所有站点都存在。

侧栏注册

论坛侧栏由 zib_bbs_register_sidebar()widgets_init 中注册:

add_action('widgets_init', 'zib_bbs_register_sidebar');

它把页面和位置组合成侧栏 id:

页面 key位置 key生成示例
hometop_contentbbs_home_top_content
homebottom_contentbbs_home_bottom_content
platesidebarbbs_plate_sidebar
platetop_contentbbs_plate_top_content
platebottom_contentbbs_plate_bottom_content
postssidebarbbs_posts_sidebar
poststop_contentbbs_posts_top_content
postsbottom_contentbbs_posts_bottom_content

完整位置还包括:

位置 key说明
sidebar侧边栏,宽度较窄,不适合大尺寸模块
top_fluid顶部全宽度
top_content主内容上方
bottom_content主内容下方
bottom_fluid底部全宽度

发帖页额外注册:

bbs_new_posts_sidebar_top
bbs_new_posts_sidebar_bottom

新增论坛页面容器时,优先复用 zib_register_sidebar($sidebars),不要直接手写另一套 register_sidebar() 外壳。这样才能保留主题小工具的统一 .zib-widget 结构、自定义预览和标题样式。

页面挂载

论坛小工具侧栏通过论坛模板 Hook 挂载,而不是直接写在模板固定位置:

add_action('bbs_plate_page_content', function () {
    dynamic_sidebar('bbs_plate_top_content');
}, 1);

add_action('bbs_plate_page_content', function () {
    dynamic_sidebar('bbs_plate_bottom_content');
}, 99);

add_action('bbs_home_tab_content_top', function () {
    dynamic_sidebar('bbs_home_top_content');
});

add_action('bbs_home_tab_content_bottom', function () {
    dynamic_sidebar('bbs_home_bottom_content');
});

add_action('bbs_posts_page_content_top', function () {
    dynamic_sidebar('bbs_posts_top_content');
});

add_action('bbs_posts_page_content_bottom', function () {
    dynamic_sidebar('bbs_posts_bottom_content');
});

这说明扩展论坛页面时可以先找 bbs_*_page_*bbs_home_tab_content_*bbs_posts_page_content_* 这些 Hook。只有需要改变页面结构时,才考虑模板覆盖。

模板骨架与侧栏 class

论坛首页、版块页、帖子页最终都会进入 zib_bbs_page_template($type)。它不是只输出主内容,而是固定分成全宽顶部、页面头部、容器、主内容、侧栏、全宽底部和页面底部:

function zib_bbs_page_template($type = 'home')
{
    do_action('bbs_locate_template');
    do_action('bbs_locate_template_' . $type);

    if (!_pz('bbs_s', true)) {
        bbs_locate_template_nocan_edit();
    }

    get_header();
    $page_type = $type;
    ?>
    <main id="forum">
        <div class="fluid-widget-wrap">
            <?php dynamic_sidebar('bbs_' . $page_type . '_top_fluid'); ?>
        </div>
        <?php do_action('bbs_' . $page_type . '_page_header'); ?>
        <div class="container">
            <div class="content-wrap">
                <div class="content-layout">
                    <?php do_action('bbs_' . $page_type . '_page_content'); ?>
                </div>
            </div>
            <div class="<?php echo apply_filters('bbs_' . $page_type . '_sidebar_class', 'sidebar'); ?>">
                <?php dynamic_sidebar('bbs_' . $page_type . '_sidebar'); ?>
                <?php do_action('bbs_' . $page_type . '_page_sidebar'); ?>
            </div>
        </div>
        <div class="fluid-widget-wrap">
            <?php dynamic_sidebar('bbs_' . $page_type . '_bottom_fluid'); ?>
        </div>
        <?php do_action('bbs_' . $page_type . '_page_footer'); ?>
    </main>
    <?php
    get_footer();
}

这段结构决定了不同位置适合放什么模块:

位置适合内容注意点
top_fluid / bottom_fluid横幅、全宽推荐、视觉模块不在 .container 内,宽度更大
page_header / page_footer页面级头尾结构通过 Hook 输出,不是后台小工具位置
top_content / bottom_content主内容上下的列表、提示、推荐跟随主内容宽度
sidebar版块信息、版主、紧凑列表宽度小,不适合大卡片或横向滚动大模块
page_sidebar代码追加的侧栏内容在后台小工具之后输出

侧栏容器 class 可以通过 bbs_home_sidebar_classbbs_plate_sidebar_classbbs_posts_sidebar_class 调整。比如某个论坛专题页需要隐藏侧栏,应优先用页面级 Filter,而不是在模板里删掉侧栏结构:

add_filter('bbs_plate_sidebar_class', 'zib_docs_bbs_plate_sidebar_class');

function zib_docs_bbs_plate_sidebar_class($class)
{
    if (!zib_bbs_get_the_plate_id()) {
        return $class;
    }

    return $class . ' hide-sm';
}

如果是发帖编辑页,侧栏不是 zib_bbs_page_template() 的普通侧栏,而是 bbs_new_posts_sidebar_topbbs_new_posts_sidebar_bottombbs_posts_edit_page_sidebar 组合输出。不要把发帖页扩展误挂到 bbs_posts_sidebar

版块信息小工具

版块信息小工具先判断当前是否存在版块对象:

function zib_bbs_widget_ui_plate_info_is_show($show_class, $args, $instance)
{
    $obj = zib_bbs_get_the_plate();
    if (!$obj) {
        return false;
    }

    return $show_class;
}

渲染时仍然再次读取和判断:

function zib_bbs_widget_ui_plate_info($args, $instance)
{
    $obj = zib_bbs_get_the_plate();
    if (!$obj) {
        return;
    }

    echo zib_bbs_get_plate_info_mini_box($obj, 'mb20');
}

这个模式很重要:is_show_filter 负责避免空外壳,渲染函数负责防御异常上下文。不要只在后台说明“只能添加到版块页”,前台仍要做对象判断。

版块头部与侧栏信息盒

版块页头部 zib_bbs_get_plate_header() 和侧栏信息盒 zib_bbs_get_plate_info_mini_box() 都读同一个版块对象,但适配不同展示场景。头部用于主内容区,强调背景、标题、分区、统计、版主入口和发布按钮;侧栏信息盒用于窄栏,强调缩略图、关注、发布、统计和更多菜单。

共享的数据来源包括:

数据读取函数或字段用途
标题get_the_title($plate_id)头部标题、侧栏标题
摘要get_the_excerpt($plate_id)版块说明
缩略图zib_bbs_get_thumbnail($post)背景、封面、侧栏图
权限徽章zib_bbs_get_plate_badge_popover()发帖限制、查看限制等提示
热门徽章zib_bbs_get_hot_badge()热门版块标识
帖子数zib_bbs_get_plate_posts_cut_count()统计展示
回复数zib_bbs_get_plate_reply_cut_count()互动统计
阅读数zib_bbs_get_plate_views_cut_count()热度统计
关注数zib_bbs_get_plate_follow_cut_count()关注提示

主内容版块头部会把更多菜单和版主按钮放到同一块:

$more_dropdown  = zib_bbs_get_plate_header_more_btn($plate_id, '', true);
$moderator_btns = zib_bbs_get_plate_header_moderator_btns($post);
$moderator_btns = $moderator_btns ? '<div class="mt10 moderator-btns em09 ">' . $moderator_btns . ' </div>' : '';

侧栏信息盒也复用更多菜单,但按钮组合改为发帖和关注:

$follow_btn = zib_bbs_get_all_plate_follow_btn($plate_id, 'but hollow c-red', zib_get_svg('add') . $zib_bbs->plate_follow_name);
$new_btn    = zib_bbs_get_posts_add_page_link(array('plate_id' => $plate_id), 'but hollow c-blue mr10');
$more_dropdown = zib_bbs_get_plate_header_more_btn($plate_id);

更多菜单由 zib_bbs_get_plate_more_dropdown() 统一生成,内部按权限逐项加入创建、编辑、发帖限制、查看权限、收费关注、管理版主和删除入口。扩展版块管理菜单时,优先包装或过滤现有入口函数,不要在头部和侧栏各写一份菜单:

$edit = zib_bbs_get_plate_edit_link($plate_id, false, '', zib_get_svg('set', null, 'icon mr6 fa-fw') . sprintf(__('编辑此%s', 'zib_language'), $name), 'a');
$allow_view = zib_bbs_get_plate_allow_view_set_link($plate_id);
$follow_pay = zib_bbs_get_plate_follow_pay_set_link($plate_id);

版主按钮同样有降级逻辑:有版主时显示头像和查看弹窗,没有版主时优先显示申请入口;如果当前用户有添加权限,则显示添加入口。二开时不要只判断 moderator meta 是否为空,还要让 zib_bbs_get_apply_moderator_link()zib_bbs_get_add_plate_moderator_link() 自己处理权限与申请状态。

分区和话题头部也遵循类似模式。bbs_plate_cat_header_excerpt 会追加分区版主入口和创建版块按钮,bbs_forum_topic_header_excerpt 会追加话题作者和发帖按钮:

add_filter('bbs_plate_cat_header_excerpt', 'zib_bbs_plate_cat_header_excerpt_filter', 10, 2);
add_filter('bbs_forum_topic_header_excerpt', 'zib_bbs_forum_topic_header_excerpt_filter', 10, 2);

因此扩展版块展示时要先判断位置:版块页头部改 zib_bbs_get_plate_header() 周边,版块侧栏改 zib_bbs_get_plate_info_mini_box() 或小工具,分区/话题头部改对应 bbs_*_header_excerpt Filter。

版主列表小工具

版主列表读取当前版块,再调用主题函数生成版主列表:

$obj = zib_bbs_get_the_plate();
$moderator_lists = zib_bbs_get_moderator_lists('plate', $obj);

按钮区遵循主题已有入口:

$apply_btn = !empty($instance['apply_btn'])
    ? zib_bbs_get_apply_moderator_link(0, 'but hollow c-blue p2-10 em09')
    : '';

if (!$apply_btn) {
    $apply_btn = zib_bbs_get_edit_plate_moderator_link($obj->ID, 'c-blue em09');
}

扩展版主相关功能时,不要自己拼接添加、编辑、申请 URL。版主申请、编辑入口背后还有权限、弹窗、Ajax 和业务状态判断。

版块列表小工具

版块列表小工具使用通用 ajax_widget_ui 协议延迟加载:

$ias_args = array(
    'type'   => 'ias',
    'loader' => $placeholder,
    'query'  => array(
        'action' => 'ajax_widget_ui',
        'id'     => 'zib_bbs_widget_ui_plate_lists',
        'index'  => $index,
    ),
);

echo zib_get_ias_ajaxpager($ias_args);

Ajax 回调使用保存的小工具配置生成查询参数:

$posts_args = array(
    'cat'       => $instance['cat'],
    'filter'    => $instance['filter'],
    'orderby'   => $instance['orderby'],
    'showposts' => $instance['showposts'],
);

渲染时根据样式分支调用主题函数:

if ('card' == $style) {
    $lists = zib_bbs_get_plate_slide_card($posts_args);
} else {
    $lists = '<div class="plate-lists">' . zib_bbs_get_plate_main_lists($posts_args) . '</div>';
}

zib_ajax_send_ajaxpager($lists, true);

字段配置里的 cat 使用 options => 'categories' 并指定 taxonomy => 'plate_cat',排序选项来自 zib_bbs_get_plate_order_options()。新增版块列表筛选时,优先扩展查询参数和排序选项,不要绕过 zib_bbs_get_plate_main_lists() 重新写一套列表 HTML。

帖子列表小工具

帖子列表小工具支持“当前版块”模式:

if (!empty($instance['current_plate'])) {
    $current_plate = zib_bbs_get_the_plate_id();
    if (!$current_plate) {
        return false;
    }
}

渲染时把当前版块 id 带入 Ajax query:

'query' => array(
    'action'        => 'ajax_widget_ui',
    'id'            => 'zib_bbs_widget_ui_posts_lists',
    'index'         => $index,
    'current_plate' => $current_plate,
),

Ajax 回调再合并配置:

$posts_args = array(
    'plate'         => $instance['include_plate'] ?? '',
    'plate_exclude' => $instance['exclude_plate'] ?? '',
    'topic'         => $instance['include_topic'] ?? '',
    'tag'           => $instance['include_tag'] ?? '',
    'orderby'       => $instance['orderby'] ?? 'date',
    'bbs_type'      => $instance['bbs_type'] ?? '',
    'filter'        => $instance['filter'] ?? array(),
    'allow_view'    => $instance['allow_view'] ?? array(),
    'paged'         => $paged,
    'paged_size'    => $paged_size,
);

if (!empty($_REQUEST['current_plate'])) {
    $posts_args['plate'] = $_REQUEST['current_plate'];
}

这里可以看到一个边界:前端请求只允许传当前版块这个上下文,真正的筛选、排序、数量和权限过滤仍来自后台保存的小工具配置。自定义小工具也应采用这个策略,避免用户通过请求参数任意改查询范围。

列表样式

帖子列表按 style 输出三种结构:

style渲染函数
detailzib_bbs_get_posts_list()
minizib_bbs_get_posts_mini_list()
minimalismzib_bbs_get_posts_lists_title() 包在 <posts class="forum-posts minimalism">

示例:

if ('detail' === $style) {
    $lists .= zib_bbs_get_posts_list('class=' . $lists_class . '&show_topping=' . $show_topping);
} elseif ('minimalism' === $style) {
    $lists .= '<posts class="forum-posts minimalism ' . $lists_class . '">';
    $lists .= zib_bbs_get_posts_lists_title('forum-title', 'em09', $show_topping, true, false);
    $lists .= '</posts>';
} else {
    $lists .= zib_bbs_get_posts_mini_list($lists_class, $show_topping);
}

不要把论坛帖子当普通 post 调用文章列表函数。论坛帖子还有置顶、精华、投票、提问状态、阅读权限、版块关系和论坛专用标题结构。

分页协议

帖子列表小工具根据 paginate 输出分页:

paginate行为
none不显示分页,但仍输出隐藏 .ajax-pag 兜底
ajax_listsAjax 追加列表分页
default数字分页

源码模式:

if ('none' !== $paginate) {
    $paginate = zib_bbs_get_paginate($posts->found_posts, $paged, $paged_size, $ajax_url, $paginate, 'close');
    if (!$paginate && 1 == $paged) {
        $lists .= '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
    } else {
        $lists .= $paginate;
    }
} else {
    $lists .= '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}

这个隐藏分页不是多余代码,它让前端 Ajax pager 在没有下一页时仍能正确完成状态处理。

多 Tab 帖子列表

多 Tab 小工具使用 group 字段保存多个栏目,每个栏目就是一组帖子查询参数。渲染时首个 Tab 直接输出 zib_get_ias_ajaxpager(),后续 Tab 只输出隐藏触发器:

if ($tabs_i == 1) {
    $ias_args = array(
        'type'   => 'ias',
        'loader' => $placeholder,
        'url'    => $ajax_href,
    );

    $con_html = zib_get_ias_ajaxpager($ias_args);
} else {
    $con_html = '';
    $con_html .= '<span class="post_ajax_trigger hide"><a href="' . add_query_arg('tab', $tabs_key, $ajax_href) . '" class="ajax_load ajax-next ajax-open" no-scroll="true"></a></span>';
    $con_html .= '<div class="post_ajax_loader" style="display: none;">' . $placeholder . '</div>';
}

导航链接使用主题 Tab 协议:

$tabs_nav .= '<li class="' . $nav_class . '"><a' . ($tabs_i !== 1 ? ' data-ajax' : '') . ' data-toggle="tab" href="#' . $tab_id . '">' . $tabs['title'] . '</a></li>';

Ajax 回调读取 tab 序号,取出当前栏目配置,再复用帖子列表 Ajax:

$tab = isset($_REQUEST['tab']) ? (int) $_REQUEST['tab'] : 0;
$tab_args = isset($instance['tabs'][$tab]) ? $instance['tabs'][$tab] : array();
$tab_args['style'] = $instance['style'] ?? 'mini';

zib_bbs_widget_ui_posts_lists_ajax($tab_args);

新增论坛 Tab 模块时,优先复用这个结构,不要另写独立前端请求逻辑。这样能继续使用主题的加载动画、auto_fun() 初始化和 Ajax pager 状态管理。

话题列表小工具

话题列表使用 forum_topic taxonomy:

$query = array(
    'taxonomy'   => array('forum_topic'),
    'orderby'    => $instance['orderby'],
    'hide_empty' => $instance['hide_empty'],
    'number'     => $instance['paged_size'],
    'include'    => $instance['include'],
);

$new_query = zib_bbs_get_term_query($query);

渲染每个话题时调用:

$lists .= zib_bbs_get_topic_lists($term, $style);

字段里 include 支持 Ajax 搜索、多选和排序:

array(
    'id'          => 'include',
    'options'     => 'categories',
    'query_args'  => array(
        'taxonomy' => 'forum_topic',
    ),
    'chosen'      => true,
    'multiple'    => true,
    'ajax'        => true,
    'sortable'    => true,
    'type'        => 'select',
)

如果排序选择 include,只有手动选择了话题时才有意义。文档、后台说明和默认值都要把这个边界讲清楚,否则用户会以为“手动排序”能自动作用于全部话题。

注册写法

论坛小工具统一通过 Zib_CFSwidget::create() 注册,并挂在 after_setup_theme

add_action('after_setup_theme', 'zib_bbs_widget_create_posts');

function zib_bbs_widget_create_posts()
{
    global $zib_bbs;

    Zib_CFSwidget::create('zib_bbs_widget_ui_posts_lists', array(
        'title'       => sprintf(__('[%1$s]%2$s列表', 'zib_language'), $zib_bbs->forum_name, $zib_bbs->posts_name),
        'zib_title'   => true,
        'zib_affix'   => true,
        'zib_show'    => true,
        'callback'    => 'zib_bbs_widget_ui_posts_lists',
        'description' => __('显示帖子列表,支持筛选、样式、排序和翻页。', 'zib_language'),
        'fields'      => array(
            // fields
        ),
    ));
}

真实源码里部分 callback 由 Codestar 的 id 与函数名约定推断。二次开发时建议显式写出 callback,降低维护时的猜测成本。

自定义论坛小工具示例

下面示例保留主题的命名函数、array()is_show_filterZib_CFSwidget::create() 写法,只演示结构:

function zib_docs_bbs_widget_plate_notice_is_show($show_class, $args, $instance)
{
    if (!zib_bbs_get_the_plate()) {
        return false;
    }

    if (empty($instance['text'])) {
        return false;
    }

    return $show_class;
}

function zib_docs_bbs_widget_plate_notice($args, $instance)
{
    $obj = zib_bbs_get_the_plate();
    if (!$obj || empty($instance['text'])) {
        return;
    }

    echo '<div class="zib-widget">';
    echo '<div class="box-body">';
    echo wp_kses_post($instance['text']);
    echo '</div>';
    echo '</div>';
}

function zib_docs_bbs_widget_create_plate_notice()
{
    Zib_CFSwidget::create('zib_docs_bbs_widget_plate_notice', array(
        'title'          => __('版块提示', 'zib_language'),
        'zib_title'      => true,
        'zib_affix'      => true,
        'zib_show'       => true,
        'is_show_filter' => 'zib_docs_bbs_widget_plate_notice_is_show',
        'callback'       => 'zib_docs_bbs_widget_plate_notice',
        'fields'         => array(
            array(
                'title'   => __('提示内容', 'zib_language'),
                'id'      => 'text',
                'type'    => 'textarea',
                'default' => '',
            ),
        ),
    ));
}
add_action('after_setup_theme', 'zib_docs_bbs_widget_create_plate_notice');

如果这个提示只适合版块页侧栏,后台说明要写清楚;前台也要用 zib_bbs_get_the_plate() 再判断一次。

扩展建议

需求推荐入口
注册论坛页面容器zib_bbs_register_sidebar() 同类写法 + zib_register_sidebar()
在论坛首页上下方插入模块bbs_home_tab_content_top / bbs_home_tab_content_bottom
在版块页主内容插入模块bbs_plate_page_content 优先级 199
在帖子页主内容插入模块bbs_posts_page_content_top / bbs_posts_page_content_bottom
显示当前版块信息zib_bbs_get_the_plate() + zib_bbs_get_plate_info_mini_box()
显示版主zib_bbs_get_moderator_lists()
显示版块列表zib_bbs_get_plate_main_lists()zib_bbs_get_plate_slide_card()
显示帖子列表zib_bbs_get_posts_query() + 论坛帖子列表函数
显示话题列表zib_bbs_get_term_query() + zib_bbs_get_topic_lists()
异步加载小工具ajax_widget_ui + {id}_ajax
多 Tab 异步加载data-toggle="tab" + data-ajax + .post_ajax_trigger

风险清单

  • 不要把论坛帖子小工具改成普通文章查询,forum_post 有独立权限、版块、话题、标签、置顶和精华状态。
  • 不要让前端请求参数直接决定查询范围,筛选和排序应来自后台保存的小工具配置。
  • 不要把当前版块模式放到非版块页后仍强制显示,zib_bbs_get_the_plate_id() 为空时应隐藏。
  • 不要在全宽位置使用只适合侧栏的小卡片,也不要在侧栏放横向滚动大模块。
  • 不要删除空分页兜底结构,Ajax pager 依赖它处理结束状态。
  • 不要复制论坛列表 HTML 后长期维护,优先调用主题已有列表函数。
  • 不要在论坛关闭时加载小工具扩展,先判断论坛模块和相关函数是否存在。

On this page