Adjust 默认 p1–p6 → Facebook 宏 */ private static $defaultMacroParams = [ 'p1' => '{{campaign.name}}', 'p2' => '{{campaign.id}}', 'p3' => '{{adset.name}}', 'p4' => '{{adset.id}}', 'p5' => '{{ad.name}}', 'p6' => '{{ad.id}}', ]; /** * 目标 URL 是否需要归因宏合并 */ public static function needsAttributionMerge($targetUrl) { $targetUrl = trim((string) $targetUrl); if ($targetUrl === '') { return false; } if (stripos($targetUrl, '%7B%7B') !== false || stripos($targetUrl, '%7D%7D') !== false) { return true; } if (preg_match('/\{\{[a-zA-Z0-9_.]+\}\}/', $targetUrl)) { return true; } $parts = parse_url($targetUrl); $host = strtolower((string) ($parts['host'] ?? '')); return self::isAdjustHost($host); } /** * 从 DB_FP URL 解析需追加到广告链接的宏参数(键名 => Facebook 宏模板) * * @return array */ public static function parseAdjustMacroKeys($targetUrl) { $targetUrl = trim((string) $targetUrl); if ($targetUrl === '') { return []; } $parts = parse_url($targetUrl); $query = []; if (!empty($parts['query'])) { parse_str($parts['query'], $query); } $keys = []; foreach ($query as $key => $value) { if (!self::isMacroPlaceholder($value)) { continue; } $macro = self::extractMacroTemplate($value); if ($macro !== '') { $keys[(string) $key] = $macro; } } if (empty($keys) && self::isAdjustHost(strtolower((string) ($parts['host'] ?? '')))) { return self::$defaultMacroParams; } return $keys; } /** * 将落地页 query 合并进目标 URL,替换宏占位符 * * @param array $incomingQuery */ public static function merge($targetUrl, array $incomingQuery) { $targetUrl = trim((string) $targetUrl); if ($targetUrl === '') { return ''; } $parts = parse_url($targetUrl); if ($parts === false) { return $targetUrl; } $targetQuery = []; if (!empty($parts['query'])) { parse_str($parts['query'], $targetQuery); } $merged = []; foreach ($targetQuery as $key => $value) { $key = (string) $key; $value = (string) $value; if (self::isMacroPlaceholder($value) && self::hasIncomingValue($incomingQuery, $key)) { $merged[$key] = (string) $incomingQuery[$key]; continue; } if (!self::isMacroPlaceholder($value)) { $merged[$key] = $value; } } foreach (self::$passthroughKeys as $ptKey) { if (!self::hasIncomingValue($incomingQuery, $ptKey)) { continue; } $merged[$ptKey] = (string) $incomingQuery[$ptKey]; } return self::buildUrl($parts, $merged); } /** * @param array $query */ private static function buildUrl(array $parts, array $query) { $scheme = $parts['scheme'] ?? 'https'; $host = $parts['host'] ?? ''; $port = isset($parts['port']) ? ':' . $parts['port'] : ''; $path = $parts['path'] ?? ''; $frag = isset($parts['fragment']) ? '#' . $parts['fragment'] : ''; $pairs = []; foreach ($query as $key => $value) { if (in_array((string) $key, self::$stripKeys, true)) { continue; } $pairs[] = rawurlencode((string) $key) . '=' . rawurlencode((string) $value); } $queryStr = implode('&', $pairs); $url = $scheme . '://' . $host . $port . $path; if ($queryStr !== '') { $url .= '?' . $queryStr; } return $url . $frag; } /** * @param mixed $value */ public static function isMacroPlaceholder($value) { $value = trim((string) $value); if ($value === '') { return false; } if (preg_match('/^\{\{[a-zA-Z0-9_.]+\}\}$/', $value)) { return true; } $decoded = rawurldecode($value); if ($decoded !== $value && preg_match('/^\{\{[a-zA-Z0-9_.]+\}\}$/', $decoded)) { return true; } return (bool) preg_match('/^%7B%7B[a-zA-Z0-9_.]+%7D%7D$/i', $value); } /** * @param mixed $value */ public static function extractMacroTemplate($value) { $value = trim((string) $value); if ($value === '') { return ''; } if (preg_match('/^\{\{([a-zA-Z0-9_.]+)\}\}$/', $value, $m)) { return '{{' . $m[1] . '}}'; } $decoded = rawurldecode($value); if (preg_match('/^\{\{([a-zA-Z0-9_.]+)\}\}$/', $decoded, $m)) { return '{{' . $m[1] . '}}'; } if (preg_match('/^%7B%7B([a-zA-Z0-9_.]+)%7D%7D$/i', $value, $m)) { return '{{' . $m[1] . '}}'; } return ''; } /** * @param array $incomingQuery */ private static function hasIncomingValue(array $incomingQuery, $key) { if (!array_key_exists($key, $incomingQuery)) { return false; } $value = $incomingQuery[$key]; if ($value === null || $value === '') { return false; } if (is_array($value)) { return false; } if (self::isMacroPlaceholder($value)) { return false; } return true; } private static function isAdjustHost($host) { if ($host === '') { return false; } foreach (self::$adjustHostSuffixes as $suffix) { if ($host === $suffix || substr($host, -strlen('.' . $suffix)) === '.' . $suffix) { return true; } } return false; } }