Guest User

aioverview

a guest
Aug 10th, 2026
143
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 27.47 KB | None | 0 0
  1. <?php
  2. // Безопасный вывод строк
  3. function safeHtml(?string $string): string {
  4.     return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8');
  5. }
  6.  
  7. // Очистка Google-ссылок (goto?url=...)
  8. function cleanGoogleUrl(?string $url): string {
  9.     if (!$url) return '';
  10.     if (strpos($url, 'google.com/goto?url=') !== false) {
  11.         $parts = parse_url($url);
  12.         if (isset($parts['query'])) {
  13.             parse_str($parts['query'], $query);
  14.             if (!empty($query['url'])) {
  15.                 return urldecode($query['url']);
  16.             }
  17.         }
  18.     }
  19.     return $url;
  20. }
  21.  
  22. // Выбор языка интерфейса (по умолчанию русский)
  23. $uiLang = $_POST['ui_lang'] ?? 'ru';
  24. if (!in_array($uiLang, ['ru', 'uk'])) {
  25.     $uiLang = 'ru';
  26. }
  27.  
  28. // Словари локализации
  29. $translations = [
  30.     'ru' => [
  31.         'title' => 'SERP & AI Insights',
  32.         'subtitle' => 'Анализ поисковой выдачи Google и интеллектуальных ответов AI Overview',
  33.         'query_placeholder' => 'Введите поисковый запрос...',
  34.         'search_btn' => 'Анализировать',
  35.         'country_label' => 'Целевая страна (gl)',
  36.         'lang_label' => 'Язык поиска (hl)',
  37.         'ui_lang_label' => 'Язык интерфейса',
  38.         'no_ai_msg' => '⚠️ <strong>AI Overview не был сгенерирован Google для этого запроса.</strong><br>Ниже представлена стандартная органическая выдача.',
  39.         'serp_title' => 'Органическая поисковая выдача (SERP)',
  40.         'no_results' => 'Результаты поиска не найдены.',
  41.         'view_raw_json' => 'Показать необработанные данные JSON ответа',
  42.         'sources_title' => 'Источники AI Overview:',
  43.         'no_title' => 'Без названия',
  44.         'countries' => [
  45.             'ua' => 'Украина (UA)',
  46.             'us' => 'США (US)',
  47.             'gb' => 'Великобритания (GB)',
  48.             'de' => 'Германия (DE)',
  49.             'fr' => 'Франция (FR)',
  50.             'it' => 'Италия (IT)',
  51.             'es' => 'Испания (ES)',
  52.             'pl' => 'Польша (PL)',
  53.             'ca' => 'Канада (CA)',
  54.             'au' => 'Австралия (AU)',
  55.             'nl' => 'Нидерланды (NL)',
  56.             'tr' => 'Турция (TR)',
  57.             'br' => 'Бразилия (BR)',
  58.             'in' => 'Индия (IN)',
  59.             'jp' => 'Япония (JP)',
  60.             'sg' => 'Сингапур (SG)',
  61.             'ch' => 'Швейцария (CH)',
  62.             'at' => 'Австрия (AT)',
  63.             'be' => 'Бельгия (BE)',
  64.             'il' => 'Израиль (IL)',
  65.             'se' => 'Швеция (SE)',
  66.             'no' => 'Норвегия (NO)',
  67.             'fi' => 'Финляндия (FI)',
  68.             'cz' => 'Чехия (CZ)',
  69.             'ro' => 'Румыния (RO)',
  70.             'kz' => 'Казахстан (KZ)',
  71.         ],
  72.         'languages' => [
  73.             'ru' => 'Русский (ru)',
  74.             'uk' => 'Украинский (uk)',
  75.             'en' => 'Английский (en)',
  76.             'de' => 'Немецкий (de)',
  77.             'fr' => 'Французский (fr)',
  78.             'es' => 'Испанский (es)',
  79.             'it' => 'Итальянский (it)',
  80.             'pl' => 'Польский (pl)',
  81.             'tr' => 'Турецкий (tr)',
  82.             'pt' => 'Португальский (pt)',
  83.             'ja' => 'Японский (ja)',
  84.             'zh-CN' => 'Китайский (zh-CN)',
  85.             'hi' => 'Хинди (hi)',
  86.             'nl' => 'Нидерландский (nl)',
  87.             'ar' => 'Арабский (ar)',
  88.             'sv' => 'Шведский (sv)',
  89.             'cs' => 'Чешский (cs)',
  90.             'ro' => 'Румынский (ro)',
  91.             'kk' => 'Казахский (kk)',
  92.         ]
  93.     ],
  94.     'uk' => [
  95.         'title' => 'SERP & AI Insights',
  96.         'subtitle' => 'Аналіз пошукової видачі Google та інтелектуальних відповідей AI Overview',
  97.         'query_placeholder' => 'Введіть пошуковий запит...',
  98.         'search_btn' => 'Аналізувати',
  99.         'country_label' => 'Цільова країна (gl)',
  100.         'lang_label' => 'Мова пошуку (hl)',
  101.         'ui_lang_label' => 'Мова інтерфейсу',
  102.         'no_ai_msg' => '⚠️ <strong>AI Overview не був згенерований Google для цього запиту.</strong><br>Нижче виведена стандартна органічна видача.',
  103.         'serp_title' => 'Органічна пошукова видача (SERP)',
  104.         'no_results' => 'Результати пошуку не знайдені.',
  105.         'view_raw_json' => 'Показати необроблені дані JSON відповіді',
  106.         'sources_title' => 'Джерела AI Overview:',
  107.         'no_title' => 'Без назви',
  108.         'countries' => [
  109.             'ua' => 'Україна (UA)',
  110.             'us' => 'США (US)',
  111.             'gb' => 'Великобританія (GB)',
  112.             'de' => 'Німеччина (DE)',
  113.             'fr' => 'Франція (FR)',
  114.             'it' => 'Італія (IT)',
  115.             'es' => 'Іспанія (ES)',
  116.             'pl' => 'Польща (PL)',
  117.             'ca' => 'Канада (CA)',
  118.             'au' => 'Австралія (AU)',
  119.             'nl' => 'Нідерланди (NL)',
  120.             'tr' => 'Туреччина (TR)',
  121.             'br' => 'Бразилія (BR)',
  122.             'in' => 'Індія (IN)',
  123.             'jp' => 'Японія (JP)',
  124.             'sg' => 'Сінгапур (SG)',
  125.             'ch' => 'Швейцарія (CH)',
  126.             'at' => 'Австрія (AT)',
  127.             'be' => 'Бельгія (BE)',
  128.             'il' => 'Ізраїль (IL)',
  129.             'se' => 'Швеція (SE)',
  130.             'no' => 'Норвегія (NO)',
  131.             'fi' => 'Фінляндія (FI)',
  132.             'cz' => 'Чехія (CZ)',
  133.             'ro' => 'Румунія (RO)',
  134.             'kz' => 'Казахстан (KZ)',
  135.         ],
  136.         'languages' => [
  137.             'ru' => 'Російська (ru)',
  138.             'uk' => 'Українська (uk)',
  139.             'en' => 'Англійська (en)',
  140.             'de' => 'Німецька (de)',
  141.             'fr' => 'Французька (fr)',
  142.             'es' => 'Іспанська (es)',
  143.             'it' => 'Італійська (it)',
  144.             'pl' => 'Польська (pl)',
  145.             'tr' => 'Турецька (tr)',
  146.             'pt' => 'Португальська (pt)',
  147.             'ja' => 'Японська (ja)',
  148.             'zh-CN' => 'Китайська (zh-CN)',
  149.             'hi' => 'Хінді (hi)',
  150.             'nl' => 'Нідерландська (nl)',
  151.             'ar' => 'Арабська (ar)',
  152.             'sv' => 'Шведська (sv)',
  153.             'cs' => 'Чеська (cs)',
  154.             'ro' => 'Румунська (ro)',
  155.             'kk' => 'Казахська (kk)',
  156.         ]
  157.     ]
  158. ];
  159.  
  160. $t = $translations[$uiLang];
  161.  
  162. $aiOverview = null;
  163. $organicResults = [];
  164. $searchedQuery = '';
  165. $selectedGl = 'ua';
  166. $selectedHl = 'uk';
  167. $rawJson = null;
  168.  
  169. if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  170.     $searchedQuery = isset($_POST['query']) ? trim($_POST['query']) : '';
  171.     $selectedGl = trim($_POST['gl'] ?? 'ua');
  172.     $selectedHl = trim($_POST['hl'] ?? 'uk');
  173.  
  174.     if (!empty($searchedQuery)) {
  175.         set_time_limit(90);
  176.  
  177.         $apiKey = '';
  178.         $encodedQuery = urlencode($searchedQuery);
  179.         $targetUrl = "https://www.google.com/search?q={$encodedQuery}&gl={$selectedGl}&hl={$selectedHl}&brd_ai_overview=2";
  180.  
  181.         $data = [
  182.             'zone' => 'serp_api1',
  183.             'url' => $targetUrl,
  184.             'format' => 'json',
  185.             'data_format' => 'parsed'
  186.         ];
  187.  
  188.         $ch = curl_init('https://api.brightdata.com/request');
  189.  
  190.         curl_setopt_array($ch, [
  191.             CURLOPT_POST => true,
  192.             CURLOPT_POSTFIELDS => json_encode($data),
  193.             CURLOPT_RETURNTRANSFER => true,
  194.             CURLOPT_TIMEOUT => 60,
  195.             CURLOPT_SSL_VERIFYPEER => false,
  196.             CURLOPT_HTTPHEADER => [
  197.                 'Authorization: Bearer ' . $apiKey,
  198.                 'Content-Type: application/json'
  199.             ]
  200.         ]);
  201.  
  202.         $response = curl_exec($ch);
  203.         curl_close($ch);
  204.  
  205.         if ($response) {
  206.             $decoded = json_decode($response, true);
  207.            
  208.             $body = $decoded['body'] ?? [];
  209.             if (is_string($body)) {
  210.                 $body = json_decode($body, true);
  211.             }
  212.  
  213.             if (isset($body['ai_overview'])) {
  214.                 $aiOverview = $body['ai_overview'];
  215.             }
  216.  
  217.             if (isset($body['organic']) && is_array($body['organic'])) {
  218.                 $organicResults = $body['organic'];
  219.             }
  220.  
  221.             $rawJson = json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  222.         }
  223.     }
  224. }
  225. ?>
  226.  
  227. <!DOCTYPE html>
  228. <html lang="<?= safeHtml($uiLang) ?>">
  229. <head>
  230.     <meta charset="UTF-8">
  231.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  232.     <title><?= safeHtml($t['title']) ?></title>
  233.     <style>
  234.         :root {
  235.             --primary: #1a73e8;
  236.             --primary-hover: #1557b0;
  237.             --bg-main: #f8fafc;
  238.             --bg-card: #ffffff;
  239.             --text-dark: #1e293b;
  240.             --text-muted: #64748b;
  241.             --border: #e2e8f0;
  242.             --ai-accent: #7c3aed;
  243.             --ai-bg: #f5f3ff;
  244.             --ai-border: #ddd6fe;
  245.             --radius: 12px;
  246.             --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  247.         }
  248.  
  249.         body {
  250.             font-family: var(--font);
  251.             margin: 0;
  252.             padding: 0;
  253.             color: var(--text-dark);
  254.             background: var(--bg-main);
  255.             line-height: 1.5;
  256.         }
  257.  
  258.         /* Header Navigation */
  259.         .header {
  260.             background: var(--bg-card);
  261.             border-bottom: 1px solid var(--border);
  262.             padding: 15px 30px;
  263.             position: sticky;
  264.             top: 0;
  265.             z-index: 100;
  266.         }
  267.         .header-container {
  268.             max-width: 1000px;
  269.             margin: 0 auto;
  270.             display: flex;
  271.             justify-content: space-between;
  272.             align-items: center;
  273.         }
  274.         .logo-area {
  275.             display: flex;
  276.             align-items: center;
  277.             gap: 10px;
  278.         }
  279.         .logo-title {
  280.             font-size: 20px;
  281.             font-weight: 700;
  282.             color: var(--text-dark);
  283.             text-decoration: none;
  284.             display: flex;
  285.             align-items: center;
  286.             gap: 8px;
  287.         }
  288.         .logo-title span {
  289.             background: linear-gradient(135deg, var(--primary), var(--ai-accent));
  290.             -webkit-background-clip: text;
  291.             -webkit-text-fill-color: transparent;
  292.         }
  293.         .lang-selector-form select {
  294.             padding: 6px 12px;
  295.             font-size: 13px;
  296.             border: 1px solid var(--border);
  297.             border-radius: 6px;
  298.             background: var(--bg-card);
  299.             color: var(--text-dark);
  300.             cursor: pointer;
  301.             outline: none;
  302.         }
  303.  
  304.         /* Container */
  305.         .container {
  306.             max-width: 850px;
  307.             margin: 40px auto;
  308.             padding: 0 20px;
  309.         }
  310.  
  311.         .service-intro {
  312.             text-align: center;
  313.             margin-bottom: 30px;
  314.         }
  315.         .service-intro h1 {
  316.             font-size: 28px;
  317.             font-weight: 800;
  318.             margin-bottom: 8px;
  319.             color: var(--text-dark);
  320.         }
  321.         .service-intro p {
  322.             color: var(--text-muted);
  323.             margin: 0;
  324.             font-size: 15px;
  325.         }
  326.  
  327.         /* Search Form Container */
  328.         .search-box-card {
  329.             background: var(--bg-card);
  330.             padding: 24px;
  331.             border-radius: var(--radius);
  332.             box-shadow: 0 4px 12px rgba(15, 23, 42, 0.05);
  333.             border: 1px solid var(--border);
  334.             margin-bottom: 30px;
  335.         }
  336.         .form-main-row {
  337.             display: flex;
  338.             gap: 12px;
  339.             margin-bottom: 20px;
  340.         }
  341.         input[type="text"] {
  342.             flex-grow: 1;
  343.             padding: 14px 20px;
  344.             font-size: 16px;
  345.             border: 1px solid var(--border);
  346.             border-radius: var(--radius);
  347.             outline: none;
  348.             background: #fdfdfd;
  349.             transition: border-color 0.2s, box-shadow 0.2s;
  350.         }
  351.         input[type="text"]:focus {
  352.             border-color: var(--primary);
  353.             box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.15);
  354.             background: #fff;
  355.         }
  356.         button[type="submit"] {
  357.             padding: 14px 28px;
  358.             font-size: 15px;
  359.             font-weight: 600;
  360.             background: var(--primary);
  361.             color: white;
  362.             border: none;
  363.             border-radius: var(--radius);
  364.             cursor: pointer;
  365.             transition: background-color 0.2s;
  366.         }
  367.         button[type="submit"]:hover {
  368.             background: var(--primary-hover);
  369.         }
  370.        
  371.         /* Grid Parameters */
  372.         .form-options-grid {
  373.             display: grid;
  374.             grid-template-columns: repeat(2, 1fr);
  375.             gap: 16px;
  376.         }
  377.         .option-field {
  378.             display: flex;
  379.             flex-direction: column;
  380.             gap: 6px;
  381.         }
  382.         .option-field label {
  383.             font-size: 12px;
  384.             font-weight: 700;
  385.             text-transform: uppercase;
  386.             letter-spacing: 0.5px;
  387.             color: var(--text-muted);
  388.         }
  389.         .option-field select {
  390.             padding: 10px 14px;
  391.             border: 1px solid var(--border);
  392.             border-radius: 8px;
  393.             font-size: 14px;
  394.             background: var(--bg-card);
  395.             color: var(--text-dark);
  396.             outline: none;
  397.             transition: border-color 0.2s;
  398.         }
  399.         .option-field select:focus {
  400.             border-color: var(--primary);
  401.         }
  402.        
  403.         /* AI Overview Card */
  404.         .ai-card {
  405.             background: var(--ai-bg);
  406.             border: 1px solid var(--ai-border);
  407.             padding: 24px;
  408.             border-radius: var(--radius);
  409.             margin-bottom: 35px;
  410.             box-shadow: 0 4px 15px rgba(124, 58, 237, 0.04);
  411.         }
  412.         .ai-title {
  413.             font-size: 16px;
  414.             font-weight: 700;
  415.             color: var(--ai-accent);
  416.             margin-bottom: 14px;
  417.             display: flex;
  418.             align-items: center;
  419.             gap: 8px;
  420.             text-transform: uppercase;
  421.             letter-spacing: 0.5px;
  422.         }
  423.         .ai-text {
  424.             line-height: 1.7;
  425.             font-size: 15px;
  426.             color: #334155;
  427.         }
  428.         .ai-text p { margin: 0 0 12px 0; }
  429.         .ai-text p:last-child { margin-bottom: 0; }
  430.         .ai-text ul { margin: 0 0 12px 20px; padding: 0; }
  431.         .ai-text li { margin-bottom: 6px; }
  432.         .ai-sources {
  433.             margin-top: 20px;
  434.             border-top: 1px solid var(--ai-border);
  435.             padding-top: 14px;
  436.         }
  437.         .ai-sources-title {
  438.             font-weight: 700;
  439.             font-size: 13px;
  440.             margin-bottom: 10px;
  441.             color: var(--text-muted);
  442.             text-transform: uppercase;
  443.             letter-spacing: 0.5px;
  444.         }
  445.         .source-chip {
  446.             display: inline-flex;
  447.             align-items: center;
  448.             background: var(--bg-card);
  449.             padding: 6px 12px;
  450.             border-radius: 20px;
  451.             border: 1px solid var(--ai-border);
  452.             font-size: 13px;
  453.             margin-right: 8px;
  454.             margin-bottom: 8px;
  455.             text-decoration: none;
  456.             color: var(--ai-accent);
  457.             font-weight: 500;
  458.             transition: background-color 0.2s, transform 0.1s;
  459.         }
  460.         .source-chip:hover {
  461.             background: #ede9fe;
  462.             transform: translateY(-1px);
  463.         }
  464.  
  465.         /* Organic Results Section */
  466.         .section-title {
  467.             font-size: 18px;
  468.             font-weight: 700;
  469.             margin-bottom: 20px;
  470.             color: var(--text-dark);
  471.             border-bottom: 2px solid var(--border);
  472.             padding-bottom: 8px;
  473.         }
  474.         .organic-card {
  475.             background: var(--bg-card);
  476.             padding: 20px;
  477.             border-radius: var(--radius);
  478.             margin-bottom: 16px;
  479.             box-shadow: 0 2px 6px rgba(15, 23, 42, 0.02);
  480.             border: 1px solid var(--border);
  481.             transition: box-shadow 0.2s, transform 0.2s;
  482.         }
  483.         .organic-card:hover {
  484.             box-shadow: 0 4px 12px rgba(15, 23, 42, 0.05);
  485.             transform: translateY(-1px);
  486.         }
  487.         .organic-site {
  488.             font-size: 12px;
  489.             color: var(--text-muted);
  490.             margin-bottom: 6px;
  491.             font-family: monospace;
  492.         }
  493.         .organic-title {
  494.             font-size: 18px;
  495.             font-weight: 600;
  496.             margin: 0 0 8px 0;
  497.         }
  498.         .organic-title a {
  499.             color: var(--primary);
  500.             text-decoration: none;
  501.         }
  502.         .organic-title a:hover {
  503.             text-decoration: underline;
  504.         }
  505.         .organic-snippet {
  506.             font-size: 14px;
  507.             color: #475569;
  508.             line-height: 1.6;
  509.         }
  510.  
  511.         .no-ai {
  512.             background: #fffbeb;
  513.             color: #b45309;
  514.             padding: 16px 20px;
  515.             border-radius: var(--radius);
  516.             margin-bottom: 30px;
  517.             border: 1px solid #fef3c7;
  518.             font-size: 14px;
  519.         }
  520.  
  521.         /* Raw Data Toggle */
  522.         details {
  523.             margin-top: 40px;
  524.             background: var(--bg-card);
  525.             border: 1px solid var(--border);
  526.             border-radius: var(--radius);
  527.             padding: 12px;
  528.         }
  529.         summary {
  530.             font-weight: 600;
  531.             font-size: 14px;
  532.             color: var(--text-muted);
  533.             cursor: pointer;
  534.             outline: none;
  535.             user-select: none;
  536.         }
  537.         details[open] summary {
  538.             margin-bottom: 12px;
  539.         }
  540.     </style>
  541. </head>
  542. <body>
  543.  
  544. <header class="header">
  545.     <div class="header-container">
  546.         <div class="logo-area">
  547.             <a href="" class="logo-title">🔍 <span><?= safeHtml($t['title']) ?></span></a>
  548.         </div>
  549.         <div class="lang-selector-top">
  550.             <!-- Кнопка смены языка интерфейса отправляет скрытую форму для сохранения текущего запроса -->
  551.             <form id="ui_lang_form" class="lang-selector-form" method="POST" action="">
  552.                 <input type="hidden" name="query" value="<?= safeHtml($searchedQuery) ?>">
  553.                 <input type="hidden" name="gl" value="<?= safeHtml($selectedGl) ?>">
  554.                 <input type="hidden" name="hl" value="<?= safeHtml($selectedHl) ?>">
  555.                 <select name="ui_lang" onchange="document.getElementById('ui_lang_form').submit()">
  556.                     <option value="ru" <?= $uiLang === 'ru' ? 'selected' : '' ?>>Русский (RU)</option>
  557.                     <option value="uk" <?= $uiLang === 'uk' ? 'selected' : '' ?>>Українська (UK)</option>
  558.                 </select>
  559.             </form>
  560.         </div>
  561.     </div>
  562. </header>
  563.  
  564. <div class="container">
  565.     <div class="service-intro">
  566.         <h1><?= safeHtml($t['title']) ?></h1>
  567.         <p><?= safeHtml($t['subtitle']) ?></p>
  568.     </div>
  569.  
  570.     <div class="search-box-card">
  571.         <form method="POST" action="">
  572.             <input type="hidden" name="ui_lang" value="<?= safeHtml($uiLang) ?>">
  573.            
  574.             <div class="form-main-row">
  575.                 <input
  576.                     type="text"
  577.                     name="query"
  578.                     placeholder="<?= safeHtml($t['query_placeholder']) ?>"
  579.                     value="<?= safeHtml($searchedQuery ?: '') ?>"
  580.                     required
  581.                 >
  582.                 <button type="submit"><?= safeHtml($t['search_btn']) ?></button>
  583.             </div>
  584.            
  585.             <div class="form-options-grid">
  586.                 <div class="option-field">
  587.                     <label for="gl"><?= safeHtml($t['country_label']) ?></label>
  588.                     <select name="gl" id="gl">
  589.                         <?php foreach ($t['countries'] as $code => $name): ?>
  590.                             <option value="<?= safeHtml($code) ?>" <?= $selectedGl === $code ? 'selected' : '' ?>>
  591.                                 <?= safeHtml($name) ?>
  592.                             </option>
  593.                         <?php endforeach; ?>
  594.                     </select>
  595.                 </div>
  596.  
  597.                 <div class="option-field">
  598.                     <label for="hl"><?= safeHtml($t['lang_label']) ?></label>
  599.                     <select name="hl" id="hl">
  600.                         <?php foreach ($t['languages'] as $code => $name): ?>
  601.                             <option value="<?= safeHtml($code) ?>" <?= $selectedHl === $code ? 'selected' : '' ?>>
  602.                                 <?= safeHtml($name) ?>
  603.                             </option>
  604.                         <?php endforeach; ?>
  605.                     </select>
  606.                 </div>
  607.             </div>
  608.         </form>
  609.     </div>
  610.  
  611.     <?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($searchedQuery)): ?>
  612.  
  613.         <!-- 1. AI OVERVIEW -->
  614.         <?php if ($aiOverview): ?>
  615.             <div class="ai-card">
  616.                 <div class="ai-title">✨ AI Overview</div>
  617.                 <div class="ai-text">
  618.                     <?php
  619.                         if (isset($aiOverview['texts']) && is_array($aiOverview['texts'])) {
  620.                             foreach ($aiOverview['texts'] as $textBlock) {
  621.                                 if (isset($textBlock['snippet'])) {
  622.                                     echo '<p>' . nl2br(safeHtml($textBlock['snippet'])) . '</p>';
  623.                                 }
  624.                                 if (isset($textBlock['list']) && is_array($textBlock['list'])) {
  625.                                     echo '<ul>';
  626.                                     foreach ($textBlock['list'] as $li) {
  627.                                         $itemText = is_array($li) ? ($li['text'] ?? $li['snippet'] ?? '') : $li;
  628.                                         if ($itemText) echo '<li>' . safeHtml($itemText) . '</li>';
  629.                                     }
  630.                                     echo '</ul>';
  631.                                 }
  632.                             }
  633.                         } elseif (isset($aiOverview['text'])) {
  634.                             echo '<p>' . nl2br(safeHtml($aiOverview['text'])) . '</p>';
  635.                         }
  636.                     ?>
  637.                 </div>
  638.  
  639.                 <!-- Сбор источников -->
  640.                 <?php
  641.                     $finalSources = [];
  642.  
  643.                     $rawSources = array_merge(
  644.                         $aiOverview['references'] ?? [],
  645.                         $aiOverview['sources'] ?? []
  646.                     );
  647.  
  648.                     if (isset($aiOverview['texts']) && is_array($aiOverview['texts'])) {
  649.                         foreach ($aiOverview['texts'] as $tb) {
  650.                             if (!empty($tb['links']) && is_array($tb['links'])) {
  651.                                 foreach ($tb['links'] as $l) {
  652.                                     $rawSources[] = $l;
  653.                                 }
  654.                             }
  655.                         }
  656.                     }
  657.  
  658.                     foreach ($rawSources as $src) {
  659.                         $url = cleanGoogleUrl($src['link'] ?? $src['url'] ?? '');
  660.                         $title = $src['title'] ?? $src['source'] ?? $src['display_link'] ?? '';
  661.  
  662.                         if (!$url && !empty($organicResults)) {
  663.                             foreach ($organicResults as $org) {
  664.                                 $orgSource = strtolower($org['source'] ?? '');
  665.                                 $orgLink = strtolower($org['link'] ?? '');
  666.                                 $searchTitle = strtolower($title);
  667.  
  668.                                 if ($searchTitle && (strpos($orgSource, $searchTitle) !== false || strpos($orgLink, $searchTitle) !== false)) {
  669.                                     $url = $org['link'];
  670.                                     if (!$title) $title = $org['title'];
  671.                                     break;
  672.                                 }
  673.                             }
  674.                         }
  675.  
  676.                         if ($url) {
  677.                             $finalSources[$url] = [
  678.                                 'url' => $url,
  679.                                 'title' => $title ?: parse_url($url, PHP_URL_HOST)
  680.                             ];
  681.                         }
  682.                     }
  683.  
  684.                     if (!empty($finalSources)):
  685.                 ?>
  686.                     <div class="ai-sources">
  687.                         <div class="ai-sources-title"><?= safeHtml($t['sources_title']) ?></div>
  688.                         <?php foreach ($finalSources as $source): ?>
  689.                             <a href="<?= safeHtml($source['url']) ?>" target="_blank" rel="noopener noreferrer" class="source-chip">
  690.                                 🔗 <?= safeHtml($source['title']) ?>
  691.                             </a>
  692.                         <?php endforeach; ?>
  693.                     </div>
  694.                 <?php endif; ?>
  695.             </div>
  696.         <?php else: ?>
  697.             <div class="no-ai">
  698.                 <?= $t['no_ai_msg'] ?>
  699.             </div>
  700.         <?php endif; ?>
  701.  
  702.         <!-- 2. ОРГАНИЧЕСКИЙ РЕЗУЛЬТАТ -->
  703.         <h2 class="section-title"><?= safeHtml($t['serp_title']) ?></h2>
  704.        
  705.         <?php if (!empty($organicResults)): ?>
  706.             <?php foreach ($organicResults as $item): ?>
  707.                 <div class="organic-card">
  708.                     <div class="organic-site">
  709.                         <?= safeHtml($item['source'] ?? $item['display_link'] ?? '') ?>
  710.                     </div>
  711.                     <h3 class="organic-title">
  712.                         <a href="<?= safeHtml($item['link'] ?? '#') ?>" target="_blank" rel="noopener noreferrer">
  713.                             <?= safeHtml($item['title'] ?? $t['no_title']) ?>
  714.                         </a>
  715.                     </h3>
  716.                     <div class="organic-snippet">
  717.                         <?= safeHtml($item['description'] ?? '') ?>
  718.                     </div>
  719.                 </div>
  720.             <?php endforeach; ?>
  721.         <?php else: ?>
  722.             <p><?= safeHtml($t['no_results']) ?></p>
  723.         <?php endif; ?>
  724.  
  725.         <details>
  726.             <summary><?= safeHtml($t['view_raw_json']) ?></summary>
  727.             <pre><?= safeHtml($rawJson) ?></pre>
  728.         </details>
  729.  
  730.     <?php endif; ?>
  731. </div>
  732.  
  733. </body>
  734. </html>
Advertisement
Add Comment
Please, Sign In to add comment