wpbridge/includes/UpdateSource/SourceModel.php
feibisi 98e2d9325e feat: P0/P1 UX 改进 - 内联折叠配置面板
- 批量操作改为下拉选择(不再使用 prompt)
- 优先级语义化:首选源/备选源/最后选择
- 术语本地化:API URL→更新地址,认证令牌→访问密码
- URL 自动推断:从 URL 识别源类型(GitHub/GitLab/Gitee/JSON)
- 内联折叠配置面板(参考 WPMind 设计,替代弹窗)
- 安全修复:URL 协议验证、hostname 精确匹配
- 添加 is_inline 属性标记内联源

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 00:27:32 +08:00

271 lines
6.5 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\UpdateSource;
use WPBridge\Security\Encryption;
use WPBridge\Core\Logger;
// 防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* 更新源模型类
*/
class SourceModel {
/**
* 唯一标识
*
* @var string
*/
public string $id = '';
/**
* 源名称
*
* @var string
*/
public string $name = '';
/**
* 源类型(见 SourceType 枚举)
*
* @var string
*/
public string $type = '';
/**
* API URL
*
* @var string
*/
public string $api_url = '';
/**
* 插件/主题 slug
*
* @var string
*/
public string $slug = '';
/**
* 项目类型plugin 或 theme
*
* @var string
*/
public string $item_type = 'plugin';
/**
* 认证令牌
*
* @var string
*/
public string $auth_token = '';
/**
* Git 分支(可选)
*
* @var string
*/
public string $branch = '';
/**
* 是否启用
*
* @var bool
*/
public bool $enabled = true;
/**
* 优先级(数字越小优先级越高)
*
* @var int
*/
public int $priority = 50;
/**
* 是否是预置源
*
* @var bool
*/
public bool $is_preset = false;
/**
* 是否是内联源(项目专属,通过快速设置创建)
*
* @var bool
*/
public bool $is_inline = false;
/**
* 额外元数据
*
* @var array
*/
public array $metadata = [];
/**
* 从数组创建实例
*
* @param array $data 数据数组
* @return self
*/
public static function from_array( array $data ): self {
$model = new self();
$model->id = $data['id'] ?? '';
$model->name = $data['name'] ?? '';
$model->type = $data['type'] ?? SourceType::JSON;
$model->api_url = $data['api_url'] ?? '';
$model->slug = $data['slug'] ?? '';
$model->item_type = $data['item_type'] ?? 'plugin';
$model->auth_token = $data['auth_token'] ?? '';
$model->branch = $data['branch'] ?? '';
$model->enabled = (bool) ( $data['enabled'] ?? true );
$model->priority = (int) ( $data['priority'] ?? 50 );
$model->is_preset = (bool) ( $data['is_preset'] ?? false );
$model->is_inline = (bool) ( $data['is_inline'] ?? false );
$model->metadata = $data['metadata'] ?? [];
return $model;
}
/**
* 转换为数组
*
* @return array
*/
public function to_array(): array {
return [
'id' => $this->id,
'name' => $this->name,
'type' => $this->type,
'api_url' => $this->api_url,
'slug' => $this->slug,
'item_type' => $this->item_type,
'auth_token' => $this->auth_token,
'branch' => $this->branch,
'enabled' => $this->enabled,
'priority' => $this->priority,
'is_preset' => $this->is_preset,
'is_inline' => $this->is_inline,
'metadata' => $this->metadata,
];
}
/**
* 验证模型
*
* @return array 错误数组,空数组表示验证通过
*/
public function validate(): array {
$errors = [];
// 验证类型
if ( ! SourceType::is_valid( $this->type ) ) {
$errors['type'] = __( '无效的源类型', 'wpbridge' );
}
// 验证 API URL
if ( empty( $this->api_url ) ) {
$errors['api_url'] = __( 'API URL 不能为空', 'wpbridge' );
} elseif ( ! filter_var( $this->api_url, FILTER_VALIDATE_URL ) ) {
$errors['api_url'] = __( '无效的 URL 格式', 'wpbridge' );
} else {
// 检查协议是否为 http/https
$scheme = parse_url( $this->api_url, PHP_URL_SCHEME );
if ( ! in_array( $scheme, [ 'http', 'https' ], true ) ) {
$errors['api_url'] = __( 'URL 必须使用 http 或 https 协议', 'wpbridge' );
}
}
// 验证项目类型
if ( ! in_array( $this->item_type, [ 'plugin', 'theme' ], true ) ) {
$errors['item_type'] = __( '项目类型必须是 plugin 或 theme', 'wpbridge' );
}
// 验证优先级
if ( $this->priority < 0 || $this->priority > 100 ) {
$errors['priority'] = __( '优先级必须在 0-100 之间', 'wpbridge' );
}
return $errors;
}
/**
* 是否有效
*
* @return bool
*/
public function is_valid(): bool {
return empty( $this->validate() );
}
/**
* 获取处理器实例
*
* @return Handlers\HandlerInterface|null
*/
public function get_handler(): ?Handlers\HandlerInterface {
$handler_class = SourceType::get_handler_class( $this->type );
if ( null === $handler_class || ! class_exists( $handler_class ) ) {
return null;
}
return new $handler_class( $this );
}
/**
* 获取检查 URL
*
* @return string
*/
public function get_check_url(): string {
$handler = $this->get_handler();
if ( null === $handler ) {
return $this->api_url;
}
return $handler->get_check_url();
}
/**
* 获取请求头
*
* @return array
*/
public function get_headers(): array {
$headers = [];
if ( ! empty( $this->auth_token ) ) {
// 解密 auth_token
$decrypted_token = Encryption::decrypt( $this->auth_token );
// 如果解密失败且数据看起来是加密的,记录错误并返回空
if ( empty( $decrypted_token ) ) {
if ( Encryption::is_encrypted( $this->auth_token ) ) {
Logger::error( 'Token 解密失败', [ 'source' => $this->id ] );
return [];
}
// 可能是未加密的旧数据,直接使用
$decrypted_token = $this->auth_token;
}
// 根据类型设置不同的认证头
if ( SourceType::is_git_type( $this->type ) ) {
$headers['Authorization'] = 'token ' . $decrypted_token;
} else {
$headers['X-API-Key'] = $decrypted_token;
}
}
return $headers;
}
}