430 lines
15 KiB
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>';
|
|
}
|
|
}
|
|
?>
|