wpbridge/includes/CLI/BridgeCommand.php
wenpai e9d28817fe style: phpcbf 自动格式化 — 全量 WPCS 3.x 规范对齐
74 文件 14,082 处自动修复:空格→Tab 缩进、括号间距、
函数声明空格、前置自增、尾逗号等纯格式化改动。
零逻辑变更,php -l + token 级对比验证通过。

新增 phpcs.xml.dist / phpstan.neon.dist 项目配置。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 00:43:46 +08:00

641 lines
14 KiB
PHP

<?php
/**
* WP-CLI 命令
*
* @package WPBridge
*/
namespace WPBridge\CLI;
use WPBridge\Core\Settings;
use WPBridge\Core\Plugin;
use WPBridge\UpdateSource\SourceManager;
use WPBridge\UpdateSource\SourceModel;
use WPBridge\UpdateSource\SourceType;
use WPBridge\Cache\HealthChecker;
use WPBridge\Performance\BackgroundUpdater;
use WP_CLI;
use WP_CLI\Utils;
// 防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* 管理 WPBridge 更新源和缓存
*
* ## EXAMPLES
*
* # 列出所有更新源
* $ wp bridge source list
*
* # 添加更新源
* $ wp bridge source add https://example.com/updates.json --name="My Source"
*
* # 检查所有源
* $ wp bridge check
*
* # 清除缓存
* $ wp bridge cache clear
*/
class BridgeCommand {
/**
* 设置实例
*
* @var Settings
*/
private Settings $settings;
/**
* 源管理器
*
* @var SourceManager
*/
private SourceManager $source_manager;
/**
* 构造函数
*/
public function __construct() {
$this->settings = new Settings();
$this->source_manager = new SourceManager( $this->settings );
}
/**
* 列出所有更新源
*
* ## OPTIONS
*
* [--format=<format>]
* : 输出格式
* ---
* default: table
* options:
* - table
* - json
* - csv
* - yaml
* ---
*
* [--enabled]
* : 只显示启用的源
*
* ## EXAMPLES
*
* $ wp bridge source list
* $ wp bridge source list --format=json
* $ wp bridge source list --enabled
*
* @subcommand source list
*/
public function source_list( $args, $assoc_args ) {
$enabled_only = Utils\get_flag_value( $assoc_args, 'enabled', false );
$sources = $enabled_only
? $this->source_manager->get_enabled()
: $this->source_manager->get_all();
if ( empty( $sources ) ) {
WP_CLI::warning( '没有更新源' );
return;
}
$items = [];
foreach ( $sources as $source ) {
$items[] = [
'id' => $source->id,
'name' => $source->name,
'type' => $source->type,
'api_url' => $source->api_url,
'slug' => $source->slug ?: '(all)',
'enabled' => $source->enabled ? 'yes' : 'no',
'priority' => $source->priority,
'preset' => $source->is_preset ? 'yes' : 'no',
];
}
$format = Utils\get_flag_value( $assoc_args, 'format', 'table' );
Utils\format_items(
$format,
$items,
[
'id',
'name',
'type',
'api_url',
'slug',
'enabled',
'priority',
'preset',
]
);
}
/**
* 添加更新源
*
* ## OPTIONS
*
* <url>
* : 更新源 URL
*
* [--name=<name>]
* : 源名称
*
* [--type=<type>]
* : 源类型
* ---
* default: json
* options:
* - json
* - github
* - gitlab
* - gitee
* - arkpress
* - aspirecloud
* ---
*
* [--slug=<slug>]
* : 插件/主题 slug
*
* [--item-type=<item_type>]
* : 项目类型
* ---
* default: plugin
* options:
* - plugin
* - theme
* ---
*
* [--priority=<priority>]
* : 优先级 (0-100)
* ---
* default: 50
* ---
*
* ## EXAMPLES
*
* $ wp bridge source add https://example.com/updates.json --name="My Plugin"
* $ wp bridge source add github.com/user/repo --type=github --name="GitHub Plugin"
*
* @subcommand source add
*/
public function source_add( $args, $assoc_args ) {
$url = $args[0];
$source = new SourceModel();
$source->api_url = $url;
$source->name = Utils\get_flag_value( $assoc_args, 'name', '' );
$source->type = Utils\get_flag_value( $assoc_args, 'type', SourceType::JSON );
$source->slug = Utils\get_flag_value( $assoc_args, 'slug', '' );
$source->item_type = Utils\get_flag_value( $assoc_args, 'item-type', 'plugin' );
$source->priority = (int) Utils\get_flag_value( $assoc_args, 'priority', 50 );
$source->enabled = true;
// 自动检测类型
if ( empty( $assoc_args['type'] ) ) {
$source->type = $this->detect_source_type( $url );
}
// 自动生成名称
if ( empty( $source->name ) ) {
$source->name = $this->generate_source_name( $url, $source->type );
}
// 验证
$errors = $source->validate();
if ( ! empty( $errors ) ) {
WP_CLI::error( implode( "\n", $errors ) );
}
if ( $this->source_manager->add( $source ) ) {
WP_CLI::success( sprintf( '已添加更新源: %s', $source->name ) );
} else {
WP_CLI::error( '添加失败' );
}
}
/**
* 删除更新源
*
* ## OPTIONS
*
* <id>
* : 源 ID
*
* [--yes]
* : 跳过确认
*
* ## EXAMPLES
*
* $ wp bridge source remove source_abc123
*
* @subcommand source remove
*/
public function source_remove( $args, $assoc_args ) {
$source_id = $args[0];
$source = $this->source_manager->get( $source_id );
if ( null === $source ) {
WP_CLI::error( '源不存在' );
}
if ( $source->is_preset ) {
WP_CLI::error( '不能删除预置源' );
}
WP_CLI::confirm( sprintf( '确定要删除 "%s" 吗?', $source->name ), $assoc_args );
if ( $this->source_manager->delete( $source_id ) ) {
WP_CLI::success( '已删除' );
} else {
WP_CLI::error( '删除失败' );
}
}
/**
* 启用更新源
*
* ## OPTIONS
*
* <id>
* : 源 ID
*
* ## EXAMPLES
*
* $ wp bridge source enable source_abc123
*
* @subcommand source enable
*/
public function source_enable( $args, $assoc_args ) {
$source_id = $args[0];
if ( $this->source_manager->toggle( $source_id, true ) ) {
WP_CLI::success( '已启用' );
} else {
WP_CLI::error( '操作失败' );
}
}
/**
* 禁用更新源
*
* ## OPTIONS
*
* <id>
* : 源 ID
*
* ## EXAMPLES
*
* $ wp bridge source disable source_abc123
*
* @subcommand source disable
*/
public function source_disable( $args, $assoc_args ) {
$source_id = $args[0];
if ( $this->source_manager->toggle( $source_id, false ) ) {
WP_CLI::success( '已禁用' );
} else {
WP_CLI::error( '操作失败' );
}
}
/**
* 检查所有更新源
*
* ## OPTIONS
*
* [--format=<format>]
* : 输出格式
* ---
* default: table
* options:
* - table
* - json
* ---
*
* ## EXAMPLES
*
* $ wp bridge check
*
* @subcommand check
*/
public function check( $args, $assoc_args ) {
$sources = $this->source_manager->get_enabled_sorted();
if ( empty( $sources ) ) {
WP_CLI::warning( '没有启用的更新源' );
return;
}
$checker = new HealthChecker();
$results = [];
foreach ( $sources as $source ) {
WP_CLI::log( sprintf( '检查 %s...', $source->name ) );
$status = $checker->check( $source, true );
$results[] = [
'id' => $source->id,
'name' => $source->name,
'status' => $status->status,
'response_time' => $status->response_time . 'ms',
'error' => $status->error ?: '-',
];
}
$format = Utils\get_flag_value( $assoc_args, 'format', 'table' );
WP_CLI::log( '' );
Utils\format_items(
$format,
$results,
[
'id',
'name',
'status',
'response_time',
'error',
]
);
// 统计
$healthy = count( array_filter( $results, fn( $r ) => $r['status'] === 'healthy' ) );
$total = count( $results );
WP_CLI::log( '' );
WP_CLI::log( sprintf( '健康: %d/%d', $healthy, $total ) );
}
/**
* 清除缓存
*
* ## EXAMPLES
*
* $ wp bridge cache clear
*
* @subcommand cache clear
*/
public function cache_clear( $args, $assoc_args ) {
Plugin::clear_all_cache();
WP_CLI::success( '缓存已清除' );
}
/**
* 查看缓存状态
*
* ## EXAMPLES
*
* $ wp bridge cache status
*
* @subcommand cache status
*/
public function cache_status( $args, $assoc_args ) {
$cache = new \WPBridge\Cache\CacheManager();
$stats = $cache->get_stats();
WP_CLI::log( sprintf( 'Transient 缓存数: %d', $stats['transient_count'] ) );
WP_CLI::log( sprintf( '对象缓存: %s', $stats['object_cache'] ? '是' : '否' ) );
WP_CLI::log( sprintf( '对象缓存类型: %s', $stats['object_cache_type'] ) );
// 后台更新状态
$updater = new BackgroundUpdater( $this->settings );
$status = $updater->get_status();
WP_CLI::log( '' );
WP_CLI::log( '后台更新:' );
WP_CLI::log( sprintf( ' 已调度: %s', $status['scheduled'] ? '是' : '否' ) );
if ( $status['next_run'] ) {
WP_CLI::log( sprintf( ' 下次运行: %s (%s)', $status['next_run'], $status['next_run_human'] ) );
}
}
/**
* 生成诊断报告
*
* ## OPTIONS
*
* [--format=<format>]
* : 输出格式
* ---
* default: text
* options:
* - text
* - json
* ---
*
* ## EXAMPLES
*
* $ wp bridge diagnose
*
* @subcommand diagnose
*/
public function diagnose( $args, $assoc_args ) {
$format = Utils\get_flag_value( $assoc_args, 'format', 'text' );
$report = [
'wpbridge_version' => WPBRIDGE_VERSION,
'wordpress_version' => get_bloginfo( 'version' ),
'php_version' => PHP_VERSION,
'sources' => $this->source_manager->get_stats(),
'settings' => $this->settings->get_all(),
];
if ( $format === 'json' ) {
WP_CLI::log( wp_json_encode( $report, JSON_PRETTY_PRINT ) );
} else {
WP_CLI::log( '=== WPBridge 诊断报告 ===' );
WP_CLI::log( '' );
WP_CLI::log( sprintf( 'WPBridge 版本: %s', $report['wpbridge_version'] ) );
WP_CLI::log( sprintf( 'WordPress 版本: %s', $report['wordpress_version'] ) );
WP_CLI::log( sprintf( 'PHP 版本: %s', $report['php_version'] ) );
WP_CLI::log( '' );
WP_CLI::log( '更新源统计:' );
WP_CLI::log( sprintf( ' 总数: %d', $report['sources']['total'] ) );
WP_CLI::log( sprintf( ' 已启用: %d', $report['sources']['enabled'] ) );
WP_CLI::log( '' );
WP_CLI::log( '设置:' );
WP_CLI::log( sprintf( ' 调试模式: %s', $report['settings']['debug_mode'] ? '是' : '否' ) );
WP_CLI::log( sprintf( ' 缓存时间: %d 秒', $report['settings']['cache_ttl'] ) );
WP_CLI::log( sprintf( ' 请求超时: %d 秒', $report['settings']['request_timeout'] ) );
}
}
/**
* 导出配置
*
* ## OPTIONS
*
* [<file>]
* : 导出文件路径
*
* ## EXAMPLES
*
* $ wp bridge config export
* $ wp bridge config export /path/to/config.json
*
* @subcommand config export
*/
public function config_export( $args, $assoc_args ) {
$sources = $this->source_manager->get_all();
$settings = $this->settings->get_all();
$export = [
'version' => WPBRIDGE_VERSION,
'exported' => current_time( 'mysql' ),
'sources' => array_map( fn( $s ) => $s->to_array(), $sources ),
'settings' => $settings,
];
// 移除敏感信息
foreach ( $export['sources'] as &$source ) {
$source['auth_token'] = '';
}
$json = wp_json_encode( $export, JSON_PRETTY_PRINT );
if ( ! empty( $args[0] ) ) {
$file_path = $args[0];
// 验证路径是否可写
$dir = dirname( $file_path );
if ( ! is_dir( $dir ) || ! is_writable( $dir ) ) {
WP_CLI::error( '目标目录不存在或不可写' );
}
// 使用 WordPress 文件系统 API
global $wp_filesystem;
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
WP_Filesystem();
if ( $wp_filesystem->put_contents( $file_path, $json, FS_CHMOD_FILE ) ) {
WP_CLI::success( sprintf( '已导出到 %s', $file_path ) );
} else {
WP_CLI::error( '写入文件失败' );
}
} else {
WP_CLI::log( $json );
}
}
/**
* 导入配置
*
* ## OPTIONS
*
* <file>
* : 配置文件路径
*
* [--yes]
* : 跳过确认
*
* ## EXAMPLES
*
* $ wp bridge config import /path/to/config.json
*
* @subcommand config import
*/
public function config_import( $args, $assoc_args ) {
$file = $args[0];
if ( ! file_exists( $file ) ) {
WP_CLI::error( '文件不存在' );
}
// 使用 WordPress 文件系统 API
global $wp_filesystem;
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
WP_Filesystem();
$json = $wp_filesystem->get_contents( $file );
if ( false === $json ) {
WP_CLI::error( '读取文件失败' );
}
$data = json_decode( $json, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
WP_CLI::error( 'JSON 解析失败: ' . json_last_error_msg() );
}
WP_CLI::confirm( '这将覆盖现有配置,确定继续吗?', $assoc_args );
// 导入设置
if ( ! empty( $data['settings'] ) ) {
$this->settings->update( $data['settings'] );
WP_CLI::log( '已导入设置' );
}
// 导入源
if ( ! empty( $data['sources'] ) ) {
$count = 0;
foreach ( $data['sources'] as $source_data ) {
// 跳过预置源
if ( ! empty( $source_data['is_preset'] ) ) {
continue;
}
$source = SourceModel::from_array( $source_data );
$source->id = ''; // 生成新 ID
if ( $this->source_manager->add( $source ) ) {
++$count;
}
}
WP_CLI::log( sprintf( '已导入 %d 个更新源', $count ) );
}
WP_CLI::success( '导入完成' );
}
/**
* 检测源类型
*
* @param string $url URL
* @return string
*/
private function detect_source_type( string $url ): string {
if ( strpos( $url, 'github.com' ) !== false || strpos( $url, 'github/' ) !== false ) {
return SourceType::GITHUB;
}
if ( strpos( $url, 'gitlab.com' ) !== false || strpos( $url, 'gitlab/' ) !== false ) {
return SourceType::GITLAB;
}
if ( strpos( $url, 'gitee.com' ) !== false || strpos( $url, 'gitee/' ) !== false ) {
return SourceType::GITEE;
}
return SourceType::JSON;
}
/**
* 生成源名称
*
* @param string $url URL
* @param string $type 类型
* @return string
*/
private function generate_source_name( string $url, string $type ): string {
$parsed = parse_url( $url );
$host = $parsed['host'] ?? '';
$path = $parsed['path'] ?? '';
if ( in_array( $type, [ SourceType::GITHUB, SourceType::GITLAB, SourceType::GITEE ], true ) ) {
// 提取 owner/repo
$path = trim( $path, '/' );
$path = preg_replace( '#\.git$#', '', $path );
return $path ?: $host;
}
return $host ?: $url;
}
}