Files
wpdev 265bd14903 chore: initial snapshot of 2meet-data-optimizer-spoke-addon v0.1.0
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6.

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

154 lines
4.3 KiB
PHP

<?php
/**
* WP-CLI commands for spoke-sso AddOn.
*
* 從 wp-data-optimizer/cli/class-wpdo-cli-member.php 的 sso-sync / force-logout 抽出。
*
* @package TMDO_SPOKE
* @since 0.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
final class TMDO_Spoke_CLI {
/**
* 從舊 _tmso_* usermeta 同步到 wp_wpdo_user_sso flat table。
*
* ## OPTIONS
*
* [--user-id=<id>]
* : 限定單一 user 同步(預設全部)
*
* [--batch-size=<n>]
* : 每批處理筆數(預設 500)
*
* [--dry-run]
* : 只顯示會做什麼,不實際寫入
*
* @param array $args
* @param array $assoc_args
*/
public static function sync( $args, $assoc_args ): void {
$user_id = isset( $assoc_args['user-id'] ) ? absint( $assoc_args['user-id'] ) : 0;
$batch = isset( $assoc_args['batch-size'] ) ? max( 1, absint( $assoc_args['batch-size'] ) ) : 500;
$dry_run = ! empty( $assoc_args['dry-run'] );
if ( ! class_exists( 'TMDO_API' ) && ! class_exists( 'WPDO_API' ) ) {
\WP_CLI::error( '需要 2meet-data-optimizer 核心啟用' );
}
$api = class_exists( 'TMDO_API' ) ? 'TMDO_API' : 'WPDO_API';
global $wpdb;
$where = $user_id ? $wpdb->prepare( 'AND user_id = %d', $user_id ) : '';
// 支援 _tmso_global_user_id / _tmso_picture_url / _tmso_refresh_token 三個舊 key
$rows = $wpdb->get_results(
"SELECT user_id, meta_key, meta_value FROM {$wpdb->usermeta}
WHERE meta_key IN ('_tmso_global_user_id','_tmso_picture_url','_tmso_refresh_token')
$where
LIMIT $batch",
ARRAY_A
);
if ( empty( $rows ) ) {
\WP_CLI::success( '無待同步資料' );
return;
}
$migrated = 0;
$key_map = array(
'_tmso_global_user_id' => 'hub_global_user_id',
'_tmso_picture_url' => 'picture_url',
'_tmso_refresh_token' => 'refresh_token_enc',
);
foreach ( $rows as $row ) {
$dst_key = $key_map[ $row['meta_key'] ] ?? null;
if ( ! $dst_key ) {
continue;
}
if ( $dry_run ) {
\WP_CLI::log( "[dry-run] user={$row['user_id']} {$row['meta_key']}{$dst_key}" );
} else {
$api::set_entity( 'user', (int) $row['user_id'], $dst_key, $row['meta_value'] );
}
$migrated++;
}
\WP_CLI::success( ( $dry_run ? '[dry-run] would migrate ' : 'migrated ' ) . $migrated . ' rows' );
}
/**
* 強制使用者 SSO token 過期(設 token_expires_at 為過去時間)。
*
* ## OPTIONS
*
* [--user-id=<id>]
* : Local user ID
*
* [--global-user-id=<uuid>]
* : Hub global user UUID
*
* [--all]
* : 強制全部 user 重認
*
* [--dry-run]
* : 只顯示會做什麼
*
* @param array $args
* @param array $assoc_args
*/
public static function force_logout( $args, $assoc_args ): void {
if ( ! class_exists( 'TMDO_API' ) && ! class_exists( 'WPDO_API' ) ) {
\WP_CLI::error( '需要 2meet-data-optimizer 核心啟用' );
}
$api = class_exists( 'TMDO_API' ) ? 'TMDO_API' : 'WPDO_API';
$dry_run = ! empty( $assoc_args['dry-run'] );
$user_id = isset( $assoc_args['user-id'] ) ? absint( $assoc_args['user-id'] ) : 0;
$global_uuid = isset( $assoc_args['global-user-id'] ) ? sanitize_text_field( $assoc_args['global-user-id'] ) : '';
$all = ! empty( $assoc_args['all'] );
if ( ! $user_id && ! $global_uuid && ! $all ) {
\WP_CLI::error( '需指定 --user-id / --global-user-id / --all 其中之一' );
}
global $wpdb;
$past = '2000-01-01 00:00:00';
$ids = array();
if ( $user_id ) {
$ids[] = $user_id;
} elseif ( $global_uuid ) {
$flat_table = $wpdb->prefix . 'wpdo_user_sso';
$id = $wpdb->get_var( $wpdb->prepare(
"SELECT user_id FROM $flat_table WHERE hub_global_user_id = %s LIMIT 1",
$global_uuid
) );
if ( $id ) {
$ids[] = (int) $id;
}
} elseif ( $all ) {
$flat_table = $wpdb->prefix . 'wpdo_user_sso';
$rows = $wpdb->get_col( "SELECT user_id FROM $flat_table" );
$ids = array_map( 'intval', $rows );
}
if ( empty( $ids ) ) {
\WP_CLI::warning( '找不到符合條件的 user' );
return;
}
foreach ( $ids as $uid ) {
if ( $dry_run ) {
\WP_CLI::log( "[dry-run] user={$uid} token_expires_at → {$past}" );
} else {
$api::set_entity( 'user', $uid, 'token_expires_at', $past );
}
}
\WP_CLI::success( ( $dry_run ? '[dry-run] would force-logout ' : 'force-logout ' ) . count( $ids ) . ' users' );
}
}