Unlimited Elements 2.0.9 插件 Pro 标签异常排查记录

Unlimited Elements 2.0.9 Pro 标签异常排查全记录

说出来你可能不信,为了搞清楚为什么升级个插件之后小部件的标签从”Web”变成了”Free”,我花了一整天的时间,绕了好大一个圈子,最后发现原因简单到让人哭笑不得。

这篇文章把我的整个心路历程原原本本记下来,既是给自己留个档,也给遇到同样问题的朋友当个参考。准备好瓜子,故事开始了。

发现症状

事情是这样的。我站上用的 Unlimited Elements for Elementor(以下简称 UE),买的是 Pro 版本,Freemius 授权一直正常。之前版本(2.0.6)的时候,所有 Pro 小部件在 Elementor 编辑器里显示的是漂亮的 “Web” 标签,看起来挺高级。

某天我手贱点了个升级,从 2.0.6 升到 2.0.9。再打开 Elementor 编辑器一看——好家伙,所有 Pro 小部件的标签全变成了 “Free” 或 “Pro”。啥情况?授权没到期啊?

去 Freemius 后台看了一眼,授权状态正常,是 Active 的。那问题出在插件代码里了。

第一步:翻 Changelog,发现安全更新

首先想到的是看 changelog。2.0.6 → 2.0.7 → 2.0.8 → 2.0.9,这几个版本都更新了啥?

结果发现 2.0.7 有个很关键的信息——这是一个安全更新。去 Patchstack 查了一下,好家伙,CVE-2026-27041,CVSS 9.9 分,任意文件上传漏洞,严重影响。受影响版本 ≤ 2.0.6,在 2.0.7 中修复。

当时我就想:“哦,一定是这个安全补丁改了什么东西,把 Pro 判断逻辑搞坏了。”

第二步:下源码对比,发现判断错了

说干就干。我把 2.0.6 和 2.0.7 两个版本的完整源码拉下来,diff -rq 扫了一遍。结果 diff 出来 29 个文件有差异。

我一个个翻这些文件:

  • provider_helper.class.php — 加了一些权限检查
  • assets_work.class.php — 加强了文件上传验证
  • functions_wordpress.class.php — 修了 SQL 注入
  • ……

这些都是安全相关的改动,合理。但当我翻到跟标签显示相关的文件时——browser.class.phpweb_api.class.phpglobals.class.php——逐个比对,发现这几个文件在 2.0.6 和 2.0.7 之间完全一样,一个字节都没变

也就是说,我最初的推测是错的。安全补丁并没有碰标签显示逻辑。问题不在这里。

第三步:开始瞎猜——是不是版本号有关?

排除了安全补丁,我开始天马行空地猜了。

猜测一:API 服务器根据版本号返回不同的数据?

UE 有一个远程 API,插件会跟服务器通信获取小部件的元数据。我怀疑是不是服务器端检测到版本号变了,返回了不同的标签数据。毕竟 2.0.9 是新版本,说不定远程 API 做了兼容性检查?

验证这个猜测很简单——改版本号。

UE 的版本号常量定义在 includes.php 里:

// unlimited-elements-for-elementor-premium/includes.php
define("UNLIMITED_ELEMENTS_VERSION", "2.0.9");

我在 wp-config.php 里加了这么一行来覆盖它:

// 加到 wp-config.php - 尝试覆盖版本号欺骗 API 服务器
define("UNLIMITED_ELEMENTS_VERSION", "2.0.6");

结果呢?没卵用。标签该是 “Free” 还是 “Free”。

猜测二:是不是有隐藏的测试开关?

翻代码的时候发现 unitecreator_globals.class.php 里有一段有意思的逻辑:

if (defined("UC_TEST_FREE_VERSION"))
    self::$isProVersion = false;

有个 UC_TEST_FREE_VERSION 常量,定义了就强制把 Pro 版降成 Free 版。我心想,是不是新版本里某处悄悄地 define 了这个常量?于是我在 wp-config.php 里加:

