From 551d6a46d19ced0e4693b95141ec2ecc6205ee6a Mon Sep 17 00:00:00 2001 From: Hoang Minh Huong Date: Fri, 3 Apr 2026 12:58:19 +0700 Subject: [PATCH 1/6] use all-type native key union to detect custom settings on import --- src/Unparsers/Field.php | 67 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/Unparsers/Field.php b/src/Unparsers/Field.php index 17e90b6..fddcd4b 100644 --- a/src/Unparsers/Field.php +++ b/src/Unparsers/Field.php @@ -34,7 +34,8 @@ public function unparse() { ->unparse_conditional_logic() ->unparse_tooltip() ->unparse_admin_columns() - ->ensure_boolean( 'save_field' ); + ->ensure_boolean( 'save_field' ) + ->unparse_custom_settings(); $func = "unparse_field_{$this->type}"; if ( method_exists( $this, $func ) ) { @@ -311,4 +312,68 @@ private function get_js_option_value( array $js_options, string $key ) { return null; } + + /** + * Detect keys in the JSON that are not native MetaBox field settings + * and move them into the custom_settings array format that the Builder + * UI can read and display in the Advanced tab. + * + * Requires meta-box-builder plugin to be active (safe fallback if not). + */ + private function unparse_custom_settings(): self { + // Safe fallback: only run when Builder's FieldKeys helper is available. + if ( ! class_exists( '\\MBB\\Helpers\\FieldKeys' ) ) { + return $this; + } + + // All native keys across EVERY registered field type. + // Using the full union ensures per-type lookups don't accidentally + // flag a native key as "custom" when the field type registry is partial. + $all_native_keys = \MBB\Helpers\FieldKeys::get_all_native_keys(); + + // Truly structural keys that are never in the API registry: + // - Identity keys not exposed as "controls" (_id, fields). + // - Complex objects already transformed by dedicated unparse_*() methods. + $structural_keys = [ + '_id', + 'fields', + 'conditional_logic', + 'text_limiter', + 'tooltip', + 'admin_columns', + // Defaults injected by unparse_default_values() — present in registry + // for most types but kept here as safety net for unknown field types. + 'save_field', 'label_description', 'desc', 'size', + 'hide_from_rest', 'hide_from_front', + 'before', 'after', 'class', 'sanitize_callback', + 'required', 'disabled', 'readonly', 'prepend', 'append', + ]; + + $known_keys = array_merge( $all_native_keys, $structural_keys ); + + // Move any unrecognized key into custom_settings format. + $custom_settings = $this->settings['custom_settings'] ?? []; + + foreach ( array_keys( $this->settings ) as $key ) { + if ( in_array( $key, $known_keys, true ) ) { + continue; + } + + // Format required by Builder UI's KeyValue control. + $uid = uniqid(); + $custom_settings[ $uid ] = [ + 'id' => $uid, + 'key' => $key, + 'value' => (string) $this->settings[ $key ], + ]; + + unset( $this->settings[ $key ] ); + } + + if ( ! empty( $custom_settings ) ) { + $this->settings['custom_settings'] = $custom_settings; + } + + return $this; + } } From aba2a8fb922f6deae35e1a3544ac919291a8e222 Mon Sep 17 00:00:00 2001 From: Hoang Minh Huong Date: Sat, 4 Apr 2026 14:16:43 +0700 Subject: [PATCH 2/6] detect and preserve custom attributes on JSON import --- src/Unparsers/Field.php | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/src/Unparsers/Field.php b/src/Unparsers/Field.php index fddcd4b..0946388 100644 --- a/src/Unparsers/Field.php +++ b/src/Unparsers/Field.php @@ -10,6 +10,9 @@ class Field extends Base { private $choice_types = [ 'select', 'radio', 'checkbox_list', 'select_advanced', 'button_group', 'image_select', 'autocomplete' ]; + /** Keys from the original imported JSON, captured before any default values are injected. */ + private array $original_keys = []; + /** * This is revert of parse method. While parse method converts to the minimal format, * this method converts back to the original format. @@ -19,6 +22,8 @@ class Field extends Base { * @return void */ public function unparse() { + $this->original_keys = array_keys( $this->settings ); + $this->unparse_default_values() ->unparse_boolean_values() ->unparse_numeric_values() @@ -34,13 +39,13 @@ public function unparse() { ->unparse_conditional_logic() ->unparse_tooltip() ->unparse_admin_columns() - ->ensure_boolean( 'save_field' ) - ->unparse_custom_settings(); + ->ensure_boolean( 'save_field' ); $func = "unparse_field_{$this->type}"; if ( method_exists( $this, $func ) ) { $this->$func(); } + $this->unparse_custom_settings(); } private function unparse_datalist() { @@ -326,36 +331,13 @@ private function unparse_custom_settings(): self { return $this; } - // All native keys across EVERY registered field type. - // Using the full union ensures per-type lookups don't accidentally - // flag a native key as "custom" when the field type registry is partial. - $all_native_keys = \MBB\Helpers\FieldKeys::get_all_native_keys(); - - // Truly structural keys that are never in the API registry: - // - Identity keys not exposed as "controls" (_id, fields). - // - Complex objects already transformed by dedicated unparse_*() methods. - $structural_keys = [ - '_id', - 'fields', - 'conditional_logic', - 'text_limiter', - 'tooltip', - 'admin_columns', - // Defaults injected by unparse_default_values() — present in registry - // for most types but kept here as safety net for unknown field types. - 'save_field', 'label_description', 'desc', 'size', - 'hide_from_rest', 'hide_from_front', - 'before', 'after', 'class', 'sanitize_callback', - 'required', 'disabled', 'readonly', 'prepend', 'append', - ]; - - $known_keys = array_merge( $all_native_keys, $structural_keys ); + $all_keys = \MBB\Helpers\FieldKeys::all(); // Move any unrecognized key into custom_settings format. $custom_settings = $this->settings['custom_settings'] ?? []; - foreach ( array_keys( $this->settings ) as $key ) { - if ( in_array( $key, $known_keys, true ) ) { + foreach ( $this->original_keys as $key ) { + if ( in_array( $key, $all_keys, true ) ) { continue; } From 31259900167f392d3dbebfa8776fbc4efb11e640 Mon Sep 17 00:00:00 2001 From: Hoang Minh Huong Date: Mon, 6 Apr 2026 13:58:16 +0700 Subject: [PATCH 3/6] add handle JSON notation and dot notation --- src/Unparsers/Field.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Unparsers/Field.php b/src/Unparsers/Field.php index 0946388..32ee40d 100644 --- a/src/Unparsers/Field.php +++ b/src/Unparsers/Field.php @@ -341,12 +341,22 @@ private function unparse_custom_settings(): self { continue; } + // Dot notation: "parent.child" → traverse nested array. + $value = strpos( $key, '.' ) !== false + ? array_reduce( explode( '.', $key ), fn( $carry, $part ) => $carry[ $part ] ?? null, $this->settings ) + : $this->settings[ $key ]; + + // JSON notation: encode arrays/objects instead of casting to "Array". + if ( is_array( $value ) || is_object( $value ) ) { + $value = wp_json_encode( $value ); + } + // Format required by Builder UI's KeyValue control. $uid = uniqid(); $custom_settings[ $uid ] = [ 'id' => $uid, 'key' => $key, - 'value' => (string) $this->settings[ $key ], + 'value' => (string) $value, ]; unset( $this->settings[ $key ] ); From 42582c17a90bc92fc710c67d806b99846e70eab1 Mon Sep 17 00:00:00 2001 From: Hoang Minh Huong Date: Tue, 14 Apr 2026 12:08:08 +0700 Subject: [PATCH 4/6] flatten nested arrays/objects in custom settings to dot notation --- src/Unparsers/Field.php | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Unparsers/Field.php b/src/Unparsers/Field.php index 32ee40d..e6c4d5b 100644 --- a/src/Unparsers/Field.php +++ b/src/Unparsers/Field.php @@ -346,18 +346,40 @@ private function unparse_custom_settings(): self { ? array_reduce( explode( '.', $key ), fn( $carry, $part ) => $carry[ $part ] ?? null, $this->settings ) : $this->settings[ $key ]; - // JSON notation: encode arrays/objects instead of casting to "Array". + $flatten_array = function ( $array, $prefix ) use ( &$flatten_array ) { + $result = []; + foreach ( $array as $k => $v ) { + $new_key = $prefix === '' ? $k : $prefix . '.' . $k; + if ( is_array( $v ) || is_object( $v ) ) { + $result = array_merge( $result, $flatten_array( (array) $v, $new_key ) ); + } else { + $result[ $new_key ] = $v; + } + } + return $result; + }; + + $values_to_add = []; if ( is_array( $value ) || is_object( $value ) ) { - $value = wp_json_encode( $value ); + $flat = $flatten_array( (array) $value, $key ); + foreach ( $flat as $f_key => $f_val ) { + $values_to_add[ $f_key ] = (string) $f_val; + } + // JSON notation: encode arrays/objects instead of casting to "Array". + $values_to_add[ $key ] = wp_json_encode( $value ); + } else { + $values_to_add[ $key ] = (string) $value; } // Format required by Builder UI's KeyValue control. - $uid = uniqid(); - $custom_settings[ $uid ] = [ - 'id' => $uid, - 'key' => $key, - 'value' => (string) $value, - ]; + foreach ( $values_to_add as $k => $v ) { + $uid = uniqid(); + $custom_settings[ $uid ] = [ + 'id' => $uid, + 'key' => $k, + 'value' => $v, + ]; + } unset( $this->settings[ $key ] ); } From b48fb03e1ae28a2c0e11d77ae715d930a15e649c Mon Sep 17 00:00:00 2001 From: Hoang Minh Huong Date: Tue, 14 Apr 2026 15:18:09 +0700 Subject: [PATCH 5/6] reuse existing array_to_dot_notation method for custom settings --- src/Unparsers/Field.php | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/src/Unparsers/Field.php b/src/Unparsers/Field.php index e6c4d5b..7cdebcc 100644 --- a/src/Unparsers/Field.php +++ b/src/Unparsers/Field.php @@ -341,34 +341,16 @@ private function unparse_custom_settings(): self { continue; } - // Dot notation: "parent.child" → traverse nested array. - $value = strpos( $key, '.' ) !== false - ? array_reduce( explode( '.', $key ), fn( $carry, $part ) => $carry[ $part ] ?? null, $this->settings ) - : $this->settings[ $key ]; - - $flatten_array = function ( $array, $prefix ) use ( &$flatten_array ) { - $result = []; - foreach ( $array as $k => $v ) { - $new_key = $prefix === '' ? $k : $prefix . '.' . $k; - if ( is_array( $v ) || is_object( $v ) ) { - $result = array_merge( $result, $flatten_array( (array) $v, $new_key ) ); - } else { - $result[ $new_key ] = $v; - } - } - return $result; - }; + $value = $this->settings[ $key ]; $values_to_add = []; if ( is_array( $value ) || is_object( $value ) ) { - $flat = $flatten_array( (array) $value, $key ); - foreach ( $flat as $f_key => $f_val ) { - $values_to_add[ $f_key ] = (string) $f_val; - } + $values_to_add = $this->array_to_dot_notation( (array) $value, $key ); // JSON notation: encode arrays/objects instead of casting to "Array". $values_to_add[ $key ] = wp_json_encode( $value ); } else { - $values_to_add[ $key ] = (string) $value; + $tmp = $this->array_to_dot_notation( [ $key => $value ] ); + $values_to_add[ $key ] = $tmp[ $key ] ?? ''; } // Format required by Builder UI's KeyValue control. @@ -376,8 +358,8 @@ private function unparse_custom_settings(): self { $uid = uniqid(); $custom_settings[ $uid ] = [ 'id' => $uid, - 'key' => $k, - 'value' => $v, + 'key' => (string) $k, + 'value' => (string) $v, ]; } From cf879a33db192a9b6240f8b102bed32142bfa5bf Mon Sep 17 00:00:00 2001 From: Anh Tran Date: Tue, 14 Apr 2026 16:41:49 +0700 Subject: [PATCH 6/6] Convert array to dot notation --- src/Unparsers/Field.php | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Unparsers/Field.php b/src/Unparsers/Field.php index 7cdebcc..e6f7dd2 100644 --- a/src/Unparsers/Field.php +++ b/src/Unparsers/Field.php @@ -337,29 +337,18 @@ private function unparse_custom_settings(): self { $custom_settings = $this->settings['custom_settings'] ?? []; foreach ( $this->original_keys as $key ) { - if ( in_array( $key, $all_keys, true ) ) { + if ( in_array( $key, $all_keys, true ) || ! isset( $this->settings[ $key ] ) ) { continue; } - $value = $this->settings[ $key ]; - - $values_to_add = []; - if ( is_array( $value ) || is_object( $value ) ) { - $values_to_add = $this->array_to_dot_notation( (array) $value, $key ); - // JSON notation: encode arrays/objects instead of casting to "Array". - $values_to_add[ $key ] = wp_json_encode( $value ); - } else { - $tmp = $this->array_to_dot_notation( [ $key => $value ] ); - $values_to_add[ $key ] = $tmp[ $key ] ?? ''; - } - - // Format required by Builder UI's KeyValue control. - foreach ( $values_to_add as $k => $v ) { + $value = $this->settings[ $key ]; + $values = $this->array_to_dot_notation( [ $key => $value ] ); + foreach ( $values as $k => $v ) { $uid = uniqid(); $custom_settings[ $uid ] = [ 'id' => $uid, - 'key' => (string) $k, - 'value' => (string) $v, + 'key' => $k, + 'value' => $v, ]; }