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

搜索体系

梳理子比主题站内搜索页、搜索弹窗、类型 Tab、筛选、排序、关键词、搜索历史、模块接入与 Meilisearch。

它不是一个独立搜索接口

子比主题的搜索体系主要围绕 WordPress 搜索页、主题搜索弹窗、Ajax 加载、Tab 切换、筛选、排序和模块接入构建。它不是一套开放搜索 API,也不是只靠 REST 搜索工作的独立服务。

二次开发搜索时,先判断目标是“改变搜索页展示”“增加搜索类型”“增加筛选条件”“改变查询字段”“接入外部搜索引擎”,再选择对应入口。

核心文件

search.php
inc/functions/zib-search.php
action/main.php
action/function.php
inc/functions/rest-api/function.php
inc/class/ms-class.php
文件作用
search.php搜索结果页模板
inc/functions/zib-search.php搜索页内容、Tab、筛选、排序、关键词、查询改写
action/main.php搜索弹窗内容 Ajax
action/function.php菜单搜索 Ajax
inc/functions/rest-api/function.php补充 WordPress REST 搜索范围
inc/class/ms-class.phpMeilisearch 客户端封装

搜索入口

入口函数或动作用途
搜索结果页zib_search_content()输出搜索页主内容
主搜索框zib_get_main_search()读取主题配置并输出搜索卡片
搜索卡片zib_get_search_box()输出表单、热门关键词、历史搜索、热门文章
搜索按钮zib_get_search_link()生成可唤起搜索弹窗的按钮
搜索弹窗search_boxAjax 加载完整搜索框
菜单搜索menu_search菜单或导航里的搜索交互

搜索表单一般提交到站点首页,通过 stypetremuserorderby 等参数控制搜索范围和展示。

搜索类型

默认搜索类型:

function zib_get_search_types()
{
    $types = array(
        'post' => __('文章', 'zib_language'),
        'user' => __('用户', 'zib_language'),
    );
    return apply_filters('search_types', $types);
}

论坛模块会加入:

type含义
plate版块
forum帖子

商城模块会加入:

type含义
product商品

新增搜索类型时至少要同时处理:

  1. search_types,让搜索框知道新类型。
  2. search_main_tabs_array,让搜索页 Tab 出现新类型。
  3. main_search_tab_content_{type},输出该类型结果。
  4. 查询逻辑或自定义查询函数,保证结果来源正确。
  5. 筛选、排序和分页参数,保证 Ajax 路由切换可用。

示例结构:

add_filter('search_types', 'zib_docs_search_types');

function zib_docs_search_types($types)
{
    $types['custom'] = '自定义内容';
    return $types;
}

add_filter('search_main_tabs_array', 'zib_docs_search_tabs');

function zib_docs_search_tabs($tabs)
{
    $tabs['custom'] = array(
        'title'         => '自定义内容',
        'content_class' => 'posts-row',
        'route'         => true,
        'loader'        => zib_get_author_tab_loader('post'),
    );

    return $tabs;
}

add_filter('main_search_tab_content_custom', 'zib_docs_search_custom_content');

function zib_docs_search_custom_content($html = '')
{
    return '<div class="zib-widget">搜索结果内容</div>';
}

示例只展示挂载位置。实际落地时,要用自己的查询结果、分页和安全输出替换静态 HTML。

搜索页 Tab

搜索页主函数会先构造 postuser 两个 Tab,再通过 Filter 交给模块扩展:

$tabs_args = apply_filters('search_main_tabs_array', $tabs_args);
$tab_content = apply_filters('main_search_tab_content_' . $type, '');

Tab 链接会带 ajax-replace="true"route="1",用于前端 Ajax 切换和路由标题更新。新增 Tab 时,尽量保持主题的 routeloadercontent_class 结构,不要把 Tab 写成完全独立页面。

筛选项

搜索页筛选由 zib_get_search_tab_nav_filter() 输出,筛选数据来自:

zib_get_search_facets_datas()

默认支持的筛选维度:

type筛选
postcategorytopicspost_tag
forumplate_idforum_topicforum_tag
productshop_catshop_tagshop_discount
plateplate_cat