// 加到 wp-config.php - 确保这个常量没被定义
// 也试过改成 true 看有啥效果
define("UC_TEST_FREE_VERSION", false);

结果还是一样——和这个常量毫无关系。

第四步:上检测代码,逐个版本对比

猜来猜去解决不了问题,还是得上检测手段。我先搞清楚一件事:标签显示由哪个函数决定。

顺藤摸瓜:

  1. Elementor 面板的标签 → browser.class.php:isWebAddonFree() 第 529 行
  2. ↓ 调用
  3. provider_web_api.class.php:isProductActive()
  4. ↓ 调用
  5. HelperProviderUC::isActivatedByFreemius()
  6. ↓ 调用
  7. Freemius SDK 的 $uefe_fs->is_paying()

于是我在主题的 functions.php 里加了这段检测代码,把每一步的返回值都打出来:

// 加到主题 functions.php - 调试检测代码
add_action('init', function() {
    if (!class_exists('UniteCreatorWebAPI') || !class_exists('HelperProviderUC')) {
        return;
    }

    global $uefe_fs;

    $webApi = new UniteCreatorWebAPI();
    $webApi->setProduct('unlimitedelements');

    $debug = [
        'isProductActive'      => $webApi->isProductActive() ? 'true' : 'false',
        'isFreemiusActive'     => HelperProviderUC::isActivatedByFreemius() ? 'true' : 'false',
        'UE Version'           => UNLIMITED_ELEMENTS_VERSION,
        'Freemius version'     => $uefe_fs->get_plugin_version(),
        'is_paying'            => $uefe_fs->is_paying() ? 'true' : 'false',
        'GlobalsUC::$isProVersion' => (GlobalsUC::$isProVersion ?? false) ? 'true' : 'false',
        'pro_path_exists'      => file_exists(GlobalsUC::$pathPro ?? '') ? 'true' : 'false',
    ];

    error_log('[UE_DEBUG] ' . print_r($debug, true));
});

然后在 wp-config.php 里确保打开了调试日志:

// 加到 wp-config.php - 开启调试日志
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

保存后刷新页面,去看 /wp-content/debug.log,结果出来了:

[UE_DEBUG] Array
(
    [isProductActive] => false
    [isFreemiusActive] => true
    [UE Version] => 2.0.9
    [Freemius version] => 2.6.2
    [is_paying] => true
    [GlobalsUC::$isProVersion] => false
    [pro_path_exists] => false
)

有意思了。is_paying = trueisProductActive = false

再看 provider_web_api.class.php 里的 isProductActive() 源码就明白了:

public function isProductActive() {
    if(GlobalsUC::$isProVersion == false)
        return(false);  // <-- 在这里就返回了!

    if($this->isFreemiusActive() == false)
        return(false);

    return(true);
}

函数第一行就先检查 GlobalsUC::$isProVersion,如果为 false 就直接 return false,根本走不到 Freemius 检查那里。怪不得 is_paying = trueisProductActive = false——Freemius 根本没被问到!

第五步:二分法锁定问题版本

既然知道了检测路径,接下来用二分法找到底是哪个版本引入的问题。

我把这个检测代码保留了,然后反复切换插件版本:2.0.6 → 2.0.7 → 2.0.8 → 2.0.9。再贴个 debug.log 看看:

// === 2.0.6 ===
[UE_DEBUG] isProductActive: true, isFreemiusActive: true, is_paying: true, $isProVersion: true

// === 2.0.7 ===
[UE_DEBUG] isProductActive: true, isFreemiusActive: true, is_paying: true, $isProVersion: true

// === 2.0.8 ===
[UE_DEBUG] isProductActive: true, isFreemiusActive: true, is_paying: true, $isProVersion: true

