fix(2meet-data-optimizer): serialize arrays in text fields instead of storing "Array"

- TMDO_Type_Caster: text / textarea values that are arrays or objects are
  stored with maybe_serialize() (was (string) → the literal "Array", which
  bridge-restore-native then wrote back over native meta); from_db()
  unserializes them with TMDO_Safe_Unserialize. text columns are
  VARCHAR(255): longer values are stored NULL (native keeps them) and
  logged as text_overflow.
- Add `wp tmdo repair-array-literals [--entity] [--confirm]`
  (TMDO_Array_Literal_Repair): rewrites flat "Array" values from native
  meta and lists native rows that are "Array" themselves (backup needed).
- Verify: TypeCasterSerializeTest 13 OK, ArrayLiteralRepairTest 3 OK;
  unit 685 OK, integration 483 OK, phpcs (phpcs.xml) 0, stan OK;
  live `serialized` 3 PASS; dev36 repair dry-run lists the value corrupted
  by the 2026-09-26 round trip (user #1 community-events-location)
- PR: PR-055
This commit is contained in:
2026-09-26 18:45:59 +08:00
parent 20bd59ddae
commit 08f9e313e0
9 changed files with 472 additions and 3 deletions
+35 -3
View File
@@ -70,10 +70,12 @@ final class TMDO_Type_Caster {
return $value;
case 'text':
return (string) $value;
return self::fit_text( is_scalar( $value ) ? (string) $value : maybe_serialize( $value ), $field_def );
case 'textarea':
return (string) $value;
// Arrays / objects are serialized like WP stores them in native meta;
// (string) would store the literal "Array".
return is_scalar( $value ) ? (string) $value : maybe_serialize( $value );
default:
// 未知型別:嘗試序列化(向後相容 WordPress get_metadata 行為)
@@ -81,6 +83,33 @@ final class TMDO_Type_Caster {
}
}
/** 'text' columns are VARCHAR(255) (TMDO_Schema_Manager type map). */
private const TEXT_MAX_CHARS = 255;
/**
* Keep a text value only if it fits its VARCHAR(255) column; otherwise
* store NULL (native meta keeps the full value) and log the overflow.
*
* @param string $value Value to store.
* @param array<string, mixed> $field_def Field definition.
* @return string|null
*/
private static function fit_text( string $value, array $field_def ): ?string {
$length = mb_strlen( $value );
if ( $length <= self::TEXT_MAX_CHARS ) {
return $value;
}
TMDO_Logger::warning(
'text_overflow',
array(
'group' => $field_def['group'] ?? '',
'key' => $field_def['key'] ?? '',
'length' => $length,
)
);
return null;
}
/**
* DB → PHP 轉換(讀取)
*/
@@ -111,9 +140,12 @@ final class TMDO_Type_Caster {
return ( json_last_error() === JSON_ERROR_NONE ) ? $decoded : $value;
case 'enum':
return (string) $value;
case 'text':
case 'textarea':
return (string) $value;
// Arrays were stored serialized (to_db); plain strings pass through.
return TMDO_Safe_Unserialize::run( (string) $value );
default:
// v2.13.3: object-injection-safe unserialize (fixes L-DESER-1).