wpavatar/wpavatar.php
feibisi 26b98fa13a Bump version to 1.9.4 and improve URL escaping
Updated plugin version to 1.9.4 in main files and readme. Enhanced security by adding proper escaping to all URL outputs in the admin interface to address an XSS vulnerability. Updated changelog and stable tag in readme to reflect these changes.
2025-09-14 01:05:21 +08:00

487 lines
17 KiB
PHP

<?php
/**
* Plugin Name: WPAvatar
* Version: 1.9.4
* Plugin URI: https://wpavatar.com/download
* Description: Replace Gravatar with Cravatar, a perfect replacement of Gravatar in China.
* Author: WPfanyi
* Author URI: https://wpfanyi.com/
* Text Domain: wpavatar
* Domain Path: /languages
* Network: true
*/
defined('ABSPATH') || exit;
define('WPAVATAR_VERSION', '1.9.4');
define('WPAVATAR_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPAVATAR_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WPAVATAR_CACHE_DIR', WP_CONTENT_DIR . '/uploads/cravatar');
// Create necessary plugin directories if they don't exist
if (!file_exists(WPAVATAR_PLUGIN_DIR . 'assets')) {
wp_mkdir_p(WPAVATAR_PLUGIN_DIR . 'assets');
}
if (!file_exists(WPAVATAR_PLUGIN_DIR . 'assets/css')) {
wp_mkdir_p(WPAVATAR_PLUGIN_DIR . 'assets/css');
$css_file = WPAVATAR_PLUGIN_DIR . 'admin.css';
if (file_exists($css_file)) {
copy($css_file, WPAVATAR_PLUGIN_DIR . 'assets/css/admin.css');
}
}
if (!file_exists(WPAVATAR_PLUGIN_DIR . 'assets/js')) {
wp_mkdir_p(WPAVATAR_PLUGIN_DIR . 'assets/js');
$js_file = WPAVATAR_PLUGIN_DIR . 'admin.js';
if (file_exists($js_file)) {
copy($js_file, WPAVATAR_PLUGIN_DIR . 'assets/js/admin.js');
}
}
if (!file_exists(WPAVATAR_PLUGIN_DIR . 'assets/images')) {
wp_mkdir_p(WPAVATAR_PLUGIN_DIR . 'assets/images');
}
// 检查必要的插件目录是否存在
if (!file_exists(WPAVATAR_PLUGIN_DIR . 'includes')) {
wp_mkdir_p(WPAVATAR_PLUGIN_DIR . 'includes');
if (file_exists(WPAVATAR_PLUGIN_DIR . 'core.php')) {
copy(WPAVATAR_PLUGIN_DIR . 'core.php', WPAVATAR_PLUGIN_DIR . 'includes/core.php');
}
if (file_exists(WPAVATAR_PLUGIN_DIR . 'admin.php')) {
copy(WPAVATAR_PLUGIN_DIR . 'admin.php', WPAVATAR_PLUGIN_DIR . 'includes/admin.php');
}
// 创建营销组件文件
if (file_exists(WPAVATAR_PLUGIN_DIR . 'marketing.php')) {
$marketing_content = file_get_contents(WPAVATAR_PLUGIN_DIR . 'marketing.php');
file_put_contents(WPAVATAR_PLUGIN_DIR . 'includes/marketing.php', $marketing_content);
}
}
// 包含核心文件
require_once WPAVATAR_PLUGIN_DIR . 'includes/core.php';
require_once WPAVATAR_PLUGIN_DIR . 'includes/admin.php';
require_once WPAVATAR_PLUGIN_DIR . 'includes/multisite.php';
require_once WPAVATAR_PLUGIN_DIR . 'includes/marketing.php';
require_once WPAVATAR_PLUGIN_DIR . 'includes/wpcy-compatibility.php';
// Register AJAX actions
add_action('wp_ajax_wpavatar_purge_cache', 'wpavatar_purge_cache_ajax');
function wpavatar_purge_cache_ajax() {
check_ajax_referer('wpavatar_admin_nonce', 'nonce');
// Check user capability
if (!current_user_can('manage_options')) {
wp_send_json_error(__('权限不足', 'wpavatar'));
return;
}
// Get cache directory path
$dir = wpavatar_get_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR);
// Add blog-specific directory if multisite
if (is_multisite()) {
$blog_id = get_current_blog_id();
$dir = trailingslashit($dir) . 'site-' . $blog_id . '/';
}
$dir = rtrim($dir, '/\\') . '/';
if (!file_exists($dir) || !is_dir($dir)) {
wp_send_json_error(__('缓存目录不存在', 'wpavatar'));
return;
}
$files = glob($dir . '*.jpg');
$count = 0;
if ($files) {
foreach ($files as $file) {
if (strpos($file, $dir) === 0 && file_exists($file)) {
if (@unlink($file)) {
$count++;
}
}
}
}
wp_send_json_success(sprintf(__('已清空 %d 个缓存文件', 'wpavatar'), $count));
}
add_action('wp_ajax_wpavatar_purge_all_cache', 'wpavatar_purge_all_cache_ajax');
function wpavatar_purge_all_cache_ajax() {
check_ajax_referer('wpavatar_admin_nonce', 'nonce');
// Check user capability for network admin
if (!current_user_can('manage_network_options')) {
wp_send_json_error(__('权限不足', 'wpavatar'));
return;
}
// Get base cache directory path
$base_dir = wpavatar_get_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR);
$base_dir = rtrim($base_dir, '/\\') . '/';
if (!file_exists($base_dir) || !is_dir($base_dir)) {
wp_send_json_error(__('缓存目录不存在', 'wpavatar'));
return;
}
$count = 0;
// Get all site cache directories
$site_dirs = glob($base_dir . 'site-*', GLOB_ONLYDIR);
if ($site_dirs) {
foreach ($site_dirs as $site_dir) {
$files = glob($site_dir . '/*.jpg');
if ($files) {
foreach ($files as $file) {
if (file_exists($file)) {
if (@unlink($file)) {
$count++;
}
}
}
}
}
}
// Also check legacy non-site specific files
$legacy_files = glob($base_dir . '*.jpg');
if ($legacy_files) {
foreach ($legacy_files as $file) {
if (file_exists($file)) {
if (@unlink($file)) {
$count++;
}
}
}
}
wp_send_json_success(sprintf(__('已清空所有站点的 %d 个缓存文件', 'wpavatar'), $count));
}
add_action('wp_ajax_wpavatar_check_cache', 'wpavatar_check_cache_ajax');
function wpavatar_check_cache_ajax() {
check_ajax_referer('wpavatar_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(__('权限不足', 'wpavatar'));
return;
}
$result = \WPAvatar\Cache::check_cache_status();
wp_send_json_success($result);
}
add_action('wp_ajax_wpavatar_check_all_cache', 'wpavatar_check_all_cache_ajax');
function wpavatar_check_all_cache_ajax() {
check_ajax_referer('wpavatar_admin_nonce', 'nonce');
if (!current_user_can('manage_network_options')) {
wp_send_json_error(__('权限不足', 'wpavatar'));
return;
}
$result = \WPAvatar\Cache::check_all_cache_status();
wp_send_json_success($result);
}
add_action('plugins_loaded', function () {
// Load text domain for translations
load_plugin_textdomain('wpavatar', false, dirname(plugin_basename(__FILE__)) . '/languages');
// Create default avatar if it doesn't exist
$default_avatar = WPAVATAR_PLUGIN_DIR . 'assets/images/default-avatar.png';
if (!file_exists($default_avatar)) {
$placeholder = WPAVATAR_PLUGIN_DIR . 'assets/images/placeholder-avatar.png';
if (file_exists($placeholder)) {
copy($placeholder, $default_avatar);
} else {
$avatar_image = imagecreatetruecolor(96, 96);
$bg_color = imagecolorallocate($avatar_image, 238, 238, 238);
$text_color = imagecolorallocate($avatar_image, 68, 68, 68);
imagefill($avatar_image, 0, 0, $bg_color);
imagestring($avatar_image, 5, 30, 40, 'Avatar', $text_color);
wp_mkdir_p(dirname($default_avatar));
imagepng($avatar_image, $default_avatar);
imagedestroy($avatar_image);
}
}
// Fetch Cravatar logo if it doesn't exist
$cravatar_logo = WPAVATAR_PLUGIN_DIR . 'assets/images/cravatar-logo.png';
if (!file_exists($cravatar_logo) && function_exists('file_get_contents')) {
$logo_url = 'https://cn.cravatar.com/avatar/00000000000000000000000000000000?d=cravatar_logo';
$logo_data = @file_get_contents($logo_url);
if ($logo_data) {
wp_mkdir_p(dirname($cravatar_logo));
@file_put_contents($cravatar_logo, $logo_data);
}
}
// Initialize multisite network support first
if (is_multisite()) {
\WPAvatar\Network::init();
}
// Initialize compatibility layer
\WPAvatar\Compatibility::init();
// Initialize core components
\WPAvatar\Core::init();
\WPAvatar\Cravatar::init();
\WPAvatar\Cache::init();
\WPAvatar\Shortcode::init();
// Initialize Marketing component
\WPAvatar\Marketing::init();
// Initialize admin settings (will be conditionally disabled in multisite if network managed)
if (is_admin()) {
// Check if we should initialize settings in site admin
$should_init_settings = true;
if (is_multisite() && get_site_option('wpavatar_network_enforce', 0) && !is_network_admin()) {
$should_init_settings = false;
}
if ($should_init_settings) {
\WPAvatar\Settings::init();
}
}
// Filter text to replace Gravatar references with Cravatar
add_filter('gettext', 'wpavatar_replace_gravatar_text', 20, 3);
add_filter('ngettext', 'wpavatar_replace_gravatar_text_plural', 20, 4);
});
/**
* Replace Gravatar text with Cravatar in translations
*
* @param string $translated_text Translated text
* @param string $text Original text
* @param string $domain Text domain
* @return string Modified translated text
*/
function wpavatar_replace_gravatar_text($translated_text, $text, $domain) {
// Get enable_cravatar setting
$enable_cravatar = wpavatar_get_option('wpavatar_enable_cravatar', 1);
if (!$enable_cravatar) {
return $translated_text;
}
$current_screen = function_exists('get_current_screen') ? get_current_screen() : null;
$screen_id = $current_screen ? $current_screen->id : '';
$is_discussion_page = ($screen_id === 'options-discussion' ||
(isset($_GET['page']) && $_GET['page'] === 'discussion'));
$is_comments_page = ($screen_id === 'edit-comments' ||
$screen_id === 'comment');
$is_profile_page = ($screen_id === 'profile' ||
$screen_id === 'user-edit');
if ($is_discussion_page || $is_comments_page || $is_profile_page) {
$translated_text = str_replace('Gravatar', 'Cravatar', $translated_text);
$translated_text = str_replace('gravatar', 'cravatar', $translated_text);
}
return $translated_text;
}
/**
* Replace Gravatar text with Cravatar in plural translations
*
* @param string $translated_text Translated text
* @param string $single Singular text
* @param string $plural Plural text
* @param int $number Number for plural form
* @return string Modified translated text
*/
function wpavatar_replace_gravatar_text_plural($translated_text, $single, $plural, $number) {
// Get enable_cravatar setting
$enable_cravatar = wpavatar_get_option('wpavatar_enable_cravatar', 1);
if (!$enable_cravatar) {
return $translated_text;
}
$current_screen = function_exists('get_current_screen') ? get_current_screen() : null;
$screen_id = $current_screen ? $current_screen->id : '';
$is_relevant_page = ($screen_id === 'options-discussion' ||
$screen_id === 'edit-comments' ||
$screen_id === 'comment' ||
$screen_id === 'profile' ||
$screen_id === 'user-edit' ||
(isset($_GET['page']) && $_GET['page'] === 'discussion'));
if ($is_relevant_page) {
$translated_text = str_replace('Gravatar', 'Cravatar', $translated_text);
$translated_text = str_replace('gravatar', 'cravatar', $translated_text);
}
return $translated_text;
}
/**
* Helper function to get the correct option based on multisite status
*
* @param string $option_name Option name
* @param mixed $default Default value
* @return mixed Option value
*/
function wpavatar_get_option($option_name, $default = false) {
if (is_multisite()) {
// Check if network settings are enabled
if (get_site_option('wpavatar_network_enabled', 1)) {
// Check if this option is controlled by network
$network_controlled_options = get_site_option('wpavatar_network_controlled_options', array());
// Convert to array if it's a string (for backward compatibility)
if (!is_array($network_controlled_options)) {
$network_controlled_options = explode(',', $network_controlled_options);
}
// If this option is controlled by network or network enforces all settings
if (in_array($option_name, $network_controlled_options) || get_site_option('wpavatar_network_enforce', 0)) {
return get_site_option($option_name, $default);
}
}
}
// Default to site option
return get_option($option_name, $default);
}
// Register activation hook
register_activation_hook(__FILE__, function() {
// Set default options for single site
add_option('wpavatar_enable_cravatar', 1);
add_option('wpavatar_cdn_type', 'cravatar_route');
add_option('wpavatar_cravatar_route', 'cravatar.com');
add_option('wpavatar_third_party_mirror', 'weavatar.com');
add_option('wpavatar_custom_cdn', '');
add_option('wpavatar_hash_method', 'md5');
add_option('wpavatar_timeout', 5);
add_option('wpavatar_enable_cache', 1);
add_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR);
add_option('wpavatar_cache_expire', 15);
add_option('wpavatar_seo_alt', '%s的头像');
add_option('wpavatar_fallback_mode', 1);
add_option('wpavatar_fallback_avatar', 'default');
add_option('wpavatar_shortcode_size', 96);
add_option('wpavatar_shortcode_class', 'wpavatar');
add_option('wpavatar_shortcode_shape', 'square');
// 添加营销组件默认设置
add_option('wpavatar_commenters_count', 15);
add_option('wpavatar_commenters_size', 45);
add_option('wpavatar_users_count', 15);
add_option('wpavatar_users_size', 40);
// Create cache directory
wp_mkdir_p(WPAVATAR_CACHE_DIR);
// Create index.php to prevent directory listing
$index_file = rtrim(WPAVATAR_CACHE_DIR, '/\\') . '/index.php';
if (!file_exists($index_file)) {
@file_put_contents($index_file, '<?php // Silence is golden.');
}
// Create .htaccess to configure cache
$htaccess_file = rtrim(WPAVATAR_CACHE_DIR, '/\\') . '/.htaccess';
if (!file_exists($htaccess_file)) {
$htaccess_content = "# Prevent directory listing\n";
$htaccess_content .= "Options -Indexes\n";
$htaccess_content .= "# Cache images for one week\n";
$htaccess_content .= "<IfModule mod_expires.c>\n";
$htaccess_content .= "ExpiresActive On\n";
$htaccess_content .= "ExpiresByType image/jpeg \"access plus 1 week\"\n";
$htaccess_content .= "</IfModule>\n";
@file_put_contents($htaccess_file, $htaccess_content);
}
// Set default options for multisite
if (is_multisite()) {
// Network settings
add_site_option('wpavatar_network_enabled', 1);
add_site_option('wpavatar_network_enforce', 0);
// Define default network controlled options
$default_controlled = array(
'wpavatar_enable_cravatar',
'wpavatar_cdn_type',
'wpavatar_cravatar_route',
'wpavatar_third_party_mirror',
'wpavatar_custom_cdn'
);
add_site_option('wpavatar_network_controlled_options', $default_controlled);
// Copy regular options to network options
foreach ([
'wpavatar_enable_cravatar',
'wpavatar_cdn_type',
'wpavatar_cravatar_route',
'wpavatar_third_party_mirror',
'wpavatar_custom_cdn',
'wpavatar_hash_method',
'wpavatar_timeout',
'wpavatar_enable_cache',
'wpavatar_cache_path',
'wpavatar_cache_expire',
'wpavatar_seo_alt',
'wpavatar_fallback_mode',
'wpavatar_fallback_avatar',
'wpavatar_shortcode_size',
'wpavatar_shortcode_class',
'wpavatar_shortcode_shape',
// 营销组件设置
'wpavatar_commenters_count',
'wpavatar_commenters_size',
'wpavatar_users_count',
'wpavatar_users_size'
] as $option_name) {
add_site_option($option_name, get_option($option_name));
}
// If current site is not the main site, create site-specific cache dir
if (!is_main_site()) {
$blog_id = get_current_blog_id();
$cache_dir = trailingslashit(WPAVATAR_CACHE_DIR) . 'site-' . $blog_id;
wp_mkdir_p($cache_dir);
$index_file = $cache_dir . '/index.php';
if (!file_exists($index_file)) {
@file_put_contents($index_file, '<?php // Silence is golden.');
}
}
}
// Schedule daily cache purge
if (!wp_next_scheduled('wpavatar_purge_cache')) {
wp_schedule_event(time(), 'daily', 'wpavatar_purge_cache');
}
});
// Register deactivation hook
register_deactivation_hook(__FILE__, function() {
// Clear scheduled events
wp_clear_scheduled_hook('wpavatar_purge_cache');
});