|null */ private static ?array $shadow_cache = null; /** * Enable shadow_read_only for a module (only meaningful in the verify state). * * @param string $module Module identifier. * @return bool True on success. */ public static function enable_shadow_read( string $module ): bool { $flags = self::all_shadow(); $flags[ $module ] = true; update_option( self::SHADOW_OPTION_KEY, $flags, false ); self::$shadow_cache = null; return true; } /** * Disable shadow_read_only for a module. * * @param string $module Module identifier. * @return bool */ public static function disable_shadow_read( string $module ): bool { $flags = self::all_shadow(); unset( $flags[ $module ] ); update_option( self::SHADOW_OPTION_KEY, $flags, false ); self::$shadow_cache = null; return true; } /** * Whether shadow_read_only is currently active for a module. * * Returns true only when: * - Module is in the `verify` state, AND * - Shadow flag has been explicitly enabled. * * @param string $module Module identifier. * @return bool */ public static function is_shadow_read_active( string $module ): bool { if ( 'verify' !== self::get( $module ) ) { return false; } $flags = self::all_shadow(); return ! empty( $flags[ $module ] ); } /** * Return all shadow flags. * * @return array */ public static function all_shadow(): array { if ( null !== self::$shadow_cache ) { return self::$shadow_cache; } $saved = get_option( self::SHADOW_OPTION_KEY, array() ); if ( ! is_array( $saved ) ) { $saved = array(); } self::$shadow_cache = $saved; return $saved; } /** * Check if a module is fully complete (reads and writes on custom table only). * * @param string $module Module identifier. * @return bool True if module state is 'complete'. */ public static function is_complete( string $module ): bool { return 'complete' === self::get( $module ); } /** * Check if dual-write is active for a module. * * @param string $module Module identifier. * @return bool True if module is in a write-active state. */ public static function is_write_active( string $module ): bool { return in_array( self::get( $module ), self::WRITE_ACTIVE_STATES, true ); } /** * Check if reads should come from the custom/zone table. * * @param string $module Module identifier. * @return bool True if module is in a read-custom state. */ public static function is_read_custom( string $module ): bool { return in_array( self::get( $module ), self::READ_CUSTOM_STATES, true ); } /** * Check if query interceptors should be active. * * @param string $module Module identifier. * @return bool True if module is in a query-active state. */ public static function is_query_active( string $module ): bool { return in_array( self::get( $module ), self::QUERY_ACTIVE_STATES, true ); } /** * Reset a module to idle (rollback). * * @param string $module Module identifier. * @return void */ public static function reset( string $module ): void { self::set( $module, 'idle' ); self::$cache = null; // Invalidate request-level cache. } /** * Request-level cache for all() to avoid repeated get_option() calls. * * @var array|null */ private static ?array $cache = null; /** * Return all module states. * * @return array */ public static function all(): array { if ( null !== self::$cache ) { return self::$cache; } $saved = get_option( self::OPTION_KEY, array() ); if ( ! is_array( $saved ) ) { $saved = array(); } $all_modules = array_merge( self::ADDON_MODULES, self::ZONE_MODULES ); $defaults = array_fill_keys( $all_modules, 'idle' ); self::$cache = array_merge( $defaults, $saved ); return self::$cache; } /** * Return only AddOn module states. * * @return array */ public static function addon_modules(): array { $all = self::all(); return array_intersect_key( $all, array_flip( self::ADDON_MODULES ) ); } /** * Return only zone module states. * * @return array */ public static function zone_modules(): array { $all = self::all(); return array_intersect_key( $all, array_flip( self::ZONE_MODULES ) ); } }