子比主题--自动标签匹配(标题 -> 标签)

文章最后更新时间:2026-04-16 13:19:57

20260416132108736-20260416052108192245

 

有一下三种方式能实现,只需要用一个代码就行

放置位置:
* /wp-content/themes/zibll/func.php

* 子比主题 安全版 标题自动标签
* 精简无报错、防500、兼容所有WP版本

/**
 * 子比主题 安全版 标题自动标签
 * 精简无报错、防500、兼容所有WP版本
 */
if (!defined('ABSPATH')) exit;

// 配置项,自己按需改
function zib_auto_tag_config(){
    return [
        'open'        => true,       // 总开关
        'max_num'     => 5,          // 最多几个标签
        'auto_create' => false,      // 不自动新建标签(更安全)
        'stop_word'   => ['的','了','和','与','教程','方法','分享','怎么','如何']
    ];
}

// 保存文章自动打标签
add_action('save_post', function($post_id){
    $cfg = zib_auto_tag_config();
    if(!$cfg['open']) return;

    // 安全判断,避免死循环、自动保存、草稿
    if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if(wp_is_post_revision($post_id)) return;

    $post = get_post($post_id);
    if(!$post || $post->post_type !== 'post') return;
    if(empty($post->post_title)) return;

    // 获取已有标签
    $old_tags = wp_get_post_tags($post_id, ['fields'=>'names']);
    $title    = $post->post_title;

    // 读取网站所有已有标签
    $all_tags = get_tags(['number'=>0, 'hide_empty'=>false]);
    $match    = [];

    foreach($all_tags as $tag){
        if(count($match) >= $cfg['max_num']) break;
        // 标题包含标签名 就匹配
        if(str_contains($title, $tag->name)){
            $match[] = $tag->name;
        }
    }

    // 合并原有标签+自动标签,不覆盖
    $new_tags = array_unique(array_merge($old_tags, $match));
    wp_set_post_tags($post_id, $new_tags);
}, 20);

* 子比Zibll 增强版 标题自动标签
* 特性:长词优先、同义词映射、停用词过滤、防崩溃、防死循环

<?php
/**
 * 子比Zibll 增强版 标题自动标签
 * 特性:长词优先、同义词映射、停用词过滤、防崩溃、防死循环
 */
if (!defined('ABSPATH')) exit;

// ========== 自定义配置区 ==========
function zib_auto_tag_config()
{
    return [
        'enable'       => true,
        'max_tag'      => 6,
        'auto_create'  => false,
        'append_mode'  => true,
        // 同义词映射
        'map'          => [
            '建站'     => '网站搭建',
            '源码'     => '程序源码',
            '美化'     => '主题美化'
        ],
        // 过滤无用词
        'stop_words'   => ['的','了','和','与','及','教程','技巧','分享','详解','怎么','如何']
    ];
}

// 获取全部标签 按字数倒序(长词优先)
function zib_get_all_tags_sort()
{
    $tags = get_tags(['number' => 0, 'hide_empty' => false]);
    usort($tags, function ($a, $b) {
        return mb_strlen($b->name) - mb_strlen($a->name);
    });
    return $tags;
}

// 核心自动匹配
add_action('save_post', function ($post_id) {
    $cfg = zib_auto_tag_config();
    if (!$cfg['enable']) return;

    // 安全拦截
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) return;

    $post = get_post($post_id);
    if (!$post || $post->post_type !== 'post' || empty($post->post_title)) return;

    $title = $post->post_title;
    $match_tags = [];

    // 1. 同义词强制匹配
    foreach ($cfg['map'] as $key => $tag) {
        if (str_contains($title, $key) && !in_array($tag, $match_tags)) {
            $match_tags[] = $tag;
        }
    }

    // 2. 长词优先匹配全站标签
    $all_tags = zib_get_all_tags_sort();
    foreach ($all_tags as $tag) {
        if (count($match_tags) >= $cfg['max_tag']) break;
        $name = $tag->name;
        if (in_array($name, $cfg['stop_words'])) continue;
        if (str_contains($title, $name) && !in_array($name, $match_tags)) {
            $match_tags[] = $name;
        }
    }

    // 合并原有标签
    if ($cfg['append_mode']) {
        $old = wp_get_post_tags($post_id, ['fields' => 'names']);
        $match_tags = array_unique(array_merge($old, $match_tags));
    }

    wp_set_post_tags($post_id, $match_tags);
}, 20);

