李锋镝的博客

  • 首页
  • 时间轴
  • 说说
  • 左邻右舍
  • 博友圈
  • 关于我
    • 关于我
    • 另一个网站
    • 我的导航站
    • 网站地图
    • 赞助
  • 留言
  • 走心评论
  • 系列文章
  • Now
  • 每日心情
  • 🚇开往
Destiny
自是人生长恨水长东
  1. 首页
  2. 原创
  3. WordPress
  4. 正文

WordPress评论添加UserAgent以及地理位置信息

2025年12月1日 约 1,440 字5 分钟 588点热度 1人点赞 3条评论
本文最后更新于 2025年12月1日,距今已 231 天,其中的信息可能已经发生变化,请注意甄别。

看到大家的博客评论下面都带有UserAgent信息,还有地理位置信息,一直都很羡慕,奈何网上苦寻插件不见。要么就是插件年久失修,要么就是直接主题集成,无一我想要的。昨天又想起这事,心痒难耐,遂决定自己敲一个出来(其实是AI代笔)。

首先看了下WordPress的评论表,发现UserAgent和用户IP都存的有,这就很方便了,至少数据不需要自己另外新建表保存了。

解析UA信息

解析UserAgent其实很简单,代码如下:

function parse_user_agent( $u_agent = null ) {
    if( $u_agent === null && isset($_SERVER['HTTP_USER_AGENT']) ) {
        $u_agent = $_SERVER['HTTP_USER_AGENT'];
    }

    if( $u_agent === null ) {
        throw new \InvalidArgumentException('parse_user_agent requires a user agent');
    }

    $platform = null;
    $browser  = null;
    $version  = null;

    $empty = array( 'platform' => $platform, 'browser' => $browser, 'version' => $version );

    if( !$u_agent ) {
        return $empty;
    }

    if( preg_match('/\((.*?)\)/m', $u_agent, $parent_matches) ) {
        preg_match_all('/(?P<platform>BB\d+;|Android|CrOS|Tizen|iPhone|iPad|iPod|Linux|(Open|Net|Free)BSD|Macintosh|Windows(\ Phone)?|Silk|linux-gnu|BlackBerry|PlayBook|X11|(New\ )?Nintendo\ (WiiU?|3?DS|Switch)|Xbox(\ One)?)
                (?:\ [^;]*)?
                (?:;|$)/imx', $parent_matches[1], $result);

        $priority = array( 'Xbox One', 'Xbox', 'Windows Phone', 'Tizen', 'Android', 'FreeBSD', 'NetBSD', 'OpenBSD', 'CrOS', 'X11' );

        $result['platform'] = array_unique($result['platform']);
        if( count($result['platform']) > 1 ) {
            if( $keys = array_intersect($priority, $result['platform']) ) {
                $platform = reset($keys);
            } else {
                $platform = $result['platform'][0];
            }
        } elseif( isset($result['platform'][0]) ) {
            $platform = $result['platform'][0];
        }
    }

    if( $platform == 'linux-gnu' || $platform == 'X11' ) {
        $platform = 'Linux';
    } elseif( $platform == 'CrOS' ) {
        $platform = 'Chrome OS';
    }

    preg_match_all('%(?P<browser>Camino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit|
                TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|Edge|Edg|CriOS|UCBrowser|Puffin|OculusBrowser|SamsungBrowser|
                Baiduspider|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl|
                Valve\ Steam\ Tenfoot|
                NintendoBrowser|PLAYSTATION\ (\d|Vita)+)
                (?:\)?;?)
                (?:(?:[:/ ])(?P<version>[0-9A-Z.]+)|/(?:[A-Z]*))%ix',
        $u_agent, $result);

    // If nothing matched, return null (to avoid undefined index errors)
    if( !isset($result['browser'][0]) || !isset($result['version'][0]) ) {
        if( preg_match('%^(?!Mozilla)(?P<browser>[A-Z0-9\-]+)(/(?P<version>[0-9A-Z.]+))?%ix', $u_agent, $result) ) {
            return array( 'platform' => $platform ?: null, 'browser' => $result['browser'], 'version' => isset($result['version']) ? $result['version'] ?: null : null );
        }

        return $empty;
    }

    if( preg_match('/rv:(?P<version>[0-9A-Z.]+)/i', $u_agent, $rv_result) ) {
        $rv_result = $rv_result['version'];
    }

    $browser = $result['browser'][0];
    $version = $result['version'][0];

    $lowerBrowser = array_map('strtolower', $result['browser']);

    $find = function ( $search, &$key = null, &$value = null ) use ( $lowerBrowser ) {
        $search = (array)$search;

        foreach( $search as $val ) {
            $xkey = array_search(strtolower($val), $lowerBrowser);
            if( $xkey !== false ) {
                $value = $val;
                $key   = $xkey;

                return true;
            }
        }

        return false;
    };

    $findT = function ( array $search, &$key = null, &$value = null ) use ( $find ) {
        $value2 = null;
        if( $find(array_keys($search), $key, $value2) ) {
            $value = $search[$value2];

            return true;
        }

        return false;
    };

    $key = 0;
    $val = '';
    if( $findT(array( 'OPR' => 'Opera', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge' ), $key, $browser) ) {
        $version = $result['version'][$key];
    }elseif( $find('Playstation Vita', $key, $platform) ) {
        $platform = 'PlayStation Vita';
        $browser  = 'Browser';
    } elseif( $find(array( 'Kindle Fire', 'Silk' ), $key, $val) ) {
        $browser  = $val == 'Silk' ? 'Silk' : 'Kindle';
        $platform = 'Kindle Fire';
        if( !($version = $result['version'][$key]) || !is_numeric($version[0]) ) {
            $version = $result['version'][array_search('Version', $result['browser'])];
        }
    } elseif( $find('NintendoBrowser', $key) || $platform == 'Nintendo 3DS' ) {
        $browser = 'NintendoBrowser';
        $version = $result['version'][$key];
    } elseif( $find('Kindle', $key, $platform) ) {
        $browser = $result['browser'][$key];
        $version = $result['version'][$key];
    } elseif( $find('Opera', $key, $browser) ) {
        $find('Version', $key);
        $version = $result['version'][$key];
    } elseif( $find('Puffin', $key, $browser) ) {
        $version = $result['version'][$key];
        if( strlen($version) > 3 ) {
            $part = substr($version, -2);
            if( ctype_upper($part) ) {
                $version = substr($version, 0, -2);

                $flags = array( 'IP' => 'iPhone', 'IT' => 'iPad', 'AP' => 'Android', 'AT' => 'Android', 'WP' => 'Windows Phone', 'WT' => 'Windows' );
                if( isset($flags[$part]) ) {
                    $platform = $flags[$part];
                }
            }
        }
    } elseif( $find(array( 'IEMobile', 'Edge', 'Midori', 'Vivaldi', 'OculusBrowser', 'SamsungBrowser', 'Valve Steam Tenfoot', 'Chrome', 'HeadlessChrome' ), $key, $browser) ) {
        $version = $result['version'][$key];
    } elseif( $rv_result && $find('Trident') ) {
        $browser = 'MSIE';
        $version = $rv_result;
    } elseif( $browser == 'AppleWebKit' ) {
        if( $platform == 'Android' ) {
            $browser = 'Android Browser';
        } elseif( strpos($platform, 'BB') === 0 ) {
            $browser  = 'BlackBerry Browser';
            $platform = 'BlackBerry';
        } elseif( $platform == 'BlackBerry' || $platform == 'PlayBook' ) {
            $browser = 'BlackBerry Browser';
        } else {
            $find('Safari', $key, $browser) || $find('TizenBrowser', $key, $browser);
        }

        $find('Version', $key);
        $version = $result['version'][$key];
    } elseif( $pKey = preg_grep('/playstation \d/i', $result['browser']) ) {
        $pKey = reset($pKey);

        $platform = 'PlayStation ' . preg_replace('/\D/', '', $pKey);
        $browser  = 'NetFront';
    }

    return array( 'platform' => $platform ?: null, 'browser' => $browser ?: null, 'version' => $version ?: null );
}

解析IP

另外一个难题是根据用户IP解析地址位置信息,网上大多都是调用Api接口或者IP库来解析的。

首先说说Api,大部分Api都有请求频率的限制,太频繁容易被限制,要么就是付费接口,那更不行,那Api这条路子基本行不通了。

第二条路子就是IP库,这个办法挺好的,但是需要经常更新IP库,不然时间久了位置信息可能不准了,不准的话,还不如不要,咱要做就要做好不是。这样的话,还得写个定时任务去更新IP库,挺麻烦的,我就想要一个轻量级的小功能而已,几行代码实现就行。

后来灵机一动,想起了自己在用的另外一款插件WP Statistics,这个插件自带的就有IP库,而且它自己还会定时去更新IP库,简直完美,那要怎么用它呢?这个简单,看它代码就行了。

经过研究,找到了GeolocationFactory这个类,里面有个函数叫getLocation,传入IP就能返回位置信息,完美!

$location_data = WP_Statistics\Service\Geolocation\GeolocationFactory::getLocation($comment_ip);
$country = $location_data['country'] ?? '未知';
$region = $location_data['region'] ?? '未知';
$city = $location_data['city'] ?? '未知';

渲染页面

剩下的就是将解析的信息渲染到页面上,WordPress提供有现成的钩子,调用即可。

function wpcdi_add_info_after_comment_content($comment_text, $comment) {
    // 仅前台显示,且评论已通过审核
    if (is_admin() || $comment->comment_approved != 1) {
        return $comment_text;
    }
    $location_icon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 20 20" fill="#e9ecef" stroke="#495057" stroke-width="2">
          <path d="M12 3c-2.5 0-4.5 2-4.5 4.5c0 2.5 2 5.5 4.5 9c2.5-3.5 4.5-6.5 4.5-9C16.5 5 14.5 3 12 3z"/>
          <circle cx="12" cy="7.5" r="1.5" fill="#495057"/>
        </svg>';

    // 获取评论者IP、UA并过滤
    $comment_ip = sanitize_text_field($comment->comment_author_IP);
    $user_agent = sanitize_text_field($comment->comment_agent);

    // 获取设备/地理信息
    $info = wpcdi_get_comment_info($comment_ip, $user_agent);

    $browser_icon = wpcdi_get_svg_icon('browser', $info['browser']);
    $os_icon = wpcdi_get_svg_icon('os', $info['os']);

    $info_html = '<div class="wpcdi-comment-info" style="padding-top:10px; font-size: 12px; color: #495057; line-height: 1.5; display: flex; align-items: center; gap: 5px;">
    <span style="display: inline-flex; align-items: center;">' . $os_icon . '<span>' . esc_html($info['os']) . '</span></span>
    <span style="display: inline-flex; align-items: center;">' . $browser_icon . '<span>' . esc_html($info['browser']) . '</span></span><span style="display: inline-flex; align-items: center;">' . $location_icon . '<span>'  . esc_html($info['location']) . '</span></span></div>';
    // 追加到评论内容后方
    return $comment_text . $info_html;
}
// 绑定评论内容钩子,优先级10,参数2
add_filter('comment_text', 'wpcdi_add_info_after_comment_content', 10, 2);

大家可以评论一下看看效果,或者有什么好的点子也可以说一下。

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/article/wordpress/4606

推荐阅读

  • WordPress实现用户评论等级排行榜插件
  • 增加了两套复古皮肤-牛皮纸、千禧网页
  • Kratos+主题新功能介绍
  • Kratos+主题新功能预览及功能演示
  • Kratos+ —— Kratos 主题二次开发记录
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: UserAgent WordPress 评论
最后更新:2025年12月1日

岁月同一天 7 月 20 日

回望过去的今天,你在写什么

  • 7 年前 2019年7月20日
    SpringBoot基于redis的分布式锁的实现(源码)

    分布式锁有很多种实现方式,下面是用redis实现的一个比较简单实用的方式。源码如下: import org.spring…

相关文章
  • 增加了两套复古皮肤-牛皮纸、千禧网页2026年7月15日
  • 做了一个WordPress文章热力图插件2025年12月16日
  • wordpress增加说说功能2025年4月2日
  • WordPress的自动更新好烦啊2021年11月11日
  • Dylan Custom Plugin 1.0.2版本更新说明2025年4月16日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >
1234567891112131415161718192021222324252627282930313233343536373839404142434446474849505152535455575859606162636465666769727476777879808182858687909293949596979899
取消回复
…

文章评论

  • yourlaiLv 1

    学到了学到了,大佬牛的

    WindowsEdge 145.0.0.0 中国-淄博市
    2026年2月23日
    00 回复
  • 鲁迅Lv 1

    再看一下

    macOSChrome 142.0.0.0 美国-Spring
    2025年12月2日
    00 回复
  • 鲁迅Lv 1

    看看我在哪儿

    macOSChrome 142.0.0.0 美国-Spring
    2025年12月2日
    00 回复
  • 报君黄金台上意,提携玉龙为君死!

    听点儿音乐吧 朋友~
    文章目录

    那年今日(07月20日)

    • 1999年:中国在西藏找到石油
    • 1973年:国际著名动作电影明星李小龙去世
    • 1926年:前苏联著名国务和党务活动家捷尔任斯基逝世
    • 1866年:德国著名数学家黎曼去世
    • 1822年:奥地利近代遗传学的奠基者孟德尔出生
    • 更多历史事件
    最新 热点 随机
    最新 热点 随机
    增加了两套复古皮肤-牛皮纸、千禧网页 Claude Design介绍与使用 Kratos+主题新功能介绍 Taste Skill 说明与使用 jasypt-spring-boot 使用说明 Kratos+主题新功能预览及功能演示
    AI时代,个人技术博客的出路在哪里?这个域名注册整整十年了,十年时间,真快啊WordPress实现用户评论等级排行榜插件WordPress网站换了个字体,差点儿把样式换崩了做了一个WordPress文章热力图插件千万级大表新增字段实战指南:告别锁表与业务中断
    如何形象的描述反应式编程中的背压(Backpressure)机制? WordPress评论框增加自定义表情 HBASE填坑日志 JVM安全点介绍 深度解析 RAG 技术:从原理到落地,构建高精度检索增强生成系统 SpringBoot常用注解
    最近评论
    皮皮社长 发布于 3 天前(07月17日) 李哥你这也太复古了点吧, :37: 你这主题排版得好,我的主题不行,主要是不懂代码,都是ai搞的。
    李锋镝 发布于 6 天前(07月14日) 外面的主题功能差异化比较大,部分功能没有还得通过插件来实现,这样样式也不统一,而且经常有爆出风险、漏...
    瓦匠 发布于 6 天前(07月14日) 很奈斯啊,都在设计属于自己的主题了,羡慕了
    rxshc 发布于 1 周前(07月13日) 工具看着挺实用的。
    李锋镝 发布于 2 周前(07月09日) https://github.com/Leonxlnx/taste-skill
    标签聚合
    SQL 架构 设计模式 Spring Redis WordPress K8s MySQL 日常 ElasticSearch 多线程 IDEA AI JAVA JVM 分布式 数据库 MCP SpringBoot AI编程
    友情链接
    • Blogs·CN
    • Honesty
    • Mr.Sun的博客
    • 临窗旋墨
    • 哥斯拉
    • 彬红茶日记
    • 志文工作室
    • 懋和道人
    • 拾趣博客导航
    • 搬砖日记
    • 旧时繁华
    • 林羽凡
    • 瓦匠个人小站
    • 皮皮社
    • 知向前端
    • 韩小韩博客
    • 风渡言

    COPYRIGHT © 2026 lifengdi.com. ALL RIGHTS RESERVED.

    域名年龄

    Theme Kratos+ By Dylan Li

    津ICP备2024022503号-3

    京公网安备11011502039375号