From 95602bf1bc9c98cc4e86b5e045c38b452b5d51c8 Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Fri, 26 Jun 2026 19:42:36 +0600 Subject: [PATCH] fix(product): persist clearing color/size/tags on save (#324) Vue ms3-combo-options omitted empty arrays from the legacy MODX form POST, so ProductDataPayloadTrait never updated cleared fields. Also sync explicitly cleared JSON fields to msProductOption and normalize empty arrays in prepareOptionValues. --- .../minishop3/src/Model/msProductData.php | 22 +++++++------- .../Services/Product/ProductDataService.php | 29 +++++++++++++------ vueManager/src/components/DynamicField.vue | 7 +++++ 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/core/components/minishop3/src/Model/msProductData.php b/core/components/minishop3/src/Model/msProductData.php index 6cd887a8..d845a2ae 100644 --- a/core/components/minishop3/src/Model/msProductData.php +++ b/core/components/minishop3/src/Model/msProductData.php @@ -139,17 +139,19 @@ public function getArraysValues() */ public function prepareOptionValues($values = null) { - if ($values) { - if (!is_array($values)) { - $values = [$values]; - } - $values = array_map('trim', $values); - $values = array_keys(array_flip($values)); - $values = array_diff($values, ['']); + if ($values === null) { + return null; + } - if (empty($values)) { - $values = null; - } + if (!is_array($values)) { + $values = [$values]; + } + $values = array_map('trim', $values); + $values = array_keys(array_flip($values)); + $values = array_diff($values, ['']); + + if (empty($values)) { + $values = null; } return $values; diff --git a/core/components/minishop3/src/Services/Product/ProductDataService.php b/core/components/minishop3/src/Services/Product/ProductDataService.php index 0624f234..96d213ec 100644 --- a/core/components/minishop3/src/Services/Product/ProductDataService.php +++ b/core/components/minishop3/src/Services/Product/ProductDataService.php @@ -164,8 +164,9 @@ public function saveCategories(msProductData $productData): void * via msProductOption::saveProductOptions() method * * @param msProductData $productData - * @param array|null $options If null: built from non-empty JSON fields on $productData only. If array: explicit - * keys/values (e.g. manager POST options-*); then $removeOther is honored. + * @param array|null $options If null: built from JSON fields explicitly present in POST/_fields on + * $productData (including cleared empty values). If array: explicit keys/values + * (e.g. manager POST options-*); then $removeOther is honored. * @param bool $removeOther When $options is non-null: if true, delete msProductOption rows whose keys are absent * from $options. When $options is null: ignored — always treated as false so category-only * options not mirrored in JSON fields are preserved (#153, #158). @@ -180,14 +181,24 @@ public function saveOptions(msProductData $productData, ?array $options = null, if ($options === null) { $options = []; - foreach ($productData->_fieldMeta as $key => $value) { - if ($value['phptype'] === 'json' && !empty($productData->get($key))) { - if (in_array($key, $repeaterKeys, true)) { - continue; - } - // Use field name as key, not numeric index from array_merge - $options[$key] = $productData->get($key); + $reflection = new \ReflectionClass($productData); + $property = $reflection->getProperty('_fields'); + $property->setAccessible(true); + $rawFields = $property->getValue($productData); + + foreach ($productData->_fieldMeta as $key => $meta) { + if (($meta['phptype'] ?? '') !== 'json') { + continue; } + if (in_array($key, $repeaterKeys, true)) { + continue; + } + if (!array_key_exists($key, $rawFields)) { + continue; + } + + $fieldValue = $productData->get($key); + $options[$key] = !empty($fieldValue) ? $fieldValue : null; } } diff --git a/vueManager/src/components/DynamicField.vue b/vueManager/src/components/DynamicField.vue index 41533117..a7e467f6 100644 --- a/vueManager/src/components/DynamicField.vue +++ b/vueManager/src/components/DynamicField.vue @@ -163,6 +163,13 @@ :value="val" /> + +