Files
torebki-fabiola.pl/zaufane-feed.php
2026-03-16 23:34:11 +01:00

227 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
const SOURCE_FEED_URL = 'https://torebki-fabiola.pl/wp-content/uploads/woo-product-feed-pro/xml/05cgea7dmzuh29qasgcgllpwabbn0lut.xml';
const BRAND_NAME = 'Fabiola';
const GOOGLE_PRODUCT_CATEGORY = '3032';
const MPN_PREFIX = 'FAB-';
const GOOGLE_NAMESPACE_URI = 'http://base.google.com/ns/1.0';
const MPN_MAP_FILE = __DIR__ . '/zaufane-feed-mpn-map.json';
function fetchFeedXml(string $url): string
{
$context = stream_context_create([
'http' => [
'method' => 'GET',
'timeout' => 20,
'ignore_errors' => true,
'header' => "User-Agent: zaufane-feed-proxy/1.0\r\n",
],
]);
$xml = @file_get_contents($url, false, $context);
if ($xml !== false && trim($xml) !== '') {
return $xml;
}
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 20,
CURLOPT_USERAGENT => 'zaufane-feed-proxy/1.0',
]);
$result = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
if (is_string($result) && trim($result) !== '') {
return $result;
}
throw new RuntimeException('Nie udalo sie pobrac feedu (cURL): ' . $error);
}
throw new RuntimeException('Nie udalo sie pobrac feedu. Sprawdz allow_url_fopen lub cURL.');
}
function loadMpnMap(string $mapFile): array
{
if (!is_file($mapFile)) {
return [];
}
$json = @file_get_contents($mapFile);
if (!is_string($json) || trim($json) === '') {
return [];
}
$decoded = json_decode($json, true);
return is_array($decoded) ? $decoded : [];
}
function saveMpnMap(string $mapFile, array $map): void
{
$dir = dirname($mapFile);
if (!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) {
throw new RuntimeException('Nie mozna utworzyc katalogu mapy MPN: ' . $dir);
}
$handle = @fopen($mapFile, 'c+');
if ($handle === false) {
throw new RuntimeException('Nie mozna zapisac mapy MPN: ' . $mapFile);
}
if (!flock($handle, LOCK_EX)) {
fclose($handle);
throw new RuntimeException('Nie mozna zablokowac pliku mapy MPN.');
}
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, json_encode($map, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
fflush($handle);
flock($handle, LOCK_UN);
fclose($handle);
}
function normalizeTitleForMpn(string $title): string
{
$title = trim($title);
if ($title === '') {
return 'produkt';
}
$transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $title);
$title = is_string($transliterated) && $transliterated !== '' ? $transliterated : $title;
$title = strtolower($title);
$title = preg_replace('/[^a-z0-9]+/', '-', $title) ?? '';
$title = trim($title, '-');
if ($title === '') {
return 'produkt';
}
return substr($title, 0, 48);
}
function getOrCreateMpn(array &$mpnMap, string $productId, string $title): string
{
if (isset($mpnMap[$productId]) && is_string($mpnMap[$productId]) && $mpnMap[$productId] !== '') {
return $mpnMap[$productId];
}
$base = normalizeTitleForMpn($title);
$mpn = MPN_PREFIX . strtoupper($base . '-' . $productId);
$mpnMap[$productId] = $mpn;
return $mpn;
}
function setElementValue(DOMDocument $dom, DOMElement $parent, string $tagName, string $value): void
{
$node = null;
foreach ($parent->childNodes as $child) {
if ($child instanceof DOMElement && $child->tagName === $tagName) {
$node = $child;
break;
}
}
if (!$node) {
$node = $dom->createElement($tagName);
$parent->appendChild($node);
}
$node->nodeValue = $value;
}
function setGoogleElementValue(DOMDocument $dom, DOMElement $parent, string $localName, string $value): void
{
$node = null;
foreach ($parent->childNodes as $child) {
if (!($child instanceof DOMElement)) {
continue;
}
if ($child->localName === $localName && $child->namespaceURI === GOOGLE_NAMESPACE_URI) {
$node = $child;
break;
}
if ($child->tagName === 'g:' . $localName) {
$node = $child;
break;
}
}
if (!$node) {
$node = $dom->createElementNS(GOOGLE_NAMESPACE_URI, 'g:' . $localName);
$parent->appendChild($node);
}
$node->nodeValue = $value;
}
try {
$sourceXml = fetchFeedXml(SOURCE_FEED_URL);
libxml_use_internal_errors(true);
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = false;
if (!$dom->loadXML($sourceXml)) {
throw new RuntimeException('Niepoprawny XML z feedu zrodlowego.');
}
$root = $dom->documentElement;
if (!$root instanceof DOMElement) {
throw new RuntimeException('Brak elementu glownego w XML.');
}
if (!$root->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'g')) {
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:g', GOOGLE_NAMESPACE_URI);
}
$mpnMap = loadMpnMap(MPN_MAP_FILE);
foreach ($dom->getElementsByTagName('product') as $productNode) {
if (!($productNode instanceof DOMElement)) {
continue;
}
$idNode = $productNode->getElementsByTagName('id')->item(0);
$titleNode = $productNode->getElementsByTagName('title')->item(0);
$productId = $idNode ? trim($idNode->textContent) : '';
$title = $titleNode ? trim($titleNode->textContent) : '';
if ($productId === '') {
continue;
}
$mpn = getOrCreateMpn($mpnMap, $productId, $title);
setElementValue($dom, $productNode, 'brand', BRAND_NAME);
setGoogleElementValue($dom, $productNode, 'mpn', $mpn);
setGoogleElementValue($dom, $productNode, 'google_product_category', GOOGLE_PRODUCT_CATEGORY);
}
saveMpnMap(MPN_MAP_FILE, $mpnMap);
header('Content-Type: application/xml; charset=UTF-8');
echo $dom->saveXML();
} catch (Throwable $e) {
http_response_code(500);
header('Content-Type: text/plain; charset=UTF-8');
echo 'Blad generowania feedu: ' . $e->getMessage();
}