update
This commit is contained in:
68023
interblue.pl/MYL_CENNIK_B2B_PL.csv
Normal file
68023
interblue.pl/MYL_CENNIK_B2B_PL.csv
Normal file
File diff suppressed because it is too large
Load Diff
2427208
interblue.pl/exports/produkty.xml
Normal file
2427208
interblue.pl/exports/produkty.xml
Normal file
File diff suppressed because it is too large
Load Diff
9602
interblue.pl/new_products.xml
Normal file
9602
interblue.pl/new_products.xml
Normal file
File diff suppressed because it is too large
Load Diff
2
interblue.pl/output.xml
Normal file
2
interblue.pl/output.xml
Normal file
File diff suppressed because one or more lines are too long
@@ -1,14 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* csv2xml.php
|
||||
* Konwersja CSV (z separatorem ;) -> XML.
|
||||
* Obsługa: HTTP (GET url=… [&filename=…]) lub CLI (php csv2xml.php URL [OUTPUT.xml])
|
||||
* csv2xml_new.php — konwersja CSV (podana struktura; delimiter ;) -> XML
|
||||
* HTTP: csv2xml_new.php?url=URL_CSV[&filename=produkty.xml][&download=1]
|
||||
* CLI: php csv2xml_new.php URL_CSV [produkty.xml]
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
$_GET['url'] = 'https://makeyourlight.com/uploads/feed/myl_produkty_polski.csv';
|
||||
$_GET['filename'] = 'produkty.xml';
|
||||
$_GET['download'] = 0;
|
||||
|
||||
$isCli = (php_sapi_name() === 'cli');
|
||||
|
||||
@@ -18,7 +14,7 @@ if ($isCli) {
|
||||
exit(1);
|
||||
}
|
||||
$csvUrl = $argv[1];
|
||||
$outName = $argv[2] ?? 'output.xml';
|
||||
$outName = $argv[2] ?? 'produkty.xml';
|
||||
$download = false;
|
||||
} else {
|
||||
if (!isset($_GET['url']) || !filter_var($_GET['url'], FILTER_VALIDATE_URL)) {
|
||||
@@ -27,118 +23,164 @@ if ($isCli) {
|
||||
exit;
|
||||
}
|
||||
$csvUrl = $_GET['url'];
|
||||
$outName = isset($_GET['filename']) && preg_match('/^[\w\-.]+$/', $_GET['filename'])
|
||||
? $_GET['filename']
|
||||
: 'produkty.xml';
|
||||
$outName = isset($_GET['filename']) && preg_match('/^[\w\-.]+$/', $_GET['filename']) ? $_GET['filename'] : 'produkty.xml';
|
||||
$download = isset($_GET['download']) && $_GET['download'] == '1';
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Pobierz CSV
|
||||
// 1) Pobranie CSV (bez łamania wielowierszowych pól) + zapis do temp
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
$csvRaw = @file_get_contents($csvUrl);
|
||||
if ($csvRaw === false) {
|
||||
$err = error_get_last();
|
||||
$msg = $err['message'] ?? 'Nieznany błąd pobierania CSV.';
|
||||
if ($isCli) fwrite(STDERR, "Błąd pobierania CSV: $msg\n");
|
||||
else { http_response_code(502); echo "Błąd pobierania CSV: $msg"; }
|
||||
if ($isCli) fwrite(STDERR, "Błąd pobierania CSV: $msg\n"); else { http_response_code(502); echo "Błąd pobierania CSV: $msg"; }
|
||||
exit(1);
|
||||
}
|
||||
// Usuń BOM; ewentualnie konwersja do UTF-8 (częste Windows-1250)
|
||||
$csvRaw = preg_replace('/^\xEF\xBB\xBF/', '', $csvRaw);
|
||||
if (!mb_check_encoding($csvRaw, 'UTF-8')) {
|
||||
$csvRaw = @iconv('WINDOWS-1250', 'UTF-8//TRANSLIT', $csvRaw) ?: $csvRaw;
|
||||
}
|
||||
// Zapis do pliku tymczasowego
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'csv2xml_');
|
||||
file_put_contents($tmpFile, $csvRaw);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Parsowanie CSV
|
||||
// 2) Odczyt CSV fgetcsv (delimiter ;, enclosure ", obsługa wielowierszowości)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
$lines = preg_split("/\r\n|\n|\r/", $csvRaw);
|
||||
$rows = [];
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) === '') continue;
|
||||
$parsed = str_getcsv($line, ';', '"', "\\");
|
||||
if (is_array($parsed)) $rows[] = $parsed;
|
||||
$fh = fopen($tmpFile, 'r');
|
||||
if ($fh === false) {
|
||||
if ($isCli) fwrite(STDERR, "Nie można otworzyć pliku tymczasowego.\n"); else { http_response_code(500); echo "Nie można otworzyć pliku tymczasowego."; }
|
||||
@unlink($tmpFile);
|
||||
exit(1);
|
||||
}
|
||||
$rows = [];
|
||||
while (($row = fgetcsv($fh, 0, ';', '"', '\\')) !== false) {
|
||||
// Pomijaj puste wiersze
|
||||
if (count($row) === 1 && trim((string)$row[0]) === '') continue;
|
||||
$rows[] = $row;
|
||||
}
|
||||
fclose($fh);
|
||||
@unlink($tmpFile);
|
||||
if (count($rows) < 2) {
|
||||
$msg = "CSV nie zawiera danych (brakuje nagłówków lub wierszy).";
|
||||
if ($isCli) fwrite(STDERR, $msg . PHP_EOL);
|
||||
else { http_response_code(422); echo $msg; }
|
||||
if ($isCli) fwrite(STDERR, $msg . PHP_EOL); else { http_response_code(422); echo $msg; }
|
||||
exit(1);
|
||||
}
|
||||
$headers = array_map('trim', $rows[0]);
|
||||
$dataRows = array_slice($rows, 1);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Helpery
|
||||
// 3) Nagłówki + mapowanie do bezpiecznych tagów XML
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
$headers = array_map(function ($h) { return trim((string)$h); }, $rows[0]);
|
||||
$dataRows = array_slice($rows, 1);
|
||||
|
||||
// Napraw znane literówki/nawiasy w nagłówkach (np. „PRODUKT SZE [cm}”)
|
||||
$headerFix = function(string $h): string {
|
||||
$h = preg_replace('/\}\s*$/', ']', $h); // zamień końcowe } -> ]
|
||||
$h = preg_replace('/\s+;$/', '', $h); // zgubiona spacja przed średnikiem
|
||||
$h = preg_replace('/\s{2,}/', ' ', $h); // wielokrotne spacje
|
||||
return trim($h);
|
||||
};
|
||||
$headers = array_map($headerFix, $headers);
|
||||
|
||||
function normalize_tag_name(string $name): string {
|
||||
$map = [
|
||||
'ą'=>'a','ć'=>'c','ę'=>'e','ł'=>'l','ń'=>'n','ó'=>'o','ś'=>'s','ż'=>'z','ź'=>'z',
|
||||
'Ą'=>'A','Ć'=>'C','Ę'=>'E','Ł'=>'L','Ń'=>'N','Ó'=>'O','Ś'=>'S','Ż'=>'Z','Ź'=>'Z',
|
||||
];
|
||||
$map = ['ą'=>'a','ć'=>'c','ę'=>'e','ł'=>'l','ń'=>'n','ó'=>'o','ś'=>'s','ż'=>'z','ź'=>'z',
|
||||
'Ą'=>'A','Ć'=>'C','Ę'=>'E','Ł'=>'L','Ń'=>'N','Ó'=>'O','Ś'=>'S','Ż'=>'Z','Ź'=>'Z'];
|
||||
$name = strtr($name, $map);
|
||||
$name = preg_replace('/[^\p{L}\p{N}\-_ ]/u', ' ', $name);
|
||||
// usuń nawiasy/ukośniki/kropki itd. -> spacje
|
||||
$name = preg_replace('/[^\p{L}\p{N}\s_-]+/u', ' ', $name);
|
||||
// spacje -> _
|
||||
$name = preg_replace('/\s+/', '_', $name);
|
||||
$name = preg_replace('/[^\p{L}\p{N}_-]/u', '_', $name);
|
||||
// zredukuj _
|
||||
$name = preg_replace('/_+/', '_', $name);
|
||||
$name = trim($name, '_');
|
||||
if ($name === '') $name = 'field';
|
||||
if (preg_match('/^\d/', $name)) $name = 'f_' . $name;
|
||||
return $name;
|
||||
}
|
||||
function addChildWithCDATA(SimpleXMLElement $parent, string $name, string $value): SimpleXMLElement {
|
||||
$child = $parent->addChild($name);
|
||||
$node = dom_import_simplexml($child);
|
||||
if ($node) $node->appendChild($node->ownerDocument->createCDATASection($value));
|
||||
return $child;
|
||||
}
|
||||
function looks_like_html(string $v): bool {
|
||||
return (bool) preg_match('/<[^>]+>|\&(?:nbsp|lt|gt|amp|quot|#\d+);/i', $v);
|
||||
}
|
||||
function is_photo_col(string $header): bool {
|
||||
return (bool) preg_match('/^Photo\d+$/i', trim($header));
|
||||
}
|
||||
|
||||
// Mapa tagów
|
||||
$tagMap = [];
|
||||
foreach ($headers as $h) $tagMap[$h] = normalize_tag_name($h);
|
||||
foreach ($headers as $h) {
|
||||
$tagMap[$h] = normalize_tag_name($h);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Budowa XML
|
||||
// 4) Helpery
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
function looks_like_html_or_multiline(string $v): bool {
|
||||
if (strpos($v, "\n") !== false || strpos($v, "\r") !== false) return true;
|
||||
return (bool) preg_match('/<[^>]+>|\&(?:nbsp|lt|gt|amp|quot|#\d+);/i', $v);
|
||||
}
|
||||
function addChildWithCDATA(SimpleXMLElement $parent, string $name, string $val): SimpleXMLElement {
|
||||
$child = $parent->addChild($name);
|
||||
$node = dom_import_simplexml($child);
|
||||
if ($node) $node->appendChild($node->ownerDocument->createCDATASection($val));
|
||||
return $child;
|
||||
}
|
||||
function is_photo_header(string $h): bool {
|
||||
return (bool) preg_match('/(foto|packshot)/i', $h);
|
||||
}
|
||||
function split_possible_photos(string $v): array {
|
||||
// rozdziel po ; , | spacji i nowych liniach
|
||||
$parts = preg_split('/[;\|,\s]+/u', trim($v));
|
||||
$parts = array_values(array_filter(array_map('trim', $parts), fn($x) => $x !== ''));
|
||||
return $parts;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// 5) Budowa XML
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Products/>');
|
||||
|
||||
foreach ($dataRows as $row) {
|
||||
// dopasuj liczbę kolumn
|
||||
if (count($row) < count($headers)) $row = array_pad($row, count($headers), '');
|
||||
$product = $xml->addChild('Product');
|
||||
$photos = [];
|
||||
|
||||
$photosCollected = [];
|
||||
|
||||
foreach ($headers as $i => $header) {
|
||||
$value = isset($row[$i]) ? trim($row[$i]) : '';
|
||||
$tagName = $tagMap[$header] ?? 'field_' . $i;
|
||||
$rawVal = isset($row[$i]) ? (string)$row[$i] : '';
|
||||
$val = trim($rawVal);
|
||||
$tag = $tagMap[$header] ?? ('field_' . $i);
|
||||
|
||||
if (is_photo_col($header)) { if ($value !== '') $photos[] = $value; continue; }
|
||||
// Specjalne traktowanie dla FOTO / PACKSHOT (może być wiele URL-i)
|
||||
if (is_photo_header($header)) {
|
||||
if ($val !== '') {
|
||||
foreach (split_possible_photos($val) as $u) $photosCollected[] = $u;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === '') { $product->addChild($tagName, ''); continue; }
|
||||
// Domyślnie wpisujemy każde pole (także puste) aby zachować pełnię danych
|
||||
if ($val === '') { $product->addChild($tag, ''); continue; }
|
||||
|
||||
if (looks_like_html($value) || preg_match('/opis/i', $header)) {
|
||||
addChildWithCDATA($product, $tagName, $value);
|
||||
// Opisy i HTML (np. „OPIS PL (krótki)”, „OPIS PL;”) → CDATA
|
||||
if (looks_like_html_or_multiline($val) || preg_match('/\bopis\b/i', $header)) {
|
||||
addChildWithCDATA($product, $tag, $val);
|
||||
} else {
|
||||
$product->addChild($tagName, $value);
|
||||
// Zadbaj o przycięcie EAN/SYMBOL z nietypowych spacji
|
||||
if (preg_match('/^(EAN|SYMBOL)$/ui', $header)) {
|
||||
$val = preg_replace('/\s+/', '', $val);
|
||||
}
|
||||
$product->addChild($tag, $val);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($photos)) {
|
||||
$photosNode = $product->addChild('Photos');
|
||||
foreach ($photos as $p) $photosNode->addChild('Photo', $p);
|
||||
if (!empty($photosCollected)) {
|
||||
$pNode = $product->addChild('Photos');
|
||||
foreach ($photosCollected as $p) $pNode->addChild('Photo', $p);
|
||||
}
|
||||
}
|
||||
|
||||
// Formatowanie DOM
|
||||
// Formatowanie ładnego XML
|
||||
$dom = new DOMDocument('1.0', 'UTF-8');
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
$dom->loadXML($xml->asXML());
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Zapis wyniku
|
||||
// 6) Zapis wyniku (CLI → plik; HTTP → exports/ + JSON lub pobranie)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
if ($isCli) {
|
||||
if (@$dom->save($outName) === false) {
|
||||
@@ -149,48 +191,43 @@ if ($isCli) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// HTTP — zapis do exports/
|
||||
$saveDirFs = __DIR__ . DIRECTORY_SEPARATOR . 'exports';
|
||||
if (!is_dir($saveDirFs)) {
|
||||
// spróbuj utworzyć katalog rekurencyjnie
|
||||
if (!@mkdir($saveDirFs, 0775, true) && !is_dir($saveDirFs)) {
|
||||
// HTTP zapis
|
||||
$saveDir = __DIR__ . DIRECTORY_SEPARATOR . 'exports';
|
||||
if (!is_dir($saveDir)) {
|
||||
if (!@mkdir($saveDir, 0775, true) && !is_dir($saveDir)) {
|
||||
http_response_code(500);
|
||||
echo "Nie można utworzyć katalogu zapisu: {$saveDirFs}";
|
||||
echo "Nie można utworzyć katalogu zapisu: {$saveDir}";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// sanity check nazwy pliku
|
||||
$outName = preg_match('/^[\w\-.]+$/', $outName) ? $outName : ('produkty_' . date('Ymd_His') . '.xml');
|
||||
$savePathFs = $saveDirFs . DIRECTORY_SEPARATOR . $outName;
|
||||
$savePath = $saveDir . DIRECTORY_SEPARATOR . $outName;
|
||||
|
||||
if (@$dom->save($savePathFs) === false) {
|
||||
if (@$dom->save($savePath) === false) {
|
||||
http_response_code(500);
|
||||
echo "Nie udało się zapisać pliku: {$savePathFs}";
|
||||
echo "Nie udało się zapisać pliku: {$savePath}";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Zbuduj publiczny URL (relatywny do skryptu)
|
||||
$basePath = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/')), '/');
|
||||
$publicRel = $basePath . '/exports/' . rawurlencode($outName);
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$publicUrl = $host ? ($scheme . '://' . $host . $publicRel) : $publicRel;
|
||||
// Publiczny URL pliku (o ile exports/ jest serwowany przez www)
|
||||
$basePath = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/')), '/');
|
||||
$publicRel = $basePath . '/exports/' . rawurlencode($outName);
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$publicUrl = $host ? ($scheme . '://' . $host . $publicRel) : $publicRel;
|
||||
|
||||
// Jeśli proszono o pobranie — wyślij plik
|
||||
if ($download) {
|
||||
header('Content-Type: application/xml; charset=UTF-8');
|
||||
header('Content-Disposition: attachment; filename="'.$outName.'"');
|
||||
readfile($savePathFs);
|
||||
readfile($savePath);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Domyślnie zwróć prosty JSON z informacją o zapisie
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'message' => 'Plik XML zapisany na serwerze.',
|
||||
'file_path' => $savePathFs,
|
||||
'file_url' => $publicUrl,
|
||||
'filename' => $outName,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
'status' => 'ok',
|
||||
'message' => 'Plik XML zapisany na serwerze.',
|
||||
'file_path' => $savePath,
|
||||
'file_url' => $publicUrl,
|
||||
'filename' => $outName,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
|
||||
Reference in New Issue
Block a user