WP Armour Honeypot 与 Elementor Popup 兼容性问题排查
网站上启用了 WP Armour Honeypot 防垃圾插件,普通页面上的表单一切正常。但所有通过 Elementor Popup 提交的表单,不管填什么内容,提交后都返回 “Spamming or your Javascript is disabled !!” 错误。
排查过程
先确认插件本身没问题
WP Armour 在站上跑了一段时间,非 Popup 页面的各类表单从来没出过误判。插件核心功能在常规场景下是正常的,问题大概率出在跟 Popup 的配合上。
定位到 Popup
问题只出现在 Elementor Popup 里加载的表单。常见的 Popup 有两种加载方式:
- 页面内嵌 — Popup 内容直接在页面 HTML 里,用 CSS 隐藏,靠 JS 控制显示
- 动态加载 (AJAX) — Popup 内容通过 AJAX 请求获取,再渲染到 DOM
我们的网站用的是 AJAX 动态加载模式。这意味着 Popup 里的表单在页面加载时并不存在于 DOM 中。
分析 WP Armour 的注入机制
看 WP Armour 的 JavaScript 代码(wpa.js 和 wpa_vanilla.js),它的工作流程是这样的:
页面加载 → 监听用户交互事件 (mousemove, keydown, scroll 等)
→ 用户交互触发 → 运行 wpa_add_honeypot_field()
→ 2 秒后备超时 → 同样运行 wpa_add_honeypot_field()
→ wpa_injected = true (锁定,不再重复注入)
wpa_add_honeypot_field() 的工作方式是在 DOM 中查找所有匹配选择器的表单,然后追加蜜罐字段:
function wpa_add_honeypot_field() {
var allFormSelectors = [
'form.wpcf7-form, .wpcf7 form',
'form.elementor-form',
'form.frm-fluent-form',
// ... 更多选择器
];
// 一次性查询所有现有表单并注入
document.querySelectorAll(allFormSelectors.join(', ')).forEach(...)
}
关键问题就在这里:querySelectorAll 只查询当前 DOM 中已存在的表单。AJAX 加载 Popup 时,表单被添加到 DOM 里,但 wpa_add_honeypot_field() 已经运行完毕,而且 wpa_injected 已经锁定,不会再跑第二次。
确认蜜罐字段缺失
在浏览器开发者工具里对比 Elementor Popup 中的表单和普通页面的表单。
普通页面表单(正常):
<form class="elementor-form">
<!-- 正常表单字段 -->
<input type="text" name="name">
<input type="email" name="email">
<!-- 蜜罐字段 -->
<div id="altEmail_container" class="altEmail_container">
<label for="alt_s">Alternative:</label>
<input type="text" id="alt_s" name="alt_s">
</div>
<span class="wpa_hidden_field" style="display:none;height:0;width:0;">
<label>WPA <input type="text" name="wpa_field_xyz" value="12345"></label>
</span>
</form>
Popup 中的表单(异常):
<form class="elementor-form">
<!-- 只有正常表单字段,没有蜜罐 -->
<input type="text" name="name">
<input type="email" name="email">
<!-- ❌ wpa_hidden_field 和 altEmail_container 完全不存在 -->
</form>
验证 PHP 端的检测逻辑
再看 PHP 端怎么判断是不是垃圾信息:
function wpa_check_is_spam($form_data) {
if (
(isset($form_data[$GLOBALS['wpa_field_name']])) && // ① 蜜罐隐藏字段必须在提交数据中
(isset($form_data['alt_s'])) && // ② alt_s 字段必须在提交数据中
(empty($form_data['alt_s'])) // ③ alt_s 必须为空
) {
return false; // ✅ 非垃圾
} else {
return true; // ❌ 判定为垃圾
}
}
三条检查全通过才算正常提交。Popup 表单没注入蜜罐字段,提交数据里自然没有 wpa_field_name 和 alt_s,wpa_check_is_spam() 返回 true,”Spamming or your Javascript is disabled !!” 就抛出来了。
根因
WP Armour Honeypot 的 JS 注入逻辑设计成”一次性”的:
- 只在页面加载时(或用户首次交互时)运行一次
- 有
wpa_injected开关防止重复注入 - 没有监听 DOM 变化的机制
Elementor Popup 的 AJAX 动态加载模式,恰好撞上了这个设计盲区。不是 WP Armour 有问题,也不是 Elementor 有问题,而是两者都没意识到对方的存在。
修复方法
修复思路很简单:监听 Elementor Popup 打开事件,在 Popup 打开时对弹窗内的表单重新注入蜜罐字段。
Elementor 提供了 elementor/popup/show 事件,通过 elementorFrontend.hooks.addAction 可以注册回调:
elementorFrontend.hooks.addAction('elementor/popup/show', function(popupId, popupData, popupElement) {
// 获取弹窗容器
var popupEl = popupElement || document.querySelector('.elementor-' + popupId);
if (!popupEl) return;
// 在弹窗内查找所有表单
var forms = popupEl.querySelectorAll([
'form.elementor-form',
'form.wpcf7-form',
'form.frm-fluent-form',
// ...
].join(', '));
forms.forEach(function(form) {
// 如果已经存在蜜罐字段则跳过,防止重复注入
if (form.querySelector('.wpa_hidden_field')) return;
form.insertAdjacentHTML('beforeend', wpa_hidden_field);
});
});
完整的修复方案包括三个部分:
- Elementor 事件钩子 — 监听
elementor/popup/show,在弹窗打开时注入 - 重复注入防护 — 检查
.wpa_hidden_field是否已存在,支持弹窗多次打开而不重复注入 - MutationObserver 后备方案 — 对于非 Elementor 的动态加载场景(如 Beaver Builder、自定义 AJAX 表单),也能捕获
完整的可部署代码:
(function() {
function init() {
if (typeof wpa_add_honeypot_field !== 'function' || typeof wpa_hidden_field === 'undefined') {
setTimeout(init, 500);
return;
}
if (typeof elementorFrontend !== 'undefined' && elementorFrontend.hooks) {
elementorFrontend.hooks.addAction('elementor/popup/show', function(popupId, popupData, popupElement) {
var popupEl = popupElement || document.querySelector('.elementor-' + popupId);
if (!popupEl) return;
var formSelectors = [
'form.elementor-form',
'form.wpcf7-form', '.wpcf7 form',
'form.wpforms-form',
'.gform_wrapper form',
'.frm_forms form',
'form.frm-fluent-form',
'.ff_conv_app',
'form.wpa_form', '.wpa_form form',
'form#commentform', 'form.comment-form',
'form#loginform',
'form.register',
'form.cart'
];
var forms = popupEl.querySelectorAll(formSelectors.join(', '));
forms.forEach(function(form) {
if (form.querySelector('.wpa_hidden_field')) return;
form.insertAdjacentHTML('beforeend', wpa_hidden_field);
});
var initiators = popupEl.querySelectorAll('input.wpa_initiator');
initiators.forEach(function(initiator) {
var form = initiator.closest('form');
if (form && !form.querySelector('.wpa_hidden_field')) {
initiator.insertAdjacentHTML('afterend', wpa_hidden_field);
}
});
});
}
if (!window._wpaObserver) {
window._wpaObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType !== 1) return;
var forms = node.matches && node.matches('form') ? [node] : node.querySelectorAll ? node.querySelectorAll('form') : [];
forms.forEach(function(form) {
if (!form.querySelector('.wpa_hidden_field')) {
form.insertAdjacentHTML('beforeend', wpa_hidden_field);
}
});
});
});
});
window._wpaObserver.observe(document.body, { childList: true, subtree: true });
}
}
init();
})();
安装方式:把代码加到主题的 functions.php 里,或者用自定义 JS 插件(如 WPCode):
add_action('wp_footer', function() { ?>
<script>
/* 粘贴上面的 JS 代码 */
</script>
<?php }, 999);
总结
这个问题的本质是一个典型的时序问题:WP Armour 假设所有表单都在页面加载时存在,而 Elementor Popup 打破了这一假设。
从这次的排查中学到几点:
- 安全插件的 JS 注入逻辑需要考虑动态内容。静态 DOM 查询在单页应用(SPA)和动态加载场景下会有盲区。MutationObserver 是处理这类问题的基础设施级方案。
- Elementor Popup 的事件系统很完善。
elementor/popup/show事件是处理 Popup 相关逻辑的入口点,比监听 DOM 变化更精确、更高性能。 - 给插件提建议时,带上代码。与其让作者自己理解问题再写代码,不如直接给他一个可合并的修复方案。WP Armour 的开发成本不高——让
wpa_add_honeypot_field()支持幂等调用(已注入的表单自动跳过),再加上 Elementor 事件监听,就能解决这个问题。