242 lines
8.8 KiB
PHP
242 lines
8.8 KiB
PHP
<?php
|
|
/**
|
|
* csv2xml_sku_one_photos_field.php
|
|
* CSV (;) -> XML dla struktury: SKU, EAN, Nazwa, ..., Opis, Opis dodatkowy, Photo1..Photo6
|
|
* - pomija wiersze bez SKU
|
|
* - Photo1..Photo6 łączy w jedno pole <Photos> rozdzielone przecinkami
|
|
* - Opis + Opis dodatkowy łączy w jedno pole <Opis> (CDATA)
|
|
* HTTP: csv2xml_sku_one_photos_field.php?url=URL_CSV[&filename=produkty_2.xml][&download=1]
|
|
* CLI: php csv2xml_sku_one_photos_field.php URL_CSV [produkty_2.xml]
|
|
*/
|
|
declare(strict_types=1);
|
|
|
|
$isCli = (php_sapi_name() === 'cli');
|
|
|
|
if ($isCli) {
|
|
if ($argc < 2) {
|
|
fwrite(STDERR, "Użycie: php {$argv[0]} URL [plik_wyjściowy.xml]\n");
|
|
exit(1);
|
|
}
|
|
$csvUrl = $argv[1];
|
|
$outName = $argv[2] ?? 'produkty_2.xml';
|
|
$download = false;
|
|
} else {
|
|
if (!isset($_GET['url']) || !filter_var($_GET['url'], FILTER_VALIDATE_URL)) {
|
|
http_response_code(400);
|
|
echo "Parametr ?url= jest wymagany i musi być poprawnym URL-em.";
|
|
exit;
|
|
}
|
|
$csvUrl = $_GET['url'];
|
|
$outName = (isset($_GET['filename']) && preg_match('/^[\w\-.]+$/', $_GET['filename'])) ? $_GET['filename'] : 'produkty_2.xml';
|
|
$download = isset($_GET['download']) && $_GET['download'] == '1';
|
|
}
|
|
|
|
// 1) Pobierz CSV i zapisz do pliku tymczasowego (ochrona pól wielowierszowych)
|
|
$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"; }
|
|
exit(1);
|
|
}
|
|
$csvRaw = preg_replace('/^\xEF\xBB\xBF/', '', $csvRaw);
|
|
if (!mb_check_encoding($csvRaw, 'UTF-8')) {
|
|
$csvRaw = @iconv('WINDOWS-1250', 'UTF-8//TRANSLIT', $csvRaw) ?: $csvRaw;
|
|
}
|
|
$tmpFile = tempnam(sys_get_temp_dir(), 'csv2xml_');
|
|
file_put_contents($tmpFile, $csvRaw);
|
|
|
|
// 2) Wczytaj CSV przez fgetcsv
|
|
$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) {
|
|
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; }
|
|
exit(1);
|
|
}
|
|
|
|
// 3) Nagłówki i mapowanie tagów
|
|
$headers = array_map(fn($h) => trim((string)$h, " \t\n\r\0\x0B\""), $rows[0]); // usuń też otaczające cudzysłowy
|
|
$dataRows = array_slice($rows, 1);
|
|
|
|
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'];
|
|
$name = strtr($name, $map);
|
|
$name = preg_replace('/[^\p{L}\p{N}\s_-]+/u', ' ', $name);
|
|
$name = preg_replace('/\s+/', '_', $name);
|
|
$name = preg_replace('/_+/', '_', $name);
|
|
$name = trim($name, '_');
|
|
if ($name === '') $name = 'field';
|
|
if (preg_match('/^\d/', $name)) $name = 'f_' . $name;
|
|
return $name;
|
|
}
|
|
$tagMap = [];
|
|
foreach ($headers as $h) $tagMap[$h] = normalize_tag_name($h);
|
|
|
|
// Indeksy: SKU, Photo*, Opis, Opis dodatkowy
|
|
$skuIdx = null;
|
|
$photoIdxs = [];
|
|
$opisIdx = null;
|
|
$opisDodIdx = null;
|
|
|
|
foreach ($headers as $i => $h) {
|
|
$H = mb_strtoupper($h, 'UTF-8');
|
|
if ($H === 'SKU') $skuIdx = $i;
|
|
if (preg_match('/^PHOTO\d+$/i', $h)) $photoIdxs[] = $i;
|
|
if ($H === 'OPIS') $opisIdx = $i;
|
|
if ($H === 'OPIS DODATKOWY') $opisDodIdx = $i;
|
|
}
|
|
if ($skuIdx === null) {
|
|
$msg = "Nie znaleziono kolumny 'SKU' w nagłówkach CSV.";
|
|
if ($isCli) { fwrite(STDERR, $msg . PHP_EOL); exit(1); }
|
|
http_response_code(422);
|
|
echo $msg;
|
|
exit;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 5) Budowa XML (pomijamy wiersze bez SKU; Photos jako CSV w jednym polu; Opis łączony)
|
|
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Products/>');
|
|
|
|
$exported = 0;
|
|
$skippedNoSku = 0;
|
|
|
|
foreach ($dataRows as $row) {
|
|
if (count($row) < count($headers)) $row = array_pad($row, count($headers), '');
|
|
|
|
// sprawdź SKU
|
|
$skuRaw = isset($row[$skuIdx]) ? (string)$row[$skuIdx] : '';
|
|
$skuClean = preg_replace('/\s+/', '', trim($skuRaw));
|
|
if ($skuClean === '' || $skuClean === null) { $skippedNoSku++; continue; }
|
|
|
|
$product = $xml->addChild('Product');
|
|
|
|
// Zbierz zdjęcia
|
|
$photos = [];
|
|
foreach ($photoIdxs as $pi) {
|
|
$v = isset($row[$pi]) ? trim((string)$row[$pi]) : '';
|
|
if ($v !== '') $photos[] = $v;
|
|
}
|
|
|
|
// Zbierz i połącz Opis + Opis dodatkowy
|
|
$opisVal = '';
|
|
$o1 = ($opisIdx !== null) ? trim((string)$row[$opisIdx]) : '';
|
|
$o2 = ($opisDodIdx !== null) ? trim((string)$row[$opisDodIdx]) : '';
|
|
if ($o1 !== '' && $o2 !== '') $opisVal = $o1 . "\n\n" . $o2;
|
|
elseif ($o1 !== '') $opisVal = $o1;
|
|
elseif ($o2 !== '') $opisVal = $o2;
|
|
|
|
foreach ($headers as $i => $header) {
|
|
// Pomiń kolumny Photo* oraz oryginalne kolumny Opis i Opis dodatkowy (dodamy jeden wspólny <Opis> niżej)
|
|
if (in_array($i, $photoIdxs, true)) continue;
|
|
$H = mb_strtoupper($header, 'UTF-8');
|
|
if ($H === 'OPIS' || $H === 'OPIS DODATKOWY') continue;
|
|
|
|
$rawVal = isset($row[$i]) ? (string)$row[$i] : '';
|
|
$val = trim($rawVal, " \t\n\r\0\x0B\"");
|
|
$tag = $tagMap[$header] ?? ('field_' . $i);
|
|
|
|
if ($val === '') { $product->addChild($tag, ''); continue; }
|
|
|
|
// Opis/HTML → CDATA (dla zwykłych pól)
|
|
if (looks_like_html_or_multiline($val) || preg_match('/\bopis\b/i', $header)) {
|
|
addChildWithCDATA($product, $tag, $val);
|
|
} else {
|
|
if (preg_match('/^(EAN|SKU)$/ui', $header)) $val = preg_replace('/\s+/', '', $val);
|
|
$product->addChild($tag, $val);
|
|
}
|
|
}
|
|
|
|
// Dodaj jedno pole Photos rozdzielone przecinkami
|
|
$product->addChild('Photos', !empty($photos) ? implode(',', $photos) : '');
|
|
|
|
// Dodaj połączony Opis jako jedno pole (CDATA)
|
|
// Użyj istniejącej zmapowanej nazwy "Opis" jeśli była w nagłówku, w przeciwnym wypadku "Opis"
|
|
$opisTag = $tagMap['Opis'] ?? 'Opis';
|
|
addChildWithCDATA($product, $opisTag, $opisVal);
|
|
|
|
$exported++;
|
|
}
|
|
|
|
// 6) Formatowanie i zapis
|
|
$dom = new DOMDocument('1.0', 'UTF-8');
|
|
$dom->preserveWhiteSpace = false;
|
|
$dom->formatOutput = true;
|
|
$dom->loadXML($xml->asXML());
|
|
|
|
if ($isCli) {
|
|
if (@$dom->save($outName) === false) {
|
|
fwrite(STDERR, "Nie udało się zapisać do pliku: {$outName}\n");
|
|
exit(1);
|
|
}
|
|
echo "Zapisano XML do: {$outName}\n";
|
|
echo "Wyeksportowano: {$exported}, pominięto (brak SKU): {$skippedNoSku}\n";
|
|
exit(0);
|
|
}
|
|
|
|
// 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: {$saveDir}";
|
|
exit;
|
|
}
|
|
}
|
|
$outName = preg_match('/^[\w\-.]+$/', $outName) ? $outName : ('produkty_' . date('Ymd_His') . '.xml');
|
|
$savePath = $saveDir . DIRECTORY_SEPARATOR . $outName;
|
|
|
|
if (@$dom->save($savePath) === false) {
|
|
http_response_code(500);
|
|
echo "Nie udało się zapisać pliku: {$savePath}";
|
|
exit;
|
|
}
|
|
|
|
$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;
|
|
|
|
if ($download) {
|
|
header('Content-Type: application/xml; charset=UTF-8');
|
|
header('Content-Disposition: attachment; filename="'.$outName.'"');
|
|
readfile($savePath);
|
|
exit;
|
|
}
|
|
|
|
header('Content-Type: application/json; charset=UTF-8');
|
|
echo json_encode([
|
|
'status' => 'ok',
|
|
'message' => 'Plik XML zapisany na serwerze.',
|
|
'file_path' => $savePath,
|
|
'file_url' => $publicUrl,
|
|
'filename' => $outName,
|
|
'exported' => $exported,
|
|
'skipped_no_sku' => $skippedNoSku,
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|