$ttl) return null; return file_get_contents($path); } function cacheSitemap(string $path, string $content): bool { return @file_put_contents($path, $content, LOCK_EX) !== false; } function logSitemapEvent(string $level, string $message, array $context = []): void { $entry = sprintf( "[%s] [%s] %s %s\n", date('Y-m-d H:i:s'), strtoupper($level), $message, empty($context) ? '' : json_encode($context, JSON_UNESCAPED_UNICODE) ); @file_put_contents(LOG_PATH, $entry, FILE_APPEND | LOCK_EX); } // ============================================================================= // DATABASE INTEGRATION // ============================================================================= function getDatabaseConnection(): ?PDO { if (!DB_DSN || !DB_USER) { return null; } try { return new PDO(DB_DSN, DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4", PDO::ATTR_PERSISTENT => false, ]); } catch (PDOException $e) { logSitemapEvent('ERROR', 'Database connection failed', [ 'message' => $e->getMessage(), 'code' => $e->getCode() ]); return null; } } function fetchPagesForSitemap(PDO $pdo): array { $data = [ 'pages' => [], 'posts' => [], 'categories' => [], 'products' => [], ]; // Main pages with AI relevance signals $stmt = $pdo->prepare(" SELECT p.id, p.title, p.slug AS url, p.meta_description, p.main_image, p.priority, p.changefreq, p.language, p.target_countries, UNIX_TIMESTAMP(p.updated_at) AS lastmod, p.ai_relevance_score, p.eeat_signals, p.semantic_keywords FROM pages p WHERE p.status = 'published' AND p.is_sitemap_visible = 1 AND p.noindex != 1 ORDER BY p.priority DESC, p.updated_at DESC LIMIT 5000 "); $stmt->execute(); $data['pages'] = $stmt->fetchAll(); // Blog posts with publication dates $stmt = $pdo->prepare(" SELECT post.id, post.title, post.slug AS url, post.excerpt AS description, post.featured_image AS image, post.language, post.target_countries, UNIX_TIMESTAMP(post.published_at) AS lastmod, post.category_slug, post.reading_time, post.ai_summary FROM posts post WHERE post.status = 'published' AND post.is_sitemap_visible = 1 AND post.noindex != 1 ORDER BY post.published_at DESC LIMIT 10000 "); $stmt->execute(); $data['posts'] = $stmt->fetchAll(); // Product pages with structured data signals $stmt = $pdo->prepare(" SELECT prod.id, prod.name AS title, prod.slug AS url, prod.short_description, prod.main_image, prod.price, prod.currency, prod.availability, prod.language, prod.target_markets, UNIX_TIMESTAMP(prod.updated_at) AS lastmod, prod.rating_value, prod.review_count, prod.sku FROM products prod WHERE prod.status = 'active' AND prod.is_visible = 1 ORDER BY prod.priority DESC, prod.updated_at DESC LIMIT 2000 "); $stmt->execute(); $data['products'] = $stmt->fetchAll(); // Category/taxonomy pages $stmt = $pdo->query(" SELECT cat.id, cat.name AS title, cat.slug AS url, cat.description, cat.language, cat.parent_id, UNIX_TIMESTAMP(cat.updated_at) AS lastmod, COUNT(rel.object_id) AS item_count FROM categories cat LEFT JOIN category_relations rel ON rel.category_id = cat.id WHERE cat.is_sitemap_visible = 1 GROUP BY cat.id ORDER BY cat.priority DESC LIMIT 500 "); $data['categories'] = $stmt->fetchAll(); return $data; } // ============================================================================= // URL & HREFLANG UTILITIES // ============================================================================= function buildAbsoluteUrl(string $path, string $lang = DEFAULT_LANG): string { $path = ltrim($path, '/'); if ($lang !== DEFAULT_LANG) { return rtrim(SITE_URL, '/') . '/' . $lang . '/' . $path; } return rtrim(SITE_URL, '/') . '/' . $path; } function generateHreflangLinks(string $baseUrl, array $languages, string $defaultLang = DEFAULT_LANG): string { $output = ''; foreach ($languages as $lang) { $href = ($lang === $defaultLang) ? $baseUrl : rtrim(SITE_URL, '/') . '/' . $lang . '/' . ltrim(parse_url($baseUrl, PHP_URL_PATH), '/'); $output .= ' ' . "\n"; } $output .= ' ' . "\n"; return $output; } function getChangefreq(string $contentType, ?int $updateInterval = null): string { $defaults = [ 'homepage' => 'daily', 'product' => 'weekly', 'blog' => 'weekly', 'category' => 'monthly', 'static' => 'monthly', 'legal' => 'yearly', ]; return $defaults[$contentType] ?? 'monthly'; } function calculatePriority(array $signals): float { $base = 0.5; if (!empty($signals['is_homepage'])) $base = 1.0; elseif (!empty($signals['is_product'])) $base = 0.9; elseif (!empty($signals['is_blog_featured'])) $base = 0.8; elseif (!empty($signals['is_category'])) $base = 0.7; if (!empty($signals['ai_relevance_score']) && $signals['ai_relevance_score'] > 0.8) { $base = min(1.0, $base + 0.1); } if (!empty($signals['eeat_strength']) && $signals['eeat_strength'] === 'high') { $base = min(1.0, $base + 0.05); } return round($base, 1); } // ============================================================================= // AI & 2027 RANKING SIGNALS // ============================================================================= function generateAIRelevanceMetadata(array $page): array { $metadata = []; if (!empty($page['ai_relevance_score'])) { $metadata['ai:relevance'] = number_format((float)$page['ai_relevance_score'], 2); } if (!empty($page['semantic_keywords'])) { $keywords = is_string($page['semantic_keywords']) ? json_decode($page['semantic_keywords'], true) : $page['semantic_keywords']; if (is_array($keywords) && !empty($keywords)) { $metadata['ai:keywords'] = implode(', ', array_slice($keywords, 0, 10)); } } if (!empty($page['eeat_signals'])) { $eeat = is_string($page['eeat_signals']) ? json_decode($page['eeat_signals'], true) : $page['eeat_signals']; if (is_array($eeat)) { if (!empty($eeat['expertise'])) $metadata['eeat:expertise'] = $eeat['expertise']; if (!empty($eeat['authoritativeness'])) $metadata['eeat:authority'] = $eeat['authoritativeness']; if (!empty($eeat['trustworthiness'])) $metadata['eeat:trust'] = $eeat['trustworthiness']; } } if (!empty($page['reading_time'])) { $metadata['content:readingTime'] = (int)$page['reading_time'] . 'min'; } return $metadata; } function generateCoreWebVitalsHints(array $page): string { $hints = []; if (!empty($page['lcp_element'])) { $hints[] = 'data-lcp="' . htmlspecialchars($page['lcp_element'], ENT_XML1) . '"'; } if (!empty($page['cls_score']) && (float)$page['cls_score'] < 0.1) { $hints[] = 'data-cls="good"'; } if (!empty($page['fid_ms']) && (int)$page['fid_ms'] < 100) { $hints[] = 'data-fid="good"'; } return empty($hints) ? '' : ' ' . implode(' ', $hints); } // ============================================================================= // XML GENERATION FUNCTIONS // ============================================================================= function generateSitemapHeader(): string { return '' . "\n" . '' . "\n"; } function generateUrlEntry(array $page, string $contentType = 'page'): string { $loc = buildAbsoluteUrl($page['url'], $page['language'] ?? DEFAULT_LANG); $lastmod = !empty($page['lastmod']) ? gmdate('Y-m-d\TH:i:s+00:00', (int)$page['lastmod']) : gmdate('Y-m-d\TH:i:s+00:00'); $changefreq = $page['changefreq'] ?? getChangefreq($contentType); $priority = $page['priority'] ?? calculatePriority($page); $xml = ' ' . "\n"; $xml .= ' ' . htmlspecialchars($loc, ENT_XML1) . '' . "\n"; $xml .= ' ' . $lastmod . '' . "\n"; $xml .= ' ' . $changefreq . '' . "\n"; $xml .= ' ' . $priority . '' . "\n"; if (!empty($page['language'])) { $languages = !empty($page['target_countries']) && is_string($page['target_countries']) ? array_merge([DEFAULT_LANG], array_diff(SUPPORTED_LANGUAGES, [$page['language']])) : SUPPORTED_LANGUAGES; $xml .= generateHreflangLinks($loc, $languages, $page['language']); } if (!empty($page['image']) || !empty($page['main_image'])) { $imageUrl = !empty($page['image']) ? $page['image'] : $page['main_image']; if (!str_starts_with($imageUrl, 'http')) { $imageUrl = SITE_URL . ltrim($imageUrl, '/'); } $xml .= ' ' . "\n"; $xml .= ' ' . htmlspecialchars($imageUrl, ENT_XML1) . '' . "\n"; $xml .= ' ' . htmlspecialchars($page['title'] ?? '', ENT_XML1) . '' . "\n"; if (!empty($page['description']) || !empty($page['meta_description']) || !empty($page['short_description'])) { $desc = $page['description'] ?? $page['meta_description'] ?? $page['short_description'] ?? ''; $xml .= ' ' . htmlspecialchars(mb_substr($desc, 0, 255), ENT_XML1) . '' . "\n"; } $xml .= ' ' . "\n"; } if ($contentType === 'blog' || $contentType === 'news') { $xml .= ' ' . "\n"; $xml .= ' ' . "\n"; $xml .= ' ' . SITE_NAME . '' . "\n"; $xml .= ' ' . ($page['language'] ?? DEFAULT_LANG) . '' . "\n"; $xml .= ' ' . "\n"; $xml .= ' ' . $lastmod . '' . "\n"; $xml .= ' ' . htmlspecialchars($page['title'], ENT_XML1) . '' . "\n"; $xml .= ' ' . "\n"; } if (AI_CONTENT_SIGNAL && !empty($page['ai_relevance_score'])) { $aiMeta = generateAIRelevanceMetadata($page); foreach ($aiMeta as $attr => $value) { $xml .= ' <' . $attr . '>' . htmlspecialchars($value, ENT_XML1) . '' . "\n"; } } $cwhints = generateCoreWebVitalsHints($page); if ($cwhints) { $xml .= ' ' . "\n"; } $xml .= ' ' . "\n"; return $xml; } function generateSitemapIndex(array $sitemaps): string { $xml = '' . "\n"; $xml .= '' . "\n"; $xml .= ' ' . $sitemap['lastmod'] . '' . "\n"; $xml .= ' ' . "\n"; } $xml .= '' . "\n"; return $xml; } // ============================================================================= // MAIN GENERATION LOGIC // ============================================================================= function generateXMLSitemap(array $data): string { $xml = generateSitemapHeader(); $urlCount = 0; $xml .= ' ' . "\n"; $xml .= ' ' . htmlspecialchars(SITE_URL, ENT_XML1) . '' . "\n"; $xml .= ' ' . gmdate('Y-m-d\TH:i:s+00:00') . '' . "\n"; $xml .= ' daily' . "\n"; $xml .= ' 1.0' . "\n"; $xml .= generateHreflangLinks(SITE_URL, SUPPORTED_LANGUAGES); $xml .= ' ' . "\n"; $urlCount++; foreach ($data['pages'] as $page) { $xml .= generateUrlEntry($page, 'page'); $urlCount++; } foreach ($data['products'] as $product) { $product['language'] = $product['language'] ?? DEFAULT_LANG; $xml .= generateUrlEntry($product, 'product'); $urlCount++; } foreach ($data['posts'] as $post) { $post['language'] = $post['language'] ?? DEFAULT_LANG; $xml .= generateUrlEntry($post, 'blog'); $urlCount++; } foreach ($data['categories'] as $category) { $category['language'] = $category['language'] ?? DEFAULT_LANG; $category['priority'] = 0.7; $category['changefreq'] = 'monthly'; $xml .= generateUrlEntry($category, 'category'); $urlCount++; } $xml .= '' . "\n"; logSitemapEvent('INFO', 'Sitemap generated', [ 'url_count' => $urlCount, 'languages' => count(SUPPORTED_LANGUAGES), 'ai_signals' => AI_CONTENT_SIGNAL ? 'enabled' : 'disabled' ]); return $xml; } function handleSitemapRequest(): void { initCacheDir(); $forceRegenerate = isset($_GET['force']) && $_GET['force'] === 'true'; if (!$forceRegenerate) { $cached = getCachedSitemap(CACHE_PATH, CACHE_TTL); if ($cached) { header('Content-Type: application/xml; charset=UTF-8'); header('Cache-Control: public, max-age=' . CACHE_TTL); header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime(CACHE_PATH)) . ' GMT'); header('X-Sitemap-Type: xml'); header('X-Sitemap-Source: cache'); header('X-URL-Count: ' . substr_count($cached, '')); echo $cached; return; } } $pdo = getDatabaseConnection(); if (!$pdo) { http_response_code(503); header('Content-Type: application/xml; charset=UTF-8'); echo '' . "\n"; echo '' . "\n"; echo ' DB_UNAVAILABLE' . "\n"; echo ' Database connection failed' . "\n"; echo ' ' . CACHE_TTL . '' . "\n"; echo '' . "\n"; return; } try { $startTime = microtime(true); $data = fetchPagesForSitemap($pdo); $xml = generateXMLSitemap($data); if (cacheSitemap(CACHE_PATH, $xml)) { logSitemapEvent('INFO', 'Sitemap cached', [ 'size_bytes' => strlen($xml), 'generation_time_ms' => round((microtime(true) - $startTime) * 1000, 2) ]); } header('Content-Type: application/xml; charset=UTF-8'); header('Cache-Control: public, max-age=' . CACHE_TTL); header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); header('X-Sitemap-Type: xml'); header('X-Sitemap-Source: generated'); header('X-Generation-Time: ' . round((microtime(true) - $startTime) * 1000, 2) . 'ms'); header('X-URL-Count: ' . substr_count($xml, '')); echo $xml; } catch (Throwable $e) { logSitemapEvent('CRITICAL', 'Sitemap generation failed', [ 'exception' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ]); http_response_code(500); header('Content-Type: application/xml; charset=UTF-8'); echo '' . "\n"; echo '' . "\n"; echo ' GENERATION_FAILED' . "\n"; echo ' ' . htmlspecialchars($e->getMessage(), ENT_XML1) . '' . "\n"; echo '' . "\n"; } } // ============================================================================= // EXECUTION // ============================================================================= if (php_sapi_name() === 'cli') { parse_str(implode('&', array_slice($argv, 1)), $_GET); } handleSitemapRequest(); exit;