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:
1 parent
2618530afb
commit
20387fdb06
14 files changed
+715
-6
No files matched your search
@@ -145,6 +145,17 @@ $status_labels = [
|
||||
<button type="button" class="btn-primary btn-sm" data-affiliate-copy><?php esc_html_e('复制', 'aksrt'); ?></button>
|
||||
<small data-affiliate-feedback role="status" aria-live="polite"></small>
|
||||
</div>
|
||||
<div class="uc-revenue-summary-row uc-revenue-summary-row--qr" data-affiliate-qr-row>
|
||||
<span><?php esc_html_e('推广二维码', 'aksrt'); ?></span>
|
||||
<div class="uc-revenue-qr">
|
||||
<canvas data-affiliate-qr width="148" height="148" aria-label="<?php esc_attr_e('推广链接二维码', 'aksrt'); ?>"></canvas>
|
||||
<div class="uc-revenue-qr__actions">
|
||||
<button type="button" class="btn-sm" data-affiliate-qr-download><?php esc_html_e('下载二维码', 'aksrt'); ?></button>
|
||||
<button type="button" class="btn-sm" data-affiliate-poster hidden><?php esc_html_e('生成推广海报', 'aksrt'); ?></button>
|
||||
</div>
|
||||
<small data-affiliate-qr-feedback role="status" aria-live="polite"></small>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($affiliate_on) : ?><div class="uc-revenue-summary-row"><span><?php esc_html_e('佣金比例', 'aksrt'); ?></span><strong data-affiliate-ratio><?php echo esc_html((string) $commission_ratio); ?>%</strong></div><?php endif; ?>
|
||||
<div class="uc-revenue-summary-row"><span><?php esc_html_e('推广人数', 'aksrt'); ?></span><div class="uc-revenue-count"><strong data-affiliate-promoters>—</strong><em><?php esc_html_e('人', 'aksrt'); ?></em></div></div>
|
||||
<?php if ($affiliate_on) : ?>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user