筛选数据会缓存到:

search_facets_datas / zib_cache_group

主题配置保存后会清理缓存:

add_action('csf_zibll_options_saved', 'zib_search_facets_datas_cache_delete');

如果扩展筛选数据,可以使用:

add_filter('search_facets_datas', 'zib_docs_search_facets');

function zib_docs_search_facets($data)
{
    $data['category'][] = array(
        'id'   => 1,
        'name' => '示例分类',
    );

    return $data;
}

新增筛选不只是加链接,还要在查询阶段处理对应参数。

排序项

排序由 zib_get_search_orderby_lists() 输出,核心 Filter 是:

search_orderby_array

默认排序包含:

type排序
post最新、热门、点赞、评论、收藏、销量
forum最新、热门、评论、收藏、评分、销量
product最新、热门、销量、评分、收藏
plate最新、热门、关注、帖子数、最新回帖
user昵称、注册时间

新增排序时要同时确认查询层能识别 orderby。只把排序链接显示出来,但不修改查询,会让用户看到可点击项却得不到正确结果。

查询改写

搜索页主要查询改写点:

Hook函数作用
parse_queryzib_main_search_query_search_types解析并保存当前搜索类型
pre_get_postszib_main_search_query设置文章类型、排除分类、分类筛选、作者筛选
posts_clauses_requestzib_posts_clauses_request搜索用户时阻止主文章查询
posts_searchzib_search_custom_key按自定义字段范围改写搜索 SQL

主题自定义搜索字段来自:

zib_get_search_custom_key()

常见字段包括标题、内容、摘要、分类法、评论内容、作者昵称等。开启更多字段会增加 SQL JOIN 和 LIKE 条件,站点内容量大时要评估性能。

热门关键词与搜索历史

热门关键词:

zib_update_search_keywords($s);
zib_get_search_keywords();

保存位置:

option: search_keywords

搜索历史:

zib_save_history_search($s);
zib_get_search_history_keywords();

保存位置:

cookie: history_search

热门关键词适合反映站点整体搜索趋势,搜索历史只适合当前浏览器用户。不要把搜索历史当作服务端用户行为记录使用。

管理员可以通过:

Action用途
search_keywords_edit_modal打开热门关键词编辑弹窗
search_keywords_edit保存热门关键词

这两个动作只适合站点管理场景,二次开发时不要暴露给普通用户。

模块接入方式

论坛和商城不是复制搜索页,而是接入同一套类型、Tab 和内容 Filter。

论坛接入:

add_filter('search_types', 'zib_bbs_search_types_filter');
add_filter('search_main_tabs_array', 'zib_bbs_search_main_tabs_array_filter');

论坛实际还会接入内容渲染:

add_filter('main_search_tab_content_forum', 'zib_bbs_get_search_posts', 10);
add_filter('main_search_tab_content_plate', 'zib_bbs_get_search_plate', 10);

商城接入:

add_filter('search_types', 'zib_shop_search_types_filter');
add_filter('search_main_tabs_array', 'zib_shop_search_main_tabs_array_filter');

这也是扩展搜索的推荐方式:让新模块进入主题搜索框、搜索页 Tab、Ajax 路由和筛选体系,而不是另做一个体验割裂的搜索页。

论坛搜索联动

论坛搜索不是单独页面。论坛模块开启后,inc/functions/bbs/bbs.php 会通过 search_types 注册两个搜索类型:

type查询对象内容 Filter输出函数
plateplate 版块main_search_tab_content_platezib_bbs_get_search_plate()
forumforum_post 帖子main_search_tab_content_forumzib_bbs_get_search_posts()

plate 搜索会把主查询结果交给 zib_bbs_get_main_plate() 渲染,并包在 .plate-lists.ajax-item 内。forum 搜索会复用 zib_bbs_get_posts_list(array('class' => 'alone ajax-item'))。因此扩展论坛搜索展示时,不要重新写一套列表样式,优先过滤这些输出函数前后的 Hook,或在 main_search_tab_content_plate / main_search_tab_content_forum 后追加内容。

