first commit

This commit is contained in:
2025-09-25 14:46:36 +03:00
commit fa5baeca0c
2 changed files with 277 additions and 0 deletions

57
README.md Normal file
View File

@@ -0,0 +1,57 @@
# YML → CSV (open endpoint for WP All Import)
Генерирует CSV из Prom.ua YML-фида по открытому эндпоинту. Строит `category_path` для иерархии категорий и конвертирует цену из PLN в UAH (НБУ или вручную).
## Установка
1. Создай папку `wp-content/plugins/yml2csv/`.
2. Сохрани файл плагина как `wp-content/plugins/yml2csv/yml2csv.php`.
3. Активируй плагин в админке WordPress.
## Эндпоинт
`GET /?yml2csv=1&src=...&target_cur=UAH[&rate=...][&margin=...][&precision=...]`
**Параметры:**
* `src` — URL YML-фида (обязательный).
* `target_cur` — целевая валюта. Автоконверсия реализована для `UAH` при исходном `PLN`.
* `rate` — фиксированный курс PLN→UAH. Если задан, НБУ не вызывается.
* `margin` — множитель наценки (например, `1.07` = +7%). По умолчанию `1.0`.
* `precision` — знаков после запятой для финальной цены. По умолчанию `2`.
## Примеры
```text
https://example.com/?yml2csv=1&src=http://127.0.0.1/feeds/prom_feed.yml&target_cur=UAH
https://example.com/?yml2csv=1&src=http://127.0.0.1/feeds/prom_feed.yml&target_cur=UAH&rate=9.25
https://example.com/?yml2csv=1&src=http://127.0.0.1/feeds/prom_feed.yml&target_cur=UAH&margin=1.07&precision=0
```
## Формат CSV
Колонки:
```
sku, title, description,
price, currency, // конечные значения для WooCommerce
price_src, currency_src, // исходные значения из фида
stock,
category_path, category_name, category_id,
image_urls // через запятую
```
## Маппинг в WP All Import
* Title → `title`
* Content → `description`
* Images → `image_urls` (comma-separated)
* Regular Price → `price`
* SKU → `sku`
* Categories → включить «hierarchical (parent/child)» и указать поле `category_path`
* (опц.) сохранить `price_src` / `currency_src` как метаполя
## Лицензия
MIT.

220
yml2csv.php Normal file
View File

@@ -0,0 +1,220 @@
<?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;
}