Files
masimmo.pl/import-drewmax.php

661 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// https://pacyga.pl/wp-content/uploads/woo-feed/custom/xml/komplet-produktow-3.xml
// Include PrestaShop configuration
include(dirname(__FILE__).'/config/config.inc.php');
include(dirname(__FILE__).'/init.php');
$context = Context::getContext();
// Sprawdzenie trybu działania na podstawie parametrów URL
$modeAdd = (Tools::getValue('add') === 'true');
$modeUpdate = (Tools::getValue('update') === 'true');
if (!$modeAdd && !$modeUpdate) {
die('Brak akcji. Dodaj do adresu ?add=true lub ?update=true');
}
// Plik logu aktualizacji cen
$logFile = __DIR__ . '/update_price_log.csv';
// Wczytanie XML
$xmlUrl = 'https://pacyga.pl/wp-content/uploads/woo-feed/custom/xml/komplet-produktow-3.xml';
$xml = simplexml_load_file($xmlUrl) or die("Error: Cannot create object");
// === FUNKCJE POMOCNICZE ===
// Function to find attribute group by name
function findAttributeGroupByName($name) {
$id_lang = Context::getContext()->language->id;
$sql = 'SELECT `id_attribute_group`
FROM `'._DB_PREFIX_.'attribute_group_lang`
WHERE `name` = \''.pSQL($name).'\'
AND `id_lang` = '.(int)$id_lang;
$result = Db::getInstance()->getRow($sql);
return $result ? new AttributeGroup($result['id_attribute_group']) : false;
}
// Function to find attribute by name
function findAttributeByName($id_attribute_group, $name) {
$id_lang = Context::getContext()->language->id;
$sql = 'SELECT a.`id_attribute`
FROM `'._DB_PREFIX_.'attribute` a
JOIN `'._DB_PREFIX_.'attribute_lang` al
ON a.`id_attribute` = al.`id_attribute`
WHERE al.`name` = \''.pSQL($name).'\'
AND al.`id_lang` = '.(int)$id_lang.'
AND a.`id_attribute_group` = '.(int)$id_attribute_group;
$result = Db::getInstance()->getRow($sql);
return $result ? new Attribute($result['id_attribute']) : false;
}
// Function to create attribute if it doesn't exist
function createAttribute($name, $values) {
$attributeGroup = findAttributeGroupByName($name);
if (!$attributeGroup) {
$attributeGroup = new AttributeGroup();
$attributeGroup->name = createMultiLangField($name);
$attributeGroup->public_name = createMultiLangField($name);
$attributeGroup->group_type = 'select';
$attributeGroup->add();
}
foreach ($values as $value) {
$attribute = findAttributeByName($attributeGroup->id, $value);
if (!$attribute) {
$attribute = new Attribute();
$attribute->id_attribute_group = $attributeGroup->id;
$attribute->name = createMultiLangField($value);
$attribute->add();
}
}
return $attributeGroup->id;
}
// Helper function to create a multi-language field
function createMultiLangField($field) {
$languages = Language::getLanguages(false);
$res = [];
foreach ($languages as $lang) {
$res[(int)$lang['id_lang']] = $field;
}
return $res;
}
// Helper function to create a valid link_rewrite
function createLinkRewrite($field) {
$languages = Language::getLanguages(false);
$res = [];
$linkRewrite = Tools::link_rewrite($field); // PrestaShop's function to create valid link_rewrite
foreach ($languages as $lang) {
$res[(int)$lang['id_lang']] = $linkRewrite;
}
return $res;
}
// Function to get category ID from name (nieużywana, ale poprawiona)
function getCategoryId($categoryName) {
$result = Category::searchByName(1, $categoryName); // 1 for default language id
if (!empty($result) && isset($result[0]['id_category'])) {
return (int)$result[0]['id_category'];
} else {
// Create category if not exists
$category = new Category();
$category->name = createMultiLangField($categoryName);
$category->link_rewrite = createLinkRewrite($categoryName);
$category->id_parent = 2; // Default parent category
$category->add();
return (int)$category->id;
}
}
// Function to download image from URL and associate it with a product
function addProductImage($productId, $imageUrl)
{
$image = new Image();
$image->id_product = (int)$productId;
$image->position = Image::getHighestPosition($productId) + 1;
$image->cover = true; // Set the first image as cover
$image->add();
$imagePath = $image->getPathForCreation();
$url = str_replace(' ', '%20', $imageUrl);
if (!copy($url, $imagePath . '.jpg')) {
$image->delete();
return false;
}
$imageTypes = ImageType::getImagesTypes('products');
foreach ($imageTypes as $imageType) {
ImageManager::resize(
$imagePath . '.jpg',
$imagePath . '-' . stripslashes($imageType['name']) . '.jpg',
(int)$imageType['width'],
(int)$imageType['height']
);
}
return true;
}
// Function to find product by reference
function findProductByReference($reference) {
$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;
}
// Function to find combination by product ID and attribute IDs
function findCombinationByAttributes($id_product, $attributeIds) {
if (empty($attributeIds)) {
return false;
}
sort($attributeIds);
$conditions = [];
foreach ($attributeIds as $id_attr) {
$conditions[] = 'pac.`id_attribute` = '.(int)$id_attr;
}
$sql = 'SELECT c.`id_product_attribute`
FROM `'._DB_PREFIX_.'product_attribute` c
JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
ON c.`id_product_attribute` = pac.`id_product_attribute`
WHERE c.`id_product` = '.(int)$id_product.'
GROUP BY c.`id_product_attribute`
HAVING SUM('.implode(' OR ', $conditions).') = '.count($attributeIds);
$result = Db::getInstance()->getRow($sql);
return $result ? new Combination((int)$result['id_product_attribute']) : false;
}
// znajdowanie kombinacji po indeksie/SKU (reference)
function findCombinationByReference($id_product, $reference) {
$sql = 'SELECT `id_product_attribute`
FROM `'._DB_PREFIX_.'product_attribute`
WHERE `id_product` = '.(int)$id_product.'
AND `reference` = \''.pSQL($reference).'\'';
$id = Db::getInstance()->getValue($sql);
return $id ? new Combination((int)$id) : false;
}
function parsePrice($rawPrice) {
// Na wszelki wypadek rzutujemy na string i obcinamy spacje
$rawPrice = trim((string)$rawPrice);
if ($rawPrice === '') {
return 0.0;
}
// Zostawiamy tylko cyfry, przecinek, kropkę i minus
// Usuwamy walutę, spacje, itp.
$clean = preg_replace('/[^\d,.\-]/', '', $rawPrice);
$hasComma = strpos($clean, ',') !== false;
$hasDot = strpos($clean, '.') !== false;
// Przypadek typowo polski: "1234,56" lub "125,00"
if ($hasComma && !$hasDot) {
$clean = str_replace(',', '.', $clean);
}
// Przypadek mieszany: "1.234,56" -> usuń kropki (separatory tysięcy), przecinek zamień na kropkę
elseif ($hasComma && $hasDot) {
$clean = str_replace('.', '', $clean); // usuwamy separatory tysięcy
$clean = str_replace(',', '.', $clean); // przecinek na kropkę
}
// Jeśli jest tylko kropka ("1234.56") nic nie zmieniamy
return (float)$clean;
}
// Bazowa cena dla grupy najtańszy wariant (zwraca [brutto, netto])
function getBasePricesFromGroup($products) {
$minNet = null;
$minGross = null;
foreach ($products as $p) {
$gross = parsePrice((string)$p->price);
if ($gross <= 0) {
continue;
}
$net = Tools::ps_round($gross / 1.23, 6);
if ($minNet === null || $net < $minNet) {
$minNet = $net;
$minGross = $gross;
}
}
if ($minNet === null) {
return [0.0, 0.0];
}
return [$minGross, $minNet];
}
// Czyści log aktualizacji cen - zostawia tylko wpisy z ostatnich X dni
function cleanUpdateLog($logFile, $daysToKeep = 30) {
if (!file_exists($logFile)) {
return;
}
$cutoffDate = date('Y-m-d', strtotime('-'.(int)$daysToKeep.' days'));
$lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($lines === false || empty($lines)) {
return;
}
$newLines = [];
foreach ($lines as $line) {
$parts = explode(';', $line);
if (count($parts) < 1) {
// linia uszkodzona pomijamy
continue;
}
$logDate = trim($parts[0]);
// Jeśli data ma format YYYY-MM-DD i jest nowsza/równa cutoff zostawiamy
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $logDate) && $logDate >= $cutoffDate) {
$newLines[] = $line;
}
// Jeśli format daty jest dziwny na wszelki wypadek zachowajmy wpis
elseif (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $logDate)) {
$newLines[] = $line;
}
}
// Nadpisujemy plik przefiltrowaną zawartością
if (!empty($newLines)) {
file_put_contents($logFile, implode(PHP_EOL, $newLines) . PHP_EOL);
} else {
// Jeśli wszystko stare/uszkodzone czyścimy plik
file_put_contents($logFile, '');
}
}
// Zwraca id_tax_rules_group dla stawki VAT (np. 23) dla kraju domyślnego
function getTaxRulesGroupIdForRate($rate, $id_country = null) {
if ($id_country === null) {
$id_country = (int)Configuration::get('PS_COUNTRY_DEFAULT');
}
$sql = 'SELECT trg.`id_tax_rules_group`
FROM `'._DB_PREFIX_.'tax_rules_group` trg
INNER JOIN `'._DB_PREFIX_.'tax_rule` tr
ON (trg.`id_tax_rules_group` = tr.`id_tax_rules_group`)
INNER JOIN `'._DB_PREFIX_.'tax` t
ON (tr.`id_tax` = t.`id_tax`)
WHERE trg.`active` = 1
AND tr.`id_country` = '.(int)$id_country.'
AND t.`rate` = '.(float)$rate.'
ORDER BY trg.`id_tax_rules_group` ASC';
$id = Db::getInstance()->getValue($sql);
return $id ? (int)$id : 0;
}
// === GRUPOWANIE PRODUKTÓW PO SYMBOLU ===
$productsBySymbol = [];
foreach ($xml->product as $productData) {
$symbol = (string)$productData->item_group_id;
if (!isset($productsBySymbol[$symbol])) {
$productsBySymbol[$symbol] = [];
}
$productsBySymbol[$symbol][] = $productData;
}
// ID grupy VAT 23%
$idTaxRulesGroup23 = getTaxRulesGroupIdForRate(23);
// =======================================
// =========== TRYB AKTUALIZACJI =========
// =======================================
if ($modeUpdate) {
cleanUpdateLog($logFile, 30);
$today = date('Y-m-d');
$updatedToday = [];
// Wczytanie logu aktualizacji
if (file_exists($logFile)) {
$lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$parts = explode(';', $line);
if (count($parts) >= 3) {
$logDate = trim($parts[0]);
$logType = trim($parts[1]); // np. 'product'
$logRef = trim($parts[2]);
if ($logDate === $today) {
$updatedToday[$logType.'_'.$logRef] = true;
}
}
}
}
$updatedSomething = false;
foreach ($productsBySymbol as $symbol => $products) {
if (empty($products)) {
continue;
}
// Główny produkt wybieramy deterministycznie (najniższe SKU alfabetycznie)
// aby uniknąć problemu ze zmianą kolejności w XML
usort($products, function($a, $b) {
return strcmp((string)$a->sku, (string)$b->sku);
});
$mainProductData = $products[0];
$reference = (string)$mainProductData->sku;
$key = 'product_'.$reference;
// Jeśli już zaktualizowany dzisiaj pomijamy
if (isset($updatedToday[$key])) {
continue;
}
$product = findProductByReference($reference);
if (!$product) {
// produkt nie istnieje w Presta - pomijamy w trybie update
continue;
}
// BAZA: najtańszy wariant w grupie (brutto i netto)
list($grossBase, $netPrice) = getBasePricesFromGroup($products);
if ($grossBase <= 0 || $netPrice <= 0) {
// brak sensownej ceny pomiń
continue;
}
// Flagi / liczniki
$updatedProductPrice = false;
$updatedCombinationCount = 0;
// Aktualizacja ceny produktu (NETTO) baza = najtańszy wariant
$product->price = $netPrice;
$updatedProductPrice = true;
// Produkt zawsze aktywny + delivery times
$product->active = 1;
$product->delivery_in_stock = createMultiLangField('2-7 dni roboczych');
$product->delivery_out_stock = createMultiLangField('4-10 tygodni');
// Upewnij się, że produkt ma ustawioną grupę VAT 23%
if (!empty($idTaxRulesGroup23) && (int)$product->id_tax_rules_group !== (int)$idTaxRulesGroup23) {
$product->id_tax_rules_group = (int)$idTaxRulesGroup23;
}
// Sprawdź kategorię domyślną jeśli "Strona główna", zamień na "Meble" (ID 107)
$id_lang = (int)$context->language->id;
$defaultCategory = new Category($product->id_category_default, $id_lang);
if (Validate::isLoadedObject($defaultCategory) && $defaultCategory->name == 'Strona główna') {
$newCategoryId = 107; // Meble
// Ustaw nową kategorię domyślną
$product->id_category_default = (int)$newCategoryId;
// Podmień kategorie produktu (zachowując ewentualne inne)
$categories = $product->getCategories();
$categories = array_diff($categories, [(int)$defaultCategory->id]);
$categories[] = (int)$newCategoryId;
$categories = array_unique(array_map('intval', $categories));
$product->updateCategories($categories);
}
// Zapis produktu
if ($product->update()) {
// --- STANY MAGAZYNOWE PRODUKTU (ID_PRODUCT_ATTRIBUTE = 0) ---
$mainStatus = (string)$mainProductData->Status_magazynowy;
$mainQty = ($mainStatus === 'instock') ? 100 : 0;
StockAvailable::setQuantity($product->id, 0, $mainQty);
// --- AKTUALIZACJA CEN I STANÓW KOMBINACJI NA PODSTAWIE XML ---
foreach ($products as $productDataVariant) {
$variantRef = (string)$productDataVariant->sku;
// Szukamy kombinacji po indeksie/SKU
$combination = findCombinationByReference($product->id, $variantRef);
if (!$combination) {
continue;
}
// Cena brutto wariantu z XML
$variantGross = parsePrice((string)$productDataVariant->price);
if ($variantGross <= 0) {
continue;
}
// Netto wariantu
$variantNet = Tools::ps_round($variantGross / 1.23, 6);
// Impact względem ceny bazowej produktu (najtańszy wariant)
$impact = $variantNet - $netPrice;
// zabezpieczenie na wypadek minimalnych różnic/zaokrągleń
if ($impact < 0) {
$impact = 0;
}
$impact = Tools::ps_round($impact, 6);
$combination->price = $impact;
// Stan magazynowy kombinacji
$variantStatus = (string)$productDataVariant->Status_magazynowy;
$variantQty = ($variantStatus === 'instock') ? 100 : 0;
StockAvailable::setQuantity($product->id, $combination->id, $variantQty);
if ($combination->update()) {
$updatedCombinationCount++;
}
}
// --- KONIEC AKTUALIZACJI KOMBINACJI ---
// Zapis do logu że ten produkt został dziś zaktualizowany
$logLine = $today.';product;'.$reference.';'.$product->id.PHP_EOL;
file_put_contents($logFile, $logLine, FILE_APPEND);
echo '<p><strong>Zaktualizowano produkt:</strong> '.htmlspecialchars((string)$mainProductData->title).' ('.$reference.')</p>';
echo '<p>Nowa bazowa cena brutto (najtańszy wariant) z XML: '.$grossBase.'</p>';
echo '<p>Nowa cena netto produktu (bazowa) w Presta: '.$netPrice.'</p>';
// INFO: czy zaktualizowano cenę produktu
if ($updatedProductPrice) {
echo '<p><strong>Cena produktu została zaktualizowana.</strong></p>';
} else {
echo '<p><strong>Cena produktu nie uległa zmianie.</strong></p>';
}
// INFO: ile kombinacji zaktualizowano
if ($updatedCombinationCount > 0) {
echo '<p><strong>Zaktualizowano ceny/ilości kombinacji:</strong> '.$updatedCombinationCount.' szt.</p>';
} else {
echo '<p><strong>Nie zaktualizowano żadnej kombinacji (brak dopasowanych SKU albo cen).</strong></p>';
}
$updatedSomething = true;
}
// Aktualizujemy tylko jeden produkt na jedno wywołanie
break;
}
if ($updatedSomething) {
echo '<script>setTimeout(function(){ location.reload(); }, 50);</script>';
} else {
echo '<p>Brak produktów do aktualizacji na dzisiaj (wszystkie z XML zostały już zaktualizowane).</p>';
}
exit;
}
// =======================================
// =========== TRYB DODAWANIA ============
// =======================================
$productAdded = false;
$combinationAdded = false;
// Tworzenie lub aktualizacja produktów z kombinacjami (dodawanie)
foreach ($productsBySymbol as $symbol => $products) {
if (empty($products)) {
continue;
}
// Główny produkt wybieramy deterministycznie (najniższe SKU alfabetycznie)
// aby uniknąć problemu ze zmianą kolejności w XML
usort($products, function($a, $b) {
return strcmp((string)$a->sku, (string)$b->sku);
});
$mainProductData = $products[0];
$mainProduct = findProductByReference((string)$mainProductData->sku);
// BAZA: najtańszy wariant w grupie (brutto i netto)
list($grossBase, $netPrice) = getBasePricesFromGroup($products);
if (!$mainProduct) {
// Create a new product if it doesn't exist
$mainProduct = new Product();
$mainProduct->name = createMultiLangField((string)$mainProductData->title);
$description = (string)$mainProductData->description;
$description = str_replace("\n", "<br>", $description);
$mainProduct->description = createMultiLangField($description);
// Cena BRUTTO z XML -> NETTO (23%) bazą jest najtańszy wariant
$mainProduct->price = $netPrice > 0 ? $netPrice : 0;
// VAT 23% jeśli dostępny
if (!empty($idTaxRulesGroup23)) {
$mainProduct->id_tax_rules_group = (int)$idTaxRulesGroup23;
}
// Produkt aktywny + delivery times
$mainProduct->active = 1;
$mainProduct->delivery_in_stock = createMultiLangField('2-7 dni roboczych');
$mainProduct->delivery_out_stock = createMultiLangField('4-10 tygodni');
$mainProduct->reference = (string)$mainProductData->sku;
$mainProduct->id_category_default = 2; // np. Strona główna
$mainProduct->link_rewrite = createLinkRewrite((string)$mainProductData->title);
$mainProduct->add();
// Add images to the product
if (!empty($mainProductData->image)) {
addProductImage($mainProduct->id, (string)$mainProductData->image);
}
for ($i = 1; $i <= 10; $i++) {
$imageUrl = (string)$mainProductData->{'images_' . $i};
if (!empty($imageUrl)) {
addProductImage($mainProduct->id, $imageUrl);
}
}
$productAdded = true;
}
// Ensure the product is saved before adding combinations
if (!$mainProduct->id) {
echo "Failed to create or update main product: " . (string)$mainProductData->title . "\n";
continue;
}
// Ensure the combination set is unique for the product
$addedCombinations = [];
// Add or update combinations for each product in the group
foreach ($products as $productData) {
$attributes = [
'Kolor' => (string)$productData->Kolor,
'Dlugosc' => (string)$productData->Dlugosc,
'Szerokosc' => (string)$productData->Szerokosc,
'Glebokosc' => (string)$productData->Glebokosc,
'Wysokosc' => (string)$productData->Wysokosc,
];
$attributeIds = [];
foreach ($attributes as $name => $value) {
if (!empty($value)) {
$attributeGroupId = createAttribute($name, [$value]);
$attribute = findAttributeByName($attributeGroupId, $value);
if ($attribute) {
$attributeIds[] = (int)$attribute->id;
}
}
}
// Create a unique key for the attribute set
sort($attributeIds);
$key = implode('-', $attributeIds);
// Add or update combination if it is unique
if (!empty($attributeIds) && !isset($addedCombinations[$key])) {
$combination = findCombinationByAttributes($mainProduct->id, $attributeIds);
if (!$combination) {
// Create new combination
$combination = new Combination();
$combination->id_product = (int)$mainProduct->id;
$combination->quantity = 100; // startowo, i tak zaraz nadpiszemy StockAvailable
$combination->reference = (string)$productData->sku;
$combination->add();
$combination->setAttributes($attributeIds);
$combination->save();
$combinationAdded = true;
} else {
// Update existing combination quantity if necessary
$combination->quantity = 100; // startowo
$combination->update();
}
// Mark this combination as added
$addedCombinations[$key] = true;
}
if ($combinationAdded) {
break; // Break if a new combination was added
}
}
// --- STANY MAGAZYNOWE PRODUKTU (ID_PRODUCT_ATTRIBUTE = 0) ---
$mainStatus = (string)$mainProductData->Status_magazynowy;
$mainQty = ($mainStatus === 'instock') ? 100 : 0;
StockAvailable::setQuantity($mainProduct->id, 0, $mainQty);
// --- STANY MAGAZYNOWE KOMBINACJI ---
foreach ($products as $productDataVariant) {
$variantRef = (string)$productDataVariant->sku;
$variantStatus = (string)$productDataVariant->Status_magazynowy;
$variantQty = ($variantStatus === 'instock') ? 100 : 0;
$combination = findCombinationByReference($mainProduct->id, $variantRef);
if ($combination) {
StockAvailable::setQuantity($mainProduct->id, $combination->id, $variantQty);
}
}
// Ensure the product has combinations enabled
$mainProduct->checkDefaultAttributes();
Product::updateDefaultAttribute($mainProduct->id);
if ($productAdded || $combinationAdded) {
if ($productAdded) {
echo "<p>Dodałem produkt: " . htmlspecialchars((string)$mainProductData->title) . "</p>";
}
if ($combinationAdded) {
echo "<p>Dodałem kombinację: " . htmlspecialchars((string)$mainProductData->title) . "</p>";
}
break; // Break if a new product or combination was added
}
}
// reload page after 250ms if product or combination was added (dla trybu add)
if ($productAdded || $combinationAdded) {
// echo "<script>setTimeout(function(){location.reload();}, 250);</script>";
}
?>