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
+1
View File
@@ -261,6 +261,7 @@ require_once TMDO_PATH . 'includes/migration/class-tmdo-migration-orchestrator.p
require_once TMDO_PATH . 'includes/migration/class-tmdo-post-migration.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-post-group-reconciler.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-native-restorer.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-array-literal-repair.php';
add_action( TMDO_Migration_Orchestrator::CRON_HOOK, array( 'TMDO_Migration_Orchestrator', 'cron_tick' ) );
add_action( 'wpdo_bridge_mode_changed', array( 'TMDO_Migration_Orchestrator', 'bust_attention_cache' ) );
add_action( 'tmdo_bridge_mode_changed', array( 'TMDO_Migration_Orchestrator', 'bust_attention_cache' ) );
+46
View File
@@ -217,6 +217,50 @@ class TMDO_CLI_V2 {
WP_CLI::success( $report['demoted'] ? "Restored; {$entity} is now dual_write." : 'Restored. Demote the entity to dual_write (admin Entity Bridge tab) before its next write, or re-run with --demote.' );
}
/**
* Find flat text / textarea values stored as the literal "Array" (pre-1.1.0
* type caster) and rewrite them from native meta.
*
* Native rows that are "Array" too cannot be repaired here; they are listed
* so they can be restored from a backup.
*
* ## OPTIONS
*
* [--entity=<entity>]
* : post | user | term | comment. Default: all four.
*
* [--confirm]
* : Write the repairs. Without it the command only reports.
*
* ## EXAMPLES
*
* wp tmdo repair-array-literals
* wp tmdo repair-array-literals --entity=user --confirm
*
* @param array<int, string> $args Positional arguments (unused).
* @param array<string, string> $assoc_args Named arguments.
*/
public function repair_array_literals( $args, $assoc_args ): void {
$entities = isset( $assoc_args['entity'] ) ? array( (string) $assoc_args['entity'] ) : TMDO_Entity_Registry::VALID_ENTITY_TYPES;
$apply = isset( $assoc_args['confirm'] );
foreach ( $entities as $entity ) {
if ( ! in_array( $entity, TMDO_Entity_Registry::VALID_ENTITY_TYPES, true ) ) {
WP_CLI::error( "Unknown entity: {$entity}" );
}
$report = TMDO_Array_Literal_Repair::run( $entity, $apply );
WP_CLI::log( sprintf( '%s: %d "Array" value(s) in flat tables, %d repairable from native, %d repaired.', $entity, $report['found'], $report['repairable'], $report['repaired'] ) );
foreach ( $report['unrepairable'] as $row ) {
WP_CLI::log( sprintf( ' unrepairable %s/%s #%d (%s)', $row['group'], $row['key'], $row['id'], $row['reason'] ) );
}
foreach ( $report['native_polluted'] as $row ) {
WP_CLI::warning( sprintf( 'native meta is "Array": %s %s #%d — restore it from a backup', $entity, $row['key'], $row['id'] ) );
}
}
WP_CLI::success( $apply ? 'Repair finished.' : '[dry-run] Nothing written. Re-run with --confirm to repair.' );
}
/**
* Audit per-module state + shadow_read flags.
*
@@ -851,6 +895,8 @@ WP_CLI::add_command( 'wpdo bridge-set', array( 'TMDO_CLI_V2', 'bridge_set' ) );
WP_CLI::add_command( 'tmdo bridge-set', array( 'TMDO_CLI_V2', 'bridge_set' ) );
WP_CLI::add_command( 'wpdo bridge-restore-native', array( new TMDO_CLI_V2(), 'bridge_restore_native' ) );
WP_CLI::add_command( 'tmdo bridge-restore-native', array( new TMDO_CLI_V2(), 'bridge_restore_native' ) );
WP_CLI::add_command( 'wpdo repair-array-literals', array( new TMDO_CLI_V2(), 'repair_array_literals' ) );
WP_CLI::add_command( 'tmdo repair-array-literals', array( new TMDO_CLI_V2(), 'repair_array_literals' ) );
WP_CLI::add_command( 'wpdo mode-audit', array( 'TMDO_CLI_V2', 'mode_audit' ) );
WP_CLI::add_command( 'tmdo mode-audit', array( 'TMDO_CLI_V2', 'mode_audit' ) );
WP_CLI::add_command( 'wpdo mode-set', array( 'TMDO_CLI_V2', 'mode_set' ) );
+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).
@@ -0,0 +1,111 @@
<?php
/**
* TMDO_Array_Literal_Repair — finds flat text / textarea values stored as the
* literal "Array" (pre-1.1.0 type caster cast arrays with (string)) and
* rewrites them from native meta.
*
* Native rows that are themselves "Array" (written back by an earlier
* bridge-restore-native) cannot be repaired here: they are only listed, to be
* restored from a backup.
*
* @package WP_Data_Optimizer
*/
declare(strict_types=1);
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery -- table / column names come from the registry and adapter; values are prepared.
/**
* Repairs "Array" literals in flat text / textarea columns.
*/
final class TMDO_Array_Literal_Repair {
/** What (string) array produced. */
private const LITERAL = 'Array';
/**
* Scan (and optionally repair) one entity type.
*
* @param string $entity_type Entity type.
* @param bool $apply Write repairs; false = report only.
* @return array{found:int, repairable:int, repaired:int, unrepairable:array<int, array{group:string, key:string, id:int, reason:string}>, native_polluted:array<int, array{group:string, key:string, id:int}>}
*/
public static function run( string $entity_type, bool $apply ): array {
global $wpdb;
$report = array(
'found' => 0,
'repairable' => 0,
'repaired' => 0,
'unrepairable' => array(),
'native_polluted' => array(),
);
$adapter = TMDO_Entity_Registry::get_adapter( $entity_type );
if ( ! $adapter ) {
return $report;
}
$meta_table = $adapter->get_native_meta_table();
$id_col = $adapter->get_entity_id_column();
$meta_pk = 'user' === $entity_type ? 'umeta_id' : 'meta_id';
foreach ( TMDO_Entity_Registry::get_groups_for_type( $entity_type ) as $group ) {
$table = TMDO_Schema_Manager::get_table_name( $entity_type, $group );
if ( ! TMDO_Schema_Manager::table_exists( $table ) ) {
continue;
}
foreach ( TMDO_Entity_Registry::get_group_fields( $entity_type, $group ) as $field ) {
if ( ! in_array( $field['type'] ?? 'text', array( 'text', 'textarea' ), true ) ) {
continue;
}
$key = (string) $field['key'];
$col = TMDO_Schema_Manager::sanitize_column_name( $key );
$ids = $wpdb->get_col( $wpdb->prepare( "SELECT `{$id_col}` FROM `{$table}` WHERE `{$col}` = %s", self::LITERAL ) );
foreach ( $ids as $id ) {
$id = (int) $id;
++$report['found'];
$native = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM `{$meta_table}` WHERE `{$id_col}` = %d AND meta_key = %s ORDER BY `{$meta_pk}` LIMIT 1", $id, $key ) );
$reason = null === $native ? 'no_native_row' : ( self::LITERAL === $native ? 'native_is_array_literal' : '' );
if ( '' !== $reason ) {
$report['unrepairable'][] = array(
'group' => $group,
'key' => $key,
'id' => $id,
'reason' => $reason,
);
continue;
}
++$report['repairable'];
if ( $apply ) {
$value = TMDO_Type_Caster::to_db( TMDO_Safe_Unserialize::run( $native ), $field + array( 'group' => $group ) );
$wpdb->update( $table, array( $col => $value ), array( $id_col => $id ) );
++$report['repaired'];
}
}
$polluted = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT `{$id_col}` FROM `{$meta_table}` WHERE meta_key = %s AND meta_value = %s", $key, self::LITERAL ) );
foreach ( $polluted as $id ) {
$report['native_polluted'][] = array(
'group' => $group,
'key' => $key,
'id' => (int) $id,
);
}
}
}
if ( $report['repaired'] > 0 ) {
TMDO_Cache_Orchestrator::flush_entity( $entity_type );
}
return $report;
}
}
+1
View File
@@ -647,6 +647,7 @@ require_once TMDO_PATH . 'includes/migration/class-tmdo-migration-orchestrator.p
require_once TMDO_PATH . 'includes/migration/class-tmdo-post-migration.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-post-group-reconciler.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-native-restorer.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-array-literal-repair.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-post-status-sync.php';
// Options Manager.
@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* Repair of text / textarea flat columns holding the literal "Array" (stored
* by the pre-1.1.0 type caster when the meta value was an array).
*
* Fixture (user entity, group al_prefs):
* user 1 prefs 'Array' native prefs serialized array → repairable
* user 2 loc 'Array' native loc 'Array' too → unrepairable, native polluted
* user 3 name 'Array' no native row → unrepairable
* user 4 prefs serialized array, loc 'Taipei' → untouched
*/
class ArrayLiteralRepairTest extends TestCase {
private const TABLE = 'wp_itest_wpdo_user_al_prefs';
public static function setUpBeforeClass(): void {
foreach ( array(
'includes/adapters/interface-entity-adapter.php',
'includes/engine/class-tmdo-type-caster.php',
'includes/engine/class-tmdo-schema-manager.php',
'includes/engine/class-tmdo-entity-registry.php',
'includes/engine/class-tmdo-cache-orchestrator.php',
'includes/adapters/class-tmdo-adapter-user.php',
'includes/migration/class-tmdo-array-literal-repair.php',
) as $file ) {
require_once WPDO_PLUGIN_DIR . $file;
}
}
protected function setUp(): void {
global $wpdb;
self::drop();
$wpdb->query( "CREATE TABLE `{$wpdb->usermeta}` ( umeta_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL DEFAULT 0, meta_key VARCHAR(255) DEFAULT NULL, meta_value LONGTEXT, PRIMARY KEY (umeta_id), KEY user_id (user_id) ) ENGINE=InnoDB" );
$wpdb->query( 'CREATE TABLE `' . self::TABLE . '` ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, prefs TEXT DEFAULT NULL, loc VARCHAR(255) DEFAULT NULL, name VARCHAR(255) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_entity (user_id) ) ENGINE=InnoDB' );
$prefs = serialize( array( 'core' => array( 'welcomeGuide' => false ) ) );
$wpdb->insert( self::TABLE, array( 'user_id' => 1, 'prefs' => 'Array' ) );
$wpdb->insert( self::TABLE, array( 'user_id' => 2, 'loc' => 'Array' ) );
$wpdb->insert( self::TABLE, array( 'user_id' => 3, 'name' => 'Array' ) );
$wpdb->insert( self::TABLE, array( 'user_id' => 4, 'prefs' => $prefs, 'loc' => 'Taipei' ) );
$wpdb->insert( $wpdb->usermeta, array( 'user_id' => 1, 'meta_key' => 'prefs', 'meta_value' => $prefs ) );
$wpdb->insert( $wpdb->usermeta, array( 'user_id' => 2, 'meta_key' => 'loc', 'meta_value' => 'Array' ) );
$wpdb->insert( $wpdb->usermeta, array( 'user_id' => 4, 'meta_key' => 'prefs', 'meta_value' => $prefs ) );
$GLOBALS['_wp_cache'] = array();
WPDO_Entity_Registry::init();
WPDO_Entity_Registry::register_adapter( 'user', new WPDO_Adapter_User() );
WPDO_Entity_Registry::register_group(
'user',
'al_prefs',
array(
array( 'key' => 'prefs', 'type' => 'textarea' ),
array( 'key' => 'loc', 'type' => 'text' ),
array( 'key' => 'name', 'type' => 'text' ),
)
);
WPDO_Schema_Manager::flush_table_exists_cache();
}
protected function tearDown(): void {
self::drop();
WPDO_Entity_Registry::init();
}
private static function drop(): void {
global $wpdb;
foreach ( array( $wpdb->usermeta, self::TABLE ) as $table ) {
$wpdb->query( "DROP TABLE IF EXISTS `{$table}`" );
}
}
private function flat( int $user_id, string $col ): ?string {
global $wpdb;
return $wpdb->get_var( $wpdb->prepare( "SELECT `{$col}` FROM `" . self::TABLE . '` WHERE user_id = %d', $user_id ) );
}
public function test_dry_run_reports_without_writing(): void {
$report = TMDO_Array_Literal_Repair::run( 'user', false );
$this->assertSame( 3, $report['found'] );
$this->assertSame( 1, $report['repairable'] );
$this->assertSame( 0, $report['repaired'] );
$this->assertSame( 'Array', $this->flat( 1, 'prefs' ) );
}
public function test_apply_repairs_from_native_and_lists_the_rest(): void {
$report = TMDO_Array_Literal_Repair::run( 'user', true );
$this->assertSame( 1, $report['repaired'] );
$this->assertSame( serialize( array( 'core' => array( 'welcomeGuide' => false ) ) ), $this->flat( 1, 'prefs' ) );
$this->assertSame( 'Array', $this->flat( 2, 'loc' ), 'native is "Array" too — nothing to repair from' );
$this->assertSame( 'Array', $this->flat( 3, 'name' ), 'no native row' );
$this->assertSame( 'Taipei', $this->flat( 4, 'loc' ) );
$unrepairable = array_map(
static function ( $r ) {
return $r['id'] . ':' . $r['key'] . ':' . $r['reason'];
},
$report['unrepairable']
);
sort( $unrepairable );
$this->assertSame( array( '2:loc:native_is_array_literal', '3:name:no_native_row' ), $unrepairable );
$this->assertSame( array( array( 'group' => 'al_prefs', 'key' => 'loc', 'id' => 2 ) ), $report['native_polluted'] );
}
public function test_second_run_finds_only_the_unrepairable_rows(): void {
TMDO_Array_Literal_Repair::run( 'user', true );
$report = TMDO_Array_Literal_Repair::run( 'user', true );
$this->assertSame( 2, $report['found'] );
$this->assertSame( 0, $report['repaired'] );
}
}
+1
View File
@@ -611,6 +611,7 @@ require_once TMDO_PATH . 'includes/migration/class-tmdo-migration-orchestrator.p
require_once TMDO_PATH . 'includes/migration/class-tmdo-post-migration.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-post-group-reconciler.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-native-restorer.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-array-literal-repair.php';
require_once TMDO_PATH . 'modules/options/class-tmdo-options-manager.php';
require_once TMDO_PATH . 'includes/class-tmdo-postmeta-cleaner.php';
require_once TMDO_PATH . 'includes/class-tmdo-termmeta-cleaner.php';
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* Suite: array meta values survive the flat tables (no "Array" literal).
*
* Bug (dev36, 2026-09-26): admin_prefs `community-events-location` (text) and
* `wp_persisted_preferences` (textarea) hold arrays; the type caster stored
* (string) $array = "Array", and bridge-restore-native wrote it back to native.
*
* @package WP_Data_Optimizer
*/
declare(strict_types=1);
TMDO_Live::test(
'dual_write: array written with update_user_meta is stored serialized',
static function () {
TMDO_Live_Fixtures::field( 'user', 'wp_persisted_preferences' );
TMDO_Live_Fixtures::use_modes( array( 'user' => 'dual_write' ) );
$uid = TMDO_Live_Fixtures::user();
$prefs = array( 'core' => array( 'welcomeGuide' => false ) );
$loc = array( 'ip' => '192.0.2.0' );
update_user_meta( $uid, 'wp_persisted_preferences', $prefs );
update_user_meta( $uid, 'community-events-location', $loc );
tmdo_live_assert_same( serialize( $prefs ), TMDO_Live_Fixtures::flat( 'user', $uid, 'wp_persisted_preferences' ), 'flat wp_persisted_preferences' );
tmdo_live_assert_same( serialize( $loc ), TMDO_Live_Fixtures::flat( 'user', $uid, 'community-events-location' ), 'flat community-events-location' );
}
);
TMDO_Live::test(
'backfill: native array meta is flattened serialized, not "Array"',
static function () {
TMDO_Live_Fixtures::field( 'user', 'community-events-location' );
TMDO_Live_Fixtures::use_modes( array( 'user' => 'disabled' ) );
$uid = TMDO_Live_Fixtures::user();
$loc = array( 'ip' => '198.51.100.0' );
update_user_meta( $uid, 'community-events-location', $loc );
tmdo_live_assert_same( array( serialize( $loc ) ), TMDO_Live_Fixtures::native( 'user', $uid, 'community-events-location' ), 'native precondition' );
$field = TMDO_Entity_Registry::get_field( 'user', 'community-events-location' );
TMDO_Entity_Migration_Engine::reset_checkpoint( 'user', $field['group'] );
TMDO_Entity_Migration_Engine::migrate_group( 'user', $field['group'], array( 'sleep_ms' => 0, 'resume' => false ) );
tmdo_live_assert_same( serialize( $loc ), TMDO_Live_Fixtures::flat( 'user', $uid, 'community-events-location' ), 'flat after backfill' );
}
);
TMDO_Live::test(
'read path: from_db returns the array, not a serialized string',
static function () {
$field = TMDO_Live_Fixtures::field( 'user', 'wp_persisted_preferences' );
$prefs = array( 'core/edit-post' => array( 'panels' => array( 'a', 'b' ) ) );
$stored = TMDO_Type_Caster::to_db( $prefs, $field );
tmdo_live_assert_same( $prefs, TMDO_Type_Caster::from_db( $stored, $field ), 'round trip' );
}
);
+99
View File
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* text / textarea fields holding arrays (e.g. usermeta wp_persisted_preferences,
* community-events-location) were cast with (string) and stored as "Array";
* restoring native meta then wrote "Array" back over the real data.
*/
class TypeCasterSerializeTest extends TestCase {
/** Rows passed to $wpdb->insert() (wpdo_errors via TMDO_Logger::warning). */
public static array $inserts = array();
private const TEXT = array( 'type' => 'text', 'group' => 'admin_prefs', 'key' => 'community-events-location' );
private const TEXTAREA = array( 'type' => 'textarea', 'group' => 'admin_prefs', 'key' => 'wp_persisted_preferences' );
protected function setUp(): void {
self::$inserts = array();
ini_set( 'error_log', '/dev/null' );
global $wpdb;
$wpdb = new class {
public string $prefix = 'wp_';
public function insert( string $table, array $data, $format = null ) {
TypeCasterSerializeTest::$inserts[] = array( $table, $data );
return 1;
}
};
}
public static function arrays(): array {
return array(
'flat' => array( array( 'ip' => '118.163.181.0' ) ),
'nested' => array( array( 'core/edit-post' => array( 'welcomeGuide' => false, 'panels' => array( 'a', 'b' ) ) ) ),
'empty' => array( array() ),
'list' => array( array( 1, 2, 3 ) ),
);
}
/**
* @dataProvider arrays
*/
public function test_text_array_round_trips( array $value ): void {
$stored = TMDO_Type_Caster::to_db( $value, self::TEXT );
$this->assertNotSame( 'Array', $stored );
$this->assertSame( serialize( $value ), $stored );
$this->assertSame( $value, TMDO_Type_Caster::from_db( $stored, self::TEXT ) );
}
/**
* @dataProvider arrays
*/
public function test_textarea_array_round_trips( array $value ): void {
$stored = TMDO_Type_Caster::to_db( $value, self::TEXTAREA );
$this->assertSame( serialize( $value ), $stored );
$this->assertSame( $value, TMDO_Type_Caster::from_db( $stored, self::TEXTAREA ) );
}
public function test_object_is_serialized_not_cast(): void {
$value = (object) array( 'a' => 1 );
$this->assertSame( serialize( $value ), TMDO_Type_Caster::to_db( $value, self::TEXTAREA ) );
}
public function test_plain_strings_are_unchanged(): void {
foreach ( array( 'fresh', '', 'a:b', '0' ) as $s ) {
$this->assertSame( $s, TMDO_Type_Caster::to_db( $s, self::TEXT ) );
$this->assertSame( $s, TMDO_Type_Caster::from_db( $s, self::TEXT ) );
}
$this->assertSame( '42', TMDO_Type_Caster::to_db( 42, self::TEXT ) );
}
public function test_text_over_255_chars_is_stored_as_null_and_logged(): void {
$long = str_repeat( 'é', 256 );
$this->assertNull( TMDO_Type_Caster::to_db( $long, self::TEXT ) );
$this->assertCount( 1, self::$inserts );
$this->assertStringContainsString( 'text_overflow', self::$inserts[0][1]['hook'] );
$this->assertStringContainsString( 'community-events-location', (string) self::$inserts[0][1]['context'] );
}
public function test_text_of_exactly_255_chars_is_kept(): void {
$max = str_repeat( 'é', 255 );
$this->assertSame( $max, TMDO_Type_Caster::to_db( $max, self::TEXT ) );
$this->assertCount( 0, self::$inserts );
}
public function test_serialized_array_over_255_chars_overflows_too(): void {
$value = array( 'k' => str_repeat( 'x', 300 ) );
$this->assertNull( TMDO_Type_Caster::to_db( $value, self::TEXT ) );
$this->assertSame( serialize( $value ), TMDO_Type_Caster::to_db( $value, self::TEXTAREA ), 'textarea is TEXT, no 255 limit' );
}
}