Zibll 自动标签匹配(标题 -> 标签)
*
* 放置位置:
* /wp-content/themes/zibll/func.php

<?php
/**
 * Zibll 自动标签匹配(标题 -> 标签)
 *
 * 放置位置:
 * /wp-content/themes/zibll/func.php
 *
 * 功能:
 * 1) 保存文章时,自动从标题识别关键词并关联 post_tag 标签
 * 2) 长词优先匹配,减少短词误匹配
 * 3) 支持手动同义词映射(manual_map)
 * 4) 可选“找不到就自动创建标签”(默认关闭)
 * 5) 带缓存和防重入锁,性能和稳定性更好
 */

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

if (!function_exists('zibll_title_tag_config')) {
    /**
     * 统一配置入口(你主要改这里)
     */
    function zibll_title_tag_config()
    {
        return array(
            // 总开关
            'enabled'           => true,

            // 生效的文章类型,默认只处理普通文章 post
            'post_types'        => array('post'),

            // 标签写入模式:
            // true  = 与文章现有标签合并
            // false = 使用自动识别结果覆盖原标签
            'append_mode'       => true,

            // 每篇最多自动关联多少个标签
            'max_tags_per_post' => 8,

            // 关键词最短长度(中文/英文均生效)
            'min_keyword_len'   => 2,

            // 当关键词没有对应标签时,是否自动创建新标签
            'auto_create_tag'   => false,

            // 手动映射:标题命中左侧关键词时,强制绑定右侧标签
            // '标题关键词' => '已有标签名'
            'manual_map'        => array(
                // 'chatgpt'   => 'ChatGPT',
                // 'wordpress' => 'WordPress',
                // '建站'      => 'WordPress',
            ),

            // 停用词:这些词不会作为关键词候选
            'stopwords'         => array(
                '的', '了', '和', '与', '及', '在', '是', '就', '都', '很', '也', '把', '被',
                '你', '我', '他', '她', '它', '我们', '你们', '他们',
                '一个', '一些', '一种', '这个', '那个', '这些', '那些',
                '怎么', '如何', '为什么', '教程', '指南', '方法', '技巧', '分享',
            ),
        );
    }
}

if (!function_exists('zibll_tt_strlen')) {
    /**
     * UTF-8 安全长度
     */
    function zibll_tt_strlen($text)
    {
        if (function_exists('mb_strlen')) {
            return (int) mb_strlen((string) $text, 'UTF-8');
        }
        return (int) strlen((string) $text);
    }
}

if (!function_exists('zibll_tt_substr')) {
    /**
     * UTF-8 安全截取
     */
    function zibll_tt_substr($text, $start, $length = null)
    {
        $text = (string) $text;
        if (function_exists('mb_substr')) {
            if ($length === null) {
                return (string) mb_substr($text, (int) $start, null, 'UTF-8');
            }
            return (string) mb_substr($text, (int) $start, (int) $length, 'UTF-8');
        }
        if ($length === null) {
            return (string) substr($text, (int) $start);
        }
        return (string) substr($text, (int) $start, (int) $length);
    }
}

if (!function_exists('zibll_tt_strtolower')) {
    /**
     * UTF-8 安全转小写(让英文匹配不区分大小写)
     */
    function zibll_tt_strtolower($text)
    {
        $text = (string) $text;
        if (function_exists('mb_strtolower')) {
            return (string) mb_strtolower($text, 'UTF-8');
        }
        return (string) strtolower($text);
    }
}

