Add meta feed functionality and enhance error handling in catalog API
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
defined($security_key) or exit;
|
defined($security_key) or exit;
|
||||||
|
|
||||||
|
ini_set('display_errors', '1');
|
||||||
|
ini_set('display_startup_errors', '1');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
// Catalog
|
// Catalog
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
@@ -22,7 +26,7 @@ if(isset($get_content) && $get_content!=''){
|
|||||||
foreach ($requests as $y){
|
foreach ($requests as $y){
|
||||||
$v = explode("=", $y);
|
$v = explode("=", $y);
|
||||||
//INCLUDE VARIABLES IN ARRAY
|
//INCLUDE VARIABLES IN ARRAY
|
||||||
$criterias[$v[0]] = $v[1];
|
$criterias[$v[0]] = $v[1] ?? true;
|
||||||
|
|
||||||
if ($v[0] == 'category'){
|
if ($v[0] == 'category'){
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
@@ -88,7 +92,7 @@ foreach ($products as $product) {
|
|||||||
$version_configurations[$item['version']] = [
|
$version_configurations[$item['version']] = [
|
||||||
'version_id' => $item['version'],
|
'version_id' => $item['version'],
|
||||||
'config_setting' => $item['config'],
|
'config_setting' => $item['config'],
|
||||||
'main_option_for_display' => $item['measurement'],
|
'main_option_for_display' => $item['measurement'] ?? '',
|
||||||
'configurations' => []
|
'configurations' => []
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -170,6 +174,64 @@ removeKeysRecursive($catalog,$keys_to_remove);
|
|||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
$messages = processProductCollection($catalog);
|
$messages = processProductCollection($catalog);
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
//check for METAfeed request
|
||||||
|
//------------------------------------------
|
||||||
|
if (isset($criterias['meta'])){
|
||||||
|
//------------------------------------------
|
||||||
|
// Meta Feed Configuration
|
||||||
|
//------------------------------------------
|
||||||
|
$meta_config = [
|
||||||
|
'base_url' => 'https://www.morvalwatches.com', // Product page URL
|
||||||
|
'image_base_url' => 'https://cloud.soveliti.nl',
|
||||||
|
'brand' => 'Morval Watches',
|
||||||
|
'currency' => 'EUR',
|
||||||
|
'condition' => 'new',
|
||||||
|
'availability' => 'in stock',
|
||||||
|
'google_product_category' => 'Apparel & Accessories > Jewelry > Watches',
|
||||||
|
'output_format' => 'json' // Options: 'csv', 'xml', 'json'
|
||||||
|
];
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// Product Code Based Descriptions
|
||||||
|
// Keys can be: exact code (MWTH2NB) or pattern (MWTH1*, MWTH2*)
|
||||||
|
//------------------------------------------
|
||||||
|
$meta_descriptions = [
|
||||||
|
// Pattern based (will match any product starting with this)
|
||||||
|
'MWTH1' => 'The Thomas-I exudes elegance and sophistication. Classic dimensions combined with subtle details in the dial make it an special automatic watch that can be worn on all occasions.',
|
||||||
|
'MWTH2' => 'The Thomas-II provides a view of the beating heart of the Swiss timepiece. It marks the precision and perfection with which the time is displayed.',
|
||||||
|
'MWABR' => 'Handmade Italian calf leather bracelet',
|
||||||
|
];
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
//Include meta functions
|
||||||
|
//------------------------------------------
|
||||||
|
include_once './assets/functions_meta.php';
|
||||||
|
|
||||||
|
$meta_feed = catalogToMetaFeed($messages, $meta_config);
|
||||||
|
//------------------------------------------
|
||||||
|
// Output based on format parameter
|
||||||
|
//------------------------------------------
|
||||||
|
if ($criterias['meta'] === true) {
|
||||||
|
$format = $meta_config['output_format'];
|
||||||
|
} else {
|
||||||
|
$format = $criterias['meta'];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($format) {
|
||||||
|
case 'xml':
|
||||||
|
$messages = outputMetaFeedXML($meta_feed);
|
||||||
|
break;
|
||||||
|
case 'csv':
|
||||||
|
$messages = outputMetaFeedCSV($meta_feed);
|
||||||
|
break;
|
||||||
|
case 'json':
|
||||||
|
default:
|
||||||
|
$messages = outputMetaFeedJSON($meta_feed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
exit();
|
||||||
|
}
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
//JSON_ENCODE
|
//JSON_ENCODE
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|||||||
256
assets/functions_meta.php
Normal file
256
assets/functions_meta.php
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// Function: Transform Catalog to Meta Feed
|
||||||
|
//------------------------------------------
|
||||||
|
function catalogToMetaFeed($catalog, $config) {
|
||||||
|
$meta_feed = [];
|
||||||
|
|
||||||
|
foreach ($catalog as $product) {
|
||||||
|
// Extract product data
|
||||||
|
$product_id = $product['productcode'] ?? '';
|
||||||
|
$product_name = $product['productname'] ?? '';
|
||||||
|
$original_description = cleanText($product['productdescription'] ?? '');
|
||||||
|
|
||||||
|
// Get meta description based on product code (with fallback to original)
|
||||||
|
$description = getMetaDescription($product_id, $original_description);
|
||||||
|
|
||||||
|
$base_price = floatval($product['price'] ?? 0);
|
||||||
|
$url_slug = $product['url_slug'] ?? '';
|
||||||
|
$main_image = buildImageUrl($product['full_path'] ?? '', $config['image_base_url']);
|
||||||
|
$product_url = buildProductUrl($url_slug, $config['base_url']);
|
||||||
|
|
||||||
|
// Check if product has variants
|
||||||
|
$has_variants = !empty($product['configurations']);
|
||||||
|
|
||||||
|
if (!$has_variants) {
|
||||||
|
// Simple product - no variants
|
||||||
|
$meta_feed[] = buildMetaProduct([
|
||||||
|
'id' => $product_id,
|
||||||
|
'title' => $product_name,
|
||||||
|
'description' => $description,
|
||||||
|
'price' => $base_price,
|
||||||
|
'link' => $product_url,
|
||||||
|
'image_link' => $main_image,
|
||||||
|
], $config);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Product with variants
|
||||||
|
$variants = extractVariants($product, $base_price);
|
||||||
|
|
||||||
|
foreach ($variants as $variant) {
|
||||||
|
// Combine meta description with variant suffix
|
||||||
|
$variant_description = $description . ' ' . $variant['description_suffix'];
|
||||||
|
|
||||||
|
$meta_feed[] = buildMetaProduct([
|
||||||
|
'id' => $variant['id'],
|
||||||
|
'item_group_id' => $product_id,
|
||||||
|
'title' => $variant['title'],
|
||||||
|
'description' => $variant_description,
|
||||||
|
'price' => $variant['price'],
|
||||||
|
'link' => $product_url,
|
||||||
|
'image_link' => $variant['image'],
|
||||||
|
'additional_image_link' => $variant['additional_image'] ?? '',
|
||||||
|
], $config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $meta_feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// Extract variants from configurations
|
||||||
|
//------------------------------------------
|
||||||
|
function extractVariants($product, $base_price) {
|
||||||
|
$variants = [];
|
||||||
|
$product_id = $product['productcode'];
|
||||||
|
$product_name = $product['productname'];
|
||||||
|
$image_base_url = $GLOBALS['meta_config']['image_base_url'];
|
||||||
|
|
||||||
|
$configurations = $product['configurations'] ?? [];
|
||||||
|
|
||||||
|
foreach ($configurations as $config) {
|
||||||
|
if ($config['type'] === 'group') {
|
||||||
|
$group_name = $config['assignment_name'] ?? 'Option';
|
||||||
|
$attributes = $config['attributes'] ?? [];
|
||||||
|
|
||||||
|
foreach ($attributes as $attr) {
|
||||||
|
// Calculate price
|
||||||
|
$attr_price = floatval($attr['price'] ?? 0);
|
||||||
|
$attr_modifier = intval($attr['price_modifier'] ?? 1);
|
||||||
|
$final_price = calculateVariantPrice($base_price, $attr_price, $attr_modifier);
|
||||||
|
|
||||||
|
// Get images (using image_base_url for images)
|
||||||
|
$variant_image = buildImageUrl($attr['alternative_media_full_path'] ?? '', $image_base_url);
|
||||||
|
$additional_image = buildImageUrl($attr['full_path'] ?? '', $image_base_url);
|
||||||
|
|
||||||
|
// Fallback to main product image if no variant image
|
||||||
|
if (empty($variant_image)) {
|
||||||
|
$variant_image = buildImageUrl($product['full_path'] ?? '', $image_base_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract option name from item_name
|
||||||
|
$item_name = $attr['item_name'] ?? '';
|
||||||
|
$option_name = formatAttributeName($item_name);
|
||||||
|
|
||||||
|
// Build variant
|
||||||
|
$variants[] = [
|
||||||
|
'id' => $product_id . '_' . ($attr['attribute_id'] ?? ''),
|
||||||
|
'title' => $product_name . ' - ' . $option_name,
|
||||||
|
'description_suffix' => 'Available with ' . $option_name . ' ' . strtolower($group_name) . '.',
|
||||||
|
'price' => $final_price,
|
||||||
|
'image' => $variant_image,
|
||||||
|
'additional_image' => $additional_image,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $variants;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// Build Meta product row
|
||||||
|
//------------------------------------------
|
||||||
|
function buildMetaProduct($data, $config) {
|
||||||
|
$product = [
|
||||||
|
'id' => $data['id'],
|
||||||
|
'title' => $data['title'],
|
||||||
|
'description' => $data['description'],
|
||||||
|
'availability' => $config['availability'],
|
||||||
|
'condition' => $config['condition'],
|
||||||
|
'price' => formatPrice($data['price'], $config['currency']),
|
||||||
|
'link' => $data['link'],
|
||||||
|
'image_link' => $data['image_link'],
|
||||||
|
'brand' => $config['brand'],
|
||||||
|
'google_product_category' => $config['google_product_category'],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add optional fields if present
|
||||||
|
if (!empty($data['item_group_id'])) {
|
||||||
|
$product['item_group_id'] = $data['item_group_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($data['additional_image_link'])) {
|
||||||
|
$product['additional_image_link'] = $data['additional_image_link'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $product;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// Helper Functions
|
||||||
|
//------------------------------------------
|
||||||
|
function getMetaDescription($product_code, $fallback_description = '') {
|
||||||
|
global $meta_descriptions;
|
||||||
|
|
||||||
|
// First check for exact match
|
||||||
|
if (isset($meta_descriptions[$product_code])) {
|
||||||
|
return $meta_descriptions[$product_code];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check for pattern match (starts with)
|
||||||
|
foreach ($meta_descriptions as $pattern => $description) {
|
||||||
|
if (strpos($product_code, $pattern) === 0) {
|
||||||
|
return $description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return fallback if no match
|
||||||
|
return $fallback_description;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateVariantPrice($base_price, $modifier_price, $modifier_type) {
|
||||||
|
if ($modifier_price <= 0) {
|
||||||
|
return $base_price;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($modifier_type) {
|
||||||
|
case 1: // Addition
|
||||||
|
return $base_price + $modifier_price;
|
||||||
|
case 2: // Multiplication
|
||||||
|
return $base_price * $modifier_price;
|
||||||
|
default:
|
||||||
|
return $base_price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildImageUrl($path, $base_url) {
|
||||||
|
if (empty($path)) return '';
|
||||||
|
return rtrim($base_url, '/') . '/' . ltrim($path, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildProductUrl($slug, $base_url) {
|
||||||
|
if (empty($slug)) return '';
|
||||||
|
return rtrim($base_url, '/') . '/product/' . $slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPrice($price, $currency) {
|
||||||
|
return number_format($price, 2, '.', '') . ' ' . $currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanText($text) {
|
||||||
|
$text = strip_tags($text);
|
||||||
|
$text = preg_replace('/\s+/', ' ', $text);
|
||||||
|
return trim($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAttributeName($name) {
|
||||||
|
// Remove prefix like "bracelet_"
|
||||||
|
$name = preg_replace('/^bracelet_/', '', $name);
|
||||||
|
// Replace underscores with spaces
|
||||||
|
$name = str_replace('_', ' ', $name);
|
||||||
|
// Capitalize words
|
||||||
|
return ucwords($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// Output Functions
|
||||||
|
//------------------------------------------
|
||||||
|
function outputMetaFeedCSV($meta_feed) {
|
||||||
|
header('Content-Type: text/csv; charset=utf-8');
|
||||||
|
header('Content-Disposition: attachment; filename="meta_product_feed.csv"');
|
||||||
|
|
||||||
|
$output = fopen('php://output', 'w');
|
||||||
|
|
||||||
|
// Write headers
|
||||||
|
if (!empty($meta_feed)) {
|
||||||
|
fputcsv($output, array_keys($meta_feed[0]));
|
||||||
|
|
||||||
|
// Write data
|
||||||
|
foreach ($meta_feed as $row) {
|
||||||
|
fputcsv($output, $row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
function outputMetaFeedXML($meta_feed) {
|
||||||
|
header('Content-Type: application/xml; charset=utf-8');
|
||||||
|
|
||||||
|
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:g="http://base.google.com/ns/1.0"></rss>');
|
||||||
|
$channel = $xml->addChild('channel');
|
||||||
|
$channel->addChild('title', 'Morval Product Feed');
|
||||||
|
$channel->addChild('link', $GLOBALS['meta_config']['base_url']);
|
||||||
|
$channel->addChild('description', 'Product feed for Meta catalog');
|
||||||
|
|
||||||
|
foreach ($meta_feed as $product) {
|
||||||
|
$item = $channel->addChild('item');
|
||||||
|
|
||||||
|
foreach ($product as $key => $value) {
|
||||||
|
$xml_key = 'g:' . $key;
|
||||||
|
$item->addChild($xml_key, htmlspecialchars($value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $xml->asXML();
|
||||||
|
}
|
||||||
|
|
||||||
|
function outputMetaFeedJSON($meta_feed) {
|
||||||
|
//header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode($meta_feed, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
var port, textEncoder, writableStreamClosed, writer, historyIndex = -1;
|
var port, textEncoder, writableStreamClosed, writer, historyIndex = -1;
|
||||||
const lineHistory = [];
|
const lineHistory = [];
|
||||||
maintenanceRun = 0;
|
let maintenanceRun = 0;
|
||||||
handshakeComplete = false;
|
let handshakeComplete = false;
|
||||||
|
|
||||||
// Function to log communication to API
|
// Function to log communication to API
|
||||||
async function logCommunication(data, direction) {
|
async function logCommunication(data, direction) {
|
||||||
@@ -102,6 +102,7 @@ async function connectSerial() {
|
|||||||
// Log connection failure details
|
// Log connection failure details
|
||||||
await logCommunication(`Serial connection failed: ${error.message || 'Unknown error'}`, 'disconnected');
|
await logCommunication(`Serial connection failed: ${error.message || 'Unknown error'}`, 'disconnected');
|
||||||
alert("Serial Connection Failed");
|
alert("Serial Connection Failed");
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,7 +581,7 @@ async function closePort(){
|
|||||||
|
|
||||||
maintenanceRun = 0; // reset maintenanceRun
|
maintenanceRun = 0; // reset maintenanceRun
|
||||||
// Refresh the page
|
// Refresh the page
|
||||||
window.location.reload();
|
setTimeout(() => {window.location.reload();}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closePortCarTest(){
|
async function closePortCarTest(){
|
||||||
@@ -736,7 +737,7 @@ async function updateHistory(){
|
|||||||
body: testdetails
|
body: testdetails
|
||||||
});
|
});
|
||||||
|
|
||||||
const historyresult = await response.json;
|
const historyresult = await response.json();
|
||||||
|
|
||||||
setTimeout(maintenanceTest,5000);
|
setTimeout(maintenanceTest,5000);
|
||||||
|
|
||||||
@@ -805,5 +806,6 @@ async function updateHistory(){
|
|||||||
catch(e)
|
catch(e)
|
||||||
{
|
{
|
||||||
alert(e.message || e);
|
alert(e.message || e);
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
const serialResultsDiv = document.getElementById("serialResults");
|
const serialResultsDiv = document.getElementById("serialResults");
|
||||||
|
|
||||||
|
let port, textEncoder, writableStreamClosed, writer;
|
||||||
|
let readableStreamClosed, reader, openPort = 0;
|
||||||
|
let x, serial, fw, hw, sw, commitCode;
|
||||||
|
let firmwarelocation, upgraded_version, hex_fw;
|
||||||
|
|
||||||
// Buffer for accumulating received data before logging
|
// Buffer for accumulating received data before logging
|
||||||
let receivedDataBuffer = '';
|
let receivedDataBuffer = '';
|
||||||
|
|
||||||
@@ -113,13 +118,15 @@ async function connectDevice() {
|
|||||||
// Log connection failure details
|
// Log connection failure details
|
||||||
await logCommunication(`Serial connection failed: ${error.message || 'Unknown error'}`, 'disconnected');
|
await logCommunication(`Serial connection failed: ${error.message || 'Unknown error'}`, 'disconnected');
|
||||||
|
|
||||||
if (openPort = 1){
|
if (openPort === 1){
|
||||||
closePort();
|
closePort();
|
||||||
console.log("Closing port");
|
console.log("Closing port");
|
||||||
alert("System is still trying to close the serial port. If this message continues to come up please refresh this page.");
|
alert("System is still trying to close the serial port. If this message continues to come up please refresh this page.");
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
alert("Your browser does not support this functionality. Please use latest Chrome or Edge browser.");
|
alert("Your browser does not support this functionality. Please use latest Chrome or Edge browser.");
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,6 +168,7 @@ async function listenToPort() {
|
|||||||
|
|
||||||
async function appendToTerminal(newStuff) {
|
async function appendToTerminal(newStuff) {
|
||||||
serialResultsDiv.innerHTML += newStuff;
|
serialResultsDiv.innerHTML += newStuff;
|
||||||
|
receivedDataBuffer += newStuff;
|
||||||
|
|
||||||
// Log received data
|
// Log received data
|
||||||
await logCommunication(newStuff.trim(), 'received');
|
await logCommunication(newStuff.trim(), 'received');
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ echo '
|
|||||||
<script>
|
<script>
|
||||||
var link = "'.$baseurl.'";
|
var link = "'.$baseurl.'";
|
||||||
var DEBUG = '.(debug ? 'true' : 'false').';
|
var DEBUG = '.(debug ? 'true' : 'false').';
|
||||||
var port, textEncoder, writableStreamClosed, writer, historyIndex = -1;
|
var historyIndex = -1;
|
||||||
const lineHistory = [];
|
const lineHistory = [];
|
||||||
</script>';
|
</script>';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user