a0847a27bf
- wpdo_audit CREATE 補 group_name varchar(50) 與 action varchar(20): TMDO_Audit_Logger::write_row() 一直在寫這兩個 key,表卻沒有該欄位。 SCHEMA_VERSION 2.0.0 → 2.1.0 觸發既有站台 dbDelta ALTER;主檔與兩個 test bootstrap 的 TMDO_DB_VERSION 同步(BackCompatTest 守門要求一致)。 - TMDO_Core::run() 註冊 TMDO_Audit_Logger::init():先前完全沒掛, wpdo_after_write / wpdo_after_delete 的稽核監聽器形同不存在。 - register_sso_group() 補 known_login_ips(A v3.4.4):2meet-spoke-sso 的 TMSO_Login_Notify 以 set_entity 寫此欄,未註冊時降級落 wp_usermeta。 對應測試改為 test_sso_group_has_eight_fields。 unit 379 / integration 398 GREEN Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
652 lines
28 KiB
PHP
652 lines
28 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* PHPUnit bootstrap for 2meet Data Optimizer unit tests.
|
|
*
|
|
* No WordPress installation required — stubs all globals and functions.
|
|
* WPDO_* aliases are created via class-tmdo-back-compat.php so existing
|
|
* tests that reference WPDO_* class names continue to work.
|
|
*/
|
|
|
|
require_once dirname( __DIR__ ) . '/vendor/autoload.php';
|
|
|
|
// ── Constants ──────────────────────────────────────────────────────────────
|
|
|
|
define( 'ABSPATH', '/fake/wordpress/' );
|
|
define( 'TMDO_PATH', dirname( __DIR__ ) . '/' );
|
|
define( 'TMDO_URL', 'http://localhost/wp-content/plugins/2meet-data-optimizer/' );
|
|
define( 'TMDO_FILE', dirname( __DIR__ ) . '/2meet-data-optimizer.php' );
|
|
define( 'TMDO_VERSION', '0.1.0' );
|
|
define( 'TMDO_DB_VERSION', '2.1.0' );
|
|
define( 'TMDO_IS_SQLITE', false );
|
|
define( 'TMDO_IS_MYSQL', true );
|
|
|
|
// Back-compat: tests and AddOns may still reference WPDO_ 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 );
|
|
|
|
define( 'DAY_IN_SECONDS', 86400 );
|
|
define( 'HOUR_IN_SECONDS', 3600 );
|
|
define( 'MINUTE_IN_SECONDS', 60 );
|
|
|
|
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' );
|
|
}
|
|
if ( ! defined( 'WPDO_TABLE_PREFIX' ) ) {
|
|
define( 'WPDO_TABLE_PREFIX', TMDO_TABLE_PREFIX );
|
|
}
|
|
if ( ! defined( 'WPDO_CACHE_GROUP' ) ) {
|
|
define( 'WPDO_CACHE_GROUP', TMDO_CACHE_GROUP );
|
|
}
|
|
|
|
// Raw-PHP harness has no snapshot storage — disable the FSM transition guard so
|
|
// tests can force module states directly (mirrors wp-data-optimizer harness).
|
|
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 );
|
|
}
|
|
|
|
// ── Stub $wpdb ─────────────────────────────────────────────────────────────
|
|
|
|
global $wpdb;
|
|
$wpdb = new class {
|
|
public string $prefix = 'wp_';
|
|
public string $postmeta = 'wp_postmeta';
|
|
public string $posts = 'wp_posts';
|
|
public string $options = 'wp_options';
|
|
public string $usermeta = 'wp_usermeta';
|
|
public string $comments = 'wp_comments';
|
|
public int $insert_id = 0;
|
|
public string $last_error = '';
|
|
|
|
public function prepare( string $sql, ...$args ): string {
|
|
$i = 0;
|
|
return preg_replace_callback( '/%[sdf]/', function () use ( &$i, $args ) {
|
|
return $args[ $i++ ] ?? '?';
|
|
}, $sql );
|
|
}
|
|
public function get_var( string $sql ): ?string { return null; }
|
|
public function get_row( string $sql, $output = OBJECT ) { return null; }
|
|
public function get_results( string $sql, $output = OBJECT ): array { return []; }
|
|
public function get_col( string $sql, int $col = 0 ): array { return []; }
|
|
public function insert( string $table, array $data, $format = null ): int|false { return 1; }
|
|
public function update( string $table, array $data, array $where, $format = null, $wf = null ): int|false { return 1; }
|
|
public function delete( string $table, array $where, $format = null ): int|false { return 1; }
|
|
public function replace( string $table, array $data, $format = null ): int|false { return 1; }
|
|
public function query( string $sql ): int|bool { return 1; }
|
|
public function get_charset_collate(): string { return 'DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci'; }
|
|
public function esc_like( string $s ): string { return addcslashes( $s, '_%\\' ); }
|
|
};
|
|
|
|
if ( ! defined( 'OBJECT' ) ) { define( 'OBJECT', 'OBJECT' ); }
|
|
if ( ! defined( 'ARRAY_A' ) ) { define( 'ARRAY_A', 'ARRAY_A' ); }
|
|
|
|
// ── 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 preg_replace( '/[^a-z0-9_\-]/', '', strtolower( $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, $parsed ); return array_merge( $defaults, $parsed ); }
|
|
return array_merge( $defaults, (array) $args );
|
|
}
|
|
}
|
|
// Minimal filter registry — supports add_filter / remove_filter / apply_filters.
|
|
$GLOBALS['_wp_filter_callbacks'] = [];
|
|
|
|
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( '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( '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( '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( 'is_admin' ) ) {
|
|
function is_admin(): bool { return false; }
|
|
}
|
|
if ( ! function_exists( 'is_singular' ) ) {
|
|
function is_singular( $t = '' ): bool { return false; }
|
|
}
|
|
if ( ! function_exists( 'get_the_ID' ) ) {
|
|
function get_the_ID(): int|false { return false; }
|
|
}
|
|
if ( ! function_exists( 'wp_doing_ajax' ) ) {
|
|
function wp_doing_ajax(): bool { return false; }
|
|
}
|
|
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( string $text ): string { return htmlspecialchars( $text, ENT_QUOTES, 'UTF-8' ); }
|
|
}
|
|
if ( ! function_exists( 'esc_html__' ) ) {
|
|
function esc_html__( string $text, string $domain = 'default' ): string { return htmlspecialchars( $text, ENT_QUOTES, 'UTF-8' ); }
|
|
}
|
|
if ( ! function_exists( 'esc_html_e' ) ) {
|
|
function esc_html_e( string $text, string $domain = 'default' ): void { echo htmlspecialchars( $text, ENT_QUOTES, 'UTF-8' ); }
|
|
}
|
|
if ( ! function_exists( 'esc_attr' ) ) {
|
|
function esc_attr( string $text ): string { return htmlspecialchars( $text, 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 {
|
|
$s = is_string( $s ) ? $s : (string) $s;
|
|
return addslashes( $s );
|
|
}
|
|
}
|
|
if ( ! function_exists( '_doing_it_wrong' ) ) {
|
|
function _doing_it_wrong( string $fn, string $msg, string $ver ): void {}
|
|
}
|
|
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_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 {
|
|
$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 = '' ): int|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 = '' ): int|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 = '' ): int|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( 'wp_die' ) ) {
|
|
function wp_die( $message = '' ): void { throw new RuntimeException( is_string( $message ) ? $message : 'wp_die' ); }
|
|
}
|
|
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( '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( '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( '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( '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_transient' ) ) {
|
|
function get_transient( string $key ) { return $GLOBALS['_wp_options'][ '_transient_' . $key ] ?? false; }
|
|
}
|
|
if ( ! function_exists( 'set_transient' ) ) {
|
|
function set_transient( string $key, $value, int $expiry = 0 ): bool {
|
|
$GLOBALS['_wp_options'][ '_transient_' . $key ] = $value;
|
|
return true;
|
|
}
|
|
}
|
|
if ( ! function_exists( 'delete_transient' ) ) {
|
|
function delete_transient( string $key ): bool { unset( $GLOBALS['_wp_options'][ '_transient_' . $key ] ); return true; }
|
|
}
|
|
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; }
|
|
}
|
|
|
|
// Object cache simulation.
|
|
$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; }
|
|
}
|
|
|
|
// ── Stub classes ───────────────────────────────────────────────────────────
|
|
|
|
if ( ! class_exists( 'WP_Query' ) ) {
|
|
class WP_Query {
|
|
public array $posts = [];
|
|
public int $found_posts = 0;
|
|
public int $max_num_pages = 0;
|
|
private array $args = [];
|
|
public function __construct( array $args = [] ) { $this->args = $args; }
|
|
public function get( string $key, $default = '' ) { return $this->args[ $key ] ?? $default; }
|
|
public function set( string $key, $value ): void { $this->args[ $key ] = $value; }
|
|
}
|
|
}
|
|
if ( ! class_exists( 'WP_Error' ) ) {
|
|
class WP_Error {
|
|
private string $code;
|
|
private string $message;
|
|
private array $data;
|
|
public function __construct( string $code = '', string $message = '', $data = [] ) {
|
|
$this->code = $code;
|
|
$this->message = $message;
|
|
$this->data = is_array( $data ) ? $data : [];
|
|
}
|
|
public function get_error_code(): string { return $this->code; }
|
|
public function get_error_message(): string { return $this->message; }
|
|
public function get_error_data() { return $this->data; }
|
|
}
|
|
}
|
|
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';
|
|
const EDITABLE = 'POST, PUT, PATCH';
|
|
const DELETABLE = 'DELETE';
|
|
}
|
|
}
|
|
if ( ! function_exists( 'register_rest_route' ) ) {
|
|
function register_rest_route( string $ns, string $route, array $args ): bool { return true; }
|
|
}
|
|
|
|
// WP_CLI stub (needed before loading CLI classes).
|
|
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 (TMDO_ prefix, dependency order) ──────────────────
|
|
|
|
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';
|
|
|
|
// Interceptors.
|
|
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-interceptor-base.php';
|
|
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-sync-bridge.php';
|
|
|
|
// Zones.
|
|
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';
|
|
|
|
// Query.
|
|
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';
|
|
|
|
// Migration.
|
|
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';
|
|
|
|
// Integrations.
|
|
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';
|
|
|
|
// Cache + Classifier.
|
|
require_once TMDO_PATH . 'includes/class-tmdo-cache-layer.php';
|
|
require_once TMDO_PATH . 'includes/class-tmdo-zone-classifier.php';
|
|
|
|
// Public API.
|
|
require_once TMDO_PATH . 'includes/class-tmdo-api.php';
|
|
|
|
// v2 Upgrader.
|
|
require_once TMDO_PATH . 'includes/class-tmdo-v2-upgrader.php';
|
|
|
|
// Snapshot system.
|
|
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';
|
|
|
|
// FSM safety guard.
|
|
require_once TMDO_PATH . 'includes/safety/class-tmdo-fsm-guard.php';
|
|
|
|
// Diagnostic — do NOT load site-health here; its DB checks would fail with
|
|
// the stub $wpdb and trigger false schema_drift criticals in HealthCronTest.
|
|
// TMDO_Site_Health loads lazily if needed by integration tests.
|
|
require_once TMDO_PATH . 'includes/diagnostic/class-tmdo-health-cron.php';
|
|
require_once TMDO_PATH . 'includes/diagnostic/class-tmdo-monthly-summary.php';
|
|
require_once TMDO_PATH . 'includes/diagnostic/class-tmdo-site-metrics-collector.php';
|
|
|
|
// Notifiers.
|
|
require_once TMDO_PATH . 'includes/notifications/abstract-class-tmdo-notifier.php';
|
|
require_once TMDO_PATH . 'includes/notifications/class-tmdo-email-notifier.php';
|
|
require_once TMDO_PATH . 'includes/notifications/class-tmdo-slack-notifier.php';
|
|
require_once TMDO_PATH . 'includes/notifications/class-tmdo-discord-notifier.php';
|
|
require_once TMDO_PATH . 'includes/notifications/class-tmdo-telegram-notifier.php';
|
|
|
|
// FSM advisor.
|
|
require_once TMDO_PATH . 'includes/advisor/class-tmdo-fsm-advisor.php';
|
|
require_once TMDO_PATH . 'includes/advisor/class-tmdo-module-rules.php';
|
|
require_once TMDO_PATH . 'includes/advisor/class-tmdo-module-detector.php';
|
|
require_once TMDO_PATH . 'includes/advisor/class-tmdo-fsm-automator.php';
|
|
|
|
// Export.
|
|
require_once TMDO_PATH . 'includes/export/class-tmdo-csv-writer.php';
|
|
|
|
// REST API.
|
|
require_once TMDO_PATH . 'includes/class-tmdo-rest-api.php';
|
|
|
|
// Engine (v2.0.0).
|
|
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/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';
|
|
|
|
// Migration Orchestrator (needs Entity_Registry).
|
|
require_once TMDO_PATH . 'includes/migration/class-tmdo-migration-orchestrator.php';
|
|
require_once TMDO_PATH . 'includes/migration/class-tmdo-post-migration.php';
|
|
|
|
// Options Manager.
|
|
require_once TMDO_PATH . 'modules/options/class-tmdo-options-manager.php';
|
|
|
|
// Stress testers + shadow verifiers.
|
|
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';
|
|
|
|
// Core.
|
|
require_once TMDO_PATH . 'includes/class-tmdo-core.php';
|
|
|
|
// CLI.
|
|
require_once TMDO_PATH . 'cli/class-tmdo-cli.php';
|
|
require_once TMDO_PATH . 'cli/class-tmdo-cli-v2.php';
|
|
require_once TMDO_PATH . 'cli/class-tmdo-cli-member.php';
|
|
require_once TMDO_PATH . 'cli/class-tmdo-cli-post.php';
|
|
require_once TMDO_PATH . 'cli/class-tmdo-cli-term-comment.php';
|
|
|
|
// Back-compat aliases (WPDO_* → TMDO_*) — must be last.
|
|
require_once TMDO_PATH . 'includes/class-tmdo-back-compat.php';
|
|
|
|
// Global FSM Guard bypass for all unit tests except FSMGuardTest itself.
|
|
// FSMGuardTest::setUp() clears _wp_filter_callbacks to restore guard behavior.
|
|
if ( ! function_exists( '__return_true' ) ) {
|
|
function __return_true(): bool { return true; }
|
|
}
|
|
add_filter( 'wpdo/fsm_guard/bypass', '__return_true' );
|
|
|
|
// ── AddOn stubs (classes moved to optional AddOns) ─────────────────────────
|
|
|
|
// TMDO_Listing_Stats → 2meet-data-optimizer-hivepress-addon.
|
|
// Tests that hit REST endpoints using listing stats receive stub responses.
|
|
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; }
|
|
}
|
|
class_alias( 'TMDO_Listing_Stats', 'WPDO_Listing_Stats' );
|
|
}
|