前端分流页功能
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
/**
|
||||
* 中转页浏览器 Pixel 脚本渲染(Facebook / TikTok)
|
||||
*/
|
||||
class SplitPixelBrowserRenderer
|
||||
{
|
||||
/**
|
||||
* @param array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>} $config
|
||||
* @return array{
|
||||
* head_html: string,
|
||||
* body_html: string,
|
||||
* track_lines: array<int, string>,
|
||||
* track_jobs: array<int, array<string, mixed>>
|
||||
* }
|
||||
*/
|
||||
public static function render(array $config): array
|
||||
{
|
||||
$fbRows = SplitPixelConfigService::getEnabledSorted($config, SplitPixelConfigService::PLATFORM_FACEBOOK);
|
||||
$tkRows = SplitPixelConfigService::getEnabledSorted($config, SplitPixelConfigService::PLATFORM_TIKTOK);
|
||||
|
||||
$head = [];
|
||||
$initLines = [];
|
||||
$trackLines = [];
|
||||
$jobs = [];
|
||||
|
||||
if ($fbRows !== []) {
|
||||
$head[] = self::facebookLoaderScript();
|
||||
}
|
||||
if ($tkRows !== []) {
|
||||
$head[] = self::tiktokLoaderScript();
|
||||
}
|
||||
|
||||
foreach ($fbRows as $row) {
|
||||
$pixelId = self::escapeJsString((string) ($row['pixel_id'] ?? ''));
|
||||
$testCode = trim((string) ($row['test_code'] ?? ''));
|
||||
$event = self::escapeJsString((string) ($row['event'] ?? 'PageView'));
|
||||
if ($pixelId === '') {
|
||||
continue;
|
||||
}
|
||||
if ($testCode !== '') {
|
||||
$testEsc = self::escapeJsString($testCode);
|
||||
$initLines[] = "if(typeof fbq!=='undefined'){fbq('init','{$pixelId}',{},{test_event_code:'{$testEsc}'});}";
|
||||
} else {
|
||||
$initLines[] = "if(typeof fbq!=='undefined'){fbq('init','{$pixelId}');}";
|
||||
}
|
||||
$jobs[] = [
|
||||
'platform' => 'facebook',
|
||||
'event' => (string) ($row['event'] ?? 'PageView'),
|
||||
'pixel_id' => (string) ($row['pixel_id'] ?? ''),
|
||||
];
|
||||
$trackLines[] = "if(typeof fbq!=='undefined'){fbq('track','{$event}');}";
|
||||
}
|
||||
|
||||
foreach ($tkRows as $row) {
|
||||
$pixelId = self::escapeJsString((string) ($row['pixel_id'] ?? ''));
|
||||
$event = self::mapTikTokBrowserEvent((string) ($row['event'] ?? 'PageView'));
|
||||
$eventJs = self::escapeJsString($event);
|
||||
if ($pixelId === '') {
|
||||
continue;
|
||||
}
|
||||
$initLines[] = "if(typeof ttq!=='undefined'){ttq.load('{$pixelId}');ttq.page();}";
|
||||
$jobs[] = [
|
||||
'platform' => 'tiktok',
|
||||
'event' => $event,
|
||||
'pixel_id' => (string) ($row['pixel_id'] ?? ''),
|
||||
];
|
||||
$trackLines[] = "if(typeof ttq!=='undefined'){ttq.track('{$eventJs}');}";
|
||||
}
|
||||
|
||||
return [
|
||||
'head_html' => implode("\n", $head),
|
||||
'body_html' => self::wrapScriptBlock($initLines),
|
||||
'track_lines' => $trackLines,
|
||||
'track_jobs' => $jobs,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转 orchestrator:在 setTimeout 回调内触发像素事件后再 replace
|
||||
*
|
||||
* @param array<int, string> $trackLines
|
||||
*/
|
||||
public static function renderRedirectOrchestrator(string $redirectUrlJson, array $trackLines = [], int $maxWaitMs = 1500): string
|
||||
{
|
||||
$trackBlock = $trackLines !== [] ? implode("\n ", $trackLines) : '';
|
||||
$script = self::renderRedirectOrchestratorScript();
|
||||
|
||||
return str_replace(
|
||||
['{$redirectUrlJson}', '{$maxWaitMs}', '{$trackBlock}'],
|
||||
[$redirectUrlJson, (string) $maxWaitMs, $trackBlock],
|
||||
$script
|
||||
);
|
||||
}
|
||||
|
||||
private static function renderRedirectOrchestratorScript(): string
|
||||
{
|
||||
return <<<'JS'
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var url = {$redirectUrlJson};
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
setTimeout(function () {
|
||||
{$trackBlock}
|
||||
window.location.replace(url);
|
||||
}, {$maxWaitMs});
|
||||
})();
|
||||
</script>
|
||||
JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $lines
|
||||
*/
|
||||
private static function wrapScriptBlock(array $lines): string
|
||||
{
|
||||
if ($lines === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<script type="text/javascript">' . "\n"
|
||||
. implode("\n", $lines) . "\n"
|
||||
. '</script>';
|
||||
}
|
||||
|
||||
private static function facebookLoaderScript(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<script>
|
||||
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
|
||||
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
|
||||
</script>
|
||||
HTML;
|
||||
}
|
||||
|
||||
private static function tiktokLoaderScript(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<script>
|
||||
!function(w,d,t){w.TiktokAnalyticsObject=t;var ttq=w[t]=w[t]||[];ttq.methods=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie"],ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}};for(var i=0;i<ttq.methods.length;i++)ttq.setAndDefer(ttq,ttq.methods[i]);ttq.instance=function(t){for(var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e},ttq.load=function(e,n){var i="https://analytics.tiktok.com/i18n/pixel/events.js";ttq._i=ttq._i||{},ttq._i[e]=[],ttq._i[e]._u=i,ttq._t=ttq._t||{},ttq._t[e]=+new Date,ttq._o=ttq._o||{},ttq._o[e]=n||{};var o=document.createElement("script");o.type="text/javascript",o.async=!0,o.src=i+"?sdkid="+e+"&lib="+t;var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(o,a)}}(window,document,'ttq');
|
||||
</script>
|
||||
HTML;
|
||||
}
|
||||
|
||||
private static function mapTikTokBrowserEvent(string $event): string
|
||||
{
|
||||
$map = [
|
||||
'PageView' => 'Pageview',
|
||||
'Lead' => 'SubmitForm',
|
||||
'Contact' => 'Contact',
|
||||
'AddToCart' => 'AddToCart',
|
||||
'Purchase' => 'CompletePayment',
|
||||
'Subscribe' => 'Subscribe',
|
||||
];
|
||||
|
||||
return $map[$event] ?? 'Pageview';
|
||||
}
|
||||
|
||||
private static function escapeJsString(string $value): string
|
||||
{
|
||||
return str_replace(['\\', "'"], ['\\\\', "\\'"], $value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user