?php // --- KONFIGURACJA I BEZPIECZEŃSTWO --- // Optymalizacja nagłówków dla SEO i Cache header("Content-Type: text/html; charset=utf-8"); header("X-Content-Type-Options: nosniff"); // Cache-Control sterowany dynamicznie if (isset($_GET['view']) && $_GET['view'] === 'product') { header("Cache-Control: public, max-age=300"); } else { header("Cache-Control: no-store, no-cache, must-revalidate"); } ini_set('display_errors', '0'); error_reporting(E_ALL); // --- DATABASE CONFIG --- const DB_HOST = 'localhost'; const DB_USER = 'root'; const DB_PASS = 'intermuny88'; const DB_NAME = 'cenonet'; const DB_CHARSET = 'utf8mb4'; /* SUGEROWANE INDEKSY SQL DLA BAZY 30GB (Uruchom w phpMyAdmin): 1. CREATE INDEX idx_title_ft ON produkty(title); -- Dla szybkiego wyszukiwania 2. CREATE INDEX idx_cats ON produkty(kat_1, kat_2); -- Dla drzewa kategorii 3. CREATE INDEX idx_brand ON produkty(brand); -- Dla podobnych produktów 4. CREATE INDEX idx_price_sale ON produkty(price, sale_price); -- Dla sortowania okazji */ const CACHE_ENABLED = true; const CACHE_DIR = '/dev/shm/'; // Używamy RAM dysku jeśli dostępny, dla szybkości const CACHE_PREFIX = 'cenonet_v4_opt_'; const CACHE_TIME = 3600; // 1h const QUERY_CACHE_TIME = 600; // --- USTAWIENIA DODATKOWE --- const ENABLE_MASCOT = true; const ENABLE_MOBILE_ZOOM_OPT = true; // --- ZWROTY ASYSTENTA ZAKUPOWEGO (TASK 2) --- $assistantPhrases = [ "Cześć! Wpisz czego szukasz, a przeszukam dla Ciebie tysiące ofert.", "Znajdę dla Ciebie najlepszą cenę, tylko podaj nazwę produktu.", "Szukasz prezentu? Powiedz mi dla kogo, a znajdę coś ekstra.", "Dziś mamy świetne promocje na elektronikę. Czego potrzebujesz?", "Oszczędzaj czas – wpisz nazwę, a ja pokażę Ci konkrety.", "Potrzebujesz pomocy w wyborze? Jestem do usług.", "Wyszukuję oferty z prędkością światła. Sprawdź mnie!", "Nie przepłacaj. Znajdę dla Ciebie najtańszą opcję.", "Szukasz butów, laptopa czy może nowej torebki?", "Twoje zadowolenie to mój priorytet. Co mogę znaleźć?", "Gotowy na łowy? Wpisz frazę i zaczynamy.", "Przeszukuję magazyny w czasie rzeczywistym. Co Cię interesuje?", "Lubisz okazje? Ja też! Znajdźmy coś w super cenie.", "Pomogę Ci wybrać mądrze. Czego szukasz?", "Znam się na cenach jak nikt inny. Zapytaj o produkt.", "Szukasz inspiracji? Wpisz kategorię, np. 'zegarki'.", "Szybkie zakupy to moja specjalność.", "Chcesz porównać ceny? Podaj model produktu.", "Znajdźmy razem coś wyjątkowego.", "Jestem Twoim osobistym doradcą zakupowym.", "Wpisz markę, którą lubisz, a pokażę co mamy.", "Szukasz nowości czy wyprzedaży?", "Pomogę Ci trafić w dziesiątkę z zakupem.", "Zakupy bez stresu? Zostaw to mi.", "Czekam na Twoje polecenie. Co wpisujemy?", "Mam nosa do promocji. Sprawdź co znalazłem.", "Technologia, moda, dom? Przeszukam wszystko.", "Zacznijmy poszukiwania najlepszych ofert.", "Wpisz czego potrzebujesz, a ja zajmę się resztą.", "Cześć! W czym mogę Ci dziś pomóc?" ]; $shoppingQuotes = [ "Kto kupuje to, czego nie potrzebuje, kradnie samemu sobie.", "Zawsze jest dobra pora na małe zakupy.", "Pieniądze szczęścia nie dają, ale zakupy tak.", "Życie jest krótkie, kup te buty.", "Promocja to moje drugie imię.", "Oszczędzanie jest super, ale wydawanie jeszcze lepsze.", "Styl to sposób na powiedzenie kim jesteś bez słów.", "Jakość pamięta się o wiele dłużej niż cenę.", "Zakupy online: bo nie trzeba zakładać spodni.", "Twój koszyk czuje się samotny.", "Dzień bez zakupów to dzień stracony.", "Wyprzedaż to jedyny wyścig, w którym wszyscy wygrywają.", "Cenonet - tu zaczynają się dobre decyzje." ]; // --- SYSTEM CACHE --- function getCacheKey(): string { $prefix = (isset($_GET['view']) && $_GET['view'] === 'category') ? 'cat_' : 'page_'; $device = strpos($_SERVER['HTTP_USER_AGENT'] ?? '', 'Mobile') !== false ? 'm_' : 'd_'; return CACHE_DIR . CACHE_PREFIX . $prefix . $device . md5($_SERVER['REQUEST_URI'] . serialize($_GET)); } function serveCache(): void { if (!CACHE_ENABLED || isset($_GET['nocache']) || $_SERVER['REQUEST_METHOD'] === 'POST') return; $file = getCacheKey(); if (file_exists($file) && (time() - filemtime($file) < CACHE_TIME) && filesize($file) > 500) { $content = file_get_contents($file); if (str_starts_with($content, "\x1f\x8b")) { if (str_contains($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', 'gzip')) { header('Content-Encoding: gzip'); echo $content; } else { echo gzdecode($content); } } else { echo $content; } exit; } } function saveCache(string $content): string { if (!CACHE_ENABLED || isset($_GET['nocache']) || $_SERVER['REQUEST_METHOD'] === 'POST') return $content; if (strlen($content) < 1000) return $content; $file = getCacheKey(); $compressed = gzencode($content, 6); @file_put_contents($file, $compressed); return $content; } // --- CACHE ZAPYTAŃ (JSON) --- function getQueryCache($key) { if (!CACHE_ENABLED) return null; $file = CACHE_DIR . 'sql_' . md5($key) . '.json'; if (file_exists($file) && (time() - filemtime($file) < QUERY_CACHE_TIME)) { return json_decode(file_get_contents($file), true); } return null; } function saveQueryCache($key, $data) { if (!CACHE_ENABLED) return; $file = CACHE_DIR . 'sql_' . md5($key) . '.json'; @file_put_contents($file, json_encode($data)); } // --- BAZA DANYCH (TASK 5: Strona błędu z LocalStorage) --- function getDB(): mysqli { static $mysqli; if (!$mysqli) { try { $mysqli = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME); if ($mysqli->connect_errno) throw new Exception("DB Error: " . $mysqli->connect_error); $mysqli->set_charset(DB_CHARSET); } catch (Exception $e) { http_response_code(503); die(' Przerwa techniczna - Cenonet

Serwery są przeciążone

Mamy ogromne zainteresowanie ofertami. Przepraszamy za utrudnienia. Strona odświeży się automatycznie za 10 sekund.

'); } } return $mysqli; } // --- HELPERY --- function url_slug(string $str): string { $pl = ['ą'=>'a','ć'=>'c','ę'=>'e','ł'=>'l','ń'=>'n','ó'=>'o','ś'=>'s','ź'=>'z','ż'=>'z','Ą'=>'A','Ć'=>'C','Ę'=>'E','Ł'=>'L','Ń'=>'N','Ó'=>'O','Ś'=>'S','Ź'=>'Z','Ż'=>'Z']; $str = strtr($str, $pl); $str = preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', $str); $str = preg_replace("/[\/_|+ -]+/", '-', $str); return trim(strtolower($str), '-'); } function formatPrice($price, $salePrice): array { $finalPrice = !empty($salePrice) ? str_replace(["PLN ", "PLN"], "", $salePrice) : $price; $finalPrice = str_replace(",", ".", (string)$finalPrice); if (empty($finalPrice) || !is_numeric($finalPrice)) $finalPrice = "0.00"; $parts = explode('.', number_format((float)$finalPrice, 2, '.', '')); return ['display' => $finalPrice, 'main' => $parts[0], 'fraction' => $parts[1]]; } function cleanImgUrl($url) { return str_replace("https://static.convertiser.com/", "/img/", $url); } function renderCard($row, $forceInternal = true) { $img = cleanImgUrl($row['image_link']); $price = formatPrice($row['price'], $row['sale_price']); $internalUrl = "/produkt/{$row['idd']}/" . url_slug($row['title']); $targetUrl = $forceInternal ? $internalUrl : (!empty($row['link']) ? $row['link'] : $internalUrl); $targetAttr = $forceInternal ? '_self' : '_blank'; echo '
'.htmlspecialchars($row['title']).'
'.htmlspecialchars($row['brand'] ?? '').'
'.htmlspecialchars($row['title']).'
'.$price['main'].','.$price['fraction'].' zł
'; } // --- ROUTER --- $view = $_GET['view'] ?? 'home'; // AJAX: Wyszukiwarka (TASK 2: Szybkie zapytania) if ($view === 'ajax_search') { header('Content-Type: text/html; charset=utf-8'); $q = trim($_GET['q'] ?? ''); $offset = (int)($_GET['offset'] ?? 0); $limit = 12; $db = getDB(); // TASK 3: Optymalizacja - tylko niezbędne kolumny $cols = "idd, title, image_link, price, sale_price, brand, link"; if (mb_strlen($q) < 2) { $sql = "SELECT $cols FROM produkty ORDER BY idd DESC LIMIT ?, ?"; $stmt = $db->prepare($sql); $stmt->bind_param('ii', $offset, $limit); } else { // Używamy LIKE dla kompatybilności, ale zalecany FULLTEXT $sql = "SELECT $cols FROM produkty WHERE title LIKE ? LIMIT ?, ?"; $stmt = $db->prepare($sql); $searchQuery = '%' . $q . '%'; $stmt->bind_param('sii', $searchQuery, $offset, $limit); } $stmt->execute(); $res = $stmt->get_result(); if ($res->num_rows == 0 && $offset == 0) { echo '
Nie znaleźliśmy dokładnie tego, ale zobacz te okazje:
'; $fallbackRes = $db->query("SELECT $cols FROM produkty ORDER BY idd DESC LIMIT 12"); while ($row = $fallbackRes->fetch_assoc()) { renderSearchItem($row); } } else { while ($row = $res->fetch_assoc()) { renderSearchItem($row); } if ($res->num_rows == $limit) { echo '
'; } } exit; } function renderSearchItem($row) { $img = cleanImgUrl($row['image_link']); $price = formatPrice($row['price'], $row['sale_price']); $link = "/produkt/{$row['idd']}/" . url_slug($row['title']); echo '
img
'.htmlspecialchars($row['title']).'
'.$price['main'].','.$price['fraction'].' zł
'; } // AJAX: Asystent Zakupowy (TASK 2: Zmiana logiki na SQL Search) if ($view === 'ajax_chat_bot') { header('Content-Type: application/json'); $q = trim($_GET['q'] ?? ''); $db = getDB(); // Brak sztucznego opóźnienia (usleep usunięte dla wydajności) // Wyszukiwanie produktów (Limit 4) $sql = "SELECT idd, title, image_link, price, sale_price, link FROM produkty WHERE title LIKE ? ORDER BY sale_price > 0 DESC, idd DESC LIMIT 4"; $stmt = $db->prepare($sql); $param = "%$q%"; $stmt->bind_param('s', $param); $stmt->execute(); $res = $stmt->get_result(); $products = []; while($row = $res->fetch_assoc()) { $row['image_link'] = cleanImgUrl($row['image_link']); $p = formatPrice($row['price'], $row['sale_price']); $row['price_fmt'] = $p['main'] . ',' . $p['fraction'] . ' zł'; $row['url'] = "/produkt/{$row['idd']}/" . url_slug($row['title']); $products[] = $row; } $response = [ 'message' => count($products) > 0 ? "Znalazłem dla Ciebie te okazje pasujące do \"$q\". Zobacz:" : "Przepraszam, nie znalazłem dokładnie tego produktu, ale szukam dalej w innych kategoriach...", 'products' => $products ]; echo json_encode($response); exit; } // AJAX: Dynamiczne Podkategorie (TASK 4: Cache sesji) if ($view === 'ajax_subcats') { header('Content-Type: application/json'); $parent = $_GET['parent'] ?? ''; if(!$parent) exit; // Cache po stronie serwera (1h) $cacheKey = 'subcat_' . md5($parent); $data = getQueryCache($cacheKey); if ($data === null) { $db = getDB(); $stmt = $db->prepare("SELECT DISTINCT kat_2 FROM produkty WHERE kat_1 = ? AND kat_2 != '' LIMIT 20"); $stmt->bind_param('s', $parent); $stmt->execute(); $res = $stmt->get_result(); $data = []; while($row = $res->fetch_assoc()) { $data[] = [ 'name' => $row['kat_2'], 'url' => '/kategoria/' . url_slug($parent) . '/' . url_slug($row['kat_2']) ]; } saveQueryCache($cacheKey, $data); } echo json_encode($data); exit; } // AJAX: Modal Rekomendacji (TASK 5: Pop-up "Wybrane dla Ciebie") if ($view === 'ajax_modal_recs') { header('Content-Type: application/json'); $db = getDB(); $history = isset($_GET['history']) ? explode(',', $_GET['history']) : []; $historyIds = array_filter(array_map('intval', $history)); $recs = []; // 1. Jeśli jest historia -> Produkty z tej samej kategorii if (!empty($historyIds)) { $lastId = $historyIds[0]; $stmtCat = $db->prepare("SELECT kat_1 FROM produkty WHERE idd = ?"); $stmtCat->bind_param('i', $lastId); $stmtCat->execute(); $catRes = $stmtCat->get_result()->fetch_assoc(); if ($catRes) { $cat = $catRes['kat_1']; $stmt = $db->prepare("SELECT idd, title, image_link, price, sale_price, link FROM produkty WHERE kat_1 = ? AND idd != ? LIMIT 4"); $stmt->bind_param('si', $cat, $lastId); $stmt->execute(); $res = $stmt->get_result(); while($r = $res->fetch_assoc()) $recs[] = $r; } } // 2. Jeśli brak historii -> Globalne Top (Cache) if (empty($recs)) { $cacheKey = 'recs_top_global'; $cachedRecs = getQueryCache($cacheKey); if ($cachedRecs !== null) { $recs = $cachedRecs; } else { $sql = "SELECT idd, title, image_link, price, sale_price, link FROM produkty WHERE sale_price > 0 ORDER BY (price - sale_price) DESC LIMIT 4"; $res = $db->query($sql); while($r = $res->fetch_assoc()) $recs[] = $r; saveQueryCache($cacheKey, $recs); } } foreach($recs as &$row) { $row['image_link'] = cleanImgUrl($row['image_link']); $p = formatPrice($row['price'], $row['sale_price']); $row['price_fmt'] = $p['main'] . ',' . $p['fraction'] . ' zł'; $row['target_link'] = "/produkt/{$row['idd']}/" . url_slug($row['title']); } echo json_encode($recs); exit; } // AJAX: Ładowanie więcej if ($view === 'ajax_more') { $offset = (int)($_GET['offset'] ?? 0); $limit = 10; $db = getDB(); $res = $db->query("SELECT idd, title, image_link, price, sale_price, brand, link FROM produkty ORDER BY idd DESC LIMIT $offset, $limit"); while($row = $res->fetch_assoc()) { renderCard($row); } exit; } serveCache(); ob_start('saveCache'); // --- LOGIKA WIDOKÓW --- $breadcrumbs = []; $pageData = []; // 1. PRODUKT if ($view === 'product') { $id = (int)($_GET['id'] ?? 0); $db = getDB(); // TASK 3: Unikaj SELECT * $stmt = $db->prepare("SELECT idd, title, description, image_link, price, sale_price, brand, link, kat_1, kat_2 FROM produkty WHERE idd = ?"); $stmt->bind_param('i', $id); $stmt->execute(); $product = $stmt->get_result()->fetch_assoc(); if (!$product) { header("Location: /", true, 302); exit; } $pageData = $product; $pageData['price_fmt'] = formatPrice($product['price'], $product['sale_price']); $breadcrumbs[] = ['name' => 'Strona główna', 'url' => '/']; if(!empty($product["kat_1"])) { $breadcrumbs[] = ['name' => $product["kat_1"], 'url' => '/kategoria/' . url_slug($product["kat_1"]) . '/']; } $breadcrumbs[] = ['name' => $product['title'], 'url' => '']; $metaTitle = $product['title'] . " - Najlepsza Cena"; $metaDesc = mb_substr(strip_tags($product['description']), 0, 160); } // 2. KATEGORIA if ($view === 'category') { $cats = []; $where = []; $params = []; $types = ""; $breadcrumbs[] = ['name' => 'Strona główna', 'url' => '/']; for($i=1; $i<=9; $i++) { if (!empty($_GET["p$i"])) { $val = urldecode($_GET["p$i"]); $val = strip_tags($val); $dbVal = str_replace('-', ' ', $val); if(empty($val)) continue; $cats[] = $val; $where[] = "(`kat_$i` LIKE ? OR `kat_$i` LIKE ?)"; $params[] = $dbVal; $params[] = $val; $types .= "ss"; $crumbUrl = '/kategoria/' . implode('/', array_map('url_slug', $cats)) . '/'; $breadcrumbs[] = ['name' => ucwords(strtolower($dbVal)), 'url' => $crumbUrl]; } } $page = max(1, (int)($_GET['pageno'] ?? 1)); $limit = 35; $offset = ($page - 1) * $limit; $db = getDB(); $sqlWhere = implode(" AND ", $where); if(empty($sqlWhere)) $sqlWhere = "1=1"; // CACHE DLA COUNT $cacheKeyCount = "count_" . md5($sqlWhere . serialize($params)); $totalRows = getQueryCache($cacheKeyCount); if ($totalRows === null) { $stmt = $db->prepare("SELECT COUNT(idd) FROM produkty WHERE $sqlWhere"); if($params) $stmt->bind_param($types, ...$params); $stmt->execute(); $totalRows = $stmt->get_result()->fetch_row()[0]; saveQueryCache($cacheKeyCount, $totalRows); } $totalPages = ceil($totalRows / $limit); // CACHE DLA PRODUKTÓW $cacheKeyProds = "prods_" . md5($sqlWhere . serialize($params) . $offset . $limit); $cachedProds = getQueryCache($cacheKeyProds); if ($cachedProds !== null) { $products = $cachedProds; } else { $stmt = $db->prepare("SELECT idd, title, image_link, price, sale_price, brand, link FROM produkty WHERE $sqlWhere ORDER BY idd DESC LIMIT ?, ?"); $paramsProd = $params; $paramsProd[] = $offset; $paramsProd[] = $limit; $typesProd = $types . "ii"; $stmt->bind_param($typesProd, ...$paramsProd); $stmt->execute(); $res = $stmt->get_result(); $products = []; while($r = $res->fetch_assoc()) $products[] = $r; saveQueryCache($cacheKeyProds, $products); } $catNameRaw = end($cats) ?: 'Wszystkie kategorie'; $catName = str_replace('-', ' ', $catNameRaw); $metaTitle = ucwords($catName) . " - Promocje"; } // 3. HOME / OFERTY if ($view === 'home' || $view === 'offers') { $db = getDB(); $cols = "idd, title, image_link, price, sale_price, brand, link"; if ($view === 'home') { // Cache dla Editors Choice $ecKey = "home_ec"; $editorsChoice = getQueryCache($ecKey); if ($editorsChoice === null) { $res = $db->query("SELECT $cols, (price - sale_price) as discount FROM produkty WHERE sale_price > 0 AND sale_price < price ORDER BY idd DESC LIMIT 8"); $editorsChoice = []; while($r = $res->fetch_assoc()) $editorsChoice[] = $r; saveQueryCache($ecKey, $editorsChoice); } // Cache dla Home Products $hpKey = "home_prods"; $products = getQueryCache($hpKey); if ($products === null) { $res = $db->query("SELECT $cols FROM produkty ORDER BY idd DESC LIMIT 25"); $products = []; while($r = $res->fetch_assoc()) $products[] = $r; saveQueryCache($hpKey, $products); } } else { $products = $db->query("SELECT $cols FROM produkty ORDER BY idd DESC LIMIT 60"); } $metaTitle = "CENONET - Pomagamy Ci wybrać mądrze"; } ?> Cenonet