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; }