Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/SettingsTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* Stored as a hash-map (key => true) for O(1) lookup.
*
* @var array<string, true>
*/
private $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 ] = true;
return $this->settings[ $key ] ?? null;
}

public function __set( string $key, $value ): void {
$this->accessed_keys[ $key ] = true;
$this->settings[ $key ] = $value;
}

public function __isset( string $key ): bool {
$this->accessed_keys[ $key ] = true;
return isset( $this->settings[ $key ] );
}

Expand Down
33 changes: 28 additions & 5 deletions src/Unparsers/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,44 @@ protected function unparse_conditional_logic() {

$output = [];
foreach ( $conditional_logic as $action => $condition ) {
// Fix: chỉ xử lý action đầu tiên ('visible' được ưu tiên hơn 'hidden').
// Vòng lặp cũ reset $output['when'] mỗi lần, khiến action trước bị mất.
$output['type'] = $action;
$output['relation'] = $condition['relation'];
$output['when'] = [];

foreach ( $condition['when'] as $criteria ) {
$name = $criteria[0]; // Use field name as key
// Fix: dùng uniqid() làm array key thay vì $name.
// Nếu cùng field xuất hiện nhiều lần (toán tử khác nhau),
// dùng $name làm key khiến rule sau ghi đè rule trước.
$uid = uniqid();

// Fix: convert array value thành CSV string.
// Parser/Base.php dùng Arr::from_csv() để chuyển "5,6,7" → [5,6,7].
// Chiều ngược lại (Unparser), phải convert [5,6,7] → "5,6,7"
// để Builder UI hiển thị đúng — vd: 'in' operator với tax_input.
$value = $criteria[2] ?? '';
if ( is_array( $value ) ) {
$value = implode( ',', $value );
}

$output['when'][ $name ] = [
'id' => $name,
'name' => $name,
$output['when'][ $uid ] = [
'id' => $uid,
'name' => $criteria[0],
'operator' => $criteria[1],
'value' => $criteria[2],
'value' => $value,
];
}

// Chỉ lấy action đầu tiên, thoát vòng lặp.
break;
}

// Fix: đánh dấu 'visible' và 'hidden' là đã được xử lý (accessed)
// trước khi unset, để unparse_custom_settings() không nhặt chúng lên
// và nhét vào custom_settings một cách sai lầm.
$this->accessed_keys['visible'] = true;
$this->accessed_keys['hidden'] = true;
unset( $this->visible, $this->hidden );

$this->conditional_logic = $output;
Expand Down
70 changes: 66 additions & 4 deletions src/Unparsers/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Field extends Base {

private $choice_types = [ 'select', 'radio', 'checkbox_list', 'select_advanced', 'button_group', 'image_select', 'autocomplete' ];


/**
* This is revert of parse method. While parse method converts to the minimal format,
* this method converts back to the original format.
Expand All @@ -33,13 +34,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() {
Expand Down Expand Up @@ -140,15 +145,19 @@ private function unparse_field_group() {
}

private function unparse_text_limiter() {
if ( ! isset( $this->limit ) ) {
// Accept fields that only export limit_type (limit=0 is stripped on export as default).
if ( ! isset( $this->limit ) && ! isset( $this->limit_type ) ) {
return $this;
}

$this->text_limiter = [
'limit' => $this->limit,
'limit' => $this->limit ?? 0,
'limit_type' => $this->limit_type ?? 'word',
];

// Remove flat keys so they are not double-stored alongside text_limiter.
unset( $this->settings['limit'], $this->settings['limit_type'] );

return $this;
}

Expand Down Expand Up @@ -206,6 +215,59 @@ private function unparse_admin_columns() {
return $this;
}

/**
* Pure pass-through keys that no unparse_* method ever reads via $this->key.
* They are kept in settings as-is, so they must never be classified as
* user-defined custom attributes.
*
* Every other native key is auto-tracked by SettingsTrait the moment any
* method does $this->key — no need to list them here.
*
* Extend via filter:
* add_filter( 'mbb_parser_known_field_keys', fn( $k ) => array_merge( $k, [ 'my_key' ] ) );
*/
private const STRUCTURAL_KEYS = [
'type', 'id', '_id', 'name', 'std', 'placeholder',
'columns', 'field_name', '_state', 'input_attributes',
'multiple', 'fields', 'tab', 'validation',
'custom_settings', '_callback', 'options',
];

/**
* 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 {
$always_native = apply_filters( 'mbb_parser_known_field_keys', self::STRUCTURAL_KEYS );
$known = $this->get_accessed_keys() + array_flip( $always_native );
$custom = $this->custom_settings ?? [];

foreach ( array_diff_key( $this->settings, $known ) as $key => $value ) {
if ( is_resource( $value ) ) {
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;
Expand Down
Loading