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
commit a7d7dbb164
61 changed files with 8501 additions and 0 deletions

View File

@@ -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;
}
}
/* -------------------------------------------------------------------------- */

View File

@@ -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

View File

@@ -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:"Λάθος κατά τη μετατροπή της τιμής που δόθηκε"};

View File

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

View File

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

View File

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

View File

@@ -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

1
connector-miravia/lib/select2.min.css vendored Normal file

File diff suppressed because one or more lines are too long

2
connector-miravia/lib/select2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long