true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => $timeout, CURLOPT_HTTPHEADER => ['Accept: application/json'], ]); $body = curl_exec($ch); $errno = curl_errno($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($errno !== 0) { $body = false; } } else { $context = stream_context_create([ 'http' => [ 'method' => 'GET', 'timeout' => $timeout, 'header' => "Accept: application/json\r\n", 'ignore_errors' => true, ], ]); $body = @file_get_contents($url, false, $context); if (isset($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $m)) { $httpCode = (int) $m[1]; } } if ($body === false || ($httpCode > 0 && ($httpCode < 200 || $httpCode >= 300))) { return null; } $decoded = json_decode($body, true); return is_array($decoded) ? $decoded : null; } function mapUpstreamMenu(array $raw, string $lang, string $source): array { $categories = []; foreach ($raw['categories'] ?? [] as $cat) { if (!is_array($cat)) { continue; } $id = trim((string) ($cat['id'] ?? '')); $name = trim((string) ($cat['name'] ?? '')); if ($id === '' || $name === '') { continue; } $categories[] = ['id' => $id, 'name' => $name]; } $sections = []; foreach ($raw['tables'] ?? [] as $table) { if (!is_array($table)) { continue; } $sectionName = trim((string) ($table['name'] ?? '')); if ($sectionName === '') { continue; } $items = []; foreach ($table['items'] ?? [] as $item) { if (!is_array($item)) { continue; } $items[] = [ 'position' => (string) ($item['id'] ?? ''), 'categoryId' => (string) ($item['category'] ?? ''), 'tag' => '', 'image' => (string) ($item['image'] ?? ''), 'title' => trim((string) ($item['name'] ?? '')), 'description' => trim((string) ($item['name_extra'] ?? '')), 'price' => (string) ($item['price'] ?? ''), ]; } $sections[] = [ 'categoryName' => $sectionName, 'items' => $items, ]; } $payload = [ 'status' => 'success', 'lang' => $lang, 'source' => $source, 'cachedAt' => gmdate('c'), 'categories' => $categories, 'sections' => $sections, ]; $payload['contentFingerprint'] = menuContentFingerprint($payload); return $payload; } /** * Stabilny fingerprint treści tekstowych (bez cen/obrazków/daty cache). */ function menuContentFingerprint(array $menu): string { $parts = []; foreach ($menu['categories'] ?? [] as $cat) { if (!is_array($cat)) { continue; } $parts[] = 'c|' . ($cat['id'] ?? '') . '|' . ($cat['name'] ?? ''); } foreach ($menu['sections'] ?? [] as $section) { if (!is_array($section)) { continue; } $catName = (string) ($section['categoryName'] ?? ''); $parts[] = 's|' . $catName; foreach ($section['items'] ?? [] as $item) { if (!is_array($item)) { continue; } $parts[] = implode('|', [ 'i', (string) ($item['categoryId'] ?? ''), (string) ($item['position'] ?? ''), (string) ($item['title'] ?? ''), (string) ($item['description'] ?? ''), $catName, ]); } } sort($parts, SORT_STRING); return hash('sha256', implode("\n", $parts)); } /** * Mapa pozycji do tłumaczenia: key => [title, description, categoryName, categoryId, position, ...] */ function menuCollectTranslatableUnits(array $menu): array { $units = []; $categoryNames = []; foreach ($menu['categories'] ?? [] as $cat) { if (!is_array($cat)) { continue; } $id = (string) ($cat['id'] ?? ''); if ($id === '') { continue; } $categoryNames[$id] = (string) ($cat['name'] ?? ''); $units['cat:' . $id] = [ 'type' => 'category', 'id' => $id, 'text' => (string) ($cat['name'] ?? ''), ]; } foreach ($menu['sections'] ?? [] as $sIdx => $section) { if (!is_array($section)) { continue; } $catName = (string) ($section['categoryName'] ?? ''); $units['sec:' . $sIdx] = [ 'type' => 'section', 'index' => (int) $sIdx, 'text' => $catName, ]; foreach ($section['items'] ?? [] as $item) { if (!is_array($item)) { continue; } $cid = (string) ($item['categoryId'] ?? ''); $pos = (string) ($item['position'] ?? ''); $key = 'item:' . $cid . ':' . $pos; $units[$key] = [ 'type' => 'item', 'categoryId' => $cid, 'position' => $pos, 'title' => (string) ($item['title'] ?? ''), 'description' => (string) ($item['description'] ?? ''), 'hash' => hash('sha256', ($item['title'] ?? '') . "\0" . ($item['description'] ?? '') . "\0" . $catName), ]; } } return $units; } function menuApplyTranslations(array $plMenu, array $translations, string $lang): array { $out = $plMenu; $out['lang'] = $lang; $out['source'] = 'ai'; $out['cachedAt'] = gmdate('c'); $out['sourceFingerprint'] = $plMenu['contentFingerprint'] ?? menuContentFingerprint($plMenu); foreach ($out['categories'] ?? [] as $i => $cat) { if (!is_array($cat)) { continue; } $id = (string) ($cat['id'] ?? ''); $key = 'cat:' . $id; if (isset($translations[$key]['text'])) { $out['categories'][$i]['name'] = (string) $translations[$key]['text']; } } foreach ($out['sections'] ?? [] as $sIdx => $section) { if (!is_array($section)) { continue; } $secKey = 'sec:' . $sIdx; if (isset($translations[$secKey]['text'])) { $out['sections'][$sIdx]['categoryName'] = (string) $translations[$secKey]['text']; } foreach ($section['items'] ?? [] as $iIdx => $item) { if (!is_array($item)) { continue; } $cid = (string) ($item['categoryId'] ?? ''); $pos = (string) ($item['position'] ?? ''); $key = 'item:' . $cid . ':' . $pos; if (isset($translations[$key]['title'])) { $out['sections'][$sIdx]['items'][$iIdx]['title'] = (string) $translations[$key]['title']; } if (array_key_exists('description', $translations[$key] ?? [])) { $out['sections'][$sIdx]['items'][$iIdx]['description'] = (string) $translations[$key]['description']; } } } $out['contentFingerprint'] = menuContentFingerprint($out); return $out; } function loadMenuAiConfig(): ?array { $path = __DIR__ . '/../config/menu_ai.local.php'; if (!is_file($path)) { return null; } $cfg = require $path; return is_array($cfg) ? $cfg : null; }