wpbridge/includes/Commercial/Vendors/AbstractVendor.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

324 lines
7.5 KiB
PHP

<?php
/**
* 供应商抽象基类
*
* 提供供应商通用功能实现
*
* @package WPBridge
* @since 0.9.7
*/
declare(strict_types=1);
namespace WPBridge\Commercial\Vendors;
use WPBridge\Core\Logger;
// 防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* AbstractVendor 抽象类
*/
abstract class AbstractVendor implements VendorInterface {
/**
* 供应商配置
*
* @var array
*/
protected array $config = [];
/**
* 缓存前缀
*
* @var string
*/
protected string $cache_prefix = 'wpbridge_vendor_';
/**
* 缓存时间(秒)
*
* @var int
*/
protected int $cache_ttl = 86400;
/**
* 构造函数
*
* @param array $config 配置
*/
public function __construct( array $config = [] ) {
$this->config = array_merge( $this->get_default_config(), $config );
}
/**
* 获取默认配置
*
* @return array
*/
protected function get_default_config(): array {
return [
'api_url' => '',
'api_key' => '',
'api_secret' => '',
'timeout' => 15,
'enabled' => true,
];
}
/**
* 检查供应商是否可用
*
* @return bool
*/
public function is_available(): bool {
if ( empty( $this->config['enabled'] ) ) {
return false;
}
if ( empty( $this->config['api_url'] ) ) {
return false;
}
return $this->verify_credentials();
}
/**
* 发送 API 请求
*
* @param string $endpoint 端点
* @param array $params 参数
* @param string $method 方法 (GET/POST)
* @return array|null
*/
/**
* 不应出现在 URL query 中的敏感参数名
*/
private const SENSITIVE_PARAMS = [ 'api_key', 'api_secret', 'consumer_key', 'consumer_secret', 'license_key', 'token', 'access_token' ];
protected function api_request( string $endpoint, array $params = [], string $method = 'GET' ): ?array {
$api_url = $this->config['api_url'] ?? '';
// H3/H4: 强制 HTTPS
if ( ! empty( $api_url ) && strpos( $api_url, 'http://' ) === 0 ) {
$api_url = 'https://' . substr( $api_url, 7 );
}
$url = trailingslashit( $api_url ) . ltrim( $endpoint, '/' );
$args = [
'timeout' => $this->config['timeout'],
'headers' => $this->get_request_headers(),
'sslverify' => true,
];
if ( $method === 'GET' && ! empty( $params ) ) {
// H2: 敏感参数不通过 URL 传输,移到 headers
$url_params = [];
foreach ( $params as $key => $value ) {
if ( in_array( $key, self::SENSITIVE_PARAMS, true ) ) {
$args['headers'][ 'X-' . str_replace( '_', '-', ucwords( $key, '_' ) ) ] = $value;
} else {
$url_params[ $key ] = $value;
}
}
if ( ! empty( $url_params ) ) {
$url = add_query_arg( $url_params, $url );
}
} elseif ( $method === 'POST' ) {
$args['body'] = $params;
}
$response = $method === 'GET'
? wp_remote_get( $url, $args )
: wp_remote_post( $url, $args );
if ( is_wp_error( $response ) ) {
Logger::error(
'Vendor API request failed',
[
'vendor' => $this->get_id(),
'endpoint' => $endpoint,
'error' => $response->get_error_message(),
]
);
return null;
}
$code = wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body( $response );
if ( $code !== 200 ) {
Logger::warning(
'Vendor API non-200 response',
[
'vendor' => $this->get_id(),
'endpoint' => $endpoint,
'code' => $code,
]
);
return null;
}
$data = json_decode( $body, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
Logger::error(
'Vendor API invalid JSON',
[
'vendor' => $this->get_id(),
'endpoint' => $endpoint,
]
);
return null;
}
return $data;
}
/**
* 获取请求头
*
* @return array
*/
protected function get_request_headers(): array {
return [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
];
}
/**
* 获取缓存
*
* @param string $key 缓存键
* @return mixed|null
*/
protected function get_cache( string $key ) {
$cache_key = $this->cache_prefix . $this->get_id() . '_' . md5( $key );
$cached = get_transient( $cache_key );
return $cached !== false ? $cached : null;
}
/**
* 设置缓存
*
* @param string $key 缓存键
* @param mixed $value 缓存值
* @param int $ttl 过期时间(秒)
*/
protected function set_cache( string $key, $value, int $ttl = 0 ): void {
$cache_key = $this->cache_prefix . $this->get_id() . '_' . md5( $key );
set_transient( $cache_key, $value, $ttl ?: $this->cache_ttl );
}
/**
* 清除缓存
*
* @param string $key 缓存键(可选,为空则清除所有)
*/
protected function clear_cache( string $key = '' ): void {
if ( ! empty( $key ) ) {
$cache_key = $this->cache_prefix . $this->get_id() . '_' . md5( $key );
delete_transient( $cache_key );
}
}
/**
* 清除该供应商的所有 transient 缓存
*
* @return int 删除条数
*/
public function clear_all_cache(): int {
global $wpdb;
$prefix = $this->cache_prefix . $this->get_id() . '_';
// WordPress transient 在 options 表中以 _transient_ 前缀存储
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$count = (int) $wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
'_transient_' . $wpdb->esc_like( $prefix ) . '%',
'_transient_timeout_' . $wpdb->esc_like( $prefix ) . '%'
)
);
return intdiv( $count, 2 );
}
/**
* 搜索插件(默认实现:从列表中过滤)
*
* @param string $keyword 关键词
* @return array
*/
public function search_plugins( string $keyword ): array {
$all_plugins = $this->get_plugins( 1, 1000 );
$keyword = strtolower( $keyword );
$results = [];
foreach ( $all_plugins['plugins'] as $plugin ) {
$name = strtolower( $plugin['name'] ?? '' );
$slug = strtolower( $plugin['slug'] ?? '' );
if ( strpos( $name, $keyword ) !== false || strpos( $slug, $keyword ) !== false ) {
$results[] = $plugin;
}
}
return $results;
}
/**
* 获取插件详情(默认实现:从列表中查找)
*
* @param string $slug 插件 slug
* @return array|null
*/
public function get_plugin( string $slug ): ?array {
// 先检查缓存
$cache_key = 'plugin_' . $slug;
$cached = $this->get_cache( $cache_key );
if ( $cached !== null ) {
return $cached;
}
// 从列表中查找
$all_plugins = $this->get_plugins( 1, 1000 );
foreach ( $all_plugins['plugins'] as $plugin ) {
if ( ( $plugin['slug'] ?? '' ) === $slug ) {
$this->set_cache( $cache_key, $plugin );
return $plugin;
}
}
return null;
}
/**
* 标准化插件数据
*
* @param array $raw_plugin 原始插件数据
* @return array
*/
protected function normalize_plugin( array $raw_plugin ): array {
return [
'slug' => $raw_plugin['slug'] ?? '',
'name' => $raw_plugin['name'] ?? $raw_plugin['title'] ?? '',
'version' => $raw_plugin['version'] ?? '',
'author' => $raw_plugin['author'] ?? '',
'description' => $raw_plugin['description'] ?? $raw_plugin['excerpt'] ?? '',
'homepage' => $raw_plugin['homepage'] ?? $raw_plugin['url'] ?? '',
'download_url' => $raw_plugin['download_url'] ?? $raw_plugin['download_link'] ?? '',
'tested' => $raw_plugin['tested'] ?? '',
'requires' => $raw_plugin['requires'] ?? $raw_plugin['requires_at_least'] ?? '',
'requires_php' => $raw_plugin['requires_php'] ?? '',
'last_updated' => $raw_plugin['last_updated'] ?? $raw_plugin['modified'] ?? '',
'vendor' => $this->get_id(),
];
}
}