htmlspecialchars($value, ENT_XML1 | ENT_COMPAT, 'UTF-8'); $sitemapHandler = static function () use ($absoluteSiteUrl, $xmlEscape): void { $baseUrl = $absoluteSiteUrl(); $today = date('Y-m-d'); $urls = []; $addUrl = static function ( string $path, string $priority, string $changefreq = 'weekly', ?string $lastmod = null ) use (&$urls, $baseUrl, $today): void { $urls[] = [ 'loc' => $baseUrl . ($path === '/' ? '/' : '/' . ltrim($path, '/')), 'lastmod' => $lastmod ?: $today, 'changefreq' => $changefreq, 'priority' => $priority, ]; }; $addUrl('/', '1.0', 'daily'); $addUrl('/catalog', '0.9', 'daily'); $addUrl('/promo', '0.8', 'daily'); $addUrl('/new', '0.8', 'daily'); $addUrl('/delivery', '0.6', 'monthly'); $addUrl('/payment', '0.5', 'monthly'); $addUrl('/contacts', '0.5', 'monthly'); $addUrl('/returns', '0.4', 'monthly'); try { foreach ((new CategoryRepository())->active() as $category) { $addUrl('/catalog/' . (string) $category['slug'], '0.7', 'weekly'); } foreach ((new ProductRepository())->sitemapProducts() as $product) { $dateSource = $product['updated_at'] ?: ($product['published_at'] ?: $product['created_at']); $lastmod = $dateSource ? date('Y-m-d', strtotime((string) $dateSource)) : $today; $addUrl('/product/' . (string) $product['slug'], '0.6', 'weekly', $lastmod); } } catch (Throwable) { // Keep a valid sitemap available even during database maintenance. } header('Content-Type: application/xml; charset=UTF-8'); echo "\n"; echo "\n"; foreach ($urls as $url) { echo " \n"; echo ' ' . $xmlEscape($url['loc']) . "\n"; echo ' ' . $xmlEscape($url['lastmod']) . "\n"; echo ' ' . $xmlEscape($url['changefreq']) . "\n"; echo ' ' . $xmlEscape($url['priority']) . "\n"; echo " \n"; } echo "\n"; }; $robotsHandler = static function () use ($absoluteSiteUrl): void { header('Content-Type: text/plain; charset=UTF-8'); echo "User-agent: *\n"; echo "Allow: /\n"; echo "Disallow: /admin/\n"; echo "Disallow: /public/admin/\n"; echo "Disallow: /cart\n"; echo "\n"; echo 'Sitemap: ' . $absoluteSiteUrl() . "/sitemap.xml\n"; }; $catalogSearchHandler = static function (?string $rawQuery = null): void { $queryParameters = []; parse_str((string) (parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_QUERY) ?? ''), $queryParameters); $query = trim((string) ($rawQuery ?? $_GET['q'] ?? $queryParameters['q'] ?? '')); if ($query === '') { (new CatalogController())->index(); return; } $_GET['q'] = $query; $perPage = 48; $limit = isset($_GET['limit']) ? max($perPage, min((int) $_GET['limit'], 5000)) : $perPage; $page = max(1, (int) ($_GET['page'] ?? 1)); $offset = isset($_GET['limit']) ? 0 : (($page - 1) * $perPage); $categoryRepository = new CategoryRepository(); $productRepository = new ProductRepository(); if (method_exists($productRepository, 'searchCatalog')) { $searchResult = $productRepository->searchCatalog($query, $limit, $offset, true); $products = $searchResult['products']; $total = $searchResult['total']; } else { $allProducts = $productRepository->catalogPreview(5000, 0, true); $needle = function_exists('mb_strtolower') ? mb_strtolower($query, 'UTF-8') : strtolower($query); $words = array_values(array_filter(preg_split('/\s+/u', $needle) ?: [])); $ranked = []; foreach ($allProducts as $product) { $haystack = implode(' ', [ (string) ($product['name'] ?? ''), (string) ($product['short_description'] ?? ''), (string) ($product['category_name'] ?? ''), (string) ($product['display_category_name'] ?? ''), (string) ($product['categories_path'] ?? ''), ]); $haystack = function_exists('mb_strtolower') ? mb_strtolower($haystack, 'UTF-8') : strtolower($haystack); $score = 0; if ($needle !== '' && str_contains($haystack, $needle)) { $score += 100; } foreach ($words as $word) { if ($word !== '' && str_contains($haystack, $word)) { $score += 20; } } if ($score > 0) { $product['_search_score'] = $score; $ranked[] = $product; } } usort($ranked, static fn (array $a, array $b): int => ($b['_search_score'] ?? 0) <=> ($a['_search_score'] ?? 0)); $total = count($ranked); $products = array_slice($ranked, $offset, $limit); } $shown = min($total, $offset + count($products)); $basePath = '/search/' . rawurlencode($query); view('catalog/index', [ 'title' => 'Поиск по каталогу Рыбсток - ' . $query, 'metaDescription' => 'Результаты поиска по каталогу Рыбсток: ' . $query, 'metaKeywords' => $query . ', Рыбсток, каталог продуктов', 'breadcrumbs' => [ ['title' => 'Главная', 'url' => '/'], ['title' => 'Каталог', 'url' => '/catalog'], ['title' => 'Поиск'], ], 'categories' => $categoryRepository->tree(), 'currentCategory' => null, 'products' => $products, 'searchQuery' => $query, 'pagination' => [ 'base_path' => $basePath, 'total' => $total, 'shown' => $shown, 'page' => $page, 'pages' => max(1, (int) ceil($total / $perPage)), 'per_page' => $perPage, 'limit' => $limit, 'has_more' => $shown < $total, 'next_limit' => min($total, max($shown, $limit) + $perPage), 'cumulative' => isset($_GET['limit']), ], ]); }; $router->get('/', static fn () => (new HomeController())->index()); $router->get('/robots.txt', $robotsHandler); $router->get('/sitemap.xml', $sitemapHandler); $router->post('/price-request', static fn () => (new PriceRequestController())->store()); $router->post('/wishlist-request', static fn () => (new WishlistRequestController())->store()); $router->post('/api/cart/sync', static fn () => (new CartController())->sync()); $router->get('/search', static fn () => $catalogSearchHandler()); $router->get('/search/{query}', static fn (string $query) => $catalogSearchHandler($query)); $router->get('/promo', static fn () => (new CatalogController())->promo()); $router->get('/new', static fn () => (new CatalogController())->newest()); $router->get('/catalog', static fn () => trim((string) ($_GET['q'] ?? '')) !== '' ? $catalogSearchHandler() : (new CatalogController())->index()); $router->get('/delivery', static fn () => (new PageController())->delivery()); $router->get('/payment', static fn () => (new PageController())->payment()); $router->get('/contacts', static fn () => (new PageController())->contacts()); $router->get('/returns', static fn () => (new PageController())->returns()); $router->get('/cart', static fn () => (new PageController())->cart()); $router->get('/product/{slug}', static fn (string $slug) => (new CatalogController())->product($slug)); $router->get('/catalog/{slug}', static fn (string $slug) => (new CatalogController())->category($slug)); $router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);