Fix image upload structure for Miravia API compliance

🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Miravia Connector Bot
2025-07-17 08:11:23 +02:00
co-authored by Claude
commit a7d7dbb164
61 changed files with 8501 additions and 0 deletions
@@ -0,0 +1,181 @@
/*
* jui_filter_rules v1.0.7 CSS (for Bootstrap) *********************************
*/
/*
DO NOT CHANGE this file, as it will be overwritten in next update.
Write your own classes in other css file.
*/
/* container ---------------------------------------------------------------- */
.filter_rules_container {
padding: 10px 0;
}
/* rules group -------------------------------------------------------------- */
.rules_group_container {
padding: 0;
margin: 0;
}
.rules_group_header {
padding: 5px 0;
}
.rules_group_body {
padding: 0;
margin: 0;
}
.rules_group_condition_container {
display: inline-block;
padding: 0;
}
.rules_group_condition_list {
border: 1px solid;
}
.rules_group_tools_container {
display: inline-block;
padding: 0;
margin-left: 5px;
}
.rules_group_tools_list {
border: 1px solid;
padding: 2px;
margin: 0;
width: 35px;
}
/* rules list --------------------------------------------------------------- */
.rules_list {
width: auto !important;
list-style-type: none;
padding: 0 0 0 25px;
border-left: dotted 1px !important;
border-right: none !important;
border-top: none !important;
border-bottom: none !important;
}
.rules_list_li {
margin: 0 0 2px 0 !important;
padding: 0 !important;
display: block !important;
}
.rules_list_applied_li {
}
.rules_list_error_li {
background-color: #df0202 !important;
}
/* filter name -------------------------------------------------------------- */
.filter_container {
display: inline-block;
padding: 5px 5px 5px 0;
vertical-align: top;
}
.filter_list {
}
/* operator ----------------------------------------------------------------- */
.operators_list_container {
display: inline-block;
padding: 5px;
vertical-align: top;
}
.operators_list {
}
/* filter value ------------------------------------------------------------- */
.filter_value_container {
display: inline-block;
padding: 5px;
}
.filter_input_text {
}
.filter_input_number {
width: 80px;
}
.filter_input_date {
}
.filter_input_checkbox {
margin: 0 5px 0 0;
display: inline !important;
}
.filter_input_radio {
margin: 0 5px 0 0;
}
.filter_select {
margin: 0 5px;
}
.filter_group_list {
list-style-type: none;
padding: 0;
}
.filter_group_list_item_horizontal {
display: inline !important;
margin: 0 5px;
padding: 0;
}
.filter_group_list_item_vertical {
display: block !important;
margin: 0 0 5px 5px !important;
padding: 0 !important;
}
/* filter tools ------------------------------------------------------------- */
.rule_tools_container {
display: inline-block !important;
padding: 5px !important;
vertical-align: top !important;
}
.rule_tools_list {
padding: 0;
margin: 0;
width: 35px;
}
/* no filters div ----------------------------------------------------------- */
.no_filters_found{
text-align: center;
margin-top: 10px;
margin-left: auto;
margin-right: auto;
}
/* IE8 CSS hack ------------------------------------------------------------- */
/* http://dimox.net/personal-css-hacks-for-ie6-ie7-ie8/ */
@media \0screen {
.rules_group_tools_list {
border: 1px solid;
padding: 2px;
margin: 0;
width: auto;
}
.rule_tools_list {
padding: 0;
margin: 0;
width: auto;
}
}
/* -------------------------------------------------------------------------- */
@@ -0,0 +1,5 @@
/*
* jui_filter_rules v1.0.7 CSS *************************************************
* Minified using http://gpbmike.github.io/refresh-sf/
*/
.filter_rules_container{padding:5px 10px 10px}.rules_group_container{padding:0;margin:0}.rules_group_header{padding:5px 0}.rules_group_body{padding:0;margin:0}.rules_group_condition_container{display:inline-block;padding:0}.rules_group_condition_list{border:1px solid;padding:2px 0;margin:0}.rules_group_tools_container{display:inline-block;padding:0;margin-left:5px}.rules_group_tools_list{border:1px solid;padding:2px;margin:0;width:35px}.rules_list{width:auto!important;list-style-type:none;padding:0 0 0 25px;border-left:dotted 1px!important;border-right:none!important;border-top:none!important;border-bottom:none!important}.rules_list_li{margin:0 0 2px!important;padding:0!important;background-color:#f3f3f3!important;display:block!important}.rules_list_applied_li{background-color:#CF9!important}.rules_list_error_li{background-color:#df0202!important}.filter_container{display:inline-block;padding:5px 5px 5px 0;vertical-align:top}.filter_list{padding:0;margin:0;background:0 0}.operators_list_container{display:inline-block;padding:5px;width:180px;vertical-align:top}.operators_list{padding:0;margin:0;background:0 0}.filter_value_container{display:inline-block;padding:5px}.filter_input_text{width:180px;margin:0 5px;padding:0}.filter_input_number{width:75px;margin:0 5px;padding:0}.filter_input_date{width:150px;margin:0 5px;padding:0}.filter_input_checkbox,.filter_input_radio{margin:0 5px 0 0}.filter_select{margin:0 5px}.filter_group_list{list-style-type:none;padding:0}.filter_group_list_item_horizontal{display:inline!important;margin:0 5px;padding:0}.filter_group_list_item_vertical{display:block!important;margin:0 0 5px 5px!important;padding:0!important}.rule_tools_container{display:inline-block!important;padding:5px!important;float:right!important;vertical-align:top!important}.rule_tools_list{padding:0;margin:0;width:35px}.no_filters_found{text-align:center;margin-top:10px;margin-left:auto;margin-right:auto}@media \0screen{.rules_group_tools_list{border:1px solid;padding:2px;margin:0;width:auto}.rule_tools_list{padding:0;margin:0;width:auto}}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
/**
* jui_filter_rules v1.0.7 simple localization - GREEK *************************
*/
var rsc_jui_fr={no_filters_found:"Δεν έχουν ορισθεί φίλτρα",rules_group_AND:"σε ίσχύ όλα",rules_group_OR:"σε ίσχύ ένα τουλάχιστον",rule:"κριτήριο",group:"ομάδα",tools_please_select:"&raquo;",rule_insert_before:"εισαγωγή πριν",rule_insert_after:"εισαγωγή μετά",rule_insert_inside:"εισαγωγή εντός",rule_clear:"καθαρισμός",rule_delete:"διαγραφή",group_insert_before:"εισαγωγή πριν",group_insert_after:"εισαγωγή μετά",group_insert_inside:"εισαγωγή εντός",group_delete:"διαγραφή",filter_please_select:"&raquo; επιλογή",operator_equal:"ισούται",operator_not_equal:"δεν ισούται",operator_in:"περιλαμβάνεται σε",operator_not_in:"δεν περιλαμβάνεται σε",operator_less:"μικρότερο",operator_less_or_equal:"μικρότερο είτε ίσο",operator_greater:"μεγαλύτερο",operator_greater_or_equal:"μεγαλύτερο είτε ίσο",operator_between:"ανάμεσα",operator_not_between:"όχι ανάμεσα",operator_begins_with:"ξεκινά με",operator_not_begins_with:"δεν ξεκινά με",operator_contains:"περιέχει",operator_not_contains:"δεν περιέχει",operator_ends_with:"καταλήγει σε",operator_not_ends_with:"δεν καταλήγει σε",operator_is_empty:"είναι κενό",operator_is_not_empty:"δεν είναι κενό",operator_is_null:"δεν έχει δοθεί",operator_is_not_null:"έχει δοθεί",error_no_value_given:"Δεν δόθηκε τιμή",error_invalid_number:"Η τιμή δεν είναι αριθμός",error_invalid_datetime:"Η ημερομηνία δεν είναι σωστή",error_converting_value:"Λάθος κατά τη μετατροπή της τιμής που δόθηκε"};
@@ -0,0 +1,4 @@
/**
* jui_filter_rules v1.0.7 simple localization - ENGLISH ***********************
*/
var rsc_jui_fr={no_filters_found:"No filters defined",rules_group_AND:"all rules",rules_group_OR:"any rule",rule:"rule",group:"group",tools_please_select:"&raquo;",rule_insert_before:"insert before",rule_insert_after:"insert after",rule_insert_inside:"insert inside",rule_clear:"clear",rule_delete:"delete",group_insert_before:"insert before",group_insert_after:"insert after",group_insert_inside:"insert inside",group_delete:"delete",filter_please_select:"&raquo; select",operator_equal:"equal",operator_not_equal:"not_equal",operator_in:"in",operator_not_in:"not in",operator_less:"less",operator_less_or_equal:"less or equal",operator_greater:"greater",operator_greater_or_equal:"greater or equal",operator_between:"between",operator_not_between:"not between",operator_begins_with:"begins with",operator_not_begins_with:"does not begin with",operator_contains:"contains",operator_not_contains:"does not contain",operator_ends_with:"ends with",operator_not_ends_with:"does not end with",operator_is_empty:"is empty",operator_is_not_empty:"is not empty",operator_is_null:"is null",operator_is_not_null:"is not null",error_no_value_given:"Value not given",error_invalid_number:"Value is not numeric",error_invalid_datetime:"Invalid date format",error_converting_value:"Error converting value"};
@@ -0,0 +1,55 @@
/**
* jui_filter_rules v1.0.7 simple localization - ENGLISH
*
* DO NOT CHANGE this file, as it will be overwritten in next update.
* To use different values, write and use a similar structure js file.
*
*/
var rsc_jui_fr = {
no_filters_found: "No hay filtros definidos",
rules_group_AND: "Todas las reglas",
rules_group_OR: "Cualquier regla",
rule: "regla",
group: "grupo",
tools_please_select: "Añadir",
rule_insert_before: "Insertar antes",
rule_insert_after: "Insertar después",
rule_insert_inside: "Insertar dentro",
rule_clear: "limpiar",
rule_delete: "borrar",
group_insert_before: "Insertar antes",
group_insert_after: "Insertar después",
group_insert_inside: "Insertar dentro",
group_delete: "borrar",
filter_please_select: 'Seleccione',
operator_equal: 'igual',
operator_not_equal: 'no igual',
operator_in: 'sea',
operator_not_in: 'no sea',
operator_less: 'menor',
operator_less_or_equal: 'menor o igual',
operator_greater: 'mayor',
operator_greater_or_equal: 'mayor o igual',
operator_between: 'entre',
operator_not_between: 'no esté entre',
operator_begins_with: 'empiece por',
operator_not_begins_with: 'no empiece por',
operator_contains: 'contenga',
operator_not_contains: 'no contenga',
operator_ends_with: 'termine en',
operator_not_ends_with: 'no termine en',
operator_is_empty: 'esté vacio',
operator_is_not_empty: 'no esté vacio',
operator_is_null: 'sea nulo',
operator_is_not_null: 'no sea nulo',
error_no_value_given: "Valor no declarado",
error_invalid_number: "Valor no numerico",
error_invalid_datetime: "Formato de fecha incorrecto",
error_converting_value: "Error al convertir el valor"
};
@@ -0,0 +1,55 @@
/**
* jui_filter_rules v1.0.7 simple localization - ENGLISH
*
* DO NOT CHANGE this file, as it will be overwritten in next update.
* To use different values, write and use a similar structure js file.
*
*/
var rsc_jui_fr = {
no_filters_found: "No filters defined",
rules_group_AND: "all rules",
rules_group_OR: "any rule",
rule: "rule",
group: "group",
tools_please_select: "&raquo;",
rule_insert_before: "insert before",
rule_insert_after: "insert after",
rule_insert_inside: "insert inside",
rule_clear: "clear",
rule_delete: "delete",
group_insert_before: "insert before",
group_insert_after: "insert after",
group_insert_inside: "insert inside",
group_delete: "delete",
filter_please_select: '&raquo; select',
operator_equal: 'equal',
operator_not_equal: 'not_equal',
operator_in: 'in',
operator_not_in: 'not in',
operator_less: 'less',
operator_less_or_equal: 'less or equal',
operator_greater: 'greater',
operator_greater_or_equal: 'greater or equal',
operator_between: 'between',
operator_not_between: 'not between',
operator_begins_with: 'begins with',
operator_not_begins_with: 'does not begin with',
operator_contains: 'contains',
operator_not_contains: 'does not contain',
operator_ends_with: 'ends with',
operator_not_ends_with: 'does not end with',
operator_is_empty: 'is empty',
operator_is_not_empty: 'is not empty',
operator_is_null: 'is null',
operator_is_not_null: 'is not null',
error_no_value_given: "Value not given",
error_invalid_number: "Value is not numeric",
error_invalid_datetime: "Invalid date format",
error_converting_value: "Error converting value"
};
@@ -0,0 +1,235 @@
jQuery(document).ready(function($) {
if(typeof categories == 'undefined') {
return;
}
$("#filter_editor").jui_filter_rules( getFilterConfig() );
if(filter !== false){
setRules(filter);
}
$("#miravia_rules_form").submit(function(){
var data = '';
data = getRulesJson();
$("#miravia_rules_form").append('<input type="hidden" name="filter_data" value="'+encodeURIComponent(data)+'">');
data = getActionDetailJson();
$("#miravia_rules_form").append('<input type="hidden" name="action_detail_data" value="'+encodeURIComponent(data)+'">');
});
$("#action_type").change(function(){
setActionDetail();
});
setActionDetail();
function getFilterConfig() {
var filter_config = {
bootstrap_version: "3",
filters: [
{
filterName: "Name", "filterType": "text", field: "product_name", filterLabel: "Product name",
excluded_operators: ["in", "not_in"],
filter_interface: [
{
filter_element: "input",
filter_element_attributes: {"type": "text", "value": ""}
}
]
},
{
filterName: "Price",
"filterType": "number",
"numberType": "double",
field: "price",
filterLabel: "Product price",
excluded_operators: ["in", "not_in"]
},
{
filterName: "SpecificPrice",
"filterType": "number",
"numberType": "double",
field: "specific_price",
filterLabel: "Product specific price",
excluded_operators: ["in", "not_in"]
},
{
filterName: "Stock",
"filterType": "number",
"numberType": "integer",
field: "stock",
filterLabel: "Stock",
excluded_operators: ["in", "not_in"]
},
{
filterName: "Category",
"filterType": "number",
"numberType": "integer",
field: "category",
filterLabel: "Category",
excluded_operators: ["in", "not_in", "less", "less_or_equal", "greater", "greater_or_equal"],
filter_interface: [
{
filter_element: "select"
}
],
lookup_values: categories
},
// {
// filterName: "Supplier",
// "filterType": "number",
// "numberType": "integer",
// field: "supplier",
// filterLabel: "Supplier",
// excluded_operators: ["in", "not_in", "less", "less_or_equal", "greater", "greater_or_equal"],
// filter_interface: [
// {
// filter_element: "select"
// }
// ],
// lookup_values: suppliers
// },
// {
// filterName: "Manufacturer",
// "filterType": "number",
// "numberType": "integer",
// field: "manufacturer",
// filterLabel: "Manufacturer",
// excluded_operators: ["in", "not_in", "less", "less_or_equal", "greater", "greater_or_equal"],
// filter_interface: [
// {
// filter_element: "select"
// }
// ],
// lookup_values: manufacturers
// },
],
onValidationError: function (event, data) {
alert(data["err_description"] + ' (' + data["err_code"] + ')');
if (data.hasOwnProperty("elem_filter")) {
data.elem_filter.focus();
}
}
};
return filter_config;
}
function setActionDetail()
{
var action_type = $("#action_type").val();
$("#action_detail").html(getActionDetail(action_type));
}
function getActionDetail(action_type)
{
switch (action_type) {
case 'price_stock':
return getActionDetailModifyPriceStock();
case 'name':
return getActionDetailModifyName();
case 'logistics':
return getActionDetailLogistics();
default:
return '';
}
}
function getActionDetailModifyPriceStock()
{
var v =action_detail.field || '';
var html = '<select id="action_detail_field" class="fixed-width-xl pull-left mr-1">';
html += '<option value="price,special_price" ' + ((v=='price,special_price')?'selected':'') + '>Price & Specific price</option>';
html += '<option value="price" ' + ((v=='price')?'selected':'') + '>Price</option>';
html += '<option value="special_price" ' + ((v=='special_price')?'selected':'') + '>Specific price</option>';
html += '<option value="stock" ' + ((v=='stock')?'selected':'') + '>Stock</option>';
html += '</select>';
v = action_detail.operator || '';
html += '<select id="action_detail_operator" class="fixed-width-xl pull-left">';
html += '<option value="increment" ' + ((v=='increment')?'selected':'') + '>Increment €</option>';
html += '<option value="decrement" ' + ((v=='decrement')?'selected':'') + '>Decrement €</option>';
html += '<option value="increment_percent" ' + ((v=='increment_percent')?'selected':'') + '>Increment %</option>';
html += '<option value="decrement_percent" ' + ((v=='decrement_percent')?'selected':'') + '>Decrement %</option>';
html += '<option value="multiply" ' + ((v=='multiply')?'selected':'') + '>Multiply</option>';
html += '<option value="set" ' + ((v=='set')?'selected':'') + '>Set to</option>';
html += '</select> &nbsp; ';
v = action_detail.value || '';
v = v.replaceAll('"', '');
html += '<input type="text" id="action_detail_value" value="' + v + '" class="fixed-width-xl pull-left">';
return html;
}
function getActionDetailModifyName()
{
var v =action_detail.field || '';
var html = '<select id="action_detail_field" class="fixed-width-xl pull-left mr-1">';
html += '<option value="name" ' + ((v=='name')?'selected':'') + '>Mofify "Name"</option>';
html += '<option value="description" ' + ((v=='description')?'selected':'') + '>Modify "Description"</option>';
html += '<option value="short_description" ' + ((v=='short_description')?'selected':'') + '>Modify "Short description"</option>';
html += '</select><br><br>';
v = action_detail.stringvalue || '';
html += '<textarea id="action_detail_value">' + v;
html += '</textarea>';
html += '<div>';
html += 'You can use these variables:<strong> %name%, %description%, %short_description%, %manufacturer%, %supplier%, %category%</strong>';
html += '</div>';
return html;
}
function getActionDetailLogistics()
{
var v = action_detail.delivery || '';
var html = '<select id="action_detail_field" class="fixed-width-xl pull-left mr-1">';
html += '<option value="dbs" ' + ((v=='dbs')?'selected':'') + '>Delivery by Seller</option>';
html += '<option value="dbm" ' + ((v=='dbm')?'selected':'') + '>Delivery by Miravia</option>';
html += '<option value="" ' + ((v=='')?'selected':'') + '>Use defaults</option>';
html += '</select><br><br>';
v = action_detail.warehouse || '';
html += '<input type="text" id="action_detail_value" placeholder="Warehouse code" ';
html += 'value="' + v + '" class="input fixed-width-xl pull-left mr-1">';
html += '<br><br>';
return html;
}
function getActionDetailJson()
{
var action_type = $("#action_type").val();
switch (action_type) {
case 'price_stock':
var ret = {
field: $('#action_detail_field').val(),
operator: $('#action_detail_operator').val(),
value: $('#action_detail_value').val()
};
return JSON.stringify(ret);
case 'name':
var ret = {
field: $('#action_detail_field').val(),
stringvalue: $('#action_detail_value').val()
};
return JSON.stringify(ret);
case 'logistics':
var ret = {
delivery: $('#action_detail_field').val(),
warehouse: $('#action_detail_value').val()
}
return JSON.stringify(ret);
default:
return '';
}
}
function getRulesJson()
{
return JSON.stringify($("#filter_editor").jui_filter_rules('getRules', 0, []));
}
function setRules(a_rules)
{
$("#filter_editor").jui_filter_rules('setRules', a_rules);
}
})
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long