wpbridge/includes/Commercial/GPLValidator.php
feibisi 39b30af242 feat(commercial): add vendor system for third-party GPL plugin sources
- Add VendorInterface, AbstractVendor, VendorManager for vendor abstraction
- Add WooCommerceVendor for WooCommerce API Manager integration
- Add BridgeManager with hybrid mode (official + vendor + custom sources)
- Add GPLValidator for GPL compliance checking
- Add LicenseProxy for license key management
- Add VendorAdmin with AJAX handlers for vendor CRUD operations
- Add vendors.php admin tab with stats, vendor list, custom plugins
- Add vendor management CSS styles and JS module
- Update main.php to include vendors tab

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-15 08:39:37 +08:00

359 lines
7.9 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
/**
* GPL 合规验证器
*
* 自动检测插件是否为 GPL 兼容授权
*
* @package WPBridge
* @since 0.9.7
*/
declare(strict_types=1);
namespace WPBridge\Commercial;
use WPBridge\Core\Logger;
// 防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* GPLValidator 类
*/
class GPLValidator {
/**
* GPL 兼容的授权标识
*/
private const GPL_COMPATIBLE_LICENSES = [
'gpl',
'gpl-2.0',
'gpl-2.0+',
'gpl-2.0-or-later',
'gpl-3.0',
'gpl-3.0+',
'gpl-3.0-or-later',
'gplv2',
'gplv3',
'gnu general public license',
'gnu gpl',
'lgpl',
'lgpl-2.1',
'lgpl-3.0',
'mit',
'apache-2.0',
'bsd',
];
/**
* 已知的 GPL 商业插件列表
*/
private const KNOWN_GPL_PLUGINS = [
'elementor-pro',
'wordpress-seo-premium',
'advanced-custom-fields-pro',
'gravityforms',
'wpforms',
'wpforms-lite',
'ninja-forms',
'seo-by-rank-math-pro',
'wp-rocket',
'perfmatters',
'flavor',
'updraftplus',
'updraftplus-premium',
'memberpress',
'learndash',
'woocommerce-subscriptions',
'woocommerce-memberships',
];
/**
* 已知的非 GPL 插件列表(不应桥接)
*/
private const NON_GPL_PLUGINS = [
// Envato 独占插件通常不是 GPL
];
/**
* 验证结果缓存
*
* @var array
*/
private array $cache = [];
/**
* 验证插件是否 GPL 兼容
*
* @param string $plugin_slug 插件 slug
* @param string $plugin_file 插件文件路径(可选)
* @return array 包含 is_gpl, confidence, source 的数组
*/
public function validate( string $plugin_slug, string $plugin_file = '' ): array {
// 检查缓存
if ( isset( $this->cache[ $plugin_slug ] ) ) {
return $this->cache[ $plugin_slug ];
}
$result = $this->do_validate( $plugin_slug, $plugin_file );
// 缓存结果
$this->cache[ $plugin_slug ] = $result;
return $result;
}
/**
* 执行验证
*
* @param string $plugin_slug 插件 slug
* @param string $plugin_file 插件文件路径
* @return array
*/
private function do_validate( string $plugin_slug, string $plugin_file ): array {
// 1. 检查已知列表
if ( in_array( $plugin_slug, self::KNOWN_GPL_PLUGINS, true ) ) {
return [
'is_gpl' => true,
'confidence' => 100,
'source' => 'known_list',
'license' => 'GPL-2.0+',
];
}
if ( in_array( $plugin_slug, self::NON_GPL_PLUGINS, true ) ) {
return [
'is_gpl' => false,
'confidence' => 100,
'source' => 'known_list',
'license' => 'proprietary',
];
}
// 2. 检查 WordPress.org如果存在则一定是 GPL
$wporg_result = $this->check_wordpress_org( $plugin_slug );
if ( $wporg_result !== null ) {
return [
'is_gpl' => true,
'confidence' => 100,
'source' => 'wordpress_org',
'license' => $wporg_result['license'] ?? 'GPL-2.0+',
];
}
// 3. 检查插件文件
if ( ! empty( $plugin_file ) ) {
$file_result = $this->check_plugin_file( $plugin_file );
if ( $file_result !== null ) {
return $file_result;
}
}
// 4. 无法确定
return [
'is_gpl' => null,
'confidence' => 0,
'source' => 'unknown',
'license' => 'unknown',
];
}
/**
* 检查 WordPress.org API
*
* @param string $plugin_slug 插件 slug
* @return array|null
*/
private function check_wordpress_org( string $plugin_slug ): ?array {
$cache_key = 'wpbridge_wporg_gpl_' . md5( $plugin_slug );
$cached = get_transient( $cache_key );
if ( $cached !== false ) {
return $cached === 'not_found' ? null : $cached;
}
$url = 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=' . urlencode( $plugin_slug );
$response = wp_remote_get( $url, [ 'timeout' => 5 ] );
if ( is_wp_error( $response ) ) {
return null;
}
$code = wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body( $response );
if ( $code === 200 && ! empty( $body ) ) {
$data = json_decode( $body, true );
if ( isset( $data['slug'] ) ) {
$result = [
'license' => 'GPL-2.0+', // WordPress.org 要求 GPL
'name' => $data['name'] ?? '',
];
set_transient( $cache_key, $result, DAY_IN_SECONDS );
return $result;
}
}
set_transient( $cache_key, 'not_found', HOUR_IN_SECONDS );
return null;
}
/**
* 检查插件文件中的授权信息
*
* @param string $plugin_file 插件文件路径
* @return array|null
*/
private function check_plugin_file( string $plugin_file ): ?array {
$plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file;
if ( ! file_exists( $plugin_path ) ) {
return null;
}
// 读取插件头部
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_data = get_plugin_data( $plugin_path, false, false );
$license = $plugin_data['License'] ?? '';
if ( ! empty( $license ) ) {
$is_gpl = $this->is_gpl_compatible_license( $license );
if ( $is_gpl !== null ) {
return [
'is_gpl' => $is_gpl,
'confidence' => 90,
'source' => 'plugin_header',
'license' => $license,
];
}
}
// 检查 license.txt
$plugin_dir = dirname( $plugin_path );
$license_file = $plugin_dir . '/license.txt';
if ( file_exists( $license_file ) ) {
$license_content = file_get_contents( $license_file );
if ( $this->contains_gpl_text( $license_content ) ) {
return [
'is_gpl' => true,
'confidence' => 85,
'source' => 'license_file',
'license' => 'GPL (from license.txt)',
];
}
}
// 检查 readme.txt
$readme_file = $plugin_dir . '/readme.txt';
if ( file_exists( $readme_file ) ) {
$readme_content = file_get_contents( $readme_file );
if ( preg_match( '/License:\s*(.+)/i', $readme_content, $matches ) ) {
$license = trim( $matches[1] );
$is_gpl = $this->is_gpl_compatible_license( $license );
if ( $is_gpl !== null ) {
return [
'is_gpl' => $is_gpl,
'confidence' => 80,
'source' => 'readme_file',
'license' => $license,
];
}
}
}
return null;
}
/**
* 检查授权字符串是否 GPL 兼容
*
* @param string $license 授权字符串
* @return bool|null
*/
private function is_gpl_compatible_license( string $license ): ?bool {
$license_lower = strtolower( trim( $license ) );
foreach ( self::GPL_COMPATIBLE_LICENSES as $gpl_license ) {
if ( strpos( $license_lower, $gpl_license ) !== false ) {
return true;
}
}
// 检查明确的非 GPL 标识
$non_gpl_indicators = [ 'proprietary', 'commercial', 'all rights reserved', 'envato' ];
foreach ( $non_gpl_indicators as $indicator ) {
if ( strpos( $license_lower, $indicator ) !== false ) {
return false;
}
}
return null;
}
/**
* 检查文本是否包含 GPL 授权内容
*
* @param string $content 文本内容
* @return bool
*/
private function contains_gpl_text( string $content ): bool {
$gpl_indicators = [
'GNU General Public License',
'GPL version 2',
'GPL version 3',
'GPLv2',
'GPLv3',
'free software',
'redistribute it and/or modify',
];
foreach ( $gpl_indicators as $indicator ) {
if ( stripos( $content, $indicator ) !== false ) {
return true;
}
}
return false;
}
/**
* 批量验证
*
* @param array $plugins 插件列表 [ slug => file ]
* @return array
*/
public function validate_batch( array $plugins ): array {
$results = [];
foreach ( $plugins as $slug => $file ) {
$results[ $slug ] = $this->validate( $slug, $file );
}
return $results;
}
/**
* 清除缓存
*/
public function clear_cache(): void {
$this->cache = [];
}
/**
* 添加到已知 GPL 列表(运行时)
*
* @param string $plugin_slug 插件 slug
*/
public function add_known_gpl( string $plugin_slug ): void {
$this->cache[ $plugin_slug ] = [
'is_gpl' => true,
'confidence' => 100,
'source' => 'manual',
'license' => 'GPL (manually verified)',
];
}
}