论坛筛选在 zib_get_search_tab_nav_filter() 里有特殊处理:

type筛选参数数据来源URL 值
forumplate_id后台搜索筛选配置里的版块 IDtrem=plate_{plate_id}
forumforum_topic话题 term IDtrem={term_id}
forumforum_tag标签 term IDtrem={term_id}
plateplate_cat版块分类 term IDtrem={term_id}

plate_id 不是 taxonomy,所以主题会把它编码为 plate_123。搜索说明文字和搜索框分类下拉都会识别这个格式,再通过 get_post() 读取版块标题和链接。扩展版块筛选时要保留这个协议,不能把版块 ID 当成普通 term ID。

示例:给论坛搜索追加一个版块筛选项,保持 plate_{$id} 协议:

add_filter('search_facets_datas', 'zib_docs_search_facets_plate');

function zib_docs_search_facets_plate($data)
{
    $plate_id = 123;
    $plate    = get_post($plate_id);

    if (!empty($plate->ID) && 'plate' === $plate->post_type) {
        $data['plate_id'][] = array(
            'id'   => $plate->ID,
            'name' => zib_str_cut($plate->post_title, 0, 15, '...'),
        );
    }

    return $data;
}

如果需要在搜索框里默认限定某个版块,传入的 in_cat 也要使用同样格式:

zib_get_search_box(array(
    'show_input_cat' => true,
    'show_type'      => true,
    'in_type'        => 'forum',
    'in_cat'         => 'plate_' . $plate_id,
    'placeholder'    => __('搜索当前版块内容', 'zib_language'),
), true);

论坛搜索查询边界

搜索页的主查询由 parse_querypre_get_posts 处理。zib_main_search_query_search_types() 只负责解析当前 type,并把结果保存到全局 $search_typezib_main_search_query() 负责处理 tremuser

普通 term 筛选会被转成 tax_query

$tax_query = array(array(
    'taxonomy' => $get_term->taxonomy,
    'field'    => 'id',
    'terms'    => $get_term->term_id,
));
$query->set('tax_query', $tax_query);

plate_123 不是 term ID,不能直接走这段逻辑。论坛模块在 inc/functions/bbs/inc/class.init.phpmain_post_query() 里补了这层处理:type=forum 时设置 post_type=forum_posttype=plate 时设置 post_type=plate,遇到 trem=plate_123 时追加 plate_idmeta_query

if ('forum' === $type) {
    $query->set('post_type', 'forum_post');
}

if ('plate' === $type) {
    $query->set('post_type', 'plate');
}

if ($cat && stristr($cat, 'plate_')) {
    $plate_meta_query = array(
        'key'   => 'plate_id',
        'value' => str_replace('plate_', '', $cat),
    );
    $meta_query   = $query->get('meta_query');
    $meta_query   = is_array($meta_query) ? $meta_query : array();
    $meta_query[] = $plate_meta_query;

    $query->set('meta_query', $meta_query);
}

如果你新增的是类似版块这种“不是 taxonomy 的筛选对象”,可以照这个模式处理。不要把 plate_123 强转成 (int) 后交给 get_term(),那样会把版块筛选静默丢掉。

示例:给自定义搜索类型识别一个非 taxonomy 筛选:

add_action('pre_get_posts', 'zib_docs_search_custom_object_query', 20);

function zib_docs_search_custom_object_query($query)
{
    if (!$query->is_search() || !$query->is_main_query() || $query->is_admin) {
        return;
    }

    if (empty($_REQUEST['type']) || 'custom' !== $_REQUEST['type']) {
        return;
    }

    $trem = !empty($_REQUEST['trem']) ? trim(strip_tags($_REQUEST['trem'])) : '';
    if (!$trem || strpos($trem, 'custom_') !== 0) {
        return;
    }

    $object_id = (int) str_replace('custom_', '', $trem);
    if (!$object_id) {
        return;
    }

    $query->set('meta_query', array(
        array(
            'key'   => 'custom_object_id',
            'value' => $object_id,
        ),
    ));
}

