diff --git a/app/app/Services/Tracking/ChannelResolver.php b/app/app/Services/Tracking/ChannelResolver.php new file mode 100644 index 00000000..c0aab8e7 --- /dev/null +++ b/app/app/Services/Tracking/ChannelResolver.php @@ -0,0 +1,57 @@ + $utm */ + public function channel(array $utm, ?string $referrer): string + { + $source = trim((string) ($utm['utm_source'] ?? '')); + if ($source !== '') { + return mb_substr(preg_replace('/[^a-z0-9_-]/i', '', $source) ?: 'direct', 0, 32); + } + + $host = strtolower((string) parse_url((string) $referrer, PHP_URL_HOST)); + if ($host === '') { + return 'direct'; + } + if (in_array($host, self::OWN_HOSTS, true)) { + return 'direct'; + } + if (str_contains($host, 'hh.ru')) { + return 'hh'; + } + foreach (self::SEARCH_HOSTS as $needle) { + if (str_contains($host, $needle)) { + return 'search'; + } + } + + return 'referral'; + } + + public function device(?string $userAgent): string + { + $ua = (string) $userAgent; + if (preg_match('/iPad|Tablet/i', $ua)) { + return 'tablet'; + } + if (preg_match('/Mobile|Android|iPhone/i', $ua)) { + return 'phone'; + } + + return 'desktop'; + } +} diff --git a/app/tests/Unit/ChannelResolverTest.php b/app/tests/Unit/ChannelResolverTest.php new file mode 100644 index 00000000..6e930dfb --- /dev/null +++ b/app/tests/Unit/ChannelResolverTest.php @@ -0,0 +1,39 @@ + new ChannelResolver(); + +test('метка utm_source побеждает всё остальное', function () use ($r) { + expect($r()->channel(['utm_source' => 'sms'], 'https://yandex.ru/'))->toBe('sms'); +}); + +test('поисковик по referrer → search', function () use ($r) { + expect($r()->channel([], 'https://yandex.ru/search/?text=лидерра'))->toBe('search'); + expect($r()->channel([], 'https://www.google.com/'))->toBe('search'); +}); + +test('hh.ru по referrer → hh', function () use ($r) { + expect($r()->channel([], 'https://krasnoyarsk.hh.ru/vacancy/123'))->toBe('hh'); +}); + +test('прочий сайт → referral', function () use ($r) { + expect($r()->channel([], 'https://vc.ru/post/1'))->toBe('referral'); +}); + +test('пусто → direct', function () use ($r) { + expect($r()->channel([], ''))->toBe('direct'); + expect($r()->channel([], null))->toBe('direct'); +}); + +test('свой домен в referrer не считается источником → direct', function () use ($r) { + expect($r()->channel([], 'https://liderra.ru/'))->toBe('direct'); +}); + +test('устройство по user-agent', function () use ($r) { + expect($r()->device('Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X)'))->toBe('phone'); + expect($r()->device('Mozilla/5.0 (Linux; Android 10; K)'))->toBe('phone'); + expect($r()->device('Mozilla/5.0 (iPad; CPU OS 17_0)'))->toBe('tablet'); + expect($r()->device('Mozilla/5.0 (Windows NT 10.0; Win64; x64)'))->toBe('desktop'); +});