wpbridge/includes/Admin/AdminPage.php

1004 lines
34 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
/**
* 管理页面
*
* @package WPBridge
*/
namespace WPBridge\Admin;
use WPBridge\Core\Settings;
use WPBridge\Core\Logger;
use WPBridge\Core\SourceRegistry;
use WPBridge\Security\Encryption;
use WPBridge\UpdateSource\SourceManager;
use WPBridge\UpdateSource\SourceModel;
use WPBridge\UpdateSource\SourceType;
use WPBridge\Cache\HealthChecker;
// 防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* 管理页面类
*/
class AdminPage {
/**
* 设置实例
*
* @var Settings
*/
private Settings $settings;
/**
* 源管理器
*
* @var SourceManager
*/
private SourceManager $source_manager;
/**
* 页面 slug
*
* @var string
*/
const PAGE_SLUG = 'wpbridge';
/**
* 构造函数
*
* @param Settings $settings 设置实例
*/
public function __construct( Settings $settings ) {
$this->settings = $settings;
$this->source_manager = new SourceManager( $settings );
$this->init_hooks();
}
/**
* 初始化钩子
*/
private function init_hooks(): void {
add_action( 'admin_menu', [ $this, 'add_menu' ] );
add_action( 'admin_init', [ $this, 'handle_actions' ] );
add_action( 'wp_ajax_wpbridge_toggle_source', [ $this, 'ajax_toggle_source' ] );
add_action( 'wp_ajax_wpbridge_test_source', [ $this, 'ajax_test_source' ] );
add_action( 'wp_ajax_wpbridge_clear_cache', [ $this, 'ajax_clear_cache' ] );
add_action( 'wp_ajax_wpbridge_clear_logs', [ $this, 'ajax_clear_logs' ] );
add_action( 'wp_ajax_wpbridge_generate_api_key', [ $this, 'ajax_generate_api_key' ] );
add_action( 'wp_ajax_wpbridge_revoke_api_key', [ $this, 'ajax_revoke_api_key' ] );
// 方案 B: 项目配置 AJAX
add_action( 'wp_ajax_wpbridge_set_item_source', [ $this, 'ajax_set_item_source' ] );
add_action( 'wp_ajax_wpbridge_batch_set_source', [ $this, 'ajax_batch_set_source' ] );
add_action( 'wp_ajax_wpbridge_save_defaults', [ $this, 'ajax_save_defaults' ] );
add_action( 'wp_ajax_wpbridge_quick_setup_source', [ $this, 'ajax_quick_setup_source' ] );
add_action( 'wp_ajax_wpbridge_save_item_config', [ $this, 'ajax_save_item_config' ] );
}
/**
* 添加菜单
*/
public function add_menu(): void {
add_menu_page(
__( '云桥', 'wpbridge' ),
__( '云桥', 'wpbridge' ),
'manage_options',
self::PAGE_SLUG,
[ $this, 'render_page' ],
'dashicons-networking',
80
);
}
/**
* 渲染主页面
*/
public function render_page(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- 仅用于页面路由
$action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : 'list';
// 白名单验证
$allowed_actions = [ 'list', 'add', 'edit' ];
if ( ! in_array( $action, $allowed_actions, true ) ) {
$action = 'list';
}
switch ( $action ) {
case 'add':
case 'edit':
$this->render_editor_page();
break;
default:
$this->render_main_page();
break;
}
}
/**
* 渲染主页面(新版 Tab 布局)
*/
private function render_main_page(): void {
include WPBRIDGE_PATH . 'templates/admin/main.php';
}
/**
* 渲染列表页面(旧版,保留兼容)
*/
private function render_list_page(): void {
$sources = $this->source_manager->get_all();
$stats = $this->source_manager->get_stats();
include WPBRIDGE_PATH . 'templates/admin/source-list.php';
}
/**
* 渲染编辑页面
*/
private function render_editor_page(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- 仅用于获取源 ID
$source_id = isset( $_GET['source'] ) ? sanitize_text_field( wp_unslash( $_GET['source'] ) ) : '';
$source = null;
if ( ! empty( $source_id ) ) {
$source = $this->source_manager->get( $source_id );
}
$types = SourceType::get_labels();
include WPBRIDGE_PATH . 'templates/admin/source-editor.php';
}
/**
* 渲染设置页面
*/
public function render_settings_page(): void {
$settings = $this->settings->get_all();
$logs = Logger::get_logs();
include WPBRIDGE_PATH . 'templates/admin/settings.php';
}
/**
* 处理表单提交
*/
public function handle_actions(): void {
if ( ! isset( $_POST['wpbridge_action'] ) ) {
return;
}
if ( ! wp_verify_nonce( $_POST['wpbridge_nonce'] ?? '', 'wpbridge_action' ) ) {
wp_die( __( '安全检查失败', 'wpbridge' ) );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( __( '权限不足', 'wpbridge' ) );
}
$action = sanitize_text_field( $_POST['wpbridge_action'] );
switch ( $action ) {
case 'save_source':
$this->handle_save_source();
break;
case 'delete_source':
$this->handle_delete_source();
break;
case 'save_settings':
$this->handle_save_settings();
break;
case 'save_api_settings':
$this->handle_save_api_settings();
break;
}
}
/**
* 处理保存源
*/
private function handle_save_source(): void {
$source_id = sanitize_text_field( $_POST['source_id'] ?? '' );
$source = new SourceModel();
$source->id = $source_id;
$source->name = sanitize_text_field( $_POST['name'] ?? '' );
$source->type = sanitize_text_field( $_POST['type'] ?? SourceType::JSON );
$source->api_url = esc_url_raw( $_POST['api_url'] ?? '' );
$source->slug = sanitize_text_field( $_POST['slug'] ?? '' );
$source->item_type = sanitize_text_field( $_POST['item_type'] ?? 'plugin' );
// 加密存储 auth_token
$raw_token = sanitize_text_field( $_POST['auth_token'] ?? '' );
// 如果是占位符或空值,保留原有 token
if ( $raw_token === '********' || empty( $raw_token ) ) {
$existing = $this->source_manager->get( $source_id );
$source->auth_token = $existing ? $existing->auth_token : '';
} else {
$source->auth_token = Encryption::encrypt( $raw_token );
}
$source->enabled = ! empty( $_POST['enabled'] );
// 处理语义化优先级选项
$priority_level = sanitize_text_field( $_POST['priority_level'] ?? 'secondary' );
$priority_map = [
'primary' => 10, // 首选源
'secondary' => 50, // 备选源
'fallback' => 90, // 最后选择
];
$source->priority = $priority_map[ $priority_level ] ?? 50;
// 兼容旧的数字优先级(如果直接传入)
if ( isset( $_POST['priority'] ) && is_numeric( $_POST['priority'] ) ) {
$source->priority = (int) $_POST['priority'];
}
// 验证
$errors = $source->validate();
if ( ! empty( $errors ) ) {
$this->add_notice( 'error', implode( '<br>', $errors ) );
return;
}
// 保存
if ( empty( $source_id ) ) {
$result = $this->source_manager->add( $source );
$message = __( '更新源已添加', 'wpbridge' );
} else {
$result = $this->source_manager->update( $source );
$message = __( '更新源已更新', 'wpbridge' );
}
if ( $result ) {
$this->sync_source_to_registry( $source );
$this->add_notice( 'success', $message );
wp_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) );
exit;
} else {
$this->add_notice( 'error', __( '保存失败', 'wpbridge' ) );
}
}
/**
* 处理删除源
*/
private function handle_delete_source(): void {
$source_id = sanitize_text_field( $_POST['source_id'] ?? '' );
if ( empty( $source_id ) ) {
$this->add_notice( 'error', __( '无效的源 ID', 'wpbridge' ) );
return;
}
if ( $this->source_manager->delete( $source_id ) ) {
$registry = new SourceRegistry();
$registry->delete( $source_id );
$this->add_notice( 'success', __( '更新源已删除', 'wpbridge' ) );
} else {
$this->add_notice( 'error', __( '删除失败,可能是预置源', 'wpbridge' ) );
}
wp_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) );
exit;
}
/**
* 处理保存设置
*/
private function handle_save_settings(): void {
// 验证并限制请求超时范围
$request_timeout = (int) ( $_POST['request_timeout'] ?? 10 );
$request_timeout = max( 5, min( 60, $request_timeout ) );
// 验证缓存 TTL
$valid_ttls = [ 3600, 21600, 43200, 86400 ];
$cache_ttl = (int) ( $_POST['cache_ttl'] ?? 43200 );
if ( ! in_array( $cache_ttl, $valid_ttls, true ) ) {
$cache_ttl = 43200;
}
// 保留现有的 api 设置
$current = $this->settings->get_all();
$settings = [
'debug_mode' => ! empty( $_POST['debug_mode'] ),
'cache_ttl' => $cache_ttl,
'request_timeout' => $request_timeout,
'fallback_enabled' => ! empty( $_POST['fallback_enabled'] ),
'api' => $current['api'] ?? [],
];
if ( $this->settings->update( $settings ) ) {
$this->add_notice( 'success', __( '设置已保存', 'wpbridge' ) );
} else {
$this->add_notice( 'error', __( '保存失败', 'wpbridge' ) );
}
wp_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG . '#settings' ) );
exit;
}
/**
* 处理保存 API 设置
*/
private function handle_save_api_settings(): void {
// 验证速率限制范围
$rate_limit = (int) ( $_POST['rate_limit'] ?? 100 );
$rate_limit = max( 10, min( 10000, $rate_limit ) );
// 获取当前设置
$current = $this->settings->get_all();
$api_settings = $current['api'] ?? [];
// 更新 API 设置
$api_settings['enabled'] = ! empty( $_POST['api_enabled'] );
$api_settings['require_auth'] = ! empty( $_POST['require_auth'] );
$api_settings['rate_limit'] = $rate_limit;
// 保留现有的 keys
if ( ! isset( $api_settings['keys'] ) ) {
$api_settings['keys'] = [];
}
$current['api'] = $api_settings;
if ( $this->settings->update( $current ) ) {
$this->add_notice( 'success', __( 'API 设置已保存', 'wpbridge' ) );
} else {
$this->add_notice( 'error', __( '保存失败', 'wpbridge' ) );
}
wp_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG . '#api' ) );
exit;
}
/**
* AJAX: 切换源状态
*/
public function ajax_toggle_source(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
$source_id = sanitize_text_field( $_POST['source_id'] ?? '' );
$enabled = ! empty( $_POST['enabled'] );
if ( $this->source_manager->toggle( $source_id, $enabled ) ) {
wp_send_json_success( [ 'message' => __( '状态已更新', 'wpbridge' ) ] );
} else {
wp_send_json_error( [ 'message' => __( '更新失败', 'wpbridge' ) ] );
}
}
/**
* AJAX: 测试源连通性
*/
public function ajax_test_source(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
$source_id = sanitize_text_field( $_POST['source_id'] ?? '' );
$source = $this->source_manager->get( $source_id );
if ( null === $source ) {
wp_send_json_error( [ 'message' => __( '源不存在', 'wpbridge' ) ] );
}
$checker = new HealthChecker();
$status = $checker->check( $source, true );
wp_send_json_success( [
'status' => $status->status,
'response_time' => $status->response_time,
'error' => $status->error,
] );
}
/**
* AJAX: 清除缓存
*/
public function ajax_clear_cache(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
\WPBridge\Core\Plugin::clear_all_cache();
wp_send_json_success( [ 'message' => __( '缓存已清除', 'wpbridge' ) ] );
}
/**
* AJAX: 清除日志
*/
public function ajax_clear_logs(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
Logger::clear();
wp_send_json_success( [ 'message' => __( '日志已清除', 'wpbridge' ) ] );
}
/**
* AJAX: 生成 API Key
*/
public function ajax_generate_api_key(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
$key_name = sanitize_text_field( $_POST['key_name'] ?? '' );
if ( empty( $key_name ) ) {
$key_name = __( '未命名', 'wpbridge' );
}
try {
// 生成随机 API Key
$api_key = 'wpb_' . bin2hex( random_bytes( 24 ) );
} catch ( \Exception $e ) {
wp_send_json_error( [ 'message' => __( '生成随机数失败', 'wpbridge' ) ] );
}
// 生成唯一 ID
$key_id = 'key_' . wp_generate_uuid4();
// 获取当前 API 设置
$settings = $this->settings->get_all();
$api_settings = $settings['api'] ?? [];
$keys = $api_settings['keys'] ?? [];
// 添加新 Key使用与 ApiKeyManager 一致的字段名)
$keys[] = [
'id' => $key_id,
'name' => $key_name,
'key_hash' => password_hash( $api_key, PASSWORD_DEFAULT ),
'key_prefix' => substr( $api_key, 0, 4 ) . '...' . substr( $api_key, -4 ),
'created_at' => current_time( 'mysql' ),
'last_used' => null,
];
$api_settings['keys'] = $keys;
$settings['api'] = $api_settings;
if ( $this->settings->update( $settings ) ) {
wp_send_json_success( [
'message' => __( 'API Key 已生成', 'wpbridge' ),
'api_key' => $api_key,
'key_id' => $key_id,
] );
} else {
wp_send_json_error( [ 'message' => __( '生成失败', 'wpbridge' ) ] );
}
}
/**
* AJAX: 撤销 API Key
*/
public function ajax_revoke_api_key(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
$key_id = sanitize_text_field( $_POST['key_id'] ?? '' );
if ( empty( $key_id ) ) {
wp_send_json_error( [ 'message' => __( 'Key ID 不能为空', 'wpbridge' ) ] );
}
// 获取当前 API 设置
$settings = $this->settings->get_all();
$api_settings = $settings['api'] ?? [];
$keys = $api_settings['keys'] ?? [];
// 通过 ID 查找并移除 Key
$found = false;
foreach ( $keys as $index => $key ) {
if ( isset( $key['id'] ) && $key['id'] === $key_id ) {
array_splice( $keys, $index, 1 );
$found = true;
break;
}
}
if ( ! $found ) {
wp_send_json_error( [ 'message' => __( 'Key 不存在', 'wpbridge' ) ] );
}
$api_settings['keys'] = $keys;
$settings['api'] = $api_settings;
if ( $this->settings->update( $settings ) ) {
wp_send_json_success( [ 'message' => __( 'API Key 已撤销', 'wpbridge' ) ] );
} else {
wp_send_json_error( [ 'message' => __( '撤销失败', 'wpbridge' ) ] );
}
}
/**
* 添加通知
*
* @param string $type 类型
* @param string $message 消息
*/
private function add_notice( string $type, string $message ): void {
add_settings_error( 'wpbridge', 'wpbridge_notice', $message, $type );
}
/**
* 同步更新源到 SourceRegistry方案 B
*
* @param SourceModel $source 源模型
*/
private function sync_source_to_registry( SourceModel $source ): void {
$registry = new SourceRegistry();
$existing = $registry->get( $source->id );
$type_map = [
SourceType::JSON => SourceRegistry::TYPE_JSON,
SourceType::GITHUB => SourceRegistry::TYPE_GIT,
SourceType::GITLAB => SourceRegistry::TYPE_GIT,
SourceType::GITEE => SourceRegistry::TYPE_GIT,
SourceType::WENPAI_GIT => SourceRegistry::TYPE_GIT,
SourceType::ZIP => SourceRegistry::TYPE_CUSTOM,
SourceType::ARKPRESS => SourceRegistry::TYPE_ARKPRESS,
SourceType::ASPIRECLOUD => SourceRegistry::TYPE_CUSTOM,
SourceType::FAIR => SourceRegistry::TYPE_FAIR,
SourceType::PUC => SourceRegistry::TYPE_CUSTOM,
];
$base_url = '';
$parsed = wp_parse_url( $source->api_url );
if ( ! empty( $parsed['scheme'] ) && ! empty( $parsed['host'] ) ) {
$base_url = $parsed['scheme'] . '://' . $parsed['host'];
if ( ! empty( $parsed['port'] ) ) {
$base_url .= ':' . $parsed['port'];
}
}
$auth_type = $existing['auth_type'] ?? SourceRegistry::AUTH_NONE;
$auth_secret_ref = $existing['auth_secret_ref'] ?? '';
$token = $this->decrypt_auth_token( $source->auth_token );
if ( '' !== $token ) {
if ( empty( $auth_secret_ref ) ) {
$auth_secret_ref = 'secret_' . wp_generate_uuid4();
}
update_option( 'wpbridge_secret_' . $auth_secret_ref, $token, false );
$auth_type = SourceRegistry::AUTH_TOKEN;
}
$data = [
'name' => $source->name,
'type' => $type_map[ $source->type ] ?? SourceRegistry::TYPE_CUSTOM,
'base_url' => $base_url,
'api_url' => $source->api_url,
'enabled' => $source->enabled,
'default_priority' => $source->priority,
'auth_type' => $auth_type,
'auth_secret_ref' => $auth_secret_ref,
'headers' => [],
'is_preset' => false,
];
if ( $existing ) {
$registry->update( $source->id, $data );
} else {
$data['source_key'] = $source->id;
$registry->add( $data );
}
}
/**
* 解密认证令牌
*
* @param string $token 加密令牌
* @return string
*/
private function decrypt_auth_token( string $token ): string {
if ( empty( $token ) ) {
return '';
}
$decrypted = Encryption::decrypt( $token );
if ( ! empty( $decrypted ) ) {
return $decrypted;
}
if ( ! Encryption::is_encrypted( $token ) ) {
return $token;
}
return '';
}
/**
* AJAX: 设置项目更新源
*/
public function ajax_set_item_source(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
$item_key = sanitize_text_field( $_POST['item_key'] ?? '' );
$source_key = sanitize_text_field( $_POST['source_key'] ?? '' );
if ( empty( $item_key ) ) {
wp_send_json_error( [ 'message' => __( '项目键不能为空', 'wpbridge' ) ] );
}
$source_registry = new \WPBridge\Core\SourceRegistry();
$item_manager = new \WPBridge\Core\ItemSourceManager( $source_registry );
$result = false;
if ( $source_key === 'default' ) {
// 重置为默认
$result = $item_manager->delete( $item_key );
if ( ! $result ) {
// 如果删除失败(可能不存在),也算成功
$result = true;
}
} elseif ( $source_key === 'disabled' ) {
// 禁用更新
$result = $item_manager->disable_updates( $item_key );
} else {
// 设置自定义源
$result = $item_manager->set_source( $item_key, $source_key );
}
if ( $result ) {
wp_send_json_success( [ 'message' => __( '配置已保存', 'wpbridge' ) ] );
} else {
wp_send_json_error( [ 'message' => __( '保存失败', 'wpbridge' ) ] );
}
}
/**
* AJAX: 批量设置更新源
*/
public function ajax_batch_set_source(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
$item_keys = isset( $_POST['item_keys'] ) ? array_map( 'sanitize_text_field', (array) $_POST['item_keys'] ) : [];
$action = sanitize_text_field( $_POST['bulk_action'] ?? '' );
$source_key = sanitize_text_field( $_POST['source_key'] ?? '' );
if ( empty( $item_keys ) ) {
wp_send_json_error( [ 'message' => __( '请选择项目', 'wpbridge' ) ] );
}
// 白名单验证操作类型
$allowed_actions = [ 'set_source', 'reset_default', 'disable' ];
if ( ! in_array( $action, $allowed_actions, true ) ) {
wp_send_json_error( [ 'message' => __( '无效操作', 'wpbridge' ) ] );
}
$source_registry = new \WPBridge\Core\SourceRegistry();
$item_manager = new \WPBridge\Core\ItemSourceManager( $source_registry );
$success = 0;
foreach ( $item_keys as $item_key ) {
$result = false;
switch ( $action ) {
case 'set_source':
if ( ! empty( $source_key ) ) {
$result = $item_manager->set_source( $item_key, $source_key );
}
break;
case 'reset_default':
$result = $item_manager->delete( $item_key );
if ( ! $result ) {
$result = true; // 不存在也算成功
}
break;
case 'disable':
$result = $item_manager->disable_updates( $item_key );
break;
}
if ( $result ) {
$success++;
}
}
wp_send_json_success( [
'message' => sprintf(
/* translators: %d: number of items */
__( '已更新 %d 个项目', 'wpbridge' ),
$success
),
'count' => $success,
] );
}
/**
* AJAX: 保存默认规则
*/
public function ajax_save_defaults(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
$defaults_manager = new \WPBridge\Core\DefaultsManager();
// 全局默认 - 清理输入
$global_sources = isset( $_POST['global_sources'] )
? array_map( 'sanitize_text_field', array_keys( (array) $_POST['global_sources'] ) )
: [];
$defaults_manager->set_source_order( \WPBridge\Core\DefaultsManager::SCOPE_GLOBAL, $global_sources );
// 插件默认
if ( ! empty( $_POST['plugin_override'] ) ) {
$plugin_sources = isset( $_POST['plugin_sources'] )
? array_map( 'sanitize_text_field', array_keys( (array) $_POST['plugin_sources'] ) )
: [];
$defaults_manager->set_source_order( \WPBridge\Core\DefaultsManager::SCOPE_PLUGIN, $plugin_sources );
} else {
$defaults_manager->set_source_order( \WPBridge\Core\DefaultsManager::SCOPE_PLUGIN, [] );
}
// 主题默认
if ( ! empty( $_POST['theme_override'] ) ) {
$theme_sources = isset( $_POST['theme_sources'] )
? array_map( 'sanitize_text_field', array_keys( (array) $_POST['theme_sources'] ) )
: [];
$defaults_manager->set_source_order( \WPBridge\Core\DefaultsManager::SCOPE_THEME, $theme_sources );
} else {
$defaults_manager->set_source_order( \WPBridge\Core\DefaultsManager::SCOPE_THEME, [] );
}
wp_send_json_success( [ 'message' => __( '默认规则已保存', 'wpbridge' ) ] );
}
/**
* AJAX: 快速设置更新源 (P1)
*
* 允许用户直接输入 URL自动创建内联源并关联到项目
*/
public function ajax_quick_setup_source(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
$item_key = sanitize_text_field( $_POST['item_key'] ?? '' );
$url = esc_url_raw( $_POST['url'] ?? '' );
$type = sanitize_text_field( $_POST['type'] ?? 'json' );
$name = sanitize_text_field( $_POST['name'] ?? '' );
$token = sanitize_text_field( $_POST['token'] ?? '' );
if ( empty( $item_key ) || empty( $url ) ) {
wp_send_json_error( [ 'message' => __( '缺少必要参数', 'wpbridge' ) ] );
}
// 验证 URL 格式
if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
wp_send_json_error( [ 'message' => __( '无效的 URL 格式', 'wpbridge' ) ] );
}
// 验证 URL 协议(防止 javascript: 或 data: URL
$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
if ( ! in_array( $scheme, [ 'http', 'https' ], true ) ) {
wp_send_json_error( [ 'message' => __( 'URL 必须使用 http 或 https 协议', 'wpbridge' ) ] );
}
// 验证源类型白名单
$allowed_types = [ 'json', 'github', 'gitlab', 'gitee', 'wenpai_git', 'zip', 'arkpress', 'aspirecloud', 'fair', 'puc' ];
if ( ! in_array( $type, $allowed_types, true ) ) {
$type = 'json';
}
// 生成唯一的源 key内联源使用 inline_ 前缀)
$source_key = 'inline_' . md5( $item_key . '_' . $url );
// 如果名称为空,从 URL 生成
if ( empty( $name ) ) {
$parsed_url = wp_parse_url( $url );
$name = $parsed_url['host'] ?? 'Custom Source';
}
// 创建内联源
$source = new SourceModel();
$source->id = $source_key;
$source->name = $name;
$source->type = $type;
$source->api_url = $url;
$source->enabled = true;
$source->priority = 10; // 首选源
$source->is_inline = true; // 标记为内联源
// 如果提供了 token加密存储
if ( ! empty( $token ) ) {
$source->auth_token = Encryption::encrypt( $token );
}
// 从 item_key 推断项目类型
if ( strpos( $item_key, 'plugin:' ) === 0 ) {
$source->item_type = 'plugin';
$source->slug = str_replace( 'plugin:', '', $item_key );
} elseif ( strpos( $item_key, 'theme:' ) === 0 ) {
$source->item_type = 'theme';
$source->slug = str_replace( 'theme:', '', $item_key );
}
// 保存源
$result = $this->source_manager->add( $source );
if ( ! $result ) {
wp_send_json_error( [ 'message' => __( '创建更新源失败', 'wpbridge' ) ] );
}
$this->sync_source_to_registry( $source );
// 关联到项目
$source_registry = new SourceRegistry();
$item_manager = new \WPBridge\Core\ItemSourceManager( $source_registry );
$item_manager->set( $item_key, [
'mode' => \WPBridge\Core\ItemSourceManager::MODE_CUSTOM,
'source_ids' => [ $source_key => 10 ],
] );
wp_send_json_success( [
'message' => __( '更新源已设置', 'wpbridge' ),
'source_key' => $source_key,
] );
}
/**
* AJAX: 保存项目配置 (统一接口)
*
* 支持三种模式default, custom, disabled
*/
public function ajax_save_item_config(): void {
check_ajax_referer( 'wpbridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( '权限不足', 'wpbridge' ) ] );
}
$item_key = sanitize_text_field( $_POST['item_key'] ?? '' );
$mode = sanitize_text_field( $_POST['mode'] ?? 'default' );
if ( empty( $item_key ) ) {
wp_send_json_error( [ 'message' => __( '缺少必要参数', 'wpbridge' ) ] );
}
// 验证模式白名单
$allowed_modes = [ 'default', 'custom', 'disabled' ];
if ( ! in_array( $mode, $allowed_modes, true ) ) {
wp_send_json_error( [ 'message' => __( '无效的模式', 'wpbridge' ) ] );
}
$source_registry = new \WPBridge\Core\SourceRegistry();
$item_manager = new \WPBridge\Core\ItemSourceManager( $source_registry );
$result = false;
switch ( $mode ) {
case 'default':
// 重置为默认
$result = $item_manager->delete( $item_key );
if ( ! $result ) {
$result = true; // 如果不存在也算成功
}
break;
case 'disabled':
// 禁用更新
$result = $item_manager->disable_updates( $item_key );
break;
case 'custom':
// 自定义源 - 需要 URL
$url = esc_url_raw( $_POST['url'] ?? '' );
$type = sanitize_text_field( $_POST['type'] ?? 'json' );
$name = sanitize_text_field( $_POST['name'] ?? '' );
$token = sanitize_text_field( $_POST['token'] ?? '' );
if ( empty( $url ) ) {
wp_send_json_error( [ 'message' => __( '请输入更新地址', 'wpbridge' ) ] );
}
// 验证 URL 格式
if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
wp_send_json_error( [ 'message' => __( '无效的 URL 格式', 'wpbridge' ) ] );
}
// 验证 URL 协议
$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
if ( ! in_array( $scheme, [ 'http', 'https' ], true ) ) {
wp_send_json_error( [ 'message' => __( 'URL 必须使用 http 或 https 协议', 'wpbridge' ) ] );
}
// 验证源类型白名单
$allowed_types = [ 'json', 'github', 'gitlab', 'gitee', 'wenpai_git', 'zip', 'arkpress', 'aspirecloud', 'fair', 'puc' ];
if ( ! in_array( $type, $allowed_types, true ) ) {
$type = 'json';
}
// 生成唯一的源 key
$source_key = 'inline_' . md5( $item_key . '_' . $url );
// 如果名称为空,从 URL 生成
if ( empty( $name ) ) {
$parsed_url = wp_parse_url( $url );
$name = $parsed_url['host'] ?? 'Custom Source';
}
// 创建内联源
$source = new SourceModel();
$source->id = $source_key;
$source->name = $name;
$source->type = $type;
$source->api_url = $url;
$source->enabled = true;
$source->priority = 10;
$source->is_inline = true;
if ( ! empty( $token ) ) {
$source->auth_token = Encryption::encrypt( $token );
}
// 从 item_key 推断项目类型
if ( strpos( $item_key, 'plugin:' ) === 0 ) {
$source->item_type = 'plugin';
$source->slug = str_replace( 'plugin:', '', $item_key );
} elseif ( strpos( $item_key, 'theme:' ) === 0 ) {
$source->item_type = 'theme';
$source->slug = str_replace( 'theme:', '', $item_key );
}
// 保存源
$save_result = $this->source_manager->add( $source );
if ( ! $save_result ) {
wp_send_json_error( [ 'message' => __( '创建更新源失败', 'wpbridge' ) ] );
}
$this->sync_source_to_registry( $source );
// 关联到项目
$result = $item_manager->set( $item_key, [
'mode' => \WPBridge\Core\ItemSourceManager::MODE_CUSTOM,
'source_ids' => [ $source_key => 10 ],
] );
break;
}
if ( $result ) {
wp_send_json_success( [ 'message' => __( '配置已保存', 'wpbridge' ) ] );
} else {
wp_send_json_error( [ 'message' => __( '保存失败', 'wpbridge' ) ] );
}
}
}