wpbridge/includes/UpdateSource/Handlers/GitHubHandler.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

244 lines
5.5 KiB
PHP

<?php
/**
* GitHub 处理器
*
* @package WPBridge
*/
namespace WPBridge\UpdateSource\Handlers;
use WPBridge\Core\Logger;
// 防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* GitHub 处理器类
*/
class GitHubHandler extends AbstractHandler {
/**
* GitHub API 基础 URL
*
* @var string
*/
const API_BASE = 'https://api.github.com';
/**
* 获取能力列表
*
* @return array
*/
public function get_capabilities(): array {
return [
'auth' => 'token',
'version' => 'release',
'download' => 'release',
];
}
/**
* 获取请求头
*
* @return array
*/
public function get_headers(): array {
$headers = parent::get_headers();
$headers['Accept'] = 'application/vnd.github.v3+json';
$headers['User-Agent'] = 'WPBridge/' . WPBRIDGE_VERSION;
return $headers;
}
/**
* 获取检查 URL
*
* @return string
*/
public function get_check_url(): string {
$repo = $this->parse_repo_url( $this->source->api_url );
if ( empty( $repo ) ) {
return $this->source->api_url;
}
return self::API_BASE . '/repos/' . $repo . '/releases/latest';
}
/**
* 检查更新
*
* @param string $slug 插件/主题 slug
* @param string $version 当前版本
* @return UpdateInfo|null
*/
public function check_update( string $slug, string $version ): ?UpdateInfo {
$repo = $this->parse_repo_url( $this->source->api_url );
if ( empty( $repo ) ) {
Logger::warning( 'GitHub: 无效的仓库 URL', [ 'url' => $this->source->api_url ] );
return null;
}
$url = self::API_BASE . '/repos/' . $repo . '/releases/latest';
$data = $this->request( $url );
if ( null === $data ) {
return null;
}
// 解析版本号(去除 v 前缀)
$remote_version = $data['tag_name'] ?? '';
$remote_version = ltrim( $remote_version, 'v' );
if ( empty( $remote_version ) ) {
Logger::warning( 'GitHub: 响应缺少版本信息', [ 'repo' => $repo ] );
return null;
}
// 检查是否有更新
if ( ! $this->is_newer_version( $version, $remote_version ) ) {
Logger::debug(
'GitHub: 无可用更新',
[
'repo' => $repo,
'current' => $version,
'remote' => $remote_version,
]
);
return null;
}
// 查找下载 URL
$download_url = $this->find_download_url( $data, $slug );
if ( empty( $download_url ) ) {
Logger::warning( 'GitHub: 未找到下载 URL', [ 'repo' => $repo ] );
return null;
}
// 构建更新信息
$info = new UpdateInfo();
$info->slug = $slug;
$info->version = $remote_version;
$info->download_url = $download_url;
$info->details_url = $data['html_url'] ?? '';
$info->last_updated = $data['published_at'] ?? '';
$info->changelog = $data['body'] ?? '';
Logger::info(
'GitHub: 发现更新',
[
'repo' => $repo,
'current' => $version,
'new' => $remote_version,
]
);
return $info;
}
/**
* 获取项目信息
*
* @param string $slug 插件/主题 slug
* @return array|null
*/
public function get_info( string $slug ): ?array {
$repo = $this->parse_repo_url( $this->source->api_url );
if ( empty( $repo ) ) {
return null;
}
// 获取仓库信息
$repo_url = self::API_BASE . '/repos/' . $repo;
$repo_data = $this->request( $repo_url );
// 获取最新 Release
$release_url = self::API_BASE . '/repos/' . $repo . '/releases/latest';
$release_data = $this->request( $release_url );
if ( null === $repo_data || null === $release_data ) {
return null;
}
$version = ltrim( $release_data['tag_name'] ?? '', 'v' );
return [
'name' => $repo_data['name'] ?? $slug,
'slug' => $slug,
'version' => $version,
'download_url' => $this->find_download_url( $release_data, $slug ),
'details_url' => $release_data['html_url'] ?? '',
'last_updated' => $release_data['published_at'] ?? '',
'sections' => [
'description' => $repo_data['description'] ?? '',
'changelog' => $release_data['body'] ?? '',
],
];
}
/**
* 解析仓库 URL
*
* @param string $url URL
* @return string|null owner/repo 格式
*/
private function parse_repo_url( string $url ): ?string {
// 支持多种格式
// https://github.com/owner/repo
// github.com/owner/repo
// owner/repo
$url = trim( $url );
// 移除协议
$url = preg_replace( '#^https?://#', '', $url );
// 移除 github.com
$url = preg_replace( '#^github\.com/#', '', $url );
// 移除 .git 后缀
$url = preg_replace( '#\.git$#', '', $url );
// 验证格式
if ( preg_match( '#^[\w.-]+/[\w.-]+$#', $url ) ) {
return $url;
}
return null;
}
/**
* 查找下载 URL
*
* @param array $release Release 数据
* @param string $slug 插件 slug
* @return string|null
*/
private function find_download_url( array $release, string $slug ): ?string {
// 优先查找 assets 中的 zip 文件
if ( ! empty( $release['assets'] ) ) {
foreach ( $release['assets'] as $asset ) {
$name = $asset['name'] ?? '';
// 匹配 slug.zip 或 slug-version.zip
if ( preg_match( '/\.zip$/i', $name ) ) {
if ( stripos( $name, $slug ) !== false ) {
return $asset['browser_download_url'] ?? null;
}
}
}
// 如果没有匹配 slug 的,返回第一个 zip
foreach ( $release['assets'] as $asset ) {
if ( preg_match( '/\.zip$/i', $asset['name'] ?? '' ) ) {
return $asset['browser_download_url'] ?? null;
}
}
}
// 使用 zipball_url 作为后备
return $release['zipball_url'] ?? null;
}
}