Podatki > Reguły podatkowe $configTaxRulesGroupId = 1; // ID Kategorii domyślnej, do której mają trafić produkty (np. "Kinkiety" lub ogólna) $configDefaultCategoryId = 12; // Czy włączyć produkt po imporcie? $configActive = 1; // ============================================ // Sprawdzenie trybu działania $modeAdd = (Tools::getValue('add') === 'true'); $modeUpdate = (Tools::getValue('update') === 'true'); $modeSynch = (Tools::getValue('synch') === 'true'); if (!$modeAdd && !$modeUpdate && !$modeSynch) { die('Brak akcji. Dodaj do adresu ?add=true lub ?update=true lub ?synch=true'); } // Plik logu $logFile = __DIR__ . '/sollux_import_log.csv'; // Wczytanie XML $xmlUrl = 'https://sollux-lighting.com/product_feed/sollux/XML_polski_PLN_wszystkie_vip.xml'; $xml = simplexml_load_file($xmlUrl) or die("Error: Cannot create object from XML"); // === FUNKCJE POMOCNICZE === // Tworzenie pola multilang (wymagane przez Prestę) function createMultiLangField($field) { $languages = Language::getLanguages(false); $res = []; foreach ($languages as $lang) { $res[(int)$lang['id_lang']] = $field; } return $res; } // Tworzenie przyjaznego linku function createLinkRewrite($field) { $languages = Language::getLanguages(false); $res = []; $linkRewrite = Tools::link_rewrite($field); foreach ($languages as $lang) { $res[(int)$lang['id_lang']] = $linkRewrite; } return $res; } // Pobieranie zdjęcia function addProductImage($productId, $imageUrl) { if (empty($imageUrl)) return false; $image = new Image(); $image->id_product = (int)$productId; $image->position = Image::getHighestPosition($productId) + 1; $image->cover = ($image->position == 1); // Pierwsze zdjęcie jako okładka if (!$image->add()) return false; $imagePath = $image->getPathForCreation(); $url = str_replace(' ', '%20', trim($imageUrl)); // Próba pobrania if (!@copy($url, $imagePath . '.jpg')) { $image->delete(); return false; } // Generowanie miniatur $imageTypes = ImageType::getImagesTypes('products'); foreach ($imageTypes as $imageType) { if (!ImageManager::resize( $imagePath . '.jpg', $imagePath . '-' . stripslashes($imageType['name']) . '.jpg', (int)$imageType['width'], (int)$imageType['height'] )) { // W razie błędu resize można obsłużyć wyjątek, tutaj cicho pomijamy } } return true; } // Znajdź produkt po SKU (reference) function findProductByReference($reference) { if (empty($reference)) return false; $sql = 'SELECT `id_product` FROM `'._DB_PREFIX_.'product` WHERE `reference` = \''.pSQL($reference).'\''; $result = Db::getInstance()->getRow($sql); return $result ? new Product((int)$result['id_product']) : false; } // Parsowanie ceny (zamiana przecinków na kropki, usuwanie spacji) function parsePrice($rawPrice) { $clean = str_replace([' ', ','], ['', '.'], (string)$rawPrice); return (float)$clean; } // Budowanie tabeli HTML z atrybutów XML function buildAttributesHtml($xmlAttributesNode) { if (!$xmlAttributesNode || !isset($xmlAttributesNode->attribute)) { return ''; } $html = '
'; $html .= '

Specyfikacja produktu:

'; $html .= ''; $html .= ''; foreach ($xmlAttributesNode->attribute as $attr) { $name = (string)$attr->attribute_name; $value = (string)$attr->attribute_value; if (!empty($name) && !empty($value) && $value !== 'Nie dotyczy') { $html .= ''; $html .= ''; $html .= ''; $html .= ''; } } $html .= ''; $html .= '
' . htmlspecialchars($name) . '' . htmlspecialchars($value) . '
'; $html .= '
'; return $html; } // Czyści log aktualizacji - zostawia X dni function cleanLogFile($logFile, $days = 7) { if (!file_exists($logFile)) return; $content = file($logFile); $newContent = []; $cutoff = strtotime("-$days days"); foreach ($content as $line) { $parts = explode(';', $line); if (isset($parts[0]) && strtotime($parts[0]) >= $cutoff) { $newContent[] = $line; } } file_put_contents($logFile, implode("", $newContent)); } // ============================================ // ================ LOGIKA ==================== // ============================================ // 1. TRYB SYNCHRONIZACJI (Włączanie/Wyłączanie produktów) if ($modeSynch) { echo '

Synchronizacja stanów (Active/Inactive)...

'; $db = Db::getInstance(); // Tabela tymczasowa na SKU z XML $db->execute('CREATE TABLE IF NOT EXISTS `'._DB_PREFIX_.'sollux_temp` (`sku` VARCHAR(64), PRIMARY KEY (`sku`)) ENGINE='._MYSQL_ENGINE_); $db->execute('TRUNCATE TABLE `'._DB_PREFIX_.'sollux_temp`'); // Zbieranie SKU z XML $sqlValues = []; foreach ($xml->product as $productNode) { $sku = trim((string)$productNode->sku); if ($sku) { $sqlValues[] = '(\''.pSQL($sku).'\')'; } // Wstawiamy paczkami po 100, żeby nie zapchać SQL if (count($sqlValues) >= 100) { $db->execute('INSERT IGNORE INTO `'._DB_PREFIX_.'sollux_temp` (`sku`) VALUES '.implode(',', $sqlValues)); $sqlValues = []; } } if (!empty($sqlValues)) { $db->execute('INSERT IGNORE INTO `'._DB_PREFIX_.'sollux_temp` (`sku`) VALUES '.implode(',', $sqlValues)); } // Wyłącz produkty, których nie ma w XML a są w bazie (i są powiązane z tym producentem, opcjonalnie) // Zakładamy, że sprawdzamy tylko produkty, które mają SKU. $sqlDisable = 'UPDATE `'._DB_PREFIX_.'product` p LEFT JOIN `'._DB_PREFIX_.'sollux_temp` t ON p.reference = t.sku SET p.active = 0 WHERE t.sku IS NULL AND p.id_manufacturer = '.(int)$configManufacturerId; // Bezpiecznik: wyłączamy tylko produkty tego producenta $db->execute($sqlDisable); echo "

Wyłączono produkty niedostępne w feedzie.

"; // Włącz produkty, które są w XML $sqlEnable = 'UPDATE `'._DB_PREFIX_.'product` p JOIN `'._DB_PREFIX_.'sollux_temp` t ON p.reference = t.sku SET p.active = 1 WHERE p.active = 0'; $db->execute($sqlEnable); echo "

Włączono produkty dostępne w feedzie.

"; // Posprzątaj $db->execute('DROP TABLE `'._DB_PREFIX_.'sollux_temp`'); echo '

Zakończono synchronizację.

'; exit; } // 2. TRYB AKTUALIZACJI (Ceny, Stany) if ($modeUpdate) { cleanLogFile($logFile); $today = date('Y-m-d'); $updatedCount = 0; // Wczytaj log, aby nie aktualizować tego samego produktu wielokrotnie tego samego dnia $processedSkus = []; if (file_exists($logFile)) { $lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { $data = explode(';', $line); // Date;SKU if (isset($data[0]) && $data[0] == $today && isset($data[1])) { $processedSkus[trim($data[1])] = true; } } } echo "

Rozpoczynam aktualizację...

"; foreach ($xml->product as $productNode) { $sku = trim((string)$productNode->sku); if (empty($sku)) continue; // Jeśli już zrobiony dzisiaj - pomiń if (isset($processedSkus[$sku])) continue; $product = findProductByReference($sku); if (!$product) { // Produkt nie istnieje w bazie - w trybie update pomijamy continue; } // --- Aktualizacja Ceny --- $priceGross = parsePrice($productNode->cena_detaliczna_brutto_pln); $priceNet = Tools::ps_round($priceGross / 1.23, 6); // Zakładamy 23% VAT od brutto $product->price = $priceNet; $product->active = 1; // Przywracamy aktywność przy update // --- Aktualizacja Stanu (Domyślnie 100 jeśli jest w pliku) --- // XML nie ma pola "qty", ale ma "paczkomat" i "orientacyjny czas". // Zakładamy: jest w XML = dostępny. StockAvailable::setQuantity($product->id, 0, 100); $product->update(); // Logowanie file_put_contents($logFile, $today.';'.$sku.PHP_EOL, FILE_APPEND); echo "

Zaktualizowano: $sku (Cena netto: $priceNet)

"; $updatedCount++; // Aktualizujemy 1 produkt na wywołanie, żeby nie przekroczyć czasu wykonywania? // W poprzednim skrypcie był break. Jeśli masz crona co minutę, odkomentuj break. // Jeśli odpalasz ręcznie w przeglądarce, break spowoduje konieczność ciągłego odświeżania. // Zostawiam break dla bezpieczeństwa, odświeżaj stronę (meta reload na dole). break; } if ($updatedCount > 0) { echo ''; echo '

Trwa przeładowanie strony...

'; } else { echo '

Wszystkie produkty z XML zostały już dzisiaj zaktualizowane.

'; } exit; } // 3. TRYB DODAWANIA (ADD) if ($modeAdd) { $addedCount = 0; foreach ($xml->product as $productNode) { $sku = trim((string)$productNode->sku); if (empty($sku)) continue; // Sprawdź czy produkt już istnieje if (findProductByReference($sku)) { // Produkt istnieje, w trybie ADD nic nie robimy (ewentualnie można zrobić update) continue; } // === TWORZENIE NOWEGO PRODUKTU === $product = new Product(); $product->reference = $sku; $product->ean13 = trim((string)$productNode->ean); $product->name = createMultiLangField(trim((string)$productNode->nazwa_produktu)); // Kategoria i Producent $product->id_category_default = (int)$configDefaultCategoryId; $product->id_manufacturer = (int)$configManufacturerId; $product->id_tax_rules_group = (int)$configTaxRulesGroupId; // VAT // Cena $priceGross = parsePrice($productNode->cena_detaliczna_brutto_pln); $product->price = Tools::ps_round($priceGross / 1.23, 6); // Wymiary i Waga $product->width = (float)$productNode->szerokosc; $product->height = (float)$productNode->wysokosc; $product->depth = (float)$productNode->dlugosc; $product->weight = (float)$productNode->waga_produktu; // Opisy $shortDesc = trim((string)$productNode->opis_krotki_html); $longDescRaw = trim((string)$productNode->opis_dlugi_korzysci_html); // Generowanie tabeli specyfikacji z atrybutów $attributesHtml = buildAttributesHtml($productNode->attributes); // Sklejenie opisu: Opis marketingowy + Tabela atrybutów $finalLongDesc = $longDescRaw . '

' . $attributesHtml; $product->description_short = createMultiLangField($shortDesc); $product->description = createMultiLangField($finalLongDesc); $product->link_rewrite = createLinkRewrite((string)$productNode->nazwa_produktu); $product->active = (int)$configActive; // Dodanie do bazy if ($product->add()) { // Przypisanie do kategorii (domyślna + drzewo) $product->addToCategories([(int)$configDefaultCategoryId]); // Ustawienie stanu magazynowego (100) StockAvailable::setQuantity($product->id, 0, 100); // === ZDJĘCIA === // Loop przez image_1 do image_25 for ($i = 1; $i <= 25; $i++) { $imgTag = 'image_' . $i; $imgUrl = (string)$productNode->$imgTag; if (!empty($imgUrl)) { addProductImage($product->id, $imgUrl); } } echo "

Dodano produkt: " . $product->name[Context::getContext()->language->id] . " ($sku)

"; $addedCount++; // Break po dodaniu jednego, aby odciążyć serwer (skrypt musi być wywoływany cyklicznie) break; } else { echo "

Błąd podczas dodawania produktu: $sku

"; } } if ($addedCount > 0) { echo ''; echo '

Dodano produkt. Odświeżanie...

'; } else { echo '

Brak nowych produktów do dodania (lub wszystkie już istnieją).

'; } } ?>