Files
2meet-data-optimizer/tests/integration/bootstrap.php
wpdev cd03d66151 test: 補 HookBus 與 audit 寫入的 integration 回歸網(PR-I)
HookBusIntegrationTest(14 tests,自 A 移植)
  integration bootstrap 原本只載入 hook-bus-bridge、沒載入 hook-bus 本體,
  所以連 WPDO_Hook_Bus alias 都建不出來 — 一併補 auto-promoter 與 hook-bus。

AuditLoggerIntegrationTest(4 tests,新寫)
  直接鎖住上一個 commit 修掉的 fatal:
  - after_write 必須寫出一列 audit(覆蓋 Logger::trace_id() 缺失)
  - group_name / action 兩欄必須有值(SCHEMA_VERSION 2.1.0 新增)
  - trace_id 必須是 UUIDv4 且同一 request 內共用
  - 未註冊的 key 不得寫入
  tearDownAfterClass 卸掉 listener 而非 drop 表,否則後續測試類的受管寫入
  會打到不存在的表;integration bootstrap 連帶補 remove_action() stub。

註:TermCommentBackfillTest 未移植 — 它 require HP AddOn 的
class-*-hivepress-term-comment-fields.php,屬 HP AddOn 測試套件而非核心。

unit 451 / integration 416 GREEN、PHPCS 0/0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
2026-07-31 08:55:12 +08:00

633 lines
37 KiB
PHP

