227 lines
6.3 KiB
PHP
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();
|
|
}
|
|
|