diff --git a/composer.json b/composer.json index cf236a5..dd7f9d0 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "psr-4": {"MBBParser\\": "src/"} }, "require": { + "php": ">=7.4", "wpmetabox/support": "dev-master", "riimu/kit-phpencoder": "^2.4" } diff --git a/src/SettingsTrait.php b/src/SettingsTrait.php index 7270b18..61d36f9 100644 --- a/src/SettingsTrait.php +++ b/src/SettingsTrait.php @@ -3,23 +3,41 @@ /** * Use overloading magic methods for short syntax. + * Property accesses are recorded so unparse_custom_settings() can detect + * which keys were touched (= natively handled) vs untouched (= potentially custom). */ trait SettingsTrait { protected $settings; + /** + * Keys accessed via __get / __set / __isset during this object's lifetime. + * Used by unparse_custom_settings() to detect natively-handled keys. + * + * @var string[] + */ + private array $accessed_keys = []; + public function get_settings(): array { return $this->settings; } + /** Return all keys that were accessed (touched) so far. */ + public function get_accessed_keys(): array { + return $this->accessed_keys; + } + public function __get( string $key ) { + $this->accessed_keys[] = $key; return $this->settings[ $key ] ?? null; } public function __set( string $key, $value ): void { + $this->accessed_keys[] = $key; $this->settings[ $key ] = $value; } public function __isset( string $key ): bool { + $this->accessed_keys[] = $key; return isset( $this->settings[ $key ] ); } diff --git a/src/Unparsers/Field.php b/src/Unparsers/Field.php index 124b68f..a3cfb18 100644 --- a/src/Unparsers/Field.php +++ b/src/Unparsers/Field.php @@ -10,6 +10,23 @@ class Field extends Base { private $choice_types = [ 'select', 'radio', 'checkbox_list', 'select_advanced', 'button_group', 'image_select', 'autocomplete' ]; + /** + * Native keys that no unparse_* method accesses via $this->key (magic method), + * so they are never auto-tracked by SettingsTrait. List them explicitly here to + * prevent unparse_custom_settings() from mis-classifying them as custom attributes. + * + * Every other native key is auto-tracked the moment any method reads or writes + * $this->key — no need to list those here. + */ + private const STRUCTURAL_KEYS = [ + 'type', 'id', '_id', 'name', 'std', 'placeholder', + 'columns', 'field_name', '_state', 'input_attributes', + 'multiple', 'fields', 'tab', 'validation', + 'custom_settings', '_callback', 'options', + 'limit', 'limit_type', + ]; + + /** * This is revert of parse method. While parse method converts to the minimal format, * this method converts back to the original format. @@ -33,13 +50,17 @@ public function unparse() { ->unparse_text_limiter() ->unparse_conditional_logic() ->unparse_tooltip() - ->unparse_admin_columns() - ->ensure_boolean( 'save_field' ); + ->unparse_admin_columns(); + // Field-type handler must run BEFORE unparse_custom_settings() so its + // property accesses are tracked and not mis-classified as custom attributes. $func = "unparse_field_{$this->type}"; if ( method_exists( $this, $func ) ) { $this->$func(); } + + $this->unparse_custom_settings(); + $this->ensure_boolean( 'save_field' ); } private function unparse_datalist() { @@ -206,6 +227,40 @@ private function unparse_admin_columns() { return $this; } + /** + * Detect unknown top-level keys and move them into the custom_settings + * format `{ uid: { id, key, value } }` that the Builder UI renders in + * the "Custom Settings" tab. + * + * "Known" = auto-tracked by SettingsTrait + STRUCTURAL_KEYS pass-through list. + */ + private function unparse_custom_settings(): self { + $known = array_merge( $this->get_accessed_keys(), self::STRUCTURAL_KEYS ); + $custom = $this->custom_settings ?? []; + + foreach ( $this->settings as $key => $value ) { + if ( in_array( $key, $known, true ) ) { + continue; + } + + $uid = uniqid(); + $custom[ $uid ] = [ + 'id' => $uid, + 'key' => $key, + 'value' => ( is_array( $value ) || is_object( $value ) ) + ? wp_json_encode( $value ) + : (string) $value, + ]; + unset( $this->settings[ $key ] ); + } + + if ( ! empty( $custom ) ) { + $this->custom_settings = $custom; + } + + return $this; + } + public function unparse_default_values() { $this->id = $this->id ?? uniqid(); $this->_id = $this->_id ?? $this->id;