76c01e44df
對齊 A v3.2.0。型別強制會把隱式轉換變成 TypeError,所以一次全檔加入 並跑完整測試(unit 451 / integration 398 全綠,無迴歸)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
136 lines
4.8 KiB
PHP
136 lines
4.8 KiB
PHP
<?php
|
|
/**
|
|
* TMDO_Auto_Promoter — shadow_read 穩定期後自動升級到 aeav_only。
|
|
*
|
|
* 僅針對 `shadow_read` 做自動升級(升級到 aeav_only),其他 mode 不自動動。
|
|
* 判斷條件(皆滿足才觸發):
|
|
* 1. 當前 mode = shadow_read
|
|
* 2. 進入 shadow_read 距今 ≥ `min_days`
|
|
* 3. 進入後 `shadow_diffs` 表中該 entity 的 diff 筆數 = 0
|
|
*
|
|
* 這符合「穩定期沒問題就進 production」的安全遷移原則。
|
|
* 若 diff 出現,規則永不觸發;人工介入即可。
|
|
*
|
|
* 可由 option 或 filter 調整:
|
|
* option `wpdo_auto_promote_enabled` — 全域開關(bool,預設 false)
|
|
* option `wpdo_auto_promote_min_days` — 停留天數門檻(int,預設 7)
|
|
* filter `wpdo_auto_promote_should_run` — per-entity 覆寫(bool default)
|
|
*
|
|
* @package WP_Data_Optimizer
|
|
* @since 1.5.0
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
// phpcs:disable Squiz.Commenting,Generic.Commenting,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber,Generic.CodeAnalysis.UnusedFunctionParameter,Generic.CodeAnalysis.EmptyStatement,Squiz.PHP.DisallowMultipleAssignments,Squiz.PHP.DisallowSizeFunctionsInLoops,WordPress.WP.I18n.MissingTranslatorsComment,WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents,Squiz.PHP.CommentedOutCode,Universal.NamingConventions.NoReservedKeywordParameterNames,WordPress.PHP.YodaConditions,Squiz.Commenting.InlineComment.InvalidEndChar -- PR-1 ported from UAE; cleanup PR scheduled.
|
|
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
final class TMDO_Auto_Promoter {
|
|
|
|
public const OPT_ENABLED = 'wpdo_auto_promote_enabled';
|
|
public const OPT_MIN_DAYS = 'wpdo_auto_promote_min_days';
|
|
public const DEFAULT_MIN_DAYS = 7;
|
|
|
|
public const CRON_HOOK = 'wpdo_auto_promote_check';
|
|
|
|
public static function init(): void {
|
|
add_action( self::CRON_HOOK, array( self::class, 'check_all' ) );
|
|
add_action( 'init', array( self::class, 'maybe_schedule' ), 30 );
|
|
}
|
|
|
|
/**
|
|
* 首次啟用時排入 daily cron。Option 關閉時會停掉,開啟時重排。
|
|
*/
|
|
public static function maybe_schedule(): void {
|
|
$enabled = (bool) get_option( self::OPT_ENABLED, false );
|
|
$scheduled = wp_next_scheduled( self::CRON_HOOK );
|
|
|
|
if ( $enabled && ! $scheduled ) {
|
|
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', self::CRON_HOOK );
|
|
} elseif ( ! $enabled && $scheduled ) {
|
|
wp_unschedule_event( $scheduled, self::CRON_HOOK );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 遍歷所有 entity,符合條件者升級為 aeav_only。
|
|
*
|
|
* @return array<string, string> entity_type → 結果('promoted'|'not_ready'|'no_diff_yet'|'disabled_globally')
|
|
*/
|
|
public static function check_all(): array {
|
|
$results = array();
|
|
|
|
if ( ! (bool) get_option( self::OPT_ENABLED, false ) ) {
|
|
foreach ( array( 'post', 'user', 'term', 'comment' ) as $type ) {
|
|
$results[ $type ] = 'disabled_globally';
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
$min_days = (int) get_option( self::OPT_MIN_DAYS, self::DEFAULT_MIN_DAYS );
|
|
$entered_all = get_option( TMDO_Mode_Manager::OPT_ENTERED_AT, array() );
|
|
|
|
foreach ( array( 'post', 'user', 'term', 'comment' ) as $type ) {
|
|
$results[ $type ] = self::check_entity( $type, $min_days, $entered_all );
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* 評估單一 entity 是否可升級。
|
|
*
|
|
* @return string 'promoted' | 'not_shadow_read' | 'not_ready' | 'has_diff' | 'vetoed'
|
|
*/
|
|
public static function check_entity( string $type, int $min_days, array $entered_all ): string {
|
|
$current = TMDO_Mode_Manager::get( $type );
|
|
if ( $current !== TMDO_Mode_Manager::MODE_SHADOW_READ ) {
|
|
return 'not_shadow_read';
|
|
}
|
|
|
|
$entered_at = isset( $entered_all[ $type ] ) ? (int) $entered_all[ $type ] : 0;
|
|
if ( $entered_at === 0 || ( time() - $entered_at ) < $min_days * DAY_IN_SECONDS ) {
|
|
return 'not_ready';
|
|
}
|
|
|
|
$diff_counts = TMDO_Shadow_Diff_Logger::count_by_entity();
|
|
$count = (int) ( $diff_counts[ $type ] ?? 0 );
|
|
if ( $count > 0 ) {
|
|
return 'has_diff';
|
|
}
|
|
|
|
// 讓外部 filter 最後一次 veto(例如:還在外部驗證期間)
|
|
$should = apply_filters( 'wpdo_auto_promote_should_run', true, $type, $entered_at, $count );
|
|
if ( ! $should ) {
|
|
return 'vetoed';
|
|
}
|
|
|
|
$result = TMDO_Mode_Manager::set( $type, TMDO_Mode_Manager::MODE_AEAV_ONLY );
|
|
if ( is_wp_error( $result ) ) {
|
|
TMDO_Logger::error(
|
|
'auto_promoter',
|
|
'auto_promote_failed',
|
|
$result->get_error_message(),
|
|
array( 'entity_type' => $type )
|
|
);
|
|
return 'error';
|
|
}
|
|
|
|
TMDO_Logger::info(
|
|
'auto_promote_success',
|
|
array(
|
|
'entity_type' => $type,
|
|
'entered_at' => gmdate( 'c', $entered_at ),
|
|
'days_elapsed' => (int) ( ( time() - $entered_at ) / DAY_IN_SECONDS ),
|
|
'min_days' => $min_days,
|
|
)
|
|
);
|
|
|
|
do_action( 'wpdo_auto_promoted', $type, $entered_at, $min_days );
|
|
|
|
return 'promoted';
|
|
}
|
|
}
|