Files
2meet-data-optimizer/admin/class-tmdo-setup-wizard.php
wpdev d52d604d7a
Anti-EAV Lint + Quality Gate / anti-eav-lint (push) Successful in 8s
Tests / Unit Tests (push) Successful in 10s
Tests / Integration Tests (push) Successful in 35s
Tests / PHP Lint (push) Successful in 7s
Tests / PHPCS (push) Successful in 19s
Tests / PHPStan (push) Successful in 24s
style(admin): inline style 全面改用 CSS class(PR-H)
admin/ 的 inline style= 由 348 處降到 9 處,剩下的 9 處全是 CSS custom
property 載體(--wpdo-bar-width / --wpdo-cov-pct 等動態數值),與來源外掛的
設計一致。

- wpdo-admin.css 543 → 911 行:補上 148 個 class(來源檔在 selector 與
  rule-block 層級都是既有內容的超集,逐條核對過),另加 .wpdo-form-inline
- 5 個變數改為 class 版本:$mode_cls / $badge_mode_cls / $diffs_cls /
  $ap_cls / $e_mode_cls;3 個 stress-test template 補 $mode_text_cls /
  $mode_card_cls
- 刪掉因此變成孤兒的 $mode_badge / $style / $mode_style / $badge_style /
  $e_mode_bg / $badge_bg
- entity card 改 .wpdo-eb-card、pipeline dot 改 .wpdo-stage-pill、demote 按鈕
  改 .wpdo-eb-btn-dim;.wpdo-native-counts / .wpdo-recommendation /
  .wpdo-setup-banner-btn 的重複 inline style 移除
- dashboard 的 top-views 查詢一併改用上一個 commit 的 TMDO_Zone_Warm::VIEW_KEY

PHPStan 抓出自動轉換造成的 11 個未定義變數,已全數補回定義並複驗。
2026-07-31 10:34:57 +08:00

481 lines
21 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* TMDO_Setup_Wizard — First-run onboarding wizard (v2.3.0 M5).
*
* 5 steps:
* 1. Welcome + anti-EAV concept (4-zone diagram, when needed, when not).
* 2. Baseline diagnostic — auto-runs Site Health, postmeta count, top
* meta_key consumers, predicted optimization upside.
* 3. Recommendation — checks Classifier suggestions, presents pre-checked
* modules with "OK" / "skip" choice (no auto-enable; explicit consent).
* 4. First snapshot + daily health check (already scheduled by Core, just
* confirms here).
* 5. Done — sets wpdo_setup_wizard_completed=1, wpdo_first_run_at timestamp.
*
* Trigger: admin loads any wp-admin page AND wpdo_setup_wizard_completed != '1'.
* Existing-user shielding: if any module is not idle (= already migrating),
* we mark wizard completed immediately to avoid ambushing experienced admins.
*
* @package WP_Data_Optimizer
*/
// phpcs:ignore WPDO.AntiEAV -- platform admin UI: postmeta inventory for setup wizard
declare(strict_types=1);
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Setup wizard — stateless static API.
*/
class TMDO_Setup_Wizard {
public const OPT_COMPLETED = 'wpdo_setup_wizard_completed';
public const OPT_FIRST_RUN = 'wpdo_first_run_at';
public const QUERY_PARAM = 'wpdo_wizard';
public const NONCE_NAME = 'wpdo_wizard_nonce';
public const TOTAL_STEPS = 5;
/**
* Bootstrap hooks.
*
* @return void
*/
public static function register(): void {
add_action( 'admin_init', array( __CLASS__, 'maybe_redirect_to_wizard' ) );
add_action( 'admin_menu', array( __CLASS__, 'register_menu_page' ) );
add_action( 'admin_post_wpdo_wizard_step', array( __CLASS__, 'handle_step_submit' ) );
add_action( 'admin_post_wpdo_wizard_dismiss', array( __CLASS__, 'handle_dismiss' ) );
add_action( 'admin_post_wpdo_wizard_reset', array( __CLASS__, 'handle_reset' ) );
}
/**
* On first activation, decide: run wizard, or shield (already configured).
*
* @return void
*/
public static function maybe_redirect_to_wizard(): void {
// Don't redirect on AJAX / cron / auto-saves / wp-cli.
if ( wp_doing_ajax() || wp_doing_cron() || ( defined( 'WP_CLI' ) && WP_CLI ) ) {
return;
}
if ( ! TMDO_Capability::current_user_can_admin() ) {
return;
}
if ( '1' === get_option( self::OPT_COMPLETED ) ) {
return;
}
// Shield existing users: if any module is not idle, the admin already knows
// what they're doing. Mark wizard completed silently.
if ( class_exists( 'TMDO_Feature_Flags' ) ) {
$flags = TMDO_Feature_Flags::all();
foreach ( $flags as $state ) {
if ( 'idle' !== $state ) {
update_option( self::OPT_COMPLETED, '1', false );
update_option( self::OPT_FIRST_RUN, gmdate( 'Y-m-d H:i:s' ), false );
return;
}
}
}
// Don't redirect if already on the wizard page or another WPDO admin page.
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( (string) $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( in_array( $page, array( 'wpdo-setup-wizard', '2meet-data-optimizer' ), true ) ) {
return;
}
// Only redirect from the WordPress dashboard top page (avoid plugin install + mu-plugins flow).
global $pagenow;
if ( 'index.php' !== $pagenow ) {
return;
}
wp_safe_redirect( admin_url( 'tools.php?page=wpdo-setup-wizard&step=1' ) );
exit;
}
/**
* Register the wizard as a visible submenu item under Tools > WP Data Optimizer.
*
* Appears in the Tools section so admins can re-run the wizard at any time.
* The wizard itself is idempotent: re-running it never auto-enables modules.
*
* @return void
*/
public static function register_menu_page(): void {
add_submenu_page(
'tools.php',
__( 'WPDO 設定嚮導', '2meet-data-optimizer' ),
__( 'WPDO 設定嚮導', '2meet-data-optimizer' ),
'manage_options',
'wpdo-setup-wizard',
array( __CLASS__, 'render_page' )
);
}
/**
* Render the wizard.
*
* @return void
*/
public static function render_page(): void {
if ( ! TMDO_Capability::current_user_can_admin() ) {
wp_die( esc_html__( 'Insufficient permissions.', '2meet-data-optimizer' ) );
}
$step = max( 1, min( self::TOTAL_STEPS, absint( wp_unslash( $_GET['step'] ?? 1 ) ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$completed = '1' === get_option( self::OPT_COMPLETED );
?>
<div class="wrap wpdo-max-800">
<p>
<a href="<?php echo esc_url( admin_url( 'tools.php?page=wp-data-optimizer' ) ); ?>">
&larr; <?php esc_html_e( '返回 WP Data Optimizer', '2meet-data-optimizer' ); ?>
</a>
</p>
<h1><?php esc_html_e( 'WP Data Optimizer — 設定嚮導', '2meet-data-optimizer' ); ?></h1>
<?php if ( $completed ) : ?>
<div class="wpdo-setup-banner">
<span>✅ <?php esc_html_e( '嚮導已完成。你可以重新瀏覽任何步驟,或重設為全新執行。', '2meet-data-optimizer' ); ?></span>
<a class="button button-secondary wpdo-setup-banner-btn"
href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=wpdo_wizard_reset' ), self::NONCE_NAME ) ); ?>"
>
<?php esc_html_e( '重新執行精靈', '2meet-data-optimizer' ); ?>
</a>
</div>
<?php endif; ?>
<p class="description">
<?php esc_html_e( '第', '2meet-data-optimizer' ); ?>
<strong><?php echo (int) $step; ?></strong> /
<strong><?php echo (int) self::TOTAL_STEPS; ?></strong>
<?php esc_html_e( '步', '2meet-data-optimizer' ); ?>
</p>
<div class="wpdo-setup-progress-track">
<div class="wpdo-setup-progress-fill" style="--wpdo-progress-pct:<?php echo (int) ( $step / self::TOTAL_STEPS * 100 ); ?>%"></div>
</div>
<div class="card wpdo-setup-card-full">
<?php call_user_func( array( __CLASS__, 'render_step_' . $step ) ); ?>
</div>
<p class="wpdo-mt-3">
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=wpdo_wizard_dismiss' ), self::NONCE_NAME ) ); ?>"
class="wpdo-text-skip">
<?php esc_html_e( '我熟悉了,跳過嚮導', '2meet-data-optimizer' ); ?>
</a>
</p>
</div>
<?php
}
// ─── steps ─────────────────────────────────────────────────────────
/**
* Render wizard step 1: Welcome.
*
* @return void
*/
private static function render_step_1(): void {
?>
<h2><?php esc_html_e( '👋 歡迎使用 WP Data Optimizer', '2meet-data-optimizer' ); ?></h2>
<p><?php esc_html_e( 'WPDO 解決的是 wp_postmeta 表「meta 爆炸」問題:當 postmeta 累積到數百萬行時,autoload 變大、JOIN 變慢、整站變慢。', '2meet-data-optimizer' ); ?></p>
<h3><?php esc_html_e( '4 個 Zone 是什麼?', '2meet-data-optimizer' ); ?></h3>
<table class="widefat wpdo-mb-2">
<thead>
<tr><th>Zone</th><th><?php esc_html_e( '用途', '2meet-data-optimizer' ); ?></th><th><?php esc_html_e( '舉例', '2meet-data-optimizer' ); ?></th></tr>
</thead>
<tbody>
<tr><td><strong>Hot</strong></td><td><?php esc_html_e( '高頻 + 索引欄位', '2meet-data-optimizer' ); ?></td><td><?php esc_html_e( 'price, location, vendor_id', '2meet-data-optimizer' ); ?></td></tr>
<tr><td><strong>Warm</strong></td><td><?php esc_html_e( 'TTL 暫存(會過期)', '2meet-data-optimizer' ); ?></td><td><?php esc_html_e( 'view_count, last_seen', '2meet-data-optimizer' ); ?></td></tr>
<tr><td><strong>Cold</strong></td><td><?php esc_html_e( '低頻設定型欄位', '2meet-data-optimizer' ); ?></td><td><?php esc_html_e( 'preferences, settings', '2meet-data-optimizer' ); ?></td></tr>
<tr><td><strong>Archive</strong></td><td><?php esc_html_e( '舊資料 / 已 trashed', '2meet-data-optimizer' ); ?></td><td><?php esc_html_e( '90+ 天前的記錄', '2meet-data-optimizer' ); ?></td></tr>
</tbody>
</table>
<h3><?php esc_html_e( '何時需要 WPDO', '2meet-data-optimizer' ); ?></h3>
<ul class="wpdo-list-disc">
<li><?php esc_html_e( '✅ wp_postmeta 行數 > 100k 開始考慮', '2meet-data-optimizer' ); ?></li>
<li><?php esc_html_e( '✅ 行數 > 1M 強烈建議啟用', '2meet-data-optimizer' ); ?></li>
<li><?php esc_html_e( '⚠️ 小站台(< 10k 行)可以裝著但別啟用 module', '2meet-data-optimizer' ); ?></li>
</ul>
<form method="get" action="<?php echo esc_url( admin_url( 'tools.php' ) ); ?>" class="wpdo-mt-3">
<input type="hidden" name="page" value="wpdo-setup-wizard">
<input type="hidden" name="step" value="2">
<button class="button button-primary button-hero"><?php esc_html_e( '下一步:跑健診 →', '2meet-data-optimizer' ); ?></button>
</form>
<?php
}
/**
* Render wizard step 2: Baseline diagnostic.
*
* @return void
*/
private static function render_step_2(): void {
global $wpdb;
$pm_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$wpdb->postmeta}`" ); // phpcs:ignore WordPress.DB
$autoload_bytes = (int) $wpdb->get_var( "SELECT COALESCE(SUM(LENGTH(option_value)),0) FROM `{$wpdb->options}` WHERE autoload = 'yes'" ); // phpcs:ignore WordPress.DB
$top_keys = $wpdb->get_results( "SELECT meta_key, COUNT(*) AS c FROM `{$wpdb->postmeta}` GROUP BY meta_key ORDER BY c DESC LIMIT 10", ARRAY_A ); // phpcs:ignore WordPress.DB
?>
<h2><?php esc_html_e( '🔬 Baseline 健診', '2meet-data-optimizer' ); ?></h2>
<table class="widefat striped wpdo-max-600 wpdo-mb-2">
<tbody>
<tr><th><?php esc_html_e( 'wp_postmeta 行數', '2meet-data-optimizer' ); ?></th><td><strong><?php echo esc_html( number_format_i18n( $pm_count ) ); ?></strong></td></tr>
<tr><th><?php esc_html_e( 'autoload 大小', '2meet-data-optimizer' ); ?></th><td><strong><?php echo esc_html( size_format( $autoload_bytes, 1 ) ); ?></strong></td></tr>
<tr><th><?php esc_html_e( '建議啟用 WPDO', '2meet-data-optimizer' ); ?></th>
<td>
<?php if ( $pm_count > 1_000_000 ) : ?>
<span class="wpdo-text-success-em">✅ <?php esc_html_e( '強烈建議', '2meet-data-optimizer' ); ?></span>
<?php elseif ( $pm_count > 100_000 ) : ?>
<span class="wpdo-text-warn-em">🟡 <?php esc_html_e( '可以考慮', '2meet-data-optimizer' ); ?></span>
<?php else : ?>
<span class="wpdo-text-neutral">⏸ <?php esc_html_e( '尚不必', '2meet-data-optimizer' ); ?></span>
<?php endif; ?>
</td>
</tr>
</tbody>
</table>
<h3><?php esc_html_e( 'Top 10 meta_key(依行數)', '2meet-data-optimizer' ); ?></h3>
<table class="widefat striped">
<thead><tr><th>meta_key</th><th><?php esc_html_e( '行數', '2meet-data-optimizer' ); ?></th></tr></thead>
<tbody>
<?php foreach ( (array) $top_keys as $k ) : ?>
<tr><td><code><?php echo esc_html( (string) $k['meta_key'] ); ?></code></td><td><?php echo esc_html( number_format_i18n( (int) $k['c'] ) ); ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
<form method="get" action="<?php echo esc_url( admin_url( 'tools.php' ) ); ?>" class="wpdo-mt-3">
<input type="hidden" name="page" value="wpdo-setup-wizard">
<input type="hidden" name="step" value="3">
<button class="button button-primary button-hero"><?php esc_html_e( '下一步:看建議 →', '2meet-data-optimizer' ); ?></button>
</form>
<?php
}
/**
* Render wizard step 3: Recommendations.
*
* @return void
*/
private static function render_step_3(): void {
?>
<h2><?php esc_html_e( '🧭 智能推薦', '2meet-data-optimizer' ); ?></h2>
<p><?php esc_html_e( '系統依你的環境(plugin / post_type / 行數)自動偵測哪些 module 適合啟用。每筆建議含 confidence + reasons。', '2meet-data-optimizer' ); ?></p>
<?php
$actionable = array();
if ( class_exists( 'TMDO_Module_Detector' ) ) {
$actionable = TMDO_Module_Detector::get_actionable( 0.5 );
}
?>
<?php if ( empty( $actionable ) ) : ?>
<div class="notice notice-info inline">
<p><?php esc_html_e( '目前環境暫無高 confidence 的 module 建議。可隨時前往「模組建議」tab 重新檢查。', '2meet-data-optimizer' ); ?></p>
</div>
<?php else : ?>
<h3>
<?php
printf(
/* translators: %d: count */
esc_html__( '✅ 偵測到 %d 個建議啟用的 module', '2meet-data-optimizer' ),
count( $actionable )
);
?>
</h3>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'Module', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( 'Confidence', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( '說明', '2meet-data-optimizer' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $actionable as $module => $r ) : ?>
<tr>
<td><strong><code><?php echo esc_html( (string) $module ); ?></code></strong></td>
<td><?php echo esc_html( sprintf( '%.2f', (float) $r['confidence'] ) ); ?></td>
<td>
<em><?php echo esc_html( (string) $r['description'] ); ?></em><br/>
<small><?php echo esc_html( implode( ' · ', (array) $r['reasons'] ) ); ?></small>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p class="description"><?php esc_html_e( '完成 wizard 後,到「模組建議」tab 一鍵啟用(推進到 dual_writeFSM Guard 確保不越級)。', '2meet-data-optimizer' ); ?></p>
<?php endif; ?>
<h3><?php esc_html_e( '黃金法則', '2meet-data-optimizer' ); ?></h3>
<ul class="wpdo-list-disc">
<li><?php esc_html_e( '一次只推進 1 個 module,每階段觀察至少 24-48 小時。', '2meet-data-optimizer' ); ?></li>
<li><?php esc_html_e( '進 cutover 前一定要有 snapshotFSM Guard 自動觸發)。', '2meet-data-optimizer' ); ?></li>
<li><?php esc_html_e( '出事第一件事:rewind 該 module 到 idleemergency 流程)。', '2meet-data-optimizer' ); ?></li>
</ul>
<form method="get" action="<?php echo esc_url( admin_url( 'tools.php' ) ); ?>" class="wpdo-mt-3">
<input type="hidden" name="page" value="wpdo-setup-wizard">
<input type="hidden" name="step" value="4">
<button class="button button-primary button-hero"><?php esc_html_e( '下一步:建第一個 snapshot →', '2meet-data-optimizer' ); ?></button>
</form>
<?php
}
/**
* Render wizard step 4: Snapshot + health check.
*
* @return void
*/
private static function render_step_4(): void {
// On entry, take the first snapshot if not already done in this wizard run.
$snapshot_taken = isset( $_GET['snap_done'] ) ? '1' === $_GET['snap_done'] : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
?>
<h2><?php esc_html_e( '📸 第一個 Snapshot + Daily Health Check', '2meet-data-optimizer' ); ?></h2>
<h3><?php esc_html_e( '📦 Snapshot', '2meet-data-optimizer' ); ?></h3>
<?php if ( $snapshot_taken ) : ?>
<p class="wpdo-text-success-em">✅ <?php esc_html_e( '快照已建立。可隨時於「備份快照」tab 查看與管理。', '2meet-data-optimizer' ); ?></p>
<?php else : ?>
<p><?php esc_html_e( '我們會建立一個 baseline snapshot,命名為 wizard_baseline,保留 365 天。即使你日後沒做任何 destructive 操作,這也是一個 known-good 還原點。', '2meet-data-optimizer' ); ?></p>
<form method="post" action="<?php echo esc_url( admin_url( 'tools.php?page=wpdo-setup-wizard&step=4' ) ); ?>" class="wpdo-form-inline">
<?php wp_nonce_field( self::NONCE_NAME ); ?>
<input type="hidden" name="wpdo_take_snapshot" value="1">
<p>
<button type="submit" class="button button-secondary">
<?php esc_html_e( '建立 baseline snapshot', '2meet-data-optimizer' ); ?>
</button>
</p>
</form>
<?php
// Take snapshot if action requested (POST + nonce, prevents Referer leak — v3.3.x P1-6b hardening).
if ( isset( $_POST['wpdo_take_snapshot'] ) && check_admin_referer( self::NONCE_NAME ) && class_exists( 'TMDO_Snapshot_Manager' ) ) {
$result = TMDO_Snapshot_Manager::create(
'manual',
array(),
array(
'notes' => 'wizard_baseline',
'retention_days' => 365,
)
);
if ( ! empty( $result['ok'] ) ) {
wp_safe_redirect( admin_url( 'tools.php?page=wpdo-setup-wizard&step=4&snap_done=1' ) );
exit;
}
echo '<div class="notice notice-error"><p>' . esc_html__( 'Snapshot 建立失敗,請查日誌。', '2meet-data-optimizer' ) . '</p></div>';
}
?>
<?php endif; ?>
<h3><?php esc_html_e( '🔄 Daily Health Check', '2meet-data-optimizer' ); ?></h3>
<p>
<?php
$next = wp_next_scheduled( 'wpdo_daily_health_check' );
if ( $next ) {
printf(
/* translators: %s: human-readable timestamp */
esc_html__( 'Daily cron 已排程,下次執行:%sUTC', '2meet-data-optimizer' ),
'<code>' . esc_html( gmdate( 'Y-m-d H:i:s', $next ) ) . '</code>'
);
} else {
esc_html_e( '⚠️ Daily cron 未排程;請重新啟用外掛。', '2meet-data-optimizer' );
}
?>
</p>
<p class="description"><?php esc_html_e( 'Cron 會跑 7 項 Site Health 檢查;critical 警告寫入 admin notice + audit log。', '2meet-data-optimizer' ); ?></p>
<form method="get" action="<?php echo esc_url( admin_url( 'tools.php' ) ); ?>" class="wpdo-mt-3">
<input type="hidden" name="page" value="wpdo-setup-wizard">
<input type="hidden" name="step" value="5">
<button class="button button-primary button-hero"><?php esc_html_e( '下一步:完成 →', '2meet-data-optimizer' ); ?></button>
</form>
<?php
}
/**
* Render wizard step 5: Done.
*
* @return void
*/
private static function render_step_5(): void {
// Mark complete on render of step 5.
update_option( self::OPT_COMPLETED, '1', false );
if ( ! get_option( self::OPT_FIRST_RUN ) ) {
update_option( self::OPT_FIRST_RUN, gmdate( 'Y-m-d H:i:s' ), false );
}
?>
<h2><?php esc_html_e( '🎉 完成!', '2meet-data-optimizer' ); ?></h2>
<p><?php esc_html_e( 'Setup wizard 已結束。下一步建議:', '2meet-data-optimizer' ); ?></p>
<ul class="wpdo-list-disc">
<li><a href="<?php echo esc_url( admin_url( 'tools.php?page=wp-data-optimizer&tab=entity-bridge' ) ); ?>"><?php esc_html_e( '🌉 看 Entity Bridge 健康卡片', '2meet-data-optimizer' ); ?></a> — <?php esc_html_e( 'user / post / term / comment 4 entity 即時健康+模式狀態', '2meet-data-optimizer' ); ?></li>
<li><a href="<?php echo esc_url( admin_url( 'tools.php?page=wp-data-optimizer&tab=migration-wizard' ) ); ?>"><?php esc_html_e( '🪄 開 User / Post 遷移精靈', '2meet-data-optimizer' ); ?></a> — <?php esc_html_e( '一鍵推進 disabled → dual_write → shadow_read → aeav_only', '2meet-data-optimizer' ); ?></li>
<li><a href="<?php echo esc_url( admin_url( 'tools.php?page=wp-data-optimizer&tab=classifier' ) ); ?>"><?php esc_html_e( '🔍 跑 Classifier', '2meet-data-optimizer' ); ?></a> — <?php esc_html_e( '看推薦 zone 配置', '2meet-data-optimizer' ); ?></li>
<li><a href="<?php echo esc_url( admin_url( 'site-health.php' ) ); ?>"><?php esc_html_e( '🏥 WP Site Health', '2meet-data-optimizer' ); ?></a> — <?php esc_html_e( '7 個 WPDO 健康檢查', '2meet-data-optimizer' ); ?></li>
</ul>
<p class="wpdo-mt-3">
<a class="button button-primary button-hero" href="<?php echo esc_url( admin_url( 'tools.php?page=wp-data-optimizer' ) ); ?>">
<?php esc_html_e( '前往 WPDO 儀表板', '2meet-data-optimizer' ); ?>
</a>
</p>
<?php
}
// ─── form / dismiss handlers ──────────────────────────────────────
/**
* Handle wizard step form submit.
*
* @return void
*/
public static function handle_step_submit(): void {
// v2.13.3: capability check first (matches handle_dismiss / handle_reset
// pattern; fixes L-AUTH-1). Reserved for future POST-based steps —
// currently steps use GET nav so body is a no-op redirect.
if ( ! TMDO_Capability::current_user_can_admin() ) {
wp_die( esc_html__( 'Insufficient permissions.', '2meet-data-optimizer' ) );
}
check_admin_referer( self::NONCE_NAME );
wp_safe_redirect( admin_url( 'tools.php?page=wpdo-setup-wizard&step=1' ) );
exit;
}
/**
* Handle wizard dismiss action.
*
* @return void
*/
public static function handle_dismiss(): void {
check_admin_referer( self::NONCE_NAME );
if ( TMDO_Capability::current_user_can_admin() ) {
update_option( self::OPT_COMPLETED, '1', false );
update_option( self::OPT_FIRST_RUN, gmdate( 'Y-m-d H:i:s' ), false );
}
wp_safe_redirect( admin_url( 'tools.php?page=wp-data-optimizer' ) );
exit;
}
/**
* Handle wizard reset — clears OPT_COMPLETED so wizard reruns from step 1.
*
* @return void
*/
public static function handle_reset(): void {
check_admin_referer( self::NONCE_NAME );
if ( TMDO_Capability::current_user_can_admin() ) {
update_option( self::OPT_COMPLETED, '0', false );
}
wp_safe_redirect( admin_url( 'tools.php?page=wpdo-setup-wizard&step=1' ) );
exit;
}
}