*/ private static array $created = array(); /** * Active pre_option callback, if any. * * @var callable|null */ private static $mode_filter = null; /** * Override entity modes for this process and make sure the Hook Bus is hooked. * * @param array $modes e.g. array( 'user' => 'shadow_read' ). */ public static function use_modes( array $modes ): void { self::clear_mode_filter(); $stored = get_option( 'wpdo_bridge_modes', array() ); $merged = array_merge( is_array( $stored ) ? $stored : array(), $modes ); self::$mode_filter = static function () use ( $merged ) { return $merged; }; add_filter( 'pre_option_wpdo_bridge_modes', self::$mode_filter ); self::reset_caches(); // The Hook Bus only boots when wpdo_hook_bus_enabled is on; the suite // must not depend on (or change) that site option. if ( false === has_filter( 'update_user_metadata', array( 'TMDO_Hook_Bus', 'intercept_update' ) ) ) { TMDO_Hook_Bus::init(); } } /** * Create a throwaway user. */ public static function user(): int { $login = 'tmdo_live_' . wp_generate_password( 6, false, false ); $id = wp_insert_user( array( 'user_login' => $login, 'user_email' => $login . '@example.test', 'user_pass' => wp_generate_password(), ) ); if ( is_wp_error( $id ) ) { throw new TMDO_Live_Failure( 'fixture user: ' . $id->get_error_message() ); } self::$created[] = array( 'user', (int) $id ); return (int) $id; } /** * Create a throwaway term. * * @param string $taxonomy Taxonomy. */ public static function term( string $taxonomy ): int { if ( ! taxonomy_exists( $taxonomy ) ) { throw new TMDO_Live_Skip( "taxonomy {$taxonomy} not registered on this site" ); } $t = wp_insert_term( 'tmdo-live-' . wp_generate_password( 6, false, false ), $taxonomy ); if ( is_wp_error( $t ) ) { throw new TMDO_Live_Failure( 'fixture term: ' . $t->get_error_message() ); } self::$created[] = array( 'term', (int) $t['term_id'], $taxonomy ); return (int) $t['term_id']; } /** * Native meta values for a key, read with SQL (no filters, no cache). * * @param string $type Entity type (post|user|term|comment). * @param int $id Entity ID. * @param string $key Meta key. * @return string[] */ public static function native( string $type, int $id, string $key ): array { global $wpdb; $table = _get_meta_table( $type ); $id_col = $type . '_id'; $pk = 'user' === $type ? 'umeta_id' : 'meta_id'; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table/column names are WP internals. return $wpdb->get_col( $wpdb->prepare( "SELECT meta_value FROM {$table} WHERE {$id_col} = %d AND meta_key = %s ORDER BY {$pk}", $id, $key ) ); } /** * Raw flat-table value for a key; 'NO-ROW' when the entity has no row. * * @param string $type Entity type. * @param int $id Entity ID. * @param string $key Meta key. * @return string|null */ public static function flat( string $type, int $id, string $key ) { global $wpdb; $field = self::field( $type, $key ); $table = TMDO_Schema_Manager::get_table_name( $type, $field['group'] ); $col = TMDO_Schema_Manager::sanitize_column_name( $key ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table/column come from the registry. $row = $wpdb->get_row( $wpdb->prepare( "SELECT `{$col}` AS v FROM `{$table}` WHERE {$type}_id = %d", $id ), ARRAY_A ); return null === $row ? 'NO-ROW' : $row['v']; } /** * Registry field for a key, or skip the test when the site does not register it. * * @param string $type Entity type. * @param string $key Meta key. */ public static function field( string $type, string $key ): array { $field = TMDO_Entity_Registry::get_field( $type, $key ); if ( ! $field ) { throw new TMDO_Live_Skip( "{$type} key {$key} is not registered on this site" ); } if ( ! TMDO_Schema_Manager::table_exists( TMDO_Schema_Manager::get_table_name( $type, $field['group'] ) ) ) { throw new TMDO_Live_Skip( "flat table for {$type}/{$field['group']} does not exist" ); } return $field; } /** * Delete fixtures and drop the mode override. */ public static function teardown(): void { require_once ABSPATH . 'wp-admin/includes/user.php'; foreach ( array_reverse( self::$created ) as $c ) { if ( 'user' === $c[0] ) { wp_delete_user( $c[1] ); } elseif ( 'term' === $c[0] ) { wp_delete_term( $c[1], $c[2] ); } } self::$created = array(); self::clear_mode_filter(); self::reset_caches(); } /** * Remove the pre_option override, if any. */ private static function clear_mode_filter(): void { if ( null !== self::$mode_filter ) { remove_filter( 'pre_option_wpdo_bridge_modes', self::$mode_filter ); self::$mode_filter = null; } } /** * Reset request-level caches that memoise modes or routing decisions. */ private static function reset_caches(): void { TMDO_Mode_Manager::reset_cache(); TMDO_Routing_Predicate::flush_cache(); } }