// === 2.0.9 ===
[UE_DEBUG] isProductActive: false, isFreemiusActive: true, is_paying: true, $isProVersion: false

破案了!2.0.8 一切正常,2.0.9 $isProVersion 变成了 false。这个变量是在 unitecreator_globals.class.php 里设置的,让我去看看:

// unitecreator_globals.class.php 第 244 行
self::$pathPro = self::$pathPlugin . "pro/";

if(file_exists(self::$pathPro))
    self::$isProVersion = true;

if(defined("UC_TEST_FREE_VERSION"))
    self::$isProVersion = false;

逻辑再简单不过了——检查插件目录下有没有 pro/ 目录。有就是 Pro 版,没有就是 Free 版。

检测代码已经告诉我 pro_path_exists = false。也就是说2.0.9 安装目录下根本没有 pro/ 这个文件夹

第六步:对比压缩包,找到真凶

为了确认,我把 2.0.8 和 2.0.9 的 zip 包都下下来,对比目录结构:

$ diff -rq unlimited-elements-2.0.8/ unlimited-elements-2.0.9/
只在 2.0.8 中找到: pro/childparams_pro.class.php
只在 2.0.8 中找到: pro/globals_pro.class.php
只在 2.0.8 中找到: pro/includes_pro.php
只在 2.0.8 中找到: pro/provider_settings_multisource_pro.class.php
只在 2.0.8 中找到: pro/template_engine_pro.class.php
...(还有其他 15 个文件差异)

2.0.9 的压缩包里整个 pro/ 目录凭空消失了。这 5 个文件一个都没打包进去。

这能怪谁呢?一定是发布 2.0.9 的时候打包脚本出了问题,把 pro/ 目录漏了。毕竟 2.0.7、2.0.8 都好好的,唯独 2.0.9 少了这个目录。

完整的根因链路

捋一遍整个连锁反应:

  1. 2.0.9 zip 打包时 pro/ 目录被遗漏
  2. file_exists(self::$pathPro)false(目录不存在)
  3. GlobalsUC::$isProVersion 保持默认值 false
  4. isProductActive() 第 1 行检查 if($isProVersion == false) return(false)直接返回 false
  5. Freemius 的 is_paying() 虽然返回 true,但根本没被执行到
  6. isWebAddonFree() 收到 false → 所有小部件显示 “Free” 而非 “Web”

讽刺不?一个上传安全漏洞修得严严实实的,最后栽在一个打包漏文件的问题上。

修复方法

修复简单得不能再简单了——从 2.0.8 目录把 pro/ 复制过来:

cd /path/to/wp-content/plugins/unlimited-elements-for-elementor-premium/
cp -r /path/to/backup/unlimited-elements-2.0.8/pro/ ./pro/

刷新,一切恢复正常。所有小部件又老老实实显示 “Web” 标签了。

后记

这次排查最大的感触就是——你以为的问题往往不是真正的问题

  • 一开始以为是 Freemius 授权问题 → 不是
  • 后来以为是安全补丁改了代码 → 也不是
  • 再猜版本号检测 → 还是不对
  • 最后发现是打包漏了个目录

如果一开始就直接对比各版本的 zip 包文件列表,而不是在代码逻辑里绕来绕去,能省下一大半时间。但话说回来,如果不是一层层追踪代码逻辑、加调试输出、逐个版本对比验证,也不会对 UE 的授权判断机制理解得这么透彻。

另外,file_exists() 来判断 Pro 版本这个设计本身也挺脆弱的——文件系统状态和授权状态一旦不一致,就会出现这种诡异的症状。当然,如果打包没出问题,这本来也不会有事。

建议遇到同样问题的朋友:如果你的 Unlimited Elements 升到 2.0.9 后发现 Pro 小部件不认了,先看看插件目录下有没有 pro/ 这个文件夹。八成就是少了它。


排查日期:2026-05-14 | 首发于 tudoudaily.com
发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

或许还会想看: