Files
interblue.pl/import-products.php

430 lines
15 KiB
PHP

<?php
// URL do pliku XML:
// https://sollux-lighting.com/product_feed/sollux/XML_polski_PLN_wszystkie_vip.xml
// START POMIARU CAŁEGO SKRYPTU
$scriptStartTime = microtime(true);
// Załaduj konfigurację PrestaShop
include(dirname(__FILE__).'/config/config.inc.php');
include(dirname(__FILE__).'/init.php');
$context = Context::getContext();
// ============================================
// =============== KONFIGURACJA ===============
// ============================================
// ID Producenta (Sollux Lighting) - podaj ID z PrestaShop
$configManufacturerId = 198;
// ID Grupy Podatkowej (np. dla 23% VAT)
$configTaxRulesGroupId = 1;
// ID Kategorii domyślnej, do której mają trafić produkty
$configDefaultCategoryId = 1072;
// Czy włączyć produkt po imporcie?
$configActive = 1;
// --- KONFIGURACJA CECHY (FEATURE) ---
// ID Cechy do przypisania (np. 20)
$configFeatureId = 20;
// ID Wartości Cechy do przypisania (np. 19002)
$configFeatureValueId = 19002;
// ============================================
// 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';
// MIERZENIE: Ładowanie XML
$tXmlStart = microtime(true);
$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");
$tXmlEnd = microtime(true);
$tXmlDuration = $tXmlEnd - $tXmlStart; // Czas parsowania XML
// === FUNKCJE POMOCNICZE ===
// Formatowanie czasu do wyświetlania
function formatTime($time) {
return number_format($time, 4) . ' s';
}
// 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 = '<div class="product-specs">';
$html .= '<h3>Specyfikacja produktu:</h3>';
$html .= '<table class="table table-bordered table-striped" style="width:100%; border-collapse: collapse;">';
$html .= '<tbody>';
foreach ($xmlAttributesNode->attribute as $attr) {
$name = (string)$attr->attribute_name;
$value = (string)$attr->attribute_value;
if (!empty($name) && !empty($value) && $value !== 'Nie dotyczy') {
$html .= '<tr>';
$html .= '<td style="padding: 5px; border: 1px solid #ddd; font-weight: bold; width: 40%;">' . htmlspecialchars($name) . '</td>';
$html .= '<td style="padding: 5px; border: 1px solid #ddd;">' . htmlspecialchars($value) . '</td>';
$html .= '</tr>';
}
}
$html .= '</tbody>';
$html .= '</table>';
$html .= '</div>';
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 '<h2>Synchronizacja stanów (Active/Inactive)...</h2>';
$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
$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;
$db->execute($sqlDisable);
echo "<p>Wyłączono produkty niedostępne w feedzie.</p>";
// 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 "<p>Włączono produkty dostępne w feedzie.</p>";
// Posprzątaj
$db->execute('DROP TABLE `'._DB_PREFIX_.'sollux_temp`');
$totalTime = microtime(true) - $scriptStartTime;
echo '<p>Zakończono synchronizację. Całkowity czas: '.formatTime($totalTime).'</p>';
exit;
}
// 2. TRYB AKTUALIZACJI (Ceny, Stany)
if ($modeUpdate) {
cleanLogFile($logFile);
$today = date('Y-m-d');
$updatedCount = 0;
// Wczytaj log
$processedSkus = [];
if (file_exists($logFile)) {
$lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$data = explode(';', $line);
if (isset($data[0]) && $data[0] == $today && isset($data[1])) {
$processedSkus[trim($data[1])] = true;
}
}
}
echo "<h2>Rozpoczynam aktualizację...</h2>";
foreach ($xml->product as $productNode) {
$sku = trim((string)$productNode->sku);
if (empty($sku)) continue;
if (isset($processedSkus[$sku])) continue;
// MIERZENIE: Szukanie produktu
$tStartCheck = microtime(true);
$product = findProductByReference($sku);
$tCheckDuration = microtime(true) - $tStartCheck;
if (!$product) {
continue;
}
// MIERZENIE: Update produktu
$tStartUpdate = microtime(true);
$priceGross = parsePrice($productNode->cena_detaliczna_brutto_pln);
$priceNet = Tools::ps_round($priceGross / 1.23, 6);
$product->price = $priceNet;
$product->active = 1;
StockAvailable::setQuantity($product->id, 0, 100);
$product->update();
$tUpdateDuration = microtime(true) - $tStartUpdate;
// Logowanie
file_put_contents($logFile, $today.';'.$sku.PHP_EOL, FILE_APPEND);
echo "<p>Zaktualizowano: $sku (Cena netto: $priceNet)</p>";
// RAPORT UPDATE
echo '<div style="background:#f0f0f0; border:1px solid #ccc; padding:10px; margin:10px 0;">';
echo '<strong>Raport czasu (Update):</strong><br>';
echo 'Szukanie w bazie: ' . formatTime($tCheckDuration) . '<br>';
echo 'Zapis update (SQL): ' . formatTime($tUpdateDuration) . '<br>';
echo '</div>';
$updatedCount++;
break;
}
if ($updatedCount > 0) {
echo '<script>setTimeout(function(){ location.reload(); }, 200);</script>';
echo '<p>Trwa przeładowanie strony...</p>';
} else {
echo '<p>Wszystkie produkty z XML zostały już dzisiaj zaktualizowane.</p>';
}
exit;
}
// 3. TRYB DODAWANIA (ADD)
if ($modeAdd) {
$addedCount = 0;
foreach ($xml->product as $productNode) {
$sku = trim((string)$productNode->sku);
if (empty($sku)) continue;
// MIERZENIE: Sprawdzenie istnienia
$tStartCheck = microtime(true);
$exists = findProductByReference($sku);
$tCheckDuration = microtime(true) - $tStartCheck;
if ($exists) {
continue;
}
// MIERZENIE: Przygotowanie obiektu (Pamięć)
$tStartPrep = microtime(true);
$product = new Product();
$product->reference = $sku;
$product->ean13 = trim((string)$productNode->ean);
$product->name = createMultiLangField(trim((string)$productNode->nazwa_produktu));
$product->id_category_default = (int)$configDefaultCategoryId;
$product->id_manufacturer = (int)$configManufacturerId;
$product->id_tax_rules_group = (int)$configTaxRulesGroupId;
$priceGross = parsePrice($productNode->cena_detaliczna_brutto_pln);
$product->price = Tools::ps_round($priceGross / 1.23, 6);
$product->width = (float)$productNode->szerokosc;
$product->height = (float)$productNode->wysokosc;
$product->depth = (float)$productNode->dlugosc;
$product->weight = (float)$productNode->waga_produktu;
$shortDesc = trim((string)$productNode->opis_krotki_html);
$longDescRaw = trim((string)$productNode->opis_dlugi_korzysci_html);
$attributesHtml = buildAttributesHtml($productNode->attributes);
$finalLongDesc = $longDescRaw . '<br><hr>' . $attributesHtml;
$product->description_short = createMultiLangField($shortDesc);
$product->description = createMultiLangField($finalLongDesc);
$product->link_rewrite = createLinkRewrite((string)$productNode->nazwa_produktu);
$product->active = (int)$configActive;
$tPrepDuration = microtime(true) - $tStartPrep;
// MIERZENIE: Dodanie do bazy (INSERT)
$tStartSave = microtime(true);
$saveResult = $product->add();
$tSaveDuration = microtime(true) - $tStartSave;
if ($saveResult) {
// Przypisanie do kategorii i stanu
$product->addToCategories([(int)$configDefaultCategoryId]);
StockAvailable::setQuantity($product->id, 0, 100);
// Dodanie Cechy
if (!empty($configFeatureId) && !empty($configFeatureValueId)) {
Product::addFeatureProductImport(
(int)$product->id,
(int)$configFeatureId,
(int)$configFeatureValueId
);
}
// MIERZENIE: Zdjęcia (Pobieranie + Resize)
$tStartImages = microtime(true);
$imgCount = 0;
for ($i = 1; $i <= 25; $i++) {
$imgTag = 'image_' . $i;
$imgUrl = (string)$productNode->$imgTag;
if (!empty($imgUrl)) {
if (addProductImage($product->id, $imgUrl)) {
$imgCount++;
}
}
}
$tImagesDuration = microtime(true) - $tStartImages;
echo "<p style='color:green'>Dodano produkt: " . $product->name[Context::getContext()->language->id] . " ($sku)</p>";
// --- RAPORT WYDAJNOŚCI ---
$totalProductTime = microtime(true) - $tStartCheck; // Czas od sprawdzenia do końca zdjęć
echo '<div style="border: 2px solid #333; padding: 15px; background: #fff; margin: 15px 0; font-family: monospace;">';
echo '<h3 style="margin-top:0;">⏱️ Raport czasu dla: '.$sku.'</h3>';
echo '<ul>';
echo '<li><strong>Wczytanie XML (cały plik):</strong> '.formatTime($tXmlDuration).'</li>';
echo '<li><strong>Szukanie w bazie (czy istnieje):</strong> '.formatTime($tCheckDuration).'</li>';
echo '<li><strong>Przygotowanie danych (obiekt):</strong> '.formatTime($tPrepDuration).'</li>';
echo '<li><strong>Zapis produktu do SQL (INSERT):</strong> '.formatTime($tSaveDuration).'</li>';
echo '<li><strong>ZDJĘCIA ('.$imgCount.' szt.) - Pobranie + Resize:</strong> <strong style="color:red">'.formatTime($tImagesDuration).'</strong></li>';
echo '<li>----------------------------------</li>';
echo '<li><strong>ŁĄCZNIE DLA TEGO PRODUKTU:</strong> <strong>'.formatTime($totalProductTime).'</strong></li>';
echo '</ul>';
echo '</div>';
// --------------------------
$addedCount++;
// Break po dodaniu jednego
break;
} else {
echo "<p style='color:red'>Błąd podczas dodawania produktu: $sku</p>";
}
}
if ($addedCount > 0) {
echo '<script>setTimeout(function(){ location.reload(); }, 500);</script>';
echo '<p>Dodano produkt. Odświeżanie...</p>';
} else {
echo '<p>Brak nowych produktów do dodania (lub wszystkie już istnieją).</p>';
}
}
?>