529 lines
19 KiB
PHP
529 lines
19 KiB
PHP
<?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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
$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 – referencja z pierwszego elementu grupy
|
||
$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;
|
||
}
|
||
|
||
// Nowa cena z XML (BRUTTO 23%)
|
||
$grossPrice = parsePrice((string)$mainProductData->price);
|
||
if ($grossPrice <= 0) {
|
||
// brak sensownej ceny – pomiń
|
||
continue;
|
||
}
|
||
|
||
// Przeliczenie brutto -> netto (23%)
|
||
$netPrice = Tools::ps_round($grossPrice / 1.23, 6);
|
||
|
||
// Aktualizacja ceny produktu (NETTO)
|
||
$product->price = $netPrice;
|
||
|
||
// 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();
|
||
// Usuń starą kategorię domyślną, jeśli istnieje w tablicy
|
||
$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()) {
|
||
// 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>Zaktualizowano produkt: '.htmlspecialchars((string)$mainProductData->title).' ('.$reference.')</p>';
|
||
echo '<p>Nowa cena brutto z XML: '.$grossPrice.'</p>';
|
||
echo '<p>Nowa cena netto zapisana w Presta: '.$netPrice.'</p>';
|
||
|
||
$updatedSomething = true;
|
||
}
|
||
|
||
// Aktualizujemy tylko jeden produkt na jedno wywołanie
|
||
break;
|
||
}
|
||
|
||
if ($updatedSomething) {
|
||
// Odśwież stronę, żeby przy kolejnym wywołaniu zaktualizować następny produkt
|
||
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;
|
||
}
|
||
|
||
// Get the main product data from the first product in the group
|
||
$mainProductData = $products[0];
|
||
$mainProduct = findProductByReference((string)$mainProductData->sku);
|
||
|
||
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%)
|
||
$grossPrice = parsePrice((string)$mainProductData->price);
|
||
$netPrice = Tools::ps_round($grossPrice / 1.23, 6);
|
||
$mainProduct->price = $netPrice;
|
||
|
||
// VAT 23% jeśli dostępny
|
||
if (!empty($idTaxRulesGroup23)) {
|
||
$mainProduct->id_tax_rules_group = (int)$idTaxRulesGroup23;
|
||
}
|
||
|
||
$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; // Default quantity, you can adjust this
|
||
$combination->reference = (string)$productData->sku;
|
||
$combination->add();
|
||
$combination->setAttributes($attributeIds);
|
||
$combination->save();
|
||
$combinationAdded = true;
|
||
} else {
|
||
// Update existing combination quantity if necessary
|
||
$combination->quantity = 100; // Update quantity, you can adjust this
|
||
$combination->update();
|
||
}
|
||
|
||
// Mark this combination as added
|
||
$addedCombinations[$key] = true;
|
||
}
|
||
|
||
if ($combinationAdded) {
|
||
break; // Break if a new combination was added
|
||
}
|
||
}
|
||
|
||
// 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>";
|
||
}
|
||
|
||
?>
|