feat(affiliate): attribution modes, settlement hardening, trusted proxies, QR and poster

- 归因模式可配置(first_touch/last_touch)、停佣开关、懒结算与佣金基数降级复查
- aksrt_get_client_ip 接入 aksrt_trusted_proxies 信任代理名单,防 CDN 头伪造
- 收益页 Canvas 二维码 + 750x1000 推广海报(本地打包 qrcode.min.js,无 CDN)
- 契约测试固化归因/海报/信任代理链路
This commit is contained in:
2026-09-21 20:53:22 +08:00
parent 2618530afb
commit 20387fdb06
14 changed files with 715 additions and 6 deletions
+54
View File
@@ -570,6 +570,15 @@ add_action('admin_notices', static function () {
// ============================================================
function aksrt_get_client_ip(): string {
$remote = isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : '';
// 信任代理名单:过滤返回非空 CIDR/IP 列表时,仅当 REMOTE_ADDR 命中名单
// 才采纳 CDN / 反代头;否则客户端可伪造这些头绕过 IP 维度的风控。
$trusted = apply_filters('aksrt_trusted_proxies', []);
if (is_array($trusted) && $trusted !== [] && $remote !== '' && !aksrt_ip_in_list($remote, $trusted)) {
return $remote;
}
// Prefer CDN / reverse-proxy headers (Cloudflare first), then generic proxies.
$keys = [
'HTTP_CF_CONNECTING_IP',
@@ -601,6 +610,51 @@ function aksrt_get_client_ip(): string {
return isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
}
/**
* IP 是否命中精确 IP 或 CIDR 名单(IPv4 与 IPv6)。
*
* @param string $ip 待判定 IP。
* @param array $list 精确 IP / CIDR 段列表。
*/
function aksrt_ip_in_list(string $ip, array $list): bool {
foreach ($list as $entry) {
$entry = trim((string) $entry);
if ($entry === '') {
continue;
}
if (!str_contains($entry, '/')) {
if (strcasecmp($entry, $ip) === 0) {
return true;
}
continue;
}
[$subnet, $prefix] = explode('/', $entry, 2);
$prefix = (int) $prefix;
$ip_binary = @inet_pton($ip);
$subnet_binary = @inet_pton($subnet);
if ($ip_binary === false || $subnet_binary === false || strlen($ip_binary) !== strlen($subnet_binary)) {
continue;
}
$max_bits = strlen($ip_binary) * 8;
if ($prefix < 0 || $prefix > $max_bits) {
continue;
}
$full_bytes = intdiv($prefix, 8);
$remain_bits = $prefix % 8;
if ($full_bytes > 0 && substr($ip_binary, 0, $full_bytes) !== substr($subnet_binary, 0, $full_bytes)) {
continue;
}
if ($remain_bits > 0) {
$mask = chr((0xFF << (8 - $remain_bits)) & 0xFF);
if ((substr($ip_binary, $full_bytes, 1) & $mask) !== (substr($subnet_binary, $full_bytes, 1) & $mask)) {
continue;
}
}
return true;
}
return false;
}
// Native wp-comments-post + any path that omits comment_author_IP.
// Ensures comment_author_IP (and thus aksrt_comment_addr) is the real client IP behind CDN.
add_filter('pre_comment_user_ip', static function ($ip) {