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
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Bootstrap for 2meet Data Optimizer Spoke SSO AddOn.
|
||||
*
|
||||
* 負責 spoke-sso 對 2meet-data-optimizer 反 EAV 引擎的 schema 整合:
|
||||
* - 註冊 user entity group `sso`(7 欄位 → wp_wpdo_user_sso flat table)
|
||||
* - 註冊 spoke-sso 2 張自訂表(2mso_sso_config / 2mso_user_mapping)
|
||||
* - 提供 spoke-sync / spoke-force-logout CLI
|
||||
*
|
||||
* @package TMDO_SPOKE
|
||||
* @since 0.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once TMDO_SPOKE_PATH . 'includes/class-tmdo-spoke-sso-group.php';
|
||||
require_once TMDO_SPOKE_PATH . 'includes/class-tmdo-spoke-custom-tables.php';
|
||||
|
||||
final class TMDO_Spoke_Bootstrap {
|
||||
|
||||
public static function init(): void {
|
||||
if ( ! class_exists( 'TMDO_API' ) && ! class_exists( 'WPDO_API' ) ) {
|
||||
add_action( 'admin_notices', array( __CLASS__, 'missing_core_notice' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
load_plugin_textdomain(
|
||||
'tmdo-spoke',
|
||||
false,
|
||||
dirname( plugin_basename( TMDO_SPOKE_FILE ) ) . '/languages'
|
||||
);
|
||||
|
||||
add_action( 'tmdo_register_entity_fields', array( 'TMDO_Spoke_SSO_Group', 'register' ), 10, 1 );
|
||||
add_action( 'wpdo_register_entity_fields', array( 'TMDO_Spoke_SSO_Group', 'register' ), 10, 1 );
|
||||
|
||||
add_action( 'tmdo_register_custom_tables', array( 'TMDO_Spoke_Custom_Tables', 'register' ), 10, 1 );
|
||||
add_action( 'wpdo_register_custom_tables', array( 'TMDO_Spoke_Custom_Tables', 'register' ), 10, 1 );
|
||||
|
||||
// CLI 命令延後到 cli init(class WP_CLI 存在時)
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
require_once TMDO_SPOKE_PATH . 'cli/class-tmdo-spoke-cli.php';
|
||||
\WP_CLI::add_command( 'tmdo spoke-sync', array( 'TMDO_Spoke_CLI', 'sync' ) );
|
||||
\WP_CLI::add_command( 'tmdo spoke-force-logout', array( 'TMDO_Spoke_CLI', 'force_logout' ) );
|
||||
// 向後相容:對外仍提供 wp wpdo spoke-* 路徑。
|
||||
\WP_CLI::add_command( 'wpdo spoke-sync', array( 'TMDO_Spoke_CLI', 'sync' ) );
|
||||
\WP_CLI::add_command( 'wpdo spoke-force-logout', array( 'TMDO_Spoke_CLI', 'force_logout' ) );
|
||||
}
|
||||
}
|
||||
|
||||
public static function missing_core_notice(): void {
|
||||
echo '<div class="notice notice-error"><p>';
|
||||
echo esc_html__( '2meet Data Optimizer Spoke AddOn 需要 2meet-data-optimizer 核心外掛,請先安裝並啟用。', 'tmdo-spoke' );
|
||||
echo '</p></div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* Register spoke-sso 2 custom tables to TMDO Custom Table Registry.
|
||||
*
|
||||
* @package TMDO_SPOKE
|
||||
* @since 0.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
final class TMDO_Spoke_Custom_Tables {
|
||||
|
||||
private static bool $registered = false;
|
||||
|
||||
private const TABLES = array(
|
||||
'2mso_sso_config' => array(
|
||||
'description' => 'spoke-sso Hub OAuth2 連線設定',
|
||||
),
|
||||
'2mso_user_mapping' => array(
|
||||
'description' => 'spoke-sso Hub global_user_id ↔ local user_id mapping',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Register tables to TMDO Custom Table Registry.
|
||||
*
|
||||
* @param object $registry TMDO_Custom_Table_Registry / WPDO_Custom_Table_Registry instance.
|
||||
*/
|
||||
public static function register( $registry ): void {
|
||||
if ( self::$registered ) {
|
||||
return;
|
||||
}
|
||||
if ( ! is_object( $registry ) || ! method_exists( $registry, 'register' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( self::TABLES as $suffix => $meta ) {
|
||||
$registry->register(
|
||||
'2meet-data-optimizer-spoke-addon',
|
||||
array(
|
||||
'table_name' => $suffix,
|
||||
'primary_key' => 'id',
|
||||
'description' => $meta['description'],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
self::$registered = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* SSO entity group registration(7 欄位).
|
||||
*
|
||||
* 從 wp-data-optimizer/includes/integrations/class-wpdo-member-fields.php:226-271 抽出。
|
||||
* 註冊到 user entity,flat table = wp_wpdo_user_sso。
|
||||
*
|
||||
* @package TMDO_SPOKE
|
||||
* @since 0.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
final class TMDO_Spoke_SSO_Group {
|
||||
|
||||
private static bool $registered = false;
|
||||
|
||||
/**
|
||||
* Register `sso` group on user entity.
|
||||
*
|
||||
* @param string|object $registry_class TMDO_Entity_Registry / WPDO_Entity_Registry class name or instance.
|
||||
*/
|
||||
public static function register( $registry_class ): void {
|
||||
if ( self::$registered ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析 registry:可能是 class name (string) 或 instance。
|
||||
$cls = is_string( $registry_class ) ? $registry_class
|
||||
: ( is_object( $registry_class ) ? get_class( $registry_class ) : null );
|
||||
|
||||
if ( ! $cls || ! class_exists( $cls ) || ! method_exists( $cls, 'register_group' ) ) {
|
||||
// fallback:直接呼叫已知 class
|
||||
if ( class_exists( 'TMDO_Entity_Registry' ) ) {
|
||||
$cls = 'TMDO_Entity_Registry';
|
||||
} elseif ( class_exists( 'WPDO_Entity_Registry' ) ) {
|
||||
$cls = 'WPDO_Entity_Registry';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$cls::register_group(
|
||||
'user',
|
||||
'sso',
|
||||
array(
|
||||
array(
|
||||
'key' => 'hub_global_user_id',
|
||||
'type' => 'text',
|
||||
'searchable' => true,
|
||||
'label' => 'Hub global user UUID (2mso_user_mapping.global_user_id bridge key)',
|
||||
),
|
||||
array(
|
||||
'key' => 'picture_url',
|
||||
'type' => 'text',
|
||||
'label' => 'Social / SSO profile picture URL',
|
||||
),
|
||||
array(
|
||||
'key' => 'last_id_token_hash',
|
||||
'type' => 'text',
|
||||
'label' => 'SHA-256(last_id_token) for SLO comparison — no plaintext stored',
|
||||
),
|
||||
array(
|
||||
'key' => 'refresh_token_enc',
|
||||
'type' => 'textarea',
|
||||
'label' => 'Encrypted refresh token (TMSO_Crypto — key-versioned enc_vN:ciphertext)',
|
||||
),
|
||||
array(
|
||||
'key' => 'token_expires_at',
|
||||
'type' => 'datetime',
|
||||
'searchable' => true,
|
||||
'label' => 'SSO token expiry (set to past to force re-auth on next request)',
|
||||
),
|
||||
array(
|
||||
'key' => 'sso_last_login_at',
|
||||
'type' => 'datetime',
|
||||
'label' => 'Last SSO-initiated login datetime',
|
||||
),
|
||||
array(
|
||||
'key' => 'sso_login_count',
|
||||
'type' => 'integer',
|
||||
'default' => 0,
|
||||
'label' => 'SSO login count',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
self::$registered = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user