<?php
declare(strict_types=1);
/**
* PHPUnit bootstrap for 2meet Data Optimizer integration tests.
*
* Connects to the real MariaDB database using a dedicated test prefix
* (wp_itest_) to avoid collisions with production data.
*
* Required environment variables:
* TMDO_TEST_DB_HOST (default: 127.0.0.1)
* TMDO_TEST_DB_USER (default: dbo)
* TMDO_TEST_DB_PASS (REQUIRED — no fallback)
* TMDO_TEST_DB_NAME (default: wp_wpdo_test)
*
* Back-compat aliases: also accepts WPDO_TEST_DB_* variable names.
*/
require_once dirname( __DIR__, 2 ) . '/vendor/autoload.php';
// ── Constants ──────────────────────────────────────────────────────────────
define( 'ABSPATH', '/fake/wordpress/' );
define( 'TMDO_PATH', dirname( __DIR__, 2 ) . '/' );
define( 'TMDO_URL', 'http://localhost/wp-content/plugins/2meet-data-optimizer/' );
define( 'TMDO_FILE', TMDO_PATH . '2meet-data-optimizer.php' );
$_plugin_header = file_get_contents( TMDO_FILE );
if ( false === $_plugin_header || ! preg_match( '/Version:\s*([0-9A-Za-z.\-+]+)/', $_plugin_header, $_ver ) ) {
throw new RuntimeException( 'Cannot read Version from plugin header: ' . TMDO_FILE );
}
define( 'TMDO_VERSION', $_ver[1] );
unset( $_plugin_header, $_ver );
define( 'TMDO_DB_VERSION', '2.1.0' );
define( 'TMDO_IS_SQLITE', false );
define( 'TMDO_IS_MYSQL', true );
if ( ! defined( 'TMDO_TABLE_PREFIX' ) ) { define( 'TMDO_TABLE_PREFIX', 'wpdo_' ); }
if ( ! defined( 'TMDO_CACHE_GROUP' ) ) { define( 'TMDO_CACHE_GROUP', 'wpdo' ); }
if ( ! defined( 'TMDO_MIN_PHP' ) ) { define( 'TMDO_MIN_PHP', '8.1' ); }
if ( ! defined( 'TMDO_MIN_WP' ) ) { define( 'TMDO_MIN_WP', '6.0' ); }
// Back-compat constants.
define( 'WPDO_PLUGIN_DIR', TMDO_PATH );
define( 'WPDO_PLUGIN_URL', TMDO_URL );
define( 'WPDO_PLUGIN_FILE', TMDO_FILE );
define( 'WPDO_VERSION', TMDO_VERSION );
define( 'WPDO_DB_VERSION', TMDO_DB_VERSION );
define( 'WPDO_IS_SQLITE', TMDO_IS_SQLITE );
define( 'WPDO_IS_MYSQL', TMDO_IS_MYSQL );
if ( ! defined( 'WPDO_TABLE_PREFIX' ) ) { define( 'WPDO_TABLE_PREFIX', TMDO_TABLE_PREFIX ); }
if ( ! defined( 'WPDO_CACHE_GROUP' ) ) { define( 'WPDO_CACHE_GROUP', TMDO_CACHE_GROUP ); }
// Tests force module states directly; the FSM guard would reject those jumps.
if ( ! defined( 'TMDO_FSM_GUARD_DISABLED' ) ) { define( 'TMDO_FSM_GUARD_DISABLED', true ); }
if ( ! defined( 'WPDO_FSM_GUARD_DISABLED' ) ) { define( 'WPDO_FSM_GUARD_DISABLED', TMDO_FSM_GUARD_DISABLED ); }
define( 'DAY_IN_SECONDS', 86400 );
define( 'HOUR_IN_SECONDS', 3600 );
define( 'MINUTE_IN_SECONDS', 60 );
if ( ! defined( 'OBJECT' ) ) { define( 'OBJECT', 'OBJECT' ); }
if ( ! defined( 'ARRAY_A' ) ) { define( 'ARRAY_A', 'ARRAY_A' ); }
// ── Database connection ────────────────────────────────────────────────────
$_db_host = getenv( 'TMDO_TEST_DB_HOST' ) ?: ( getenv( 'WPDO_TEST_DB_HOST' ) ?: '127.0.0.1' );
$_db_user = getenv( 'TMDO_TEST_DB_USER' ) ?: ( getenv( 'WPDO_TEST_DB_USER' ) ?: 'dbo' );
$_db_pass = getenv( 'TMDO_TEST_DB_PASS' ) ?: getenv( 'WPDO_TEST_DB_PASS' );
$_db_name = getenv( 'TMDO_TEST_DB_NAME' ) ?: ( getenv( 'WPDO_TEST_DB_NAME' ) ?: 'wp_wpdo_test' );
if ( false === $_db_pass || '' === $_db_pass ) {
throw new RuntimeException(
'Integration tests require TMDO_TEST_DB_PASS (or WPDO_TEST_DB_PASS) environment variable. ' .
'Example: TMDO_TEST_DB_PASS=yourpass ./vendor/bin/phpunit --configuration phpunit-integration.xml'
);
}
$_mysqli_init = new mysqli( $_db_host, $_db_user, $_db_pass );
if ( $_mysqli_init->connect_error ) {
throw new RuntimeException( 'Integration test DB connection failed: ' . $_mysqli_init->connect_error );
}
$_db_name_quoted = '`' . str_replace( '`', '``', $_db_name ) . '`';
if ( ! $_mysqli_init->query( "CREATE DATABASE IF NOT EXISTS {$_db_name_quoted} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" ) ) {
throw new RuntimeException( "Failed to create test database '{$_db_name}': " . $_mysqli_init->error );
}
$_mysqli_init->close();
unset( $_mysqli_init, $_db_name_quoted );
$_mysqli = new mysqli( $_db_host, $_db_user, $_db_pass, $_db_name );
if ( $_mysqli->connect_error ) {
throw new RuntimeException( 'Integration test DB connection failed: ' . $_mysqli->connect_error );
}
$_mysqli->set_charset( 'utf8mb4' );
// ── Real $wpdb ─────────────────────────────────────────────────────────────
global $wpdb;
$wpdb = new class( $_mysqli ) {
private mysqli $db;
public string $prefix = 'wp_itest_';
public string $postmeta = 'wp_itest_postmeta';
public string $posts = 'wp_itest_posts';
public string $options = 'wp_itest_options';
public string $usermeta = 'wp_itest_usermeta';
public string $users = 'wp_itest_users';
public string $terms = 'wp_itest_terms';
public string $termmeta = 'wp_itest_termmeta';
public string $comments = 'wp_itest_comments';
public string $commentmeta = 'wp_itest_commentmeta';
public int $insert_id = 0;
public string $last_error = '';
public function __construct( mysqli $db ) { $this->db = $db; }
public function prepare( string $sql, ...$args ): string {
$i = 0;
return preg_replace_callback( '/%([sdf])/', function ( $m ) use ( &$i, $args ) {
$val = $args[ $i++ ] ?? '';
if ( $m[1] === 'd' ) { return (string) (int) $val; }
if ( $m[1] === 'f' ) { return (string) (float) $val; }
return "'" . $this->db->real_escape_string( (string) $val ) . "'";
}, $sql );
}
public function get_var( string $sql ): ?string {
$result = $this->db->query( $sql );
if ( ! $result || ! ( $row = $result->fetch_row() ) ) { return null; }
return $row[0] !== null ? (string) $row[0] : null;
}
public function get_row( string $sql, $output = 'OBJECT' ) {
$result = $this->db->query( $sql );
if ( ! $result ) { return null; }
return ARRAY_A === $output ? ( $result->fetch_assoc() ?: null ) : ( $result->fetch_object() ?: null );
}
public function get_results( string $sql, $output = 'OBJECT' ): array {
$result = $this->db->query( $sql );
if ( ! $result ) { return []; }
$rows = [];
while ( $row = ( ARRAY_A === $output ? $result->fetch_assoc() : $result->fetch_object() ) ) {
$rows[] = $row;
}
return $rows;
}
public function get_col( string $sql, int $col_index = 0 ): array {
$result = $this->db->query( $sql );
if ( ! $result ) { return []; }
$values = [];
while ( $row = $result->fetch_row() ) { $values[] = $row[ $col_index ] ?? null; }
return $values;
}
public function get_charset_collate(): string {
return 'DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci';
}
public function insert( string $table, array $data, $format = null ): int|false {
$cols = implode( ', ', array_map( fn( $c ) => '`' . $c . '`', array_keys( $data ) ) );
$vals = implode( ', ', array_map(
fn( $v ) => $v === null ? 'NULL' : "'" . $this->db->real_escape_string( (string) $v ) . "'",
array_values( $data )
) );
$ok = $this->db->query( "INSERT INTO `{$table}` ({$cols}) VALUES ({$vals})" );
if ( $ok ) { $this->insert_id = (int) $this->db->insert_id; return $this->insert_id; }
$this->last_error = (string) $this->db->error;
return false;
}
public function update( string $table, array $data, array $where, $format = null, $wf = null ): int|false {
$set = implode( ', ', array_map( fn( $k, $v ) => '`' . $k . '` = ' . ( $v === null ? 'NULL' : "'" . $this->db->real_escape_string( (string) $v ) . "'" ), array_keys( $data ), $data ) );
$cond = implode( ' AND ', array_map( fn( $k, $v ) => '`' . $k . '` = ' . ( $v === null ? 'NULL' : "'" . $this->db->real_escape_string( (string) $v ) . "'" ), array_keys( $where ), $where ) );
$ok = $this->db->query( "UPDATE `{$table}` SET {$set} WHERE {$cond}" );
return $ok ? $this->db->affected_rows : false;
}
public function delete( string $table, array $where, $format = null ): int|false {
$cond = implode( ' AND ', array_map( fn( $k, $v ) => '`' . $k . '` = ' . ( $v === null ? 'NULL' : "'" . $this->db->real_escape_string( (string) $v ) . "'" ), array_keys( $where ), $where ) );
$ok = $this->db->query( "DELETE FROM `{$table}` WHERE {$cond}" );
return $ok ? $this->db->affected_rows : false;
}
public function replace( string $table, array $data, $format = null ): int|false {
$cols = implode( ', ', array_map( fn( $c ) => '`' . $c . '`', array_keys( $data ) ) );
$vals = implode( ', ', array_map( fn( $v ) => $v === null ? 'NULL' : "'" . $this->db->real_escape_string( (string) $v ) . "'", array_values( $data ) ) );
$ok = $this->db->query( "REPLACE INTO `{$table}` ({$cols}) VALUES ({$vals})" );
if ( $ok ) { $this->insert_id = (int) $this->db->insert_id; return $this->db->affected_rows; }
$this->last_error = (string) $this->db->error;
return false;
}
public function esc_like( string $s ): string { return addcslashes( $s, '_%\\' ); }
public function flush(): void {}
public function query( string $sql ): int|bool {
$result = $this->db->query( $sql );
if ( $result instanceof mysqli_result ) { $result->free(); return true; }
if ( false === $result ) { return false; }
return $this->db->affected_rows;
}
};
// ── WordPress function stubs ───────────────────────────────────────────────
if ( ! function_exists( 'trailingslashit' ) ) {
function trailingslashit( string $s ): string { return rtrim( $s, '/' ) . '/'; }
}
if ( ! function_exists( 'sanitize_key' ) ) {
function sanitize_key( string $key ): string { return strtolower( preg_replace( '/[^a-z0-9_\-]/', '', $key ) ); }
}
if ( ! function_exists( 'absint' ) ) {
function absint( $v ): int { return abs( (int) $v ); }
}
if ( ! function_exists( 'wp_unslash' ) ) {
function wp_unslash( $v ) { return is_string( $v ) ? stripslashes( $v ) : $v; }
}
if ( ! function_exists( 'esc_like' ) ) {
function esc_like( string $s ): string { return addcslashes( $s, '_%\\' ); }
}
if ( ! function_exists( 'current_time' ) ) {
function current_time( string $type, bool $gmt = false ): string|int {
if ( 'timestamp' === $type || 'U' === $type ) { return time(); }
return gmdate( 'Y-m-d H:i:s' );
}
}
if ( ! function_exists( 'wp_parse_args' ) ) {
function wp_parse_args( $args, array $defaults = [] ): array {
if ( is_string( $args ) ) { parse_str( $args, $args ); }
return array_merge( $defaults, (array) $args );
}
}
$GLOBALS['_wp_filter_callbacks'] = [];
if ( ! function_exists( 'add_filter' ) ) {
function add_filter( string $hook, $cb, int $p = 10, int $a = 1 ): bool {
$GLOBALS['_wp_filter_callbacks'][ $hook ][] = $cb;
return true;
}
}
if ( ! function_exists( 'add_action' ) ) {
function add_action( string $hook, $cb, int $p = 10, int $a = 1 ): bool {
$GLOBALS['_wp_filter_callbacks'][ $hook ][] = $cb;
return true;
}
}
if ( ! function_exists( 'remove_filter' ) ) {
function remove_filter( string $hook, $cb, int $p = 10 ): bool {
if ( isset( $GLOBALS['_wp_filter_callbacks'][ $hook ] ) ) {
$GLOBALS['_wp_filter_callbacks'][ $hook ] = array_values(
array_filter( $GLOBALS['_wp_filter_callbacks'][ $hook ], fn( $c ) => $c !== $cb )
);
}
return true;
}
}
if ( ! function_exists( 'remove_action' ) ) {
function remove_action( string $hook, $cb, int $p = 10 ): bool {
return remove_filter( $hook, $cb, $p );
}
}
if ( ! function_exists( 'apply_filters' ) ) {
function apply_filters( string $hook, $value, ...$args ) {
foreach ( $GLOBALS['_wp_filter_callbacks'][ $hook ] ?? [] as $cb ) {
$value = $cb( $value, ...$args );
}
return $value;
}
}
if ( ! function_exists( 'do_action' ) ) {
function do_action( string $hook, ...$args ): void {
foreach ( $GLOBALS['_wp_filter_callbacks'][ $hook ] ?? [] as $cb ) {
$cb( ...$args );
}
}
}
if ( ! function_exists( 'do_action_ref_array' ) ) {
function do_action_ref_array( string $hook, array $args ): void {
do_action( $hook, ...$args );
}
}
if ( ! function_exists( 'is_admin' ) ) { function is_admin(): bool { return ! empty( $GLOBALS['_wp_is_admin'] ); } }
if ( ! function_exists( 'is_singular' ) ) { function is_singular( $t = '' ): bool { return false; } }
if ( ! function_exists( 'wp_doing_ajax' ) ) { function wp_doing_ajax(): bool { return false; } }
if ( ! function_exists( 'wp_next_scheduled' ) ) { function wp_next_scheduled( string $hook ): int|false { return false; } }
if ( ! function_exists( 'wp_schedule_event' ) ) { function wp_schedule_event( int $t, string $r, string $h ): bool { return true; } }
if ( ! function_exists( 'wp_schedule_single_event' ) ) { function wp_schedule_single_event( int $ts, string $hook ): bool { return true; } }
if ( ! function_exists( 'wp_clear_scheduled_hook' ) ) { function wp_clear_scheduled_hook( string $hook ): int|false { return 0; } }
if ( ! function_exists( 'sanitize_text_field' ) ) { function sanitize_text_field( string $s ): string { return trim( strip_tags( $s ) ); } }
if ( ! function_exists( '__' ) ) { function __( string $text, string $domain = 'default' ): string { return $text; } }
if ( ! function_exists( 'esc_html' ) ) { function esc_html( $s ): string { return htmlspecialchars( (string) $s, ENT_QUOTES, 'UTF-8' ); } }
if ( ! function_exists( 'esc_attr' ) ) { function esc_attr( string $s ): string { return htmlspecialchars( $s, ENT_QUOTES, 'UTF-8' ); } }
if ( ! function_exists( 'esc_url' ) ) { function esc_url( string $url ): string { return filter_var( $url, FILTER_SANITIZE_URL ) ?: ''; } }
if ( ! function_exists( 'esc_sql' ) ) { function esc_sql( $s ): string { return addslashes( is_string( $s ) ? $s : (string) $s ); } }
if ( ! function_exists( '_doing_it_wrong' ) ) { function _doing_it_wrong( string $fn, string $msg, string $ver ): void {} }
if ( ! function_exists( 'wp_strip_all_tags' ) ) { function wp_strip_all_tags( string $s ): string { return strip_tags( $s ); } }
if ( ! function_exists( 'get_option' ) ) { function get_option( string $key, $default = false ) { return $GLOBALS['_wp_options'][ $key ] ?? $default; } }
if ( ! function_exists( 'update_option' ) ) { function update_option( string $key, $value ): bool { $GLOBALS['_wp_options'][ $key ] = $value; return true; } }
if ( ! function_exists( 'delete_option' ) ) { function delete_option( string $key ): bool { unset( $GLOBALS['_wp_options'][ $key ] ); return true; } }
if ( ! function_exists( 'get_transient' ) ) { function get_transient( string $key ) { return $GLOBALS['_wp_transients'][ $key ] ?? false; } }
if ( ! function_exists( 'set_transient' ) ) { function set_transient( string $key, $value, int $exp = 0 ): bool { $GLOBALS['_wp_transients'][ $key ] = $value; return true; } }
if ( ! function_exists( 'delete_transient' ) ) { function delete_transient( string $key ): bool { unset( $GLOBALS['_wp_transients'][ $key ] ); return true; } }
if ( ! function_exists( 'get_post_meta' ) ) { function get_post_meta( int $post_id, string $key = '', bool $single = false ) { return $GLOBALS['_wp_postmeta'][ $post_id ][ $key ] ?? ( $single ? '' : [] ); } }
if ( ! function_exists( 'update_post_meta' ) ) {
function update_post_meta( int $post_id, string $key, $value, $prev = '' ): int|bool {
global $wpdb;
$has_table = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->postmeta ) );
if ( $has_table ) {
$existing = $wpdb->get_var( $wpdb->prepare( "SELECT meta_id FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s LIMIT 1", $post_id, $key ) );
if ( $existing ) {
$wpdb->update( $wpdb->postmeta, array( 'meta_value' => is_scalar( $value ) ? (string) $value : maybe_serialize( $value ) ), array( 'meta_id' => $existing ) );
} else {
$wpdb->insert( $wpdb->postmeta, array( 'post_id' => $post_id, 'meta_key' => $key, 'meta_value' => is_scalar( $value ) ? (string) $value : maybe_serialize( $value ) ) );
}
}
$GLOBALS['_wp_postmeta'][ $post_id ][ $key ] = $value;
return true;
}
}
if ( ! function_exists( 'get_user_meta' ) ) { function get_user_meta( int $uid, string $key = '', bool $single = false ) { return $GLOBALS['_wp_usermeta'][ $uid ][ $key ] ?? ( $single ? '' : [] ); } }
if ( ! function_exists( 'update_user_meta' ) ) { function update_user_meta( int $uid, string $key, $value, $prev = '' ): bool { $GLOBALS['_wp_usermeta'][ $uid ][ $key ] = $value; return true; } }
if ( ! function_exists( 'get_term_meta' ) ) { function get_term_meta( int $tid, string $key = '', bool $single = false ) { return $GLOBALS['_wp_termmeta'][ $tid ][ $key ] ?? ( $single ? '' : [] ); } }
if ( ! function_exists( 'update_term_meta' ) ) { function update_term_meta( int $tid, string $key, $value, $prev = '' ): bool { $GLOBALS['_wp_termmeta'][ $tid ][ $key ] = $value; return true; } }
if ( ! function_exists( 'get_comment_meta' ) ) { function get_comment_meta( int $cid, string $key = '', bool $single = false ) { return $GLOBALS['_wp_commentmeta'][ $cid ][ $key ] ?? ( $single ? '' : [] ); } }
if ( ! function_exists( 'update_comment_meta' ) ) { function update_comment_meta( int $cid, string $key, $value, $prev = '' ): bool { $GLOBALS['_wp_commentmeta'][ $cid ][ $key ] = $value; return true; } }
if ( ! function_exists( 'get_post_type' ) ) { function get_post_type( $post_id ) { return $GLOBALS['_wp_post_types'][ (int) $post_id ] ?? false; } }
if ( ! function_exists( 'get_post_status' ) ) { function get_post_status( $post_id ) { return $GLOBALS['_wp_post_status'][ (int) $post_id ] ?? 'publish'; } }
if ( ! function_exists( 'is_post_publicly_viewable' ) ) {
function is_post_publicly_viewable( $post_id ): bool {
if ( isset( $GLOBALS['_wp_post_publicly_viewable'][ (int) $post_id ] ) ) { return (bool) $GLOBALS['_wp_post_publicly_viewable'][ (int) $post_id ]; }
return 'publish' === ( $GLOBALS['_wp_post_status'][ (int) $post_id ] ?? 'publish' );
}
}
if ( ! function_exists( 'current_user_can' ) ) {
function current_user_can( string $cap, ...$args ): bool {
if ( ! empty( $args ) ) { $key = $cap . ':' . implode( ',', array_map( 'strval', $args ) ); if ( isset( $GLOBALS['_wp_current_user_can'][ $key ] ) ) { return (bool) $GLOBALS['_wp_current_user_can'][ $key ]; } }
return $GLOBALS['_wp_current_user_can'][ $cap ] ?? false;
}
}
if ( ! function_exists( 'get_current_user_id' ) ) { function get_current_user_id(): int { return (int) ( $GLOBALS['_wp_current_user_id'] ?? 0 ); } }
if ( ! function_exists( 'is_multisite' ) ) { function is_multisite(): bool { return (bool) ( $GLOBALS['_wp_is_multisite'] ?? false ); } }
if ( ! function_exists( 'is_super_admin' ) ) { function is_super_admin( ?int $uid = null ): bool { return (bool) ( $GLOBALS['_wp_is_super_admin'] ?? false ); } }
if ( ! function_exists( 'switch_to_blog' ) ) { function switch_to_blog( int $blog_id ): bool { $GLOBALS['_wp_current_blog_id'] = $blog_id; return true; } }
if ( ! function_exists( 'restore_current_blog' ) ) { function restore_current_blog(): bool { unset( $GLOBALS['_wp_current_blog_id'] ); return true; } }
if ( ! function_exists( 'get_sites' ) ) { function get_sites( array $args = [] ): array { return $GLOBALS['_wp_sites'] ?? []; } }
if ( ! function_exists( 'is_plugin_active_for_network' ) ) { function is_plugin_active_for_network( string $plugin ): bool { return (bool) ( $GLOBALS['_wp_plugin_active_for_network'][ $plugin ] ?? false ); } }
if ( ! function_exists( 'is_plugin_active' ) ) { function is_plugin_active( string $plugin ): bool { return false; } }
if ( ! function_exists( 'deactivate_plugins' ) ) { function deactivate_plugins( $plugin, bool $silent = false ): void {} }
if ( ! function_exists( 'plugin_basename' ) ) { function plugin_basename( string $file ): string { return basename( dirname( $file ) ) . '/' . basename( $file ); } }
if ( ! function_exists( 'wp_generate_password' ) ) { function wp_generate_password( int $len = 12, bool $special = true ): string { return substr( str_replace( [ '/', '+', '=' ], '', base64_encode( random_bytes( $len ) ) ), 0, $len ); } }
if ( ! function_exists( 'wp_json_encode' ) ) { function wp_json_encode( $data, int $flags = 0 ): string|false { return json_encode( $data, $flags ); } }
if ( ! function_exists( 'wp_rand' ) ) { function wp_rand( int $min = 0, int $max = 0 ): int { return random_int( $min, $max ?: PHP_INT_MAX ); } }
if ( ! function_exists( 'is_wp_error' ) ) { function is_wp_error( $thing ): bool { return $thing instanceof WP_Error; } }
if ( ! function_exists( 'wp_verify_nonce' ) ) { function wp_verify_nonce( $nonce, string $action = '' ) { return $GLOBALS['_wp_valid_nonces'][ (string) $nonce ] ?? false; } }
if ( ! function_exists( 'wp_create_nonce' ) ) { function wp_create_nonce( string $action = '' ): string { $nonce = 'test_nonce_' . md5( $action ); $GLOBALS['_wp_valid_nonces'][ $nonce ] = 1; return $nonce; } }
if ( ! function_exists( 'wp_die' ) ) { function wp_die( $message = '' ): void { throw new RuntimeException( is_string( $message ) ? $message : 'wp_die' ); } }
if ( ! function_exists( 'maybe_serialize' ) ) { function maybe_serialize( $data ) { return is_array( $data ) || is_object( $data ) ? serialize( $data ) : $data; } }
if ( ! function_exists( 'maybe_unserialize' ) ) { function maybe_unserialize( $value ) { if ( ! is_string( $value ) ) { return $value; } $u = @unserialize( $value ); return ( false !== $u || 'b:0;' === $value ) ? $u : $value; } }
if ( ! function_exists( 'get_bloginfo' ) ) { function get_bloginfo( string $key ): string { return 'version' === $key ? '6.9.4' : ''; } }
if ( ! function_exists( 'sanitize_title' ) ) { function sanitize_title( string $title ): string { return strtolower( preg_replace( '/[^a-z0-9-]+/i', '-', trim( $title ) ) ); } }
if ( ! function_exists( 'taxonomy_exists' ) ) {
function taxonomy_exists( string $taxonomy ): bool {
if ( isset( $GLOBALS['_taxonomy_exists_override'] ) ) { return (bool) $GLOBALS['_taxonomy_exists_override']; }
return in_array( $taxonomy, [ 'category', 'post_tag', 'listing_category', 'listing_tag' ], true );
}
}
if ( ! function_exists( 'clean_term_cache' ) ) { function clean_term_cache( $ids, string $taxonomy = '', bool $clean_taxonomy = true ): void {} }
if ( ! function_exists( 'get_taxonomies' ) ) {
function get_taxonomies( array $args = [], string $output = 'names' ): array {
$taxonomies = [ 'category', 'post_tag', 'listing_category', 'listing_tag' ];
if ( 'objects' === $output ) {
$out = [];
foreach ( $taxonomies as $slug ) { $out[ $slug ] = (object) [ 'name' => $slug, 'labels' => (object) [ 'singular_name' => ucfirst( str_replace( '_', ' ', $slug ) ) ] ]; }
return $out;
}
return $taxonomies;
}
}
if ( ! function_exists( 'is_serialized' ) ) { function is_serialized( $data ): bool { return is_string( $data ) && strlen( $data ) >= 4 && in_array( $data[0], [ 'a', 's', 'i', 'd', 'b', 'O', 'N' ], true ) && str_ends_with( $data, ';' ); } }
if ( ! function_exists( 'wp_upload_dir' ) ) {
function wp_upload_dir( $time = null, $create_dir = true, $refresh_cache = false ): array {
$upload_path = sys_get_temp_dir() . '/wp-uploads-test';
return [
'path' => $upload_path,
'url' => 'http://localhost/wp-content/uploads',
'subdir' => '',
'basedir' => $upload_path,
'baseurl' => 'http://localhost/wp-content/uploads',
'error' => false,
];
}
}
if ( ! function_exists( 'wp_mkdir_p' ) ) {
function wp_mkdir_p( string $dir ): bool {
if ( is_dir( $dir ) ) { return true; }
return mkdir( $dir, 0777, true );
}
}
$GLOBALS['_wp_cache'] = [];
if ( ! function_exists( 'wp_cache_get' ) ) { function wp_cache_get( $key, $group = '' ) { return $GLOBALS['_wp_cache'][ $group ][ $key ] ?? false; } }
if ( ! function_exists( 'wp_cache_set' ) ) { function wp_cache_set( $key, $value, $group = '', $ttl = 0 ): bool { $GLOBALS['_wp_cache'][ $group ][ $key ] = $value; return true; } }
if ( ! function_exists( 'wp_cache_delete' ) ) { function wp_cache_delete( $key, $group = '' ): bool { unset( $GLOBALS['_wp_cache'][ $group ][ $key ] ); return true; } }
if ( ! class_exists( 'WP_Query' ) ) {
class WP_Query {
private array $vars = [];
public function get( string $key, $default = '' ) { return $this->vars[ $key ] ?? $default; }
public function set( string $key, $value ): void { $this->vars[ $key ] = $value; }
}
}
if ( ! class_exists( 'WP_Error' ) ) {
class WP_Error {
private string $code;
private string $message;
public function __construct( string $code = '', string $message = '' ) { $this->code = $code; $this->message = $message; }
public function get_error_code(): string { return $this->code; }
public function get_error_message(): string { return $this->message; }
}
}
if ( ! class_exists( 'WP_REST_Request' ) ) {
class WP_REST_Request {
private array $params = []; private array $headers = [];
public function __construct( string $method = 'GET', string $route = '' ) {}
public function get_param( string $key ) { return $this->params[ $key ] ?? null; }
public function set_param( string $key, $value ): void { $this->params[ $key ] = $value; }
public function get_header( string $key ): ?string { return $this->headers[ strtolower( $key ) ] ?? null; }
public function set_header( string $key, string $value ): void { $this->headers[ strtolower( $key ) ] = $value; }
}
}
if ( ! class_exists( 'WP_REST_Response' ) ) {
class WP_REST_Response {
private $data; private int $status; private array $headers = [];
public function __construct( $data = null, int $status = 200 ) { $this->data = $data; $this->status = $status; }
public function get_data() { return $this->data; }
public function get_status(): int { return $this->status; }
public function header( string $k, string $v ): void { $this->headers[ $k ] = $v; }
public function get_headers(): array { return $this->headers; }
}
}
if ( ! class_exists( 'WP_REST_Server' ) ) {
class WP_REST_Server { const READABLE = 'GET'; const CREATABLE = 'POST'; }
}
if ( ! function_exists( 'register_rest_route' ) ) { function register_rest_route( string $ns, string $route, array $args ): bool { return true; } }
if ( ! function_exists( 'dbDelta' ) ) {
function dbDelta( string $sql ): array {
global $wpdb;
$sql_idempotent = preg_replace( '/^CREATE TABLE/i', 'CREATE TABLE IF NOT EXISTS', trim( $sql ), 1 );
$wpdb->query( $sql_idempotent );
return [];
}
}
if ( ! function_exists( 'wp_insert_post' ) ) {
function wp_insert_post( array $postarr, bool $wp_error = false ) {
global $wpdb;
$now = current_time( 'mysql' );
$defaults = [ 'post_title' => '', 'post_type' => 'post', 'post_status' => 'publish', 'post_content' => '', 'post_excerpt' => '', 'post_content_filtered' => '', 'to_ping' => '', 'pinged' => '', 'post_date' => $now, 'post_date_gmt' => $now, 'post_modified' => $now, 'post_modified_gmt' => $now, 'post_name' => '', 'guid' => '' ];
$row = array_merge( $defaults, $postarr );
if ( '' === $row['post_name'] ) { $row['post_name'] = sanitize_title( (string) $row['post_title'] ); }
$ok = $wpdb->insert( $wpdb->posts, $row );
return $ok ? (int) $wpdb->insert_id : ( $wp_error ? new WP_Error( 'insert_failed', 'Insert failed' ) : 0 );
}
}
if ( ! function_exists( 'wp_insert_term' ) ) {
function wp_insert_term( string $term, string $taxonomy, array $args = [] ) {
global $wpdb;
$slug = $args['slug'] ?? sanitize_title( $term );
$ok = $wpdb->insert( $wpdb->terms, [ 'name' => $term, 'slug' => $slug, 'term_group' => 0 ] );
if ( ! $ok ) { return new WP_Error( 'insert_failed', 'terms insert failed' ); }
$term_id = (int) $wpdb->insert_id;
$wpdb->insert( $wpdb->prefix . 'term_taxonomy', [ 'term_id' => $term_id, 'taxonomy' => $taxonomy, 'description' => '', 'parent' => 0, 'count' => 0 ] );
return [ 'term_id' => $term_id, 'term_taxonomy_id' => (int) $wpdb->insert_id ];
}
}
if ( ! function_exists( 'wp_insert_comment' ) ) {
function wp_insert_comment( array $data ) {
global $wpdb;
$ok = $wpdb->insert( $wpdb->comments, [
'comment_post_ID' => (int) ( $data['comment_post_ID'] ?? 0 ),
'comment_author' => (string) ( $data['comment_author'] ?? '' ),
'comment_author_email' => (string) ( $data['comment_author_email'] ?? '' ),
'comment_author_url' => (string) ( $data['comment_author_url'] ?? '' ),
'comment_author_IP' => (string) ( $data['comment_author_IP'] ?? '127.0.0.1' ),
'comment_date' => (string) ( $data['comment_date'] ?? gmdate( 'Y-m-d H:i:s' ) ),
'comment_date_gmt' => (string) ( $data['comment_date_gmt'] ?? gmdate( 'Y-m-d H:i:s' ) ),
'comment_content' => (string) ( $data['comment_content'] ?? '' ),
'comment_karma' => (int) ( $data['comment_karma'] ?? 0 ),
'comment_approved' => (string) ( $data['comment_approved'] ?? '1' ),
'comment_agent' => (string) ( $data['comment_agent'] ?? '' ),
'comment_type' => (string) ( $data['comment_type'] ?? 'comment' ),
'comment_parent' => (int) ( $data['comment_parent'] ?? 0 ),
'user_id' => (int) ( $data['user_id'] ?? 0 ),
] );
if ( ! $ok ) { return false; }
return (int) $wpdb->insert_id;
}
}
if ( ! class_exists( 'WP_CLI' ) ) {
class WP_CLI {
public static function log( string $msg ): void {}
public static function warning( string $msg ): void {}
public static function success( string $msg ): void {}
public static function error( string $msg ): void {}
public static function add_command( string $name, $class ): void {}
}
}
// ── Load plugin classes ────────────────────────────────────────────────────
require_once TMDO_PATH . 'includes/class-tmdo-capability.php';
require_once TMDO_PATH . 'includes/class-tmdo-crypto.php';
require_once TMDO_PATH . 'includes/class-tmdo-safe-unserialize.php';
require_once TMDO_PATH . 'includes/class-tmdo-db.php';
require_once TMDO_PATH . 'includes/class-tmdo-logger.php';
require_once TMDO_PATH . 'includes/class-tmdo-feature-flags.php';
require_once TMDO_PATH . 'includes/class-tmdo-sqlite-compat.php';
require_once TMDO_PATH . 'includes/class-tmdo-installer.php';
require_once TMDO_PATH . 'includes/class-tmdo-schema-registry.php';
require_once TMDO_PATH . 'includes/class-tmdo-custom-table-registry.php';
require_once TMDO_PATH . 'includes/trait-tmdo-anti-eav-aware.php';
require_once TMDO_PATH . 'includes/class-tmdo-hook-bus-bridge.php';
require_once TMDO_PATH . 'includes/class-tmdo-conflict-monitor.php';
require_once TMDO_PATH . 'includes/class-tmdo-compatibility.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-interceptor-base.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-standard-post-interceptor.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-sync-bridge.php';
require_once TMDO_PATH . 'includes/zones/class-tmdo-zone-router.php';
require_once TMDO_PATH . 'includes/class-tmdo-routing-predicate.php';
require_once TMDO_PATH . 'includes/zones/class-tmdo-zone-hot.php';
require_once TMDO_PATH . 'includes/zones/class-tmdo-zone-warm.php';
require_once TMDO_PATH . 'includes/zones/class-tmdo-zone-cold.php';
require_once TMDO_PATH . 'includes/zones/class-tmdo-zone-archive.php';
require_once TMDO_PATH . 'includes/query/class-tmdo-query-interceptor-base.php';
require_once TMDO_PATH . 'includes/query/class-tmdo-query-router.php';
require_once TMDO_PATH . 'includes/query/class-tmdo-post-query-router.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-migration-base.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-migration-engine.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-hot-migration.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-warm-migration.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-cold-migration.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-archive-migration.php';
require_once TMDO_PATH . 'includes/integrations/class-tmdo-term-comment-garbage-filter.php';
require_once TMDO_PATH . 'includes/integrations/class-tmdo-term-comment-misc-bucket.php';
require_once TMDO_PATH . 'includes/integrations/class-tmdo-member-fields.php';
require_once TMDO_PATH . 'includes/integrations/class-tmdo-post-fields.php';
require_once TMDO_PATH . 'includes/integrations/class-tmdo-points-manager.php';
require_once TMDO_PATH . 'includes/integrations/class-tmdo-demo-entity-counter.php';
require_once TMDO_PATH . 'includes/class-tmdo-cache-layer.php';
require_once TMDO_PATH . 'includes/class-tmdo-zone-classifier.php';
require_once TMDO_PATH . 'includes/class-tmdo-api.php';
require_once TMDO_PATH . 'includes/class-tmdo-v2-upgrader.php';
require_once TMDO_PATH . 'includes/snapshots/class-tmdo-snapshot-manager.php';
require_once TMDO_PATH . 'includes/snapshots/class-tmdo-snapshot-writer.php';
require_once TMDO_PATH . 'includes/snapshots/class-tmdo-snapshot-reader.php';
require_once TMDO_PATH . 'includes/snapshots/class-tmdo-snapshot-pruner.php';
require_once TMDO_PATH . 'includes/safety/class-tmdo-fsm-guard.php';
require_once TMDO_PATH . 'includes/class-tmdo-rest-api.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-type-caster.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-mode-manager.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-audit-logger.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-shadow-diff-logger.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-conflict-detector.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-cache-orchestrator.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-query-compiler.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-schema-manager.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-entity-registry.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-entity-migration-engine.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-entity-health.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-auto-promoter.php';
require_once TMDO_PATH . 'includes/engine/class-tmdo-hook-bus.php';
require_once TMDO_PATH . 'includes/adapters/interface-entity-adapter.php';
require_once TMDO_PATH . 'includes/adapters/class-tmdo-adapter-post.php';
require_once TMDO_PATH . 'includes/adapters/class-tmdo-adapter-user.php';
require_once TMDO_PATH . 'includes/adapters/class-tmdo-adapter-term.php';
require_once TMDO_PATH . 'includes/adapters/class-tmdo-adapter-comment.php';
require_once TMDO_PATH . 'includes/migration/interface-migration-phase.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-migration-phase-base.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-diagnose.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-backup.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-demote.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-install-schema.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-backfill-bulk.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-backfill-unserialize.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-promote-shadow.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-verify-sample.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-promote-aeav.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-cleanup.php';
require_once TMDO_PATH . 'includes/migration/phases/class-tmdo-phase-completed.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-migration-orchestrator.php';
require_once TMDO_PATH . 'includes/migration/class-tmdo-post-migration.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';
require_once TMDO_PATH . 'includes/class-tmdo-commentmeta-cleaner.php';
require_once TMDO_PATH . 'includes/class-tmdo-term-comment-shadow-verifier.php';
require_once TMDO_PATH . 'includes/class-tmdo-term-comment-backfill.php';
require_once TMDO_PATH . 'includes/class-tmdo-term-stress-tester.php';
require_once TMDO_PATH . 'includes/class-tmdo-comment-stress-tester.php';
require_once TMDO_PATH . 'includes/class-tmdo-user-stress-tester.php';
require_once TMDO_PATH . 'includes/class-tmdo-post-stress-tester.php';
require_once TMDO_PATH . 'includes/class-tmdo-post-shadow-verifier.php';
require_once TMDO_PATH . 'includes/class-tmdo-core.php';
// Back-compat aliases (WPDO_* → TMDO_*).
require_once TMDO_PATH . 'includes/class-tmdo-back-compat.php';
// FSM Guard bypass for integration tests (real DB transitions should work).
if ( ! function_exists( '__return_true' ) ) {
function __return_true(): bool { return true; }
}
add_filter( 'wpdo/fsm_guard/bypass', '__return_true' );
// TMDO_Listing_Stats moved to HP AddOn; stub here for tests that reference it.
if ( ! class_exists( 'TMDO_Listing_Stats' ) ) {
class TMDO_Listing_Stats {
public static function register(): void {}
public static function get_view_count( int $post_id ): int { return 0; }
public static function increment_view( int $post_id, string $ip = '' ): int { return 0; }
public static function is_rate_limited( int $post_id, string $ip ): bool { return false; }
public static function flush_views_to_postmeta(): int { return 0; }
}
class_alias( 'TMDO_Listing_Stats', 'WPDO_Listing_Stats' );
}