- phpcbf 自动修复 204 个格式问题(缩进、空格、换行等) - 添加 phpcs.xml 排除 WordPress hook 回调中未使用参数的误报 - CI 现在应该通过 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
243 lines
6.1 KiB
PHP
243 lines
6.1 KiB
PHP
<?php
|
||
/**
|
||
* WenPai 插件更新器
|
||
*
|
||
* 为文派系插件提供自建更新服务支持。
|
||
* 通过文派云桥 (WenPai Bridge) 检查插件更新,
|
||
* 利用 WordPress 5.8+ 的 Update URI 机制。
|
||
*
|
||
* 当 wp-china-yes 插件激活并启用集中更新时,
|
||
* 此更新器会通过 wenpai_updater_override filter 自动让位。
|
||
*
|
||
* @package WenPai
|
||
* @version 1.0.0
|
||
* @requires WordPress 5.8+
|
||
* @requires PHP 7.4+
|
||
* @link https://feicode.com/WenPai-org
|
||
*/
|
||
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
exit;
|
||
}
|
||
|
||
if ( class_exists( 'WenPai_Updater' ) ) {
|
||
return;
|
||
}
|
||
|
||
class WenPai_Updater {
|
||
|
||
/**
|
||
* 更新器版本号。
|
||
*
|
||
* @var string
|
||
*/
|
||
const VERSION = '1.0.0';
|
||
|
||
/**
|
||
* 云桥 API 地址。
|
||
*
|
||
* @var string
|
||
*/
|
||
const API_URL = 'https://updates.wenpai.net/api/v1';
|
||
|
||
/**
|
||
* 插件主文件 basename(如 wpslug/wpslug.php)。
|
||
*
|
||
* @var string
|
||
*/
|
||
private $plugin_file;
|
||
|
||
/**
|
||
* 插件 slug(如 wpslug)。
|
||
*
|
||
* @var string
|
||
*/
|
||
private $slug;
|
||
|
||
/**
|
||
* 当前插件版本。
|
||
*
|
||
* @var string
|
||
*/
|
||
private $version;
|
||
|
||
/**
|
||
* 初始化更新器。
|
||
*
|
||
* @param string $plugin_file 插件主文件路径(plugin_basename 格式)。
|
||
* @param string $version 当前插件版本号。
|
||
*/
|
||
public function __construct( string $plugin_file, string $version ) {
|
||
$this->plugin_file = $plugin_file;
|
||
$this->slug = dirname( $plugin_file );
|
||
$this->version = $version;
|
||
|
||
// 检查是否被 wp-china-yes 集中更新接管
|
||
$is_overridden = apply_filters(
|
||
'wenpai_updater_override',
|
||
false,
|
||
$this->slug
|
||
);
|
||
|
||
if ( ! $is_overridden ) {
|
||
$this->register_hooks();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 注册 WordPress hooks。
|
||
*/
|
||
private function register_hooks(): void {
|
||
// Update URI: https://updates.wenpai.net 触发此 filter
|
||
add_filter(
|
||
'update_plugins_updates.wenpai.net',
|
||
array( $this, 'check_update' ),
|
||
10,
|
||
4
|
||
);
|
||
|
||
// 插件详情弹窗
|
||
add_filter( 'plugins_api', array( $this, 'plugin_info' ), 20, 3 );
|
||
}
|
||
|
||
/**
|
||
* 检查插件更新。
|
||
*
|
||
* WordPress 在检查更新时,对声明了 Update URI 的插件
|
||
* 触发 update_plugins_{hostname} filter。
|
||
*
|
||
* @param array|false $update 当前更新数据。
|
||
* @param array $plugin_data 插件头信息。
|
||
* @param string $plugin_file 插件文件路径。
|
||
* @param string[] $locales 语言列表。
|
||
* @return object|false 更新数据或 false。
|
||
*/
|
||
public function check_update( $update, array $plugin_data, string $plugin_file, array $locales ) {
|
||
if ( $plugin_file !== $this->plugin_file ) {
|
||
return $update;
|
||
}
|
||
|
||
$response = $this->api_request(
|
||
'update-check',
|
||
array(
|
||
'plugins' => array(
|
||
$this->plugin_file => array(
|
||
'Version' => $this->version,
|
||
),
|
||
),
|
||
)
|
||
);
|
||
|
||
if ( is_wp_error( $response ) || empty( $response['plugins'][ $this->plugin_file ] ) ) {
|
||
return $update;
|
||
}
|
||
|
||
$data = $response['plugins'][ $this->plugin_file ];
|
||
|
||
return (object) array(
|
||
'id' => $data['id'] ?? '',
|
||
'slug' => $data['slug'] ?? $this->slug,
|
||
'plugin' => $this->plugin_file,
|
||
'version' => $data['version'] ?? '',
|
||
'new_version' => $data['version'] ?? '',
|
||
'url' => $data['url'] ?? '',
|
||
'package' => $data['package'] ?? '',
|
||
'icons' => $data['icons'] ?? array(),
|
||
'banners' => $data['banners'] ?? array(),
|
||
'requires' => $data['requires'] ?? '',
|
||
'tested' => $data['tested'] ?? '',
|
||
'requires_php' => $data['requires_php'] ?? '',
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 插件详情弹窗数据。
|
||
*
|
||
* 当用户在 WP 后台点击"查看详情"时触发。
|
||
*
|
||
* @param false|object|array $result 当前结果。
|
||
* @param string $action API 动作。
|
||
* @param object $args 请求参数。
|
||
* @return false|object 插件信息或 false。
|
||
*/
|
||
public function plugin_info( $result, string $action, object $args ) {
|
||
if ( 'plugin_information' !== $action ) {
|
||
return $result;
|
||
}
|
||
|
||
if ( ! isset( $args->slug ) || $args->slug !== $this->slug ) {
|
||
return $result;
|
||
}
|
||
|
||
$response = $this->api_request( "plugins/{$this->slug}/info" );
|
||
|
||
if ( is_wp_error( $response ) ) {
|
||
return $result;
|
||
}
|
||
|
||
$info = new stdClass();
|
||
$info->name = $response['name'] ?? '';
|
||
$info->slug = $response['slug'] ?? $this->slug;
|
||
$info->version = $response['version'] ?? '';
|
||
$info->author = $response['author'] ?? '';
|
||
$info->homepage = $response['homepage'] ?? '';
|
||
$info->download_link = $response['download_link'] ?? '';
|
||
$info->requires = $response['requires'] ?? '';
|
||
$info->tested = $response['tested'] ?? '';
|
||
$info->requires_php = $response['requires_php'] ?? '';
|
||
$info->last_updated = $response['last_updated'] ?? '';
|
||
$info->icons = $response['icons'] ?? array();
|
||
$info->banners = $response['banners'] ?? array();
|
||
$info->sections = $response['sections'] ?? array();
|
||
|
||
return $info;
|
||
}
|
||
|
||
/**
|
||
* 向云桥 API 发送请求。
|
||
*
|
||
* @param string $endpoint API 端点(不含 /api/v1/ 前缀)。
|
||
* @param array|null $body POST 请求体(null 则用 GET)。
|
||
* @return array|WP_Error 解码后的响应或错误。
|
||
*/
|
||
private function api_request( string $endpoint, ?array $body = null ) {
|
||
$url = self::API_URL . '/' . ltrim( $endpoint, '/' );
|
||
|
||
$args = array(
|
||
'timeout' => 10,
|
||
'headers' => array(
|
||
'Accept' => 'application/json',
|
||
),
|
||
);
|
||
|
||
if ( null !== $body ) {
|
||
$args['headers']['Content-Type'] = 'application/json';
|
||
$args['body'] = wp_json_encode( $body );
|
||
$response = wp_remote_post( $url, $args );
|
||
} else {
|
||
$response = wp_remote_get( $url, $args );
|
||
}
|
||
|
||
if ( is_wp_error( $response ) ) {
|
||
return $response;
|
||
}
|
||
|
||
$code = wp_remote_retrieve_response_code( $response );
|
||
if ( 200 !== $code ) {
|
||
return new WP_Error(
|
||
'wenpai_bridge_error',
|
||
sprintf( 'WenPai Bridge API returned %d', $code )
|
||
);
|
||
}
|
||
|
||
$data = json_decode( wp_remote_retrieve_body( $response ), true );
|
||
if ( ! is_array( $data ) ) {
|
||
return new WP_Error(
|
||
'wenpai_bridge_parse_error',
|
||
'Invalid JSON response from WenPai Bridge'
|
||
);
|
||
}
|
||
|
||
return $data;
|
||
}
|
||
}
|