李锋镝的博客

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

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

2025年12月1日 约 1,440 字5 分钟 99 1 3
本文最后更新于 2025年12月1日,距今已 260 天,其中的信息可能已经发生变化,请注意甄别。

看到大家的博客评论下面都带有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实现用户评论等级排行榜插件
  • 写了一个订阅每日新闻的WP插件
  • Kratos+ v1.1.16版本更新说明
  • Kratos+ v1.1.14版本更新说明
  • 给主题增加了Now、每日心情、年度回顾、岁月同一天、随机漫步等功能
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: UserAgent WordPress 评论
最后更新:2025年12月1日
相关文章
  • Kratos+ v1.1.14版本更新说明2026年8月7日
  • WordPress实现用户评论等级排行榜插件2025年12月9日
  • 增加了两套复古皮肤-牛皮纸、千禧网页2026年7月15日
  • Dylan Custom Plugin 1.0.3版本更新说明2025年4月22日
  • 做了一个WordPress文章热力图插件2025年12月16日

李锋镝

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

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

文章评论

  • yourlaiLv 1

    学到了学到了,大佬牛的

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

    再看一下

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

    看看我在哪儿

    macOSChrome 142.0.0.0 美国-Texas-Spring
    2025年12月2日
    00 回复
  • Your time is limited, so don't waste it living someone else's life. Don't be trapped by dogma -- which is living with the results of other people's thinking. Don't let the noise of other's opinions drown out your own inner voice. And most important, have the courage to follow your heart and intuition. They somehow already know what you truly want to become. Everything else is secondary.

    听点儿音乐吧 朋友~
    文章目录
    最新 热点 随机
    最新 热点 随机
    每日早报 · 2026年8月18日 写了一个订阅每日新闻的WP插件 每日早报 · 2026年8月17日 每日早报 · 2026年8月16日 每日早报 · 2026年8月15日 每日早报 · 2026年8月14日
    给主题增加了Now、每日心情、年度回顾、岁月同一天、随机漫步等功能Kratos+ v1.1.16版本更新说明AI时代,个人技术博客的出路在哪里?增加了两套复古皮肤-牛皮纸、千禧网页这个域名注册整整十年了,十年时间,真快啊Kratos+ v1.1.14版本更新说明
    CC知识共享协议各协议内容解释 深度解析多级缓存架构:从设计到落地,彻底解决数据一致性难题 从零开始入门 K8s | Kubernetes 网络概念及策略控制 AI原生数据库新标杆:seekdb深度解析,轻量架构与混合搜索的双重革命 结合Apollo配置中心实现日志级别动态配置 使用RocketMQ时,服务启动过程中,Consumer在服务未启动时消费消息问题处理
    最近评论
    李锋镝 发布于 3 小时前(08月18日) 给主题加了个功能,可以在RSS中排除指定分类,哈哈哈
    李锋镝 发布于 4 小时前(08月18日) :8: 喜欢就好
    不凡 发布于 14 小时前(08月17日) 哇,配色真好看,观感很舒服! :52:
    李锋镝 发布于 17 小时前(08月17日) 哈哈哈,那个插件我之前也用过,那个内容都是固定的,我这个每天不同的时间拉取数据也不一样,等我研究研究...
    Hary 发布于 17 小时前(08月17日) emmm大哥,这样每天输出到博客,然后别人订阅rss全是这个,有点不太好,之前很流行一段时间每天60...
    标签聚合
    Redis MQ JVM MySQL 多线程 ElasticSearch 日常 Claude IDEA AI 分布式 SpringBoot SQL AI编程 K8s 数据库 WordPress JAVA 架构 Spring
    友情链接
    • 瓦匠个人小站
    • 若梦博客
    • 九仞之行
    • 老张博客
    • 知向前端
    • 哥斯拉
    • Honesty
    • 韩情脉脉
    • 皮皮社
    • Serendipity
    • Mr.Sun的博客
    • 韩小韩博客
    • 志文工作室
    • 懋和道人
    • 临窗旋墨
    • 搬砖日记
    • 林羽凡
    • 彬红茶日记
    • 蜗牛工作室
    • sssr7844的博客

    COPYRIGHT © 2026 lifengdi.com. ALL RIGHTS RESERVED.

    正在博友圈履约中

    域名年龄

    Theme Kratos+ By Dylan Li

    津ICP备2024022503号-3

    京公网安备11011502039375号