false]` so PHP returns * `__PHP_Incomplete_Class` placeholders without ever invoking magic * methods on the original class. The placeholders are then walked out of * the result tree before returning, so they cannot leak into a flat-table * JSON column or downstream consumer. * * @package WP_Data_Optimizer * @since 2.13.3 */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Static helper class — drop-in replacement for `maybe_unserialize()`. */ final class TMDO_Safe_Unserialize { /** * Object-injection-safe `maybe_unserialize()` equivalent. * * Same input/output contract as `maybe_unserialize()`: * - Non-string input is returned as-is. * - Non-serialized strings are returned as-is. * - Serialized arrays / scalars are unserialized with allowed_classes=false. * - Any object placeholders in the result are stripped to null. * * @param mixed $value Raw value (typically meta_value or option value). * @return mixed Unserialized array / scalar, or original string if not serialized. */ public static function run( $value ) { if ( ! is_string( $value ) ) { return $value; } $trimmed = trim( $value ); // Cheap inline detector — does not rely on WP's is_serialized() so this // helper works in CLI / standalone contexts. PHP serialize tokens: // a (array), O (object), s (string), i (int), d (float), b (bool), // N; (null), C (custom class — also handled by allowed_classes=false). if ( 'N;' !== $trimmed ) { if ( strlen( $trimmed ) < 4 || ':' !== ( $trimmed[1] ?? '' ) ) { return $value; } if ( ! in_array( $trimmed[0] ?? '', array( 'a', 'O', 's', 'i', 'd', 'b', 'C' ), true ) ) { return $value; } } // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- allowed_classes=false hardens against object injection. $result = @unserialize( $trimmed, array( 'allowed_classes' => false ) ); // `unserialize()` returns false on parse error. The literal payload // `b:0;` legitimately deserializes to (bool) false, so distinguish that. if ( false === $result && 'b:0;' !== $trimmed ) { return $value; } // Strip __PHP_Incomplete_Class artefacts (allowed_classes=false replaces // any object marker with this stub). They must never reach a flat-table // JSON column or a downstream consumer that might try to access props. if ( is_object( $result ) ) { return null; } if ( is_array( $result ) ) { array_walk_recursive( $result, static function ( &$v ) { if ( is_object( $v ) ) { $v = null; } } ); } return $result; } }