if (!function_exists('zibll_tt_stripos')) {
    /**
     * UTF-8 安全不区分大小写查找
     */
    function zibll_tt_stripos($haystack, $needle)
    {
        $haystack = (string) $haystack;
        $needle   = (string) $needle;
        if ($needle === '') {
            return false;
        }
        if (function_exists('mb_stripos')) {
            return mb_stripos($haystack, $needle, 0, 'UTF-8');
        }
        return stripos($haystack, $needle);
    }
}

if (!function_exists('zibll_tt_normalize')) {
    /**
     * 文本归一化:
     * - 去 HTML
     * - 全部小写
     * - 去掉标点(保留字母/数字/中文/空格/下划线/中划线)
     * - 压缩连续空格
     * - 可选去空格(用于连续子串匹配)
     */
    function zibll_tt_normalize($text, $remove_space = false)
    {
        $text = html_entity_decode((string) $text, ENT_QUOTES, 'UTF-8');
        $text = wp_strip_all_tags($text);
        $text = zibll_tt_strtolower($text);
        $text = preg_replace('/[^\p{L}\p{N}\p{Han}\s\-_]+/u', ' ', $text);
        $text = preg_replace('/\s+/u', ' ', trim($text));
        if ($remove_space) {
            $text = str_replace(' ', '', $text);
        }
        return (string) $text;
    }
}

if (!function_exists('zibll_tt_flush_tag_index')) {
    /**
     * 清理标签索引缓存
     * 在标签新增/编辑/删除时触发,确保匹配库实时更新
     */
    function zibll_tt_flush_tag_index($term_id = 0, $tt_id = 0, $taxonomy = '')
    {
        if ($taxonomy && 'post_tag' !== $taxonomy) {
            return;
        }
        delete_transient('zibll_title_tag_index_v1');
    }
}
add_action('created_term', 'zibll_tt_flush_tag_index', 10, 3);
add_action('edited_term', 'zibll_tt_flush_tag_index', 10, 3);
add_action('delete_term', 'zibll_tt_flush_tag_index', 10, 3);

if (!function_exists('zibll_tt_get_tag_index')) {
    /**
     * 构建“标签关键词索引”
     *
     * 来源:
     * - 标签 name
     * - 标签 slug
     * - 别名 meta:zib_tag_alias / tag_alias(可选)
     *
     * 关键点:
     * - 关键词按长度降序,先匹配长词,降低误命中
     * - 双层缓存:请求内 static + transient(12h)
     */
    function zibll_tt_get_tag_index($force = false)
    {
        static $runtime_cache = null;

        if (!$force && is_array($runtime_cache)) {
            return $runtime_cache;
        }

        if (!$force) {
            $cached = get_transient('zibll_title_tag_index_v1');
            if (is_array($cached)) {
                $runtime_cache = $cached;
                return $runtime_cache;
            }
        }

        $terms = get_terms(array(
            'taxonomy'   => 'post_tag',
            'hide_empty' => false,
        ));

        if (is_wp_error($terms) || empty($terms)) {
            $runtime_cache = array();
            return $runtime_cache;
        }

        $index = array();
        foreach ($terms as $term) {
            $keys = array(
                (string) $term->name,
                (string) $term->slug,
            );

            $alias = get_term_meta((int) $term->term_id, 'zib_tag_alias', true);
            if (!$alias) {
                $alias = get_term_meta((int) $term->term_id, 'tag_alias', true);
            }
            if (!empty($alias)) {
                $alias_parts = preg_split('/[,,|\/\s]+/u', (string) $alias, -1, PREG_SPLIT_NO_EMPTY);
                if (!empty($alias_parts)) {
                    $keys = array_merge($keys, $alias_parts);
                }
            }

            $keys = array_unique(array_filter(array_map('trim', $keys)));
            foreach ($keys as $key) {
                $keyword = zibll_tt_normalize($key, true);
                if ('' === $keyword || zibll_tt_strlen($keyword) < 2) {
                    continue;
                }
                $index[] = array(
                    'term_id' => (int) $term->term_id,
                    'keyword' => $keyword,
                    'len'     => zibll_tt_strlen($keyword),
                );
            }
        }

        usort($index, function ($a, $b) {
            if ((int) $a['len'] === (int) $b['len']) {
                return (int) $a['term_id'] - (int) $b['term_id'];
            }
            return ((int) $a['len'] > (int) $b['len']) ? -1 : 1;
        });

        $runtime_cache = array_values($index);
        set_transient('zibll_title_tag_index_v1', $runtime_cache, 12 * HOUR_IN_SECONDS);
        return $runtime_cache;
    }
}