搜索自定义字段只对 postforum_postshop_product 生效。zib_search_custom_key() 会检查主查询的 post_type,只有包含这些类型时才改写 posts_search。如果要让论坛搜索匹配话题、标签、评论或作者昵称,需要在后台搜索字段里启用对应项;大站要评估 commentsusersterms JOIN 带来的慢查询风险。

搜索缓存排查

搜索体系里常见的缓存点有三类:

缓存 / 存储位置清理时机排查方向
筛选项数据search_facets_datas / zib_cache_groupcsf_zibll_options_saved后台新增筛选版块、话题或标签后前台不显示
热门关键词option search_keywords管理员弹窗保存或搜索命中自动更新热门词排序、&type=forum 后缀、置顶关键词
搜索历史cookie history_search浏览器端更新单个访客看到的历史不一致

如果搜索页筛选项不更新,先确认后台搜索筛选配置是否保存成功,再清理 search_facets_datas。如果只是版块名、话题名改了但筛选仍显示旧名称,多数是对象缓存仍在返回旧筛选数据。

示例:保存版块或话题后主动清理搜索筛选缓存:

add_action('save_post_plate', 'zib_docs_search_facets_cache_delete');
add_action('edited_forum_topic', 'zib_docs_search_facets_cache_delete');
add_action('edited_forum_tag', 'zib_docs_search_facets_cache_delete');

function zib_docs_search_facets_cache_delete()
{
    wp_cache_delete('search_facets_datas', 'zib_cache_group');
}

不要把整张搜索页静态缓存给所有用户。搜索页会根据 stypetremuserorderby、分页和登录态输出不同结果,缓存插件至少要把这些查询参数纳入缓存 key;如果做不到,就排除搜索结果页和搜索弹窗。

REST 搜索的边界

inc/functions/rest-api/function.php 主要补充 WordPress 官方 REST 搜索范围,例如在论坛启用时让搜索包含 plateforum_post

REST 搜索与主题前台搜索不是同一个体系:

能力前台搜索体系REST 搜索补充
搜索页 Tab
搜索弹窗
热门关键词
搜索历史
论坛、商城展示仅补充查询范围
开放接口能力也不是完整开放接口

如果要提供第三方系统搜索接口,应单独设计 REST 路由、权限、频率限制、字段白名单和响应结构。

Meilisearch

主题支持通过配置启用 Meilisearch:

_pz('search_ms_s')
zib_get_search_ms_opt()
new zib_ms($search_ms_opt)

后台相关 Ajax:

Action用途
search_ms_rebuild重建索引
search_ms_stats查看索引状态
search_ms_sync_synonyms同步同义词

启用外部搜索后,要关注:

  • 索引字段是否覆盖文章、论坛、商城需要搜索的内容。
  • 新增、编辑、删除内容后是否同步索引。
  • 同义词、停用词、分词效果是否符合站点内容。
  • 外部搜索异常时是否能降级到 WordPress 搜索。
  • 后台重建索引是否有权限校验和执行时间控制。

扩展建议

  • 只改搜索页展示时,优先使用 main_search_tab_content_{type} 或模板层扩展。
  • 新增搜索类型时,同时接入 search_typessearch_main_tabs_array 和内容输出。
  • 新增筛选时,同时处理筛选数据、URL 参数和查询逻辑。
  • 新增排序时,同时处理排序显示和查询 orderby
  • 改搜索字段时,评估 SQL 性能,必要时启用 Meilisearch。
  • 做弹窗或下拉搜索时,优先复用 zib_get_search_box(),保持热门关键词、历史搜索和类型选择一致。

常见风险

风险说明
只加 Tab 不加内容 Filter页面会出现空结果或加载异常
只加排序链接不改查询用户看到排序已切换,但结果没有变化
user 当文章查询主题会阻止主查询,需要使用 WP_User_Query
无限制扩展 posts_search大站会出现慢查询和数据库压力
把 REST 搜索当开放接口权限、字段、频率限制都不完整
忽略 Ajax 路由属性搜索页切换会退化成跳转或状态丢失

On this page