Files
yml2csv/yml2csv.php
2025-09-25 14:46:36 +03:00

221 lines
7.9 KiB
PHP
Raw Permalink 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
/*
Plugin Name: YML → CSV
Description: Публичный эндпоинт ?yml2csv=1&src=URL[&target_cur=UAH][&rate=9.25][&margin=1.0][&precision=2] — генерирует CSV для WP All Import, строит category_path и конвертирует цену PLN→UAH.
Version: 1.2.0
Author: MrAkells
*/
if (!defined('ABSPATH')) exit;
/**
* Открытый эндпоинт: генерирует CSV на лету.
*
* Примеры:
* /?yml2csv=1&src=http://89.117.48.146/feeds/prom_feed.yml&target_cur=UAH
* /?yml2csv=1&src=http://.../feed.yml&target_cur=UAH&rate=9.25&margin=1.05&precision=0
*/
add_action('init', function () {
if (!isset($_GET['yml2csv'])) return;
$src = isset($_GET['src']) ? trim((string)$_GET['src']) : '';
if (!$src || !preg_match('~^https?://~i', $src)) {
status_header(400); echo 'Bad src'; exit;
}
$targetCur = strtoupper(trim((string)($_GET['target_cur'] ?? '')));
$rateParam = isset($_GET['rate']) ? (float)$_GET['rate'] : 0.0;
$margin = isset($_GET['margin']) ? max(0.0, (float)$_GET['margin']) : 1.0;
$precision = isset($_GET['precision']) ? max(0, (int)$_GET['precision']) : 2;
$xml = y2c_load_xml($src);
if (!$xml) { status_header(502); echo 'Fetch/parse error'; exit; }
$cats = y2c_extract_categories($xml);
$paths = y2c_build_paths($cats);
// Если нужен курс PLN→UAH (и курс не передан), притягиваем из НБУ.
$nbuRate = 0.0;
if ($rateParam <= 0 && $targetCur === 'UAH') {
$nbuRate = y2c_rate_pln_to_uah();
}
$rows = y2c_extract_offers_with_fx($xml, $cats, $paths, $targetCur, $rateParam, $nbuRate, $margin, $precision);
nocache_headers();
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: inline; filename="yml_to_wpai.csv"');
$out = fopen('php://output', 'w');
if (!$out) { status_header(500); echo 'Cannot open output'; exit; }
// BOM для Excel/WPAI
fprintf($out, "\xEF\xBB\xBF");
// Заголовки
fputcsv($out, [
'sku','title','description',
'price','currency', // конечная цена/валюта (для WooCommerce)
'price_src','currency_src', // исходные
'stock',
'category_path','category_name','category_id',
'image_urls'
]);
foreach ($rows as $r) fputcsv($out, $r);
fclose($out);
exit;
});
/** Загрузка XML по URL */
function y2c_load_xml(string $url): ?SimpleXMLElement {
$res = wp_remote_get($url, ['timeout' => 25, 'redirection' => 5]);
if (is_wp_error($res)) return null;
$code = wp_remote_retrieve_response_code($res);
if ($code < 200 || $code >= 300) return null;
$body = wp_remote_retrieve_body($res);
if (!$body) return null;
libxml_use_internal_errors(true);
$xml = simplexml_load_string($body);
return $xml ?: null;
}
/** Вытаскиваем категории (id, parentId, name) */
function y2c_extract_categories(SimpleXMLElement $xml): array {
$out = [];
$nodes = $xml->shop->categories->category ?? $xml->categories->category ?? null;
if (!$nodes) return $out;
foreach ($nodes as $c) {
$id = (string)$c['id'];
if ($id === '') continue;
$out[$id] = [
'id' => $id,
'parent' => isset($c['parentId']) ? (string)$c['parentId'] : '',
'name' => trim((string)$c),
];
}
return $out;
}
/** Строим текстовые пути категорий "Parent > Child > Leaf" */
function y2c_build_paths(array $cats): array {
$paths = [];
foreach ($cats as $id => $_) {
if (isset($paths[$id])) continue;
$chain = [];
$cur = $id;
$seen = [];
while ($cur && !isset($paths[$cur])) {
if (isset($seen[$cur])) { $chain = []; break; } // защита от цикла
$seen[$cur] = true;
$chain[] = $cur;
$cur = $cats[$cur]['parent'] ?? '';
}
$prefix = ($cur && isset($paths[$cur])) ? $paths[$cur] : [];
for ($i = count($chain)-1; $i >= 0; $i--) {
$cid = $chain[$i];
$prefix[] = $cats[$cid]['name'] ?? '';
$paths[$cid] = $prefix;
}
}
$out = [];
foreach ($paths as $id => $arr) $out[$id] = implode(' > ', array_filter($arr));
return $out;
}
/** Курс PLN→UAH из НБУ */
function y2c_rate_pln_to_uah(): float {
// НБУ JSON: [{"r030":985,"txt":"Злотий польський","rate":...,"cc":"PLN","exchangedate":"..."}]
$res = wp_remote_get('https://bank.gov.ua/NBUStatService/v1/statdirectory/exchange?valcode=PLN&json', ['timeout'=>10]);
if (is_wp_error($res)) return 0.0;
if (wp_remote_retrieve_response_code($res) !== 200) return 0.0;
$data = json_decode(wp_remote_retrieve_body($res), true);
if (!is_array($data) || empty($data[0]['rate'])) return 0.0;
return (float)$data[0]['rate'];
}
/**
* Формирование строк офферов + конвертация цены.
* - Если задан rate>0 — используем его (любая исходная валюта будет трактоваться как PLN для умножения).
* - Если rate не задан и target_cur=UAH, то:
* - если исходная валюта = PLN → умножаем на курс НБУ,
* - иначе оставляем как есть (fail-safe).
* - margin применяется поверх конверсии, после чего округляем.
*/
function y2c_extract_offers_with_fx(
SimpleXMLElement $xml,
array $cats,
array $paths,
string $targetCur,
float $rateParam,
float $nbuRate,
float $margin,
int $precision
): array {
$offers = $xml->shop->offers->offer ?? $xml->offers->offer ?? null;
$rows = [];
if (!$offers) return $rows;
foreach ($offers as $o) {
$sku = (string)($o->vendorCode ?? $o['id'] ?? '');
$title = trim((string)($o->name ?? $o->model ?? $o->title ?? ''));
$desc = trim((string)($o->description ?? ''));
$priceSrc = (float)($o->price ?? 0);
$curSrc = strtoupper((string)($o->currencyId ?? 'PLN')); // по умолчанию считаем PLN, если нет поля
// Базово — без конверсии
$priceOut = $priceSrc;
$curOut = $curSrc;
// Конвертация
if ($targetCur !== '') {
if ($rateParam > 0) {
// Принудительный курс
$priceOut = $priceSrc * $rateParam;
$curOut = $targetCur;
} elseif ($targetCur === 'UAH' && $curSrc === 'PLN' && $nbuRate > 0) {
$priceOut = $priceSrc * $nbuRate;
$curOut = 'UAH';
}
}
// Наценка (если есть)
if ($margin > 0 && $margin !== 1.0) {
$priceOut = $priceOut * $margin;
}
// Округление
$priceOut = ($precision >= 0) ? round($priceOut, $precision) : $priceOut;
$stock = (string)($o->quantity ?? $o->stock_quantity ?? '');
$catId = (string)($o->categoryId ?? '');
$catName = $cats[$catId]['name'] ?? '';
$catPath = $paths[$catId] ?? $catName;
$imgs = [];
if (isset($o->picture)) {
foreach ($o->picture as $p) { $u = trim((string)$p); if ($u) $imgs[] = $u; }
} elseif (isset($o->images)) {
foreach ($o->images->image as $p) { $u = trim((string)$p); if ($u) $imgs[] = $u; }
}
$rows[] = [
$sku,
$title,
$desc,
$priceOut,
$curOut,
$priceSrc,
$curSrc,
$stock,
$catPath,
$catName,
$catId,
implode(',', $imgs),
];
}
return $rows;
}