if (!function_exists('zibll_tt_resolve_tag_id')) {
    /**
     * 把“标签名或关键词”解析成 term_id
     *
     * 顺序:
     * 1) 按 name 查
     * 2) 按 slug 查
     * 3) 允许自动创建时,直接插入新标签
     */
    function zibll_tt_resolve_tag_id($tag_name, $allow_create = false)
    {
        $tag_name = trim((string) $tag_name);
        if ('' === $tag_name) {
            return 0;
        }

        $term = get_term_by('name', $tag_name, 'post_tag');
        if (!$term || is_wp_error($term)) {
            $term = get_term_by('slug', sanitize_title($tag_name), 'post_tag');
        }

        if ($term && !is_wp_error($term)) {
            return (int) $term->term_id;
        }

        if (!$allow_create) {
            return 0;
        }

        $created = wp_insert_term($tag_name, 'post_tag');
        if (is_wp_error($created) || empty($created['term_id'])) {
            return 0;
        }

        zibll_tt_flush_tag_index();
        return (int) $created['term_id'];
    }
}

if (!function_exists('zibll_tt_extract_keywords')) {
    /**
     * 从标题提取候选关键词
     *
     * 逻辑:
     * 1) 按空格/中划线/下划线拆分
     * 2) 过滤停用词与短词
     * 3) 对纯中文片段做 2~4 字 N-Gram 切分,提高中文命中率
     */
    function zibll_tt_extract_keywords($title, $config)
    {
        $title = zibll_tt_normalize($title, false);
        if ('' === $title) {
            return array();
        }

        $min_len   = max(2, (int) $config['min_keyword_len']);
        $stopwords = array_flip((array) $config['stopwords']);
        $parts     = preg_split('/[\s\-_]+/u', $title, -1, PREG_SPLIT_NO_EMPTY);
        $keywords  = array();

        foreach ((array) $parts as $part) {
            $part = trim((string) $part);
            if ('' === $part || isset($stopwords[$part])) {
                continue;
            }

            if (zibll_tt_strlen($part) >= $min_len) {
                $keywords[$part] = true;
            }

            if (preg_match('/^[\p{Han}]+$/u', $part)) {
                $len   = zibll_tt_strlen($part);
                $max_n = min(4, $len);
                for ($n = 2; $n <= $max_n; $n++) {
                    for ($i = 0; $i <= ($len - $n); $i++) {
                        $sub = zibll_tt_substr($part, $i, $n);
                        if ('' === $sub || isset($stopwords[$sub])) {
                            continue;
                        }
                        $keywords[$sub] = true;
                    }
                }
            }
        }

        return array_keys($keywords);
    }
}

if (!function_exists('zibll_tt_match_tags_from_title')) {
    /**
     * 从标题匹配标签(分三层)
     *
     * 第一层:标签索引直接命中(长词优先)
     * 第二层:manual_map 强制映射
     * 第三层:关键词回退匹配(必要时可自动创建标签)
     */
    function zibll_tt_match_tags_from_title($title, $config)
    {
        $matched   = array();
        $max_count = max(1, (int) $config['max_tags_per_post']);
        $title_key = zibll_tt_normalize($title, true);

        if ('' === $title_key) {
            return array();
        }

        $index = zibll_tt_get_tag_index();
        foreach ((array) $index as $row) {
            if (count($matched) >= $max_count) {
                break;
            }
            if (zibll_tt_stripos($title_key, $row['keyword']) !== false) {
                $matched[(int) $row['term_id']] = true;
            }
        }

        if (!empty($config['manual_map']) && is_array($config['manual_map'])) {
            foreach ($config['manual_map'] as $keyword => $tag_name) {
                if (count($matched) >= $max_count) {
                    break;
                }
                $map_key = zibll_tt_normalize($keyword, true);
                if ('' === $map_key || zibll_tt_stripos($title_key, $map_key) === false) {
                    continue;
                }
                $term_id = zibll_tt_resolve_tag_id($tag_name, !empty($config['auto_create_tag']));
                if ($term_id) {
                    $matched[$term_id] = true;
                }
            }
        }

        if (count($matched) < $max_count) {
            $keywords = zibll_tt_extract_keywords($title, $config);
            foreach ((array) $keywords as $keyword) {
                if (count($matched) >= $max_count) {
                    break;
                }
                $term_id = zibll_tt_resolve_tag_id($keyword, !empty($config['auto_create_tag']));
                if ($term_id) {
                    $matched[$term_id] = true;
                }
            }
        }

        return array_map('intval', array_keys($matched));
    }
}

if (!function_exists('zibll_tt_auto_set_post_tags')) {
    /**
     * 保存文章时自动写入标签
     *
     * 防护项:
     * - 跳过 autosave/revision
     * - 跳过无权限保存
     * - 跳过不在配置内的 post_type
     * - 运行锁防重入,避免循环触发
     */
    function zibll_tt_auto_set_post_tags($post_id, $post, $update)
    {
        $config = zibll_title_tag_config();
        if (empty($config['enabled'])) {
            return;
        }
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
        if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
            return;
        }
        if (!$post || empty($post->post_type)) {
            $post = get_post($post_id);
        }
        if (!$post || empty($post->post_title)) {
            return;
        }
        if (!current_user_can('edit_post', $post_id)) {
            return;
        }
        if (in_array($post->post_status, array('auto-draft', 'trash'), true)) {
            return;
        }

        $post_types = !empty($config['post_types']) ? (array) $config['post_types'] : array('post');
        if (!in_array($post->post_type, $post_types, true)) {
            return;
        }

        static $running = array();
        if (isset($running[$post_id])) {
            return;
        }
        $running[$post_id] = true;

        try {
            $matched_ids = zibll_tt_match_tags_from_title($post->post_title, $config);
            if (empty($matched_ids)) {
                return;
            }

            $existing_ids = wp_get_post_terms($post_id, 'post_tag', array('fields' => 'ids'));
            if (is_wp_error($existing_ids) || !is_array($existing_ids)) {
                $existing_ids = array();
            }

            if (!empty($config['append_mode'])) {
                $final_ids = array_values(array_unique(array_merge(array_map('intval', $existing_ids), $matched_ids)));
            } else {
                $final_ids = $matched_ids;
            }

            // 排序后比较,避免仅因顺序不同而重复写入数据库
            $old_ids = array_map('intval', $existing_ids);
            sort($old_ids);
            sort($final_ids);

            if ($old_ids !== $final_ids) {
                wp_set_post_terms($post_id, $final_ids, 'post_tag', false);
            }
        } finally {
            // 确保始终释放锁(即使中间出现异常)
            unset($running[$post_id]);
        }
    }
}

// 保存文章时执行自动标签匹配
add_action('save_post', 'zibll_tt_auto_set_post_tags', 30, 3);
© 版权声明
THE END
喜欢就支持一下吧
点赞0
评论 抢沙发

请登录后发表评论

    暂无评论内容