From 2f47a450836855bb7b3dc09ad5d9b66308b47bdf Mon Sep 17 00:00:00 2001 From: Hoang Minh Huong Date: Wed, 22 Oct 2025 06:36:44 +0700 Subject: [PATCH 1/6] fix: strip prefix from field IDs when exporting to JSON --- src/Unparsers/MetaBox.php | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/Unparsers/MetaBox.php b/src/Unparsers/MetaBox.php index 8bea71e..8693384 100644 --- a/src/Unparsers/MetaBox.php +++ b/src/Unparsers/MetaBox.php @@ -90,11 +90,44 @@ public function to_minimal_format() { unset( $settings[ $key ] ); } + // Strip prefix from field IDs before exporting to JSON (minimal format) + if ( ! empty( $settings['fields'] ) && is_array( $settings['fields'] ) ) { + $prefix = $settings['prefix'] ?? ''; + + if ( $prefix ) { + $settings['fields'] = $this->strip_prefix_from_fields( $settings['fields'], $prefix ); + } + } + ksort( $settings ); return $settings; } + /** + * Recursively strip prefix from field IDs (for export) + */ + private function strip_prefix_from_fields( $fields, $prefix ) { + foreach ( $fields as $key => &$field ) { + // Strip prefix from field ID + if ( isset( $field['id'] ) && strpos( $field['id'], $prefix ) === 0 ) { + $field['id'] = substr( $field['id'], strlen( $prefix ) ); + + // Update _id as well + if ( isset( $field['_id'] ) && strpos( $field['_id'], $prefix ) === 0 ) { + $field['_id'] = substr( $field['_id'], strlen( $prefix ) ); + } + } + + // Recursively strip from sub-fields (for group fields) + if ( ! empty( $field['fields'] ) && is_array( $field['fields'] ) ) { + $field['fields'] = $this->strip_prefix_from_fields( $field['fields'], $prefix ); + } + } + + return $fields; + } + public function unparse_schema() { $schema = $this->lookup( [ '$schema' ], '' ); if ( ! empty( $schema ) ) { @@ -449,12 +482,22 @@ public function unparse_fields() { } public function convert_fields_for_builder( $fields = [] ): array { + // Get prefix to strip from field IDs during export + $prefix = $this->settings['settings']['prefix'] ?? ''; + foreach ( $fields as $id => $field ) { $unparser = new Field( $field ); $unparser->unparse(); $field = $unparser->get_settings(); + // Strip prefix from field ID for clean export + if ( $prefix && isset( $field['id'] ) && strpos( $field['id'], $prefix ) === 0 ) { + $field['id'] = substr( $field['id'], strlen( $prefix ) ); + $field['_id'] = $field['id']; + } + + // Recursively process sub-fields if ( isset( $field['fields'] ) && is_array( $field['fields'] ) ) { $field['fields'] = $this->convert_fields_for_builder( $field['fields'] ); } From 55504aed8240dd0a1c5befec94ef9fc1ff8ef469 Mon Sep 17 00:00:00 2001 From: Hoang Minh Huong Date: Thu, 23 Oct 2025 10:06:47 +0700 Subject: [PATCH 2/6] Refactor: improve prefix stripping logic --- src/Unparsers/MetaBox.php | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Unparsers/MetaBox.php b/src/Unparsers/MetaBox.php index 8693384..355173b 100644 --- a/src/Unparsers/MetaBox.php +++ b/src/Unparsers/MetaBox.php @@ -90,14 +90,14 @@ public function to_minimal_format() { unset( $settings[ $key ] ); } - // Strip prefix from field IDs before exporting to JSON (minimal format) - if ( ! empty( $settings['fields'] ) && is_array( $settings['fields'] ) ) { - $prefix = $settings['prefix'] ?? ''; + // Strip prefix from field IDs before exporting to JSON (minimal format) + if ( ! empty( $settings['fields'] ) && is_array( $settings['fields'] ) ) { + $prefix = $settings['prefix'] ?? ''; - if ( $prefix ) { - $settings['fields'] = $this->strip_prefix_from_fields( $settings['fields'], $prefix ); - } + if ( $prefix ) { + $this->strip_prefix_from_fields( $settings['fields'], $prefix ); } + } ksort( $settings ); @@ -107,25 +107,19 @@ public function to_minimal_format() { /** * Recursively strip prefix from field IDs (for export) */ - private function strip_prefix_from_fields( $fields, $prefix ) { - foreach ( $fields as $key => &$field ) { + private function strip_prefix_from_fields( array &$fields, string $prefix ): void { + foreach ( $fields as &$field ) { // Strip prefix from field ID - if ( isset( $field['id'] ) && strpos( $field['id'], $prefix ) === 0 ) { + if ( isset( $field['id'] ) && str_starts_with( $field['id'], $prefix ) ) { $field['id'] = substr( $field['id'], strlen( $prefix ) ); - // Update _id as well - if ( isset( $field['_id'] ) && strpos( $field['_id'], $prefix ) === 0 ) { - $field['_id'] = substr( $field['_id'], strlen( $prefix ) ); - } } // Recursively strip from sub-fields (for group fields) if ( ! empty( $field['fields'] ) && is_array( $field['fields'] ) ) { - $field['fields'] = $this->strip_prefix_from_fields( $field['fields'], $prefix ); + $this->strip_prefix_from_fields( $field['fields'], $prefix ); } } - - return $fields; } public function unparse_schema() { @@ -489,13 +483,12 @@ public function convert_fields_for_builder( $fields = [] ): array { $unparser = new Field( $field ); $unparser->unparse(); - $field = $unparser->get_settings(); + $field = $unparser->get_settings(); - // Strip prefix from field ID for clean export - if ( $prefix && isset( $field['id'] ) && strpos( $field['id'], $prefix ) === 0 ) { - $field['id'] = substr( $field['id'], strlen( $prefix ) ); - $field['_id'] = $field['id']; - } + // Strip prefix from field ID for clean export + if ( $prefix && isset( $field['id'] ) && str_starts_with( $field['id'], $prefix ) ) { + $field['id'] = substr( $field['id'], strlen( $prefix ) ); + } // Recursively process sub-fields if ( isset( $field['fields'] ) && is_array( $field['fields'] ) ) { From 65ace813e9eb6489a2792271a40ec332aa7815ba Mon Sep 17 00:00:00 2001 From: Anh Tran Date: Thu, 23 Oct 2025 14:21:15 +0700 Subject: [PATCH 3/6] Format code --- src/Unparsers/MetaBox.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Unparsers/MetaBox.php b/src/Unparsers/MetaBox.php index 355173b..24db354 100644 --- a/src/Unparsers/MetaBox.php +++ b/src/Unparsers/MetaBox.php @@ -90,14 +90,14 @@ public function to_minimal_format() { unset( $settings[ $key ] ); } - // Strip prefix from field IDs before exporting to JSON (minimal format) - if ( ! empty( $settings['fields'] ) && is_array( $settings['fields'] ) ) { - $prefix = $settings['prefix'] ?? ''; + // Strip prefix from field IDs before exporting to JSON (minimal format) + if ( ! empty( $settings['fields'] ) && is_array( $settings['fields'] ) ) { + $prefix = $settings['prefix'] ?? ''; - if ( $prefix ) { - $this->strip_prefix_from_fields( $settings['fields'], $prefix ); + if ( $prefix ) { + $this->strip_prefix_from_fields( $settings['fields'], $prefix ); + } } - } ksort( $settings ); @@ -339,7 +339,7 @@ private function unparse_settings_page_tabs(): self { $tab_items = []; foreach ( $tabs as $key => $value ) { - $id = uniqid( 'tab_' ); + $id = uniqid( 'tab_' ); $tab_items[ $id ] = compact( 'id', 'key', 'value' ); } @@ -483,12 +483,12 @@ public function convert_fields_for_builder( $fields = [] ): array { $unparser = new Field( $field ); $unparser->unparse(); - $field = $unparser->get_settings(); + $field = $unparser->get_settings(); - // Strip prefix from field ID for clean export - if ( $prefix && isset( $field['id'] ) && str_starts_with( $field['id'], $prefix ) ) { - $field['id'] = substr( $field['id'], strlen( $prefix ) ); - } + // Strip prefix from field ID for clean export + if ( $prefix && isset( $field['id'] ) && str_starts_with( $field['id'], $prefix ) ) { + $field['id'] = substr( $field['id'], strlen( $prefix ) ); + } // Recursively process sub-fields if ( isset( $field['fields'] ) && is_array( $field['fields'] ) ) { From f57ed4634a74a43205dc14de18a6cfea2e7e0abf Mon Sep 17 00:00:00 2001 From: Hoang Minh Huong Date: Fri, 24 Oct 2025 15:22:16 +0700 Subject: [PATCH 4/6] fix: prevent double-prefixing of sub-fields in group fields --- src/Parsers/MetaBox.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Parsers/MetaBox.php b/src/Parsers/MetaBox.php index ceddeba..b011424 100644 --- a/src/Parsers/MetaBox.php +++ b/src/Parsers/MetaBox.php @@ -59,24 +59,28 @@ private function parse_settings() { $this->settings_parser->parse(); } - private function parse_fields( &$fields ) { - array_walk( $fields, [ $this, 'parse_field' ] ); + private function parse_fields( &$fields, $apply_prefix = true ) { + foreach ( $fields as &$field ) { + $this->parse_field( $field, $apply_prefix ); + } $fields = array_values( array_filter( $fields ) ); // Make sure to remove empty (such as empty groups) or "tab" fields. } - private function parse_field( &$field ) { + private function parse_field( &$field, $apply_prefix = true ) { $parser = new Field( $field ); $parser->parse(); $field = $parser->get_settings(); - if ( $this->settings_parser->prefix && isset( $field['id'] ) ) { + // Only apply prefix to top-level fields, not sub-fields in groups + if ( $apply_prefix && $this->settings_parser->prefix && isset( $field['id'] ) ) { $field['id'] = $this->settings_parser->prefix . $field['id']; } $this->parse_field_validation( $field ); + // Don't apply prefix to sub-fields (they should inherit parent's context) if ( isset( $field['fields'] ) ) { - $this->parse_fields( $field['fields'] ); + $this->parse_fields( $field['fields'], false ); } } From 8934d353f3ff285bae745511ed489c498eb1ead4 Mon Sep 17 00:00:00 2001 From: Anh Tran Date: Mon, 3 Nov 2025 13:56:42 +0700 Subject: [PATCH 5/6] Always add prefix to all sub-fields. Revert f57ed4634a74a43205dc14de18a6cfea2e7e0abf --- src/Parsers/MetaBox.php | 14 +++++--------- src/Unparsers/MetaBox.php | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Parsers/MetaBox.php b/src/Parsers/MetaBox.php index b011424..ceddeba 100644 --- a/src/Parsers/MetaBox.php +++ b/src/Parsers/MetaBox.php @@ -59,28 +59,24 @@ private function parse_settings() { $this->settings_parser->parse(); } - private function parse_fields( &$fields, $apply_prefix = true ) { - foreach ( $fields as &$field ) { - $this->parse_field( $field, $apply_prefix ); - } + private function parse_fields( &$fields ) { + array_walk( $fields, [ $this, 'parse_field' ] ); $fields = array_values( array_filter( $fields ) ); // Make sure to remove empty (such as empty groups) or "tab" fields. } - private function parse_field( &$field, $apply_prefix = true ) { + private function parse_field( &$field ) { $parser = new Field( $field ); $parser->parse(); $field = $parser->get_settings(); - // Only apply prefix to top-level fields, not sub-fields in groups - if ( $apply_prefix && $this->settings_parser->prefix && isset( $field['id'] ) ) { + if ( $this->settings_parser->prefix && isset( $field['id'] ) ) { $field['id'] = $this->settings_parser->prefix . $field['id']; } $this->parse_field_validation( $field ); - // Don't apply prefix to sub-fields (they should inherit parent's context) if ( isset( $field['fields'] ) ) { - $this->parse_fields( $field['fields'], false ); + $this->parse_fields( $field['fields'] ); } } diff --git a/src/Unparsers/MetaBox.php b/src/Unparsers/MetaBox.php index 24db354..e2fe173 100644 --- a/src/Unparsers/MetaBox.php +++ b/src/Unparsers/MetaBox.php @@ -112,7 +112,6 @@ private function strip_prefix_from_fields( array &$fields, string $prefix ): voi // Strip prefix from field ID if ( isset( $field['id'] ) && str_starts_with( $field['id'], $prefix ) ) { $field['id'] = substr( $field['id'], strlen( $prefix ) ); - } // Recursively strip from sub-fields (for group fields) From 6b29ec43ef56b63084239217d13cb34827a12651 Mon Sep 17 00:00:00 2001 From: Anh Tran Date: Tue, 4 Nov 2025 10:08:28 +0700 Subject: [PATCH 6/6] Normalize fields when unparse meta box. - Always keep it as a numeric array. - Remove prefix from field IDs for the settings, that can be used for export, builder, local JSON. - Add prefix to field IDs for parsed meta box, that's ready for registering. --- src/Prefixer.php | 32 ++++++++++++++++++ src/Unparsers/MetaBox.php | 69 ++++++++++++++++++++------------------- 2 files changed, 68 insertions(+), 33 deletions(-) create mode 100644 src/Prefixer.php diff --git a/src/Prefixer.php b/src/Prefixer.php new file mode 100644 index 0000000..989ad7d --- /dev/null +++ b/src/Prefixer.php @@ -0,0 +1,32 @@ +strip_prefix_from_fields( $settings['fields'], $prefix ); - } + Prefixer::remove( $settings['fields'], $settings['prefix'] ?? '' ); } ksort( $settings ); @@ -104,23 +102,6 @@ public function to_minimal_format() { return $settings; } - /** - * Recursively strip prefix from field IDs (for export) - */ - private function strip_prefix_from_fields( array &$fields, string $prefix ): void { - foreach ( $fields as &$field ) { - // Strip prefix from field ID - if ( isset( $field['id'] ) && str_starts_with( $field['id'], $prefix ) ) { - $field['id'] = substr( $field['id'], strlen( $prefix ) ); - } - - // Recursively strip from sub-fields (for group fields) - if ( ! empty( $field['fields'] ) && is_array( $field['fields'] ) ) { - $this->strip_prefix_from_fields( $field['fields'], $prefix ); - } - } - } - public function unparse_schema() { $schema = $this->lookup( [ '$schema' ], '' ); if ( ! empty( $schema ) ) { @@ -281,22 +262,53 @@ public function unparse_modified() { return $this; } - public function unparse_meta_box() { + /** + * Unparse the meta box. + * + * For fields: + * - Always keep it as a numeric array. + * - Remove prefix from field IDs for the settings, that can be used for export, builder, local JSON. + * - Add prefix to field IDs for parsed meta box, that's ready for registering. + */ + public function unparse_meta_box(): static { // If not meta box, return if ( $this->detect_post_type() !== 'meta-box' ) { return $this; } + $prefix = $this->lookup( [ 'prefix', 'settings.prefix' ], '' ); + + // If meta box is already parsed, normalize the fields array. if ( isset( $this->meta_box ) && is_array( $this->meta_box ) ) { // Fix: error on earlier versions that saved fields as object - $fields = array_values( $this->meta_box['fields'] ?? [] ); + $fields = $this->meta_box['fields'] ?? []; + $fields = array_values( $fields ); + + // Remove prefix from field IDs for the settings, that can be used for export, builder, local JSON. + Prefixer::remove( $fields, $prefix ); + $this->fields = $fields; + + // Add prefix to field IDs for parsed meta box, that's ready for registering. + Prefixer::add( $fields, $prefix ); $this->settings['meta_box']['fields'] = $fields; return $this; } + // If meta box is not parsed, normalize the fields array. + $fields = $this->fields ?: []; + $fields = array_values( $fields ); + + // Remove prefix from field IDs for the settings, that can be used for export, builder, local JSON. + Prefixer::remove( $fields, $prefix ); + $this->fields = $fields; + $meta_box = $this->get_settings(); + // Add prefix to field IDs for parsed meta box, that's ready for registering. + Prefixer::add( $fields, $prefix ); + $meta_box['fields'] = $fields; + foreach ( $this->get_unneeded_keys() as $key ) { unset( $meta_box[ $key ] ); } @@ -475,21 +487,12 @@ public function unparse_fields() { } public function convert_fields_for_builder( $fields = [] ): array { - // Get prefix to strip from field IDs during export - $prefix = $this->settings['settings']['prefix'] ?? ''; - foreach ( $fields as $id => $field ) { $unparser = new Field( $field ); $unparser->unparse(); $field = $unparser->get_settings(); - // Strip prefix from field ID for clean export - if ( $prefix && isset( $field['id'] ) && str_starts_with( $field['id'], $prefix ) ) { - $field['id'] = substr( $field['id'], strlen( $prefix ) ); - } - - // Recursively process sub-fields if ( isset( $field['fields'] ) && is_array( $field['fields'] ) ) { $field['fields'] = $this->convert_fields_for_builder( $field['fields'] ); }