diff --git a/assets/css/admin.css b/assets/css/admin.css
new file mode 100644
index 0000000..fb01efc
--- /dev/null
+++ b/assets/css/admin.css
@@ -0,0 +1,341 @@
+
+.wpavatar-settings .form-table th {
+ width: 220px;
+}
+
+.wpavatar-settings .description {
+ font-size: 13px;
+ margin: 2px 0 5px;
+ color: #666;
+}
+
+.wpavatar-tabs-wrapper {
+ margin-bottom: 20px;
+}
+
+.wpavatar-sync-tabs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+ border-bottom: 1px solid #c3c4c7;
+ margin-bottom: 20px;
+}
+
+.wpavatar-tab {
+ padding: 8px 16px;
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-size: 14px;
+ border-bottom: 2px solid transparent;
+}
+
+.wpavatar-tab.active {
+ border-bottom: 2px solid #007cba;
+ font-weight: 600;
+ background: #f0f0f1;
+}
+
+.wpavatar-tab:hover:not(.active) {
+ background: #f0f0f1;
+ border-bottom-color: #dcdcde;
+}
+
+.wpavatar-card {
+ background: #fff;
+ border: 1px solid #ccd0d4;
+ border-radius: 4px;
+ margin-top: 20px;
+ padding: 20px;
+}
+
+.wpavatar-card h2 {
+ margin-top: 0;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #f0f0f1;
+}
+
+.wpavatar-section-desc {
+ color: #666;
+ margin-bottom: 20px;
+}
+
+.wpavatar-stats-card {
+ background: #f9f9f9;
+ border-radius: 4px;
+ padding: 15px;
+ margin-bottom: 20px;
+}
+
+.wpavatar-stats-card h3 {
+ margin-top: 0;
+ margin-bottom: 15px;
+}
+
+.cache-stats-wrapper {
+ margin-top: 15px;
+ border-left: 4px solid #2271b1;
+ background: #f0f6fc;
+ padding: 12px;
+ margin-bottom: 15px;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
+}
+
+.cache-stats p {
+ margin: 0.5em 0;
+}
+
+.cache-stats .error {
+ background: #ffebee;
+ padding: 10px;
+ border-left: 4px solid #d32f2f;
+}
+
+.wpavatar-action-buttons {
+ display: flex;
+ gap: 10px;
+ margin-top: 15px;
+}
+
+.wpavatar-submit-wrapper {
+ margin-top: 20px;
+ padding-top: 15px;
+ border-top: 1px solid #f0f0f1;
+}
+
+.wpavatar-switch {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ cursor: pointer;
+}
+
+.wpavatar-switch input {
+ opacity: 0;
+ width: 0px;
+ height: 0;
+ min-width: 0;
+ margin: -.25rem -0.15rem 0 0;
+}
+
+.wpavatar-slider {
+ position: relative;
+ display: inline-block;
+ width: 40px;
+ height: 20px;
+ background-color: #ccc;
+ border-radius: 34px;
+ transition: .4s;
+}
+
+.wpavatar-slider:before {
+ position: absolute;
+ content: "";
+ height: 16px;
+ width: 16px;
+ left: 2px;
+ bottom: 2px;
+ background-color: white;
+ border-radius: 50%;
+ transition: .4s;
+}
+
+.wpavatar-switch input:checked + .wpavatar-slider {
+ background-color: #2196F3;
+}
+
+.wpavatar-switch input:focus + .wpavatar-slider {
+ box-shadow: 0 0 1px #2196F3;
+}
+
+.wpavatar-switch input:checked + .wpavatar-slider:before {
+ transform: translateX(20px);
+}
+
+.wpavatar-switch-label {
+ margin-left: 10px;
+}
+
+.wpavatar-radio {
+ display: inline-flex;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.wpavatar-radio-label {
+ margin-left: 8px;
+}
+
+.wpavatar-input {
+ border: 1px solid #ddd;
+ padding: 6px 10px;
+ border-radius: 4px;
+}
+
+.wpavatar-input:focus {
+ border-color: #2271b1;
+ box-shadow: 0 0 0 1px #2271b1;
+ outline: none;
+}
+
+.wpavatar-select {
+ border: 1px solid #ddd;
+ padding: 6px 10px;
+ border-radius: 4px;
+ height: 36px;
+ min-width: 240px;
+}
+
+.wpavatar-select:focus {
+ border-color: #2271b1;
+ box-shadow: 0 0 0 1px #2271b1;
+ outline: none;
+}
+
+.shortcode-docs {
+ margin-top: 20px;
+ background: #f9f9f9;
+ border: none;
+ border-radius: 4px;
+}
+
+.wpavatar-table-wrapper {
+ overflow-x: auto;
+}
+
+.wpavatar-table {
+ border-collapse: collapse;
+ width: 100%;
+ margin-bottom: 20px;
+ border: 1px solid #e2e4e7!important;
+}
+
+.wpavatar-table th {
+ background-color: #f6f7f7;
+ text-align: left;
+ padding: 10px;
+ border-bottom: 1px solid #e2e4e7;
+}
+
+.wpavatar-table td {
+ padding: 10px;
+ vertical-align: top;
+ border-bottom: 1px solid #f0f0f1;
+}
+
+.wpavatar-table code {
+ background: #f0f0f1;
+ padding: 3px 5px;
+ border-radius: 3px;
+ font-size: 13px;
+}
+
+.wpavatar-table ul {
+ margin: 0;
+ padding-left: 20px;
+}
+
+.notice {
+ padding: 8px 12px;
+ border-radius: 3px;
+}
+
+.notice-success {
+ background-color: #dff0d8;
+ border-left: 4px solid #46b450;
+ padding: 1px 12px;
+}
+
+.notice-error {
+ background-color: #f2dede;
+ border-left: 4px solid #dc3232;
+}
+
+.wpavatar-preview-container {
+ background: #f9f9f9;
+ border-radius: 4px;
+ padding: 15px;
+ margin-bottom: 20px;
+}
+
+.wpavatar-preview-wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 30px;
+ margin: 15px 0;
+}
+
+.wpavatar-preview-item {
+ text-align: center;
+}
+
+.wpavatar-preview-item h4 {
+ margin-bottom: 10px;
+ font-weight: normal;
+}
+
+.avatar-circle {
+ border-radius: 50% !important;
+ overflow: hidden;
+}
+
+.avatar-rounded {
+ border-radius: 8px !important;
+ overflow: hidden;
+}
+
+.default-avatar-options {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ margin-bottom: 15px;
+}
+
+.default-avatar-options label {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ cursor: pointer;
+ padding: 8px;
+ border: 2px solid transparent;
+ border-radius: 5px;
+ width: 80px;
+ text-align: center;
+}
+
+.default-avatar-options label:hover {
+ background-color: #f0f0f1;
+}
+
+.default-avatar-options input[type="radio"] {
+ margin-bottom: 8px;
+}
+
+.default-avatar-options input[type="radio"]:checked + img {
+ border: 2px solid #2271b1;
+ border-radius: 50%;
+}
+
+.avatar-option-name {
+ margin-top: 5px;
+ font-size: 12px;
+ line-height: 1.3;
+}
+
+@media (max-width: 782px) {
+ .wpavatar-settings .form-table th {
+ width: 100%;
+ }
+
+ .wpavatar-action-buttons {
+ flex-direction: column;
+ }
+
+ .default-avatar-options {
+ gap: 10px;
+ }
+
+ .wpavatar-preview-wrapper {
+ justify-content: center;
+ }
+}
diff --git a/assets/images/cravatar-default.png b/assets/images/cravatar-default.png
new file mode 100644
index 0000000..270a6f1
Binary files /dev/null and b/assets/images/cravatar-default.png differ
diff --git a/assets/images/cravatar-logo.png b/assets/images/cravatar-logo.png
new file mode 100644
index 0000000..cef123d
Binary files /dev/null and b/assets/images/cravatar-logo.png differ
diff --git a/assets/images/default-avatar.png b/assets/images/default-avatar.png
new file mode 100644
index 0000000..0b60a28
Binary files /dev/null and b/assets/images/default-avatar.png differ
diff --git a/assets/images/placeholder-avatar.png b/assets/images/placeholder-avatar.png
new file mode 100644
index 0000000..0b60a28
Binary files /dev/null and b/assets/images/placeholder-avatar.png differ
diff --git a/assets/images/wapuu-china.png b/assets/images/wapuu-china.png
new file mode 100644
index 0000000..b50809a
Binary files /dev/null and b/assets/images/wapuu-china.png differ
diff --git a/assets/images/wapuu.png b/assets/images/wapuu.png
new file mode 100644
index 0000000..a9e80ad
Binary files /dev/null and b/assets/images/wapuu.png differ
diff --git a/assets/js/admin.js b/assets/js/admin.js
new file mode 100644
index 0000000..fda06b3
--- /dev/null
+++ b/assets/js/admin.js
@@ -0,0 +1,234 @@
+jQuery(document).ready(function($) {
+ if (typeof wpavatar === 'undefined') {
+ console.error('WPAvatar admin script error: wpavatar object not defined');
+ return;
+ }
+
+ if (typeof wpavatar_l10n === 'undefined') {
+ console.error('WPAvatar admin script error: wpavatar_l10n object not defined');
+ return;
+ }
+
+ $('.wpavatar-tab').on('click', function() {
+ var tab = $(this).data('tab');
+ if (!tab) return;
+
+ $('.wpavatar-tab').removeClass('active');
+ $(this).addClass('active');
+
+ $('.wpavatar-section').hide();
+ $('#wpavatar-section-' + tab).show();
+
+ if (tab === 'cache' && $('#cache-stats').is(':empty')) {
+ setTimeout(function() {
+ $('#check-cache').trigger('click');
+ }, 300);
+ }
+
+ if (window.history && window.history.pushState) {
+ var url = new URL(window.location.href);
+ url.searchParams.set('tab', tab);
+ window.history.pushState({}, '', url);
+ }
+ });
+
+ function updateCdnOptions() {
+ var selectedType = $('input[name="wpavatar_cdn_type"]:checked').val();
+
+ $('.cdn-option').hide();
+
+ if (selectedType === 'cravatar_route') {
+ $('.cravatar-route-option').show();
+ forceMd5HashMethod(true);
+ } else if (selectedType === 'third_party') {
+ $('.third-party-option').show();
+ checkIfCravatarRelated($('select[name="wpavatar_third_party_mirror"]').val());
+ } else if (selectedType === 'custom') {
+ $('.custom-cdn-option').show();
+ checkIfCravatarRelated($('input[name="wpavatar_custom_cdn"]').val());
+ }
+ }
+
+ function checkIfCravatarRelated(value) {
+ if (value && value.toLowerCase().indexOf('cravatar') !== -1) {
+ forceMd5HashMethod(true);
+ } else {
+ // 非Cravatar服务默认选择SHA256,但不强制
+ var currentHashMethod = $('input[name="wpavatar_hash_method"]:checked').val();
+ if (!currentHashMethod) {
+ $('input[name="wpavatar_hash_method"][value="sha256"]').prop('checked', true);
+ }
+ forceMd5HashMethod(false);
+ }
+ }
+
+ function forceMd5HashMethod(force) {
+ if (force) {
+ $('input[name="wpavatar_hash_method"][value="md5"]').prop('checked', true);
+ $('input[name="wpavatar_hash_method"][value="sha256"]').prop('disabled', true);
+ $('.hash-method-notice').show();
+ } else {
+ $('input[name="wpavatar_hash_method"][value="sha256"]').prop('disabled', false);
+ $('.hash-method-notice').hide();
+ }
+ }
+
+ $('input[name="wpavatar_cdn_type"]').on('change', updateCdnOptions);
+
+ updateCdnOptions();
+
+ $('select[name="wpavatar_third_party_mirror"]').on('change', function() {
+ checkIfCravatarRelated($(this).val());
+ });
+
+ $('input[name="wpavatar_custom_cdn"]').on('input', function() {
+ checkIfCravatarRelated($(this).val());
+ });
+
+ $('#check-cache').on('click', function() {
+ var $button = $(this);
+ var $stats = $('#cache-stats');
+
+ $button.prop('disabled', true).text(wpavatar_l10n.checking);
+ $stats.html('
' + wpavatar_l10n.checking_status + '
');
+
+ $.ajax({
+ type: 'POST',
+ url: wpavatar.ajaxurl,
+ data: {
+ action: 'wpavatar_check_cache',
+ nonce: wpavatar.nonce
+ },
+ success: function(response) {
+ if (response.success) {
+ $stats.html(response.data);
+ } else {
+ $stats.html('' + (response.data || wpavatar_l10n.check_failed) + '
');
+ }
+ },
+ error: function() {
+ $stats.html('' + wpavatar_l10n.request_failed + '
');
+ },
+ complete: function() {
+ $button.prop('disabled', false).text(wpavatar_l10n.check_cache);
+ }
+ });
+ });
+
+ $('#purge-cache').on('click', function() {
+ var $button = $(this);
+ var $stats = $('#cache-stats');
+ var $status = $('#wpavatar-status');
+
+ if (!confirm(wpavatar_l10n.confirm_purge)) {
+ return;
+ }
+
+ $button.prop('disabled', true).text(wpavatar_l10n.purging);
+ $stats.html('' + wpavatar_l10n.purging_cache + '
');
+
+ $.ajax({
+ type: 'POST',
+ url: wpavatar.ajaxurl,
+ data: {
+ action: 'wpavatar_purge_cache',
+ nonce: wpavatar.nonce
+ },
+ success: function(response) {
+ if (response.success) {
+ $status.removeClass('notice-error')
+ .addClass('notice-success')
+ .text(response.data)
+ .show()
+ .delay(3000)
+ .fadeOut();
+
+ setTimeout(function() {
+ $('#check-cache').trigger('click');
+ }, 1000);
+ } else {
+ $status.removeClass('notice-success')
+ .addClass('notice-error')
+ .text(response.data || wpavatar_l10n.purge_failed)
+ .show();
+ }
+ },
+ error: function() {
+ $status.removeClass('notice-success')
+ .addClass('notice-error')
+ .text(wpavatar_l10n.request_failed)
+ .show();
+ },
+ complete: function() {
+ $button.prop('disabled', false).text(wpavatar_l10n.purge_cache);
+ }
+ });
+ });
+
+ $('#wpavatar-basic-form, #wpavatar-cache-form, #wpavatar-advanced-form, #wpavatar-shortcodes-form').on('submit', function(e) {
+ var formId = $(this).attr('id');
+ var $status = $('#wpavatar-status');
+ var isValid = true;
+
+ if (formId === 'wpavatar-basic-form' && $('input[name="wpavatar_cdn_type"]:checked').val() === 'custom') {
+ var customCdn = $('input[name="wpavatar_custom_cdn"]').val().trim();
+ if (!customCdn) {
+ $status.removeClass('notice-success')
+ .addClass('notice-error')
+ .text(wpavatar_l10n.enter_custom_cdn)
+ .show();
+ $('input[name="wpavatar_custom_cdn"]').focus();
+ isValid = false;
+ }
+ }
+
+ if (formId === 'wpavatar-cache-form' && $('input[name="wpavatar_enable_cache"]:checked').length > 0) {
+ var cachePath = $('input[name="wpavatar_cache_path"]').val().trim();
+ if (!cachePath) {
+ $status.removeClass('notice-success')
+ .addClass('notice-error')
+ .text(wpavatar_l10n.enter_cache_path)
+ .show();
+ $('input[name="wpavatar_cache_path"]').focus();
+ isValid = false;
+ }
+ }
+
+ if (!isValid) {
+ e.preventDefault();
+ return false;
+ }
+
+ return true;
+ });
+
+ if (window.location.search.indexOf('settings-updated=true') > -1) {
+ $('#wpavatar-status')
+ .removeClass('notice-error')
+ .addClass('notice-success')
+ .text(wpavatar_l10n.settings_saved)
+ .show()
+ .delay(3000)
+ .fadeOut();
+ }
+
+ var currentTab = '';
+
+ if (window.location.search.indexOf('tab=') > -1) {
+ var urlParams = new URLSearchParams(window.location.search);
+ currentTab = urlParams.get('tab');
+ }
+
+ if (!currentTab) {
+ var $activeTab = $('.wpavatar-tab.active');
+ if ($activeTab.length) {
+ currentTab = $activeTab.data('tab');
+ }
+ }
+
+ if (currentTab) {
+ $('.wpavatar-tab[data-tab="' + currentTab + '"]').trigger('click');
+ } else {
+ $('.wpavatar-tab:first').trigger('click');
+ }
+});
diff --git a/includes/admin.php b/includes/admin.php
new file mode 100644
index 0000000..ab1a9a7
--- /dev/null
+++ b/includes/admin.php
@@ -0,0 +1,708 @@
+ 'boolean']);
+ register_setting('wpavatar_basic', 'wpavatar_cdn_type', ['type' => 'string']);
+ register_setting('wpavatar_basic', 'wpavatar_cravatar_route', ['type' => 'string']);
+ register_setting('wpavatar_basic', 'wpavatar_third_party_mirror', ['type' => 'string']);
+ register_setting('wpavatar_basic', 'wpavatar_custom_cdn', ['type' => 'string']);
+ register_setting('wpavatar_basic', 'wpavatar_hash_method', ['type' => 'string']);
+ register_setting('wpavatar_basic', 'wpavatar_timeout', ['type' => 'integer']);
+
+ register_setting('wpavatar_cache', 'wpavatar_enable_cache', ['type' => 'boolean']);
+ register_setting('wpavatar_cache', 'wpavatar_cache_path', [
+ 'type' => 'string',
+ 'sanitize_callback' => [__CLASS__, 'sanitize_cache_path']
+ ]);
+ register_setting('wpavatar_cache', 'wpavatar_cache_expire', ['type' => 'integer']);
+
+ register_setting('wpavatar_advanced', 'wpavatar_seo_alt', ['type' => 'string']);
+ register_setting('wpavatar_advanced', 'wpavatar_fallback_mode', ['type' => 'boolean']);
+ register_setting('wpavatar_advanced', 'wpavatar_fallback_avatar', ['type' => 'string']);
+
+ register_setting('wpavatar_shortcodes', 'wpavatar_shortcode_size', ['type' => 'integer']);
+ register_setting('wpavatar_shortcodes', 'wpavatar_shortcode_class', ['type' => 'string']);
+ register_setting('wpavatar_shortcodes', 'wpavatar_shortcode_shape', ['type' => 'string']);
+ }
+
+ public static function sanitize_cache_path($value) {
+ $value = sanitize_text_field($value);
+ $value = rtrim($value, '/\\') . '/';
+
+ if (!preg_match('~^(?:/|\\\\|[a-zA-Z]:)~', $value)) {
+ $value = WP_CONTENT_DIR . '/' . ltrim($value, '/\\');
+ }
+
+ if (!file_exists($value)) {
+ if (!wp_mkdir_p($value)) {
+ add_settings_error(
+ 'wpavatar_cache',
+ 'cache_path_invalid',
+ __('无法创建缓存目录,请检查权限', 'wpavatar'),
+ 'error'
+ );
+ return get_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR);
+ }
+ } elseif (!is_dir($value)) {
+ add_settings_error(
+ 'wpavatar_cache',
+ 'cache_path_invalid',
+ __('指定的路径不是有效目录', 'wpavatar'),
+ 'error'
+ );
+ return get_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR);
+ } elseif (!is_writable($value)) {
+ add_settings_error(
+ 'wpavatar_cache',
+ 'cache_path_invalid',
+ __('缓存目录不可写,请检查权限', 'wpavatar'),
+ 'error'
+ );
+ return get_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR);
+ }
+
+ $index_file = $value . 'index.php';
+ if (!file_exists($index_file)) {
+ @file_put_contents($index_file, ' wp_create_nonce('wpavatar_admin_nonce'),
+ 'ajaxurl' => admin_url('admin-ajax.php'),
+ 'cache_path' => get_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR),
+ 'plugin_url' => WPAVATAR_PLUGIN_URL,
+ 'assets_url' => WPAVATAR_PLUGIN_URL . 'assets/',
+ ]);
+
+ wp_localize_script('wpavatar-admin', 'wpavatar_l10n', [
+ 'checking' => __('检查中...', 'wpavatar'),
+ 'checking_status' => __('正在检查缓存状态...', 'wpavatar'),
+ 'check_failed' => __('检查失败,请重试', 'wpavatar'),
+ 'request_failed' => __('请求失败,请检查网络连接', 'wpavatar'),
+ 'check_cache' => __('检查缓存状态', 'wpavatar'),
+ 'confirm_purge' => __('确定要清空所有缓存头像吗?此操作无法撤销。', 'wpavatar'),
+ 'purging' => __('清空中...', 'wpavatar'),
+ 'purging_cache' => __('正在清空缓存...', 'wpavatar'),
+ 'purge_failed' => __('清空失败,请重试', 'wpavatar'),
+ 'purge_cache' => __('清空缓存', 'wpavatar'),
+ 'enter_custom_cdn' => __('请输入自定义CDN域名', 'wpavatar'),
+ 'enter_cache_path' => __('请输入缓存目录路径', 'wpavatar'),
+ 'settings_saved' => __('设置已成功保存。', 'wpavatar')
+ ]);
+ }
+
+ public static function display_notices() {
+ settings_errors('wpavatar_basic');
+ settings_errors('wpavatar_cache');
+ settings_errors('wpavatar_advanced');
+ settings_errors('wpavatar_shortcodes');
+ }
+
+ public static function render_settings_page() {
+ $active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'basic';
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+

+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+ 'wpavatar-network', 'updated' => 'true'], network_admin_url('settings.php')));
+ exit;
+ }
+}
diff --git a/includes/core.php b/includes/core.php
new file mode 100644
index 0000000..a71b5dd
--- /dev/null
+++ b/includes/core.php
@@ -0,0 +1,690 @@
+ WPAVATAR_PLUGIN_URL . 'assets/images/default-avatar.png',
+ 'name' => __('默认头像', 'wpavatar')
+ ];
+ }
+
+ if (file_exists(WPAVATAR_PLUGIN_DIR . 'assets/images/wapuu.png')) {
+ $avatars['wapuu'] = [
+ 'url' => WPAVATAR_PLUGIN_URL . 'assets/images/wapuu.png',
+ 'name' => __('文派Wapuu', 'wpavatar')
+ ];
+ }
+
+ if (file_exists(WPAVATAR_PLUGIN_DIR . 'assets/images/wapuu-china.png')) {
+ $avatars['wapuu-china'] = [
+ 'url' => WPAVATAR_PLUGIN_URL . 'assets/images/wapuu-china.png',
+ 'name' => __('中国Wapuu', 'wpavatar')
+ ];
+ }
+
+ if (file_exists(WPAVATAR_PLUGIN_DIR . 'assets/images/cravatar-default.png')) {
+ $avatars['cravatar'] = [
+ 'url' => WPAVATAR_PLUGIN_URL . 'assets/images/cravatar-default.png',
+ 'name' => __('Cravatar默认', 'wpavatar')
+ ];
+ }
+
+ if (file_exists(WPAVATAR_PLUGIN_DIR . 'assets/images/cravatar-logo.png')) {
+ $avatars['cravatar-logo'] = [
+ 'url' => WPAVATAR_PLUGIN_URL . 'assets/images/cravatar-logo.png',
+ 'name' => __('Cravatar标志', 'wpavatar')
+ ];
+ }
+
+ return $avatars;
+ }
+
+ public static function modify_profile_picture_description() {
+ return '' . __('您可以在初认头像修改您的资料图片', 'wpavatar') . '';
+ }
+
+ public static function pre_get_avatar_data($args, $id_or_email) {
+ if (is_null($args)) {
+ $args = [];
+ }
+
+ $email = '';
+ if (is_numeric($id_or_email)) {
+ $user = get_user_by('id', (int)$id_or_email);
+ if ($user) {
+ $email = $user->user_email;
+ }
+ } elseif (is_string($id_or_email)) {
+ if (is_email($id_or_email)) {
+ $email = $id_or_email;
+ }
+ } elseif (is_object($id_or_email)) {
+ if (isset($id_or_email->user_id) && $id_or_email->user_id) {
+ $user = get_user_by('id', $id_or_email->user_id);
+ if ($user) {
+ $email = $user->user_email;
+ }
+ } elseif (isset($id_or_email->comment_author_email)) {
+ $email = $id_or_email->comment_author_email;
+ } elseif (isset($id_or_email->user_email)) {
+ $email = $id_or_email->user_email;
+ }
+ }
+
+ if (empty($email)) {
+ return $args;
+ }
+
+ // 确定使用的哈希方法
+ $cdn_type = get_option('wpavatar_cdn_type', 'cravatar_route');
+ $use_md5 = true;
+
+ // Cravatar只支持MD5,如果使用Cravatar相关服务,强制使用MD5
+ if ($cdn_type !== 'cravatar_route') {
+ // 检查第三方镜像或自定义CDN是否与Cravatar相关
+ $third_party_mirror = get_option('wpavatar_third_party_mirror', '');
+ $custom_cdn = get_option('wpavatar_custom_cdn', '');
+ $is_cravatar_related = (
+ strpos(strtolower($third_party_mirror), 'cravatar') !== false ||
+ strpos(strtolower($custom_cdn), 'cravatar') !== false
+ );
+
+ if (!$is_cravatar_related) {
+ // 非Cravatar服务使用用户设置的哈希方法
+ $hash_method = get_option('wpavatar_hash_method', 'sha256');
+ $use_md5 = ($hash_method === 'md5');
+ }
+ }
+
+ // 检查WordPress版本,决定是否支持SHA256
+ $wp_version = get_bloginfo('version');
+ $use_sha256_support = version_compare($wp_version, '6.8', '>=');
+
+ // 设置哈希方法
+ if (!$use_md5 && $use_sha256_support) {
+ $args['hash_method'] = 'sha256';
+ $args['hash'] = 'sha256';
+ } else {
+ $args['hash_method'] = 'md5';
+
+ if (isset($args['hash']) && $args['hash'] === 'sha256') {
+ unset($args['hash']);
+ }
+ }
+
+ // 计算邮箱地址的哈希值
+ if (!isset($args['wpavatar_email_hash'])) {
+ if (!$use_md5) {
+ $args['wpavatar_email_hash'] = hash('sha256', strtolower(trim($email)));
+ } else {
+ $args['wpavatar_email_hash'] = md5(strtolower(trim($email)));
+ }
+ }
+
+ // 设置超时属性
+ $timeout = get_option('wpavatar_timeout', 5);
+ $args['extra_attr'] = isset($args['extra_attr']) ? $args['extra_attr'] : '';
+ $args['extra_attr'] .= ' data-timeout="' . esc_attr($timeout) . '"';
+
+ return $args;
+ }
+
+ public static function replace_avatar_url($url) {
+ // 遍历所有Gravatar域名,替换为Cravatar相关域名
+ foreach (self::$gravatar_domains as $domain) {
+ if (strpos($url, $domain) !== false) {
+ $cdn_type = get_option('wpavatar_cdn_type', 'cravatar_route');
+ $cdn_domain = '';
+
+ if ($cdn_type === 'cravatar_route') {
+ $cdn_domain = get_option('wpavatar_cravatar_route', 'cravatar.com');
+ } elseif ($cdn_type === 'third_party') {
+ $cdn_domain = get_option('wpavatar_third_party_mirror', 'weavatar.com');
+ } elseif ($cdn_type === 'custom') {
+ $custom_cdn = get_option('wpavatar_custom_cdn', '');
+ if (!empty($custom_cdn)) {
+ $cdn_domain = $custom_cdn;
+ } else {
+ $cdn_domain = 'cravatar.com';
+ }
+ }
+
+ return str_replace($domain, $cdn_domain, $url);
+ }
+ }
+
+ return $url;
+ }
+
+ public static function add_seo_alt($avatar, $id_or_email, $size, $default, $alt) {
+ // 添加SEO友好的alt属性
+ $seo_alt = get_option('wpavatar_seo_alt');
+ if (!empty($seo_alt)) {
+ $user = false;
+ if (is_numeric($id_or_email)) {
+ $user = get_user_by('id', $id_or_email);
+ } elseif (is_object($id_or_email)) {
+ if (isset($id_or_email->user_id) && $id_or_email->user_id) {
+ $user = get_user_by('id', $id_or_email->user_id);
+ } elseif (isset($id_or_email->comment_author_email)) {
+ $user = (object) [
+ 'display_name' => $id_or_email->comment_author
+ ];
+ }
+ } elseif (is_string($id_or_email) && is_email($id_or_email)) {
+ $user = get_user_by('email', $id_or_email);
+ }
+
+ $alt_text = $user ? sprintf($seo_alt, $user->display_name) : __('用户头像', 'wpavatar');
+ $avatar = preg_replace('/alt=([\'"])[^\'"]*\\1/', "alt='$alt_text'", $avatar);
+ }
+
+ // 添加头像加载失败的备用显示
+ if (get_option('wpavatar_fallback_mode', 1)) {
+ $fallback_type = get_option('wpavatar_fallback_avatar', 'default');
+ $local_avatars = self::get_local_avatars();
+
+ if (isset($local_avatars[$fallback_type])) {
+ $fallback_url = $local_avatars[$fallback_type]['url'];
+
+ if (strpos($avatar, 'onerror') === false) {
+ $avatar = str_replace('
(time() - $cache_expire)) {
+ $cached_url = content_url(str_replace(WP_CONTENT_DIR, '', $cache_file));
+ return str_replace($url, esc_url($cached_url), $avatar);
+ }
+
+ if (self::cache_remote_avatar($url, $cache_file)) {
+ $cached_url = content_url(str_replace(WP_CONTENT_DIR, '', $cache_file));
+ return str_replace($url, esc_url($cached_url), $avatar);
+ }
+
+ return $avatar;
+ }
+
+ public static function get_avatar_hash($id_or_email) {
+ $email = '';
+
+ if (is_object($id_or_email)) {
+ if (isset($id_or_email->comment_author_email)) {
+ $email = $id_or_email->comment_author_email;
+ } elseif (isset($id_or_email->user_email)) {
+ $email = $id_or_email->user_email;
+ }
+ } elseif (is_numeric($id_or_email)) {
+ $user = get_user_by('id', $id_or_email);
+ $email = $user ? $user->user_email : '';
+ } elseif (is_string($id_or_email) && is_email($id_or_email)) {
+ $email = $id_or_email;
+ }
+
+ if (empty($email)) {
+ return '';
+ }
+
+ $cdn_type = get_option('wpavatar_cdn_type', 'cravatar_route');
+ $use_md5 = true;
+
+ // 确定使用的哈希方法
+ if ($cdn_type !== 'cravatar_route' &&
+ strpos(get_option('wpavatar_third_party_mirror', ''), 'cravatar') === false &&
+ strpos(get_option('wpavatar_custom_cdn', ''), 'cravatar') === false) {
+ $hash_method = get_option('wpavatar_hash_method', 'md5');
+ $use_md5 = ($hash_method === 'md5');
+ }
+
+ if (!$use_md5) {
+ return hash('sha256', strtolower(trim($email)));
+ } else {
+ return md5(strtolower(trim($email)));
+ }
+ }
+
+ public static function get_cache_path($hash, $size) {
+ $dir = get_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR);
+ $dir = trailingslashit($dir);
+ wp_mkdir_p($dir);
+ return $dir . "{$hash}-{$size}.jpg";
+ }
+
+ public static function cache_remote_avatar($url, $dest) {
+ $dir = dirname($dest);
+ if (!file_exists($dir)) {
+ if (!wp_mkdir_p($dir)) {
+ error_log('WPAvatar: Failed to create cache directory: ' . $dir);
+ return false;
+ }
+ }
+
+ if (!is_writable($dir)) {
+ error_log('WPAvatar: Cache directory is not writable: ' . $dir);
+ return false;
+ }
+
+ $timeout = get_option('wpavatar_timeout', 5);
+
+ $response = wp_remote_get($url, [
+ 'timeout' => $timeout,
+ 'user-agent' => 'WPAvatar/' . WPAVATAR_VERSION . '; ' . home_url()
+ ]);
+
+ if (is_wp_error($response)) {
+ error_log('WPAvatar: Error fetching avatar: ' . $response->get_error_message());
+ return false;
+ }
+
+ $response_code = wp_remote_retrieve_response_code($response);
+ if ($response_code !== 200) {
+ error_log('WPAvatar: Bad response code: ' . $response_code);
+ return false;
+ }
+
+ $image = wp_remote_retrieve_body($response);
+ if (empty($image)) {
+ error_log('WPAvatar: Empty image received');
+ return false;
+ }
+
+ $content_type = wp_remote_retrieve_header($response, 'content-type');
+ if (strpos($content_type, 'image/') !== 0) {
+ error_log('WPAvatar: Invalid content type: ' . $content_type);
+ return false;
+ }
+
+ $result = file_put_contents($dest, $image);
+ if ($result === false) {
+ error_log('WPAvatar: Failed to write cache file: ' . $dest);
+ return false;
+ }
+
+ @chmod($dest, 0644);
+
+ return true;
+ }
+
+ public static function cache_comment_avatar($comment_id, $comment_approved) {
+ if ($comment_approved !== 1 || !get_option('wpavatar_enable_cache', true)) {
+ return;
+ }
+
+ $comment = get_comment($comment_id);
+ if (!$comment || empty($comment->comment_author_email)) {
+ return;
+ }
+
+ $email = $comment->comment_author_email;
+ $size = get_option('wpavatar_shortcode_size', 96);
+
+ $avatar_url = get_avatar_url($email, ['size' => $size]);
+
+ $hash = self::get_avatar_hash($email);
+ if (empty($hash)) {
+ return;
+ }
+
+ $cache_file = self::get_cache_path($hash, $size);
+
+ self::cache_remote_avatar($avatar_url, $cache_file);
+
+ // 缓存2x尺寸的头像,用于高分辨率显示器
+ $retina_url = get_avatar_url($email, ['size' => $size * 2]);
+ $retina_cache_file = self::get_cache_path($hash, $size * 2);
+ self::cache_remote_avatar($retina_url, $retina_cache_file);
+ }
+
+ public static function cache_user_avatar($user_id) {
+ if (!get_option('wpavatar_enable_cache', true)) {
+ return;
+ }
+
+ $user = get_user_by('id', $user_id);
+ if (!$user) {
+ return;
+ }
+
+ $email = $user->user_email;
+ $size = get_option('wpavatar_shortcode_size', 96);
+
+ $avatar_url = get_avatar_url($email, ['size' => $size]);
+
+ $hash = self::get_avatar_hash($email);
+ if (empty($hash)) {
+ return;
+ }
+
+ $cache_file = self::get_cache_path($hash, $size);
+
+ self::cache_remote_avatar($avatar_url, $cache_file);
+
+ // 缓存2x尺寸的头像,用于高分辨率显示器
+ $retina_url = get_avatar_url($email, ['size' => $size * 2]);
+ $retina_cache_file = self::get_cache_path($hash, $size * 2);
+ self::cache_remote_avatar($retina_url, $retina_cache_file);
+ }
+
+ public static function purge_expired() {
+ $dir = get_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR);
+ if (!file_exists($dir) || !is_dir($dir)) {
+ return;
+ }
+
+ $files = glob(trailingslashit($dir) . '*.jpg');
+ if (!$files) {
+ return;
+ }
+
+ $expire_days = get_option('wpavatar_cache_expire', 7);
+ $expire_time = time() - ($expire_days * DAY_IN_SECONDS);
+
+ foreach ($files as $file) {
+ if (filemtime($file) < $expire_time) {
+ @unlink($file);
+ }
+ }
+ }
+
+ public static function schedule_purge() {
+ if (!wp_next_scheduled('wpavatar_purge_cache')) {
+ wp_schedule_event(time(), 'daily', 'wpavatar_purge_cache');
+ }
+ }
+
+ public static function check_cache_status() {
+ $dir = get_option('wpavatar_cache_path', WPAVATAR_CACHE_DIR);
+ $stats = [
+ 'path' => $dir,
+ 'exists' => file_exists($dir) && is_dir($dir),
+ 'writable' => is_writable($dir),
+ 'file_count' => 0,
+ 'size' => 0
+ ];
+
+ if ($stats['exists']) {
+ $files = glob(trailingslashit($dir) . '*.jpg');
+ $stats['file_count'] = count($files ?: []);
+ foreach ($files ?: [] as $file) {
+ $stats['size'] += filesize($file);
+ }
+ $stats['size'] = size_format($stats['size']);
+ }
+
+ ob_start();
+ ?>
+
+ $default_size,
+ 'user_id' => get_current_user_id(),
+ 'class' => $default_class,
+ 'email' => '',
+ 'shape' => $default_shape,
+ 'title' => ''
+ ], $atts);
+
+ if (empty($atts['user_id']) && empty($atts['email'])) {
+ $atts['user_id'] = get_current_user_id();
+ }
+
+ $classes = [$atts['class']];
+
+ if ($atts['shape'] === 'circle') {
+ $classes[] = 'avatar-circle';
+ $style = 'style="border-radius: 50%; overflow: hidden;"';
+ } elseif ($atts['shape'] === 'rounded') {
+ $classes[] = 'avatar-rounded';
+ $style = 'style="border-radius: 8px; overflow: hidden;"';
+ } else {
+ $classes[] = 'avatar-square';
+ $style = '';
+ }
+
+ $avatar_args = [
+ 'class' => implode(' ', $classes),
+ 'size' => intval($atts['size']),
+ 'extra_attr' => $style
+ ];
+
+ if (!empty($atts['title'])) {
+ $avatar_args['extra_attr'] .= ' title="' . esc_attr($atts['title']) . '"';
+ }
+
+ ob_start();
+ if (!empty($atts['email'])) {
+ echo get_avatar($atts['email'], intval($atts['size']), 'default', '', $avatar_args);
+ } else {
+ echo get_avatar($atts['user_id'], intval($atts['size']), 'default', '', $avatar_args);
+ }
+ return ob_get_clean();
+ }
+
+ public static function render_username($atts) {
+ $atts = shortcode_atts([
+ 'user_id' => get_current_user_id(),
+ 'before' => '',
+ 'after' => ''
+ ], $atts);
+
+ if (!empty($atts['user_id'])) {
+ $user = get_user_by('id', $atts['user_id']);
+ } else {
+ $user = wp_get_current_user();
+ }
+
+ $username = $user && $user->display_name ? $user->display_name : __('匿名用户', 'wpavatar');
+ return $atts['before'] . $username . $atts['after'];
+ }
+
+ public static function menu_item_replace($item_output, $item, $depth, $args) {
+ if (strpos($item_output, '{wpavatar}') !== false) {
+ $item_output = str_replace('{wpavatar}', do_shortcode('[wpavatar shape="circle" size="32"]'), $item_output);
+ }
+ if (strpos($item_output, '{wpavatar_username}') !== false) {
+ $item_output = str_replace('{wpavatar_username}', do_shortcode('[wpavatar_username]'), $item_output);
+ }
+ return $item_output;
+ }
+
+ public static function generate_preview($user_id = 0, $shape = 'square', $size = 96) {
+ if (!$user_id) {
+ $user_id = get_current_user_id();
+ }
+
+ $atts = [
+ 'user_id' => $user_id,
+ 'shape' => $shape,
+ 'size' => $size,
+ 'class' => 'wpavatar-preview'
+ ];
+
+ return self::render_avatar($atts);
+ }
+}
diff --git a/includes/cravatar.php b/includes/cravatar.txt
similarity index 100%
rename from includes/cravatar.php
rename to includes/cravatar.txt
diff --git a/wpavatar.php b/wpavatar.php
index ffc12c2..d495577 100644
--- a/wpavatar.php
+++ b/wpavatar.php
@@ -1,44 +1,258 @@
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;
+}
+
+function wpavatar_replace_gravatar_text_plural($translated_text, $single, $plural, $number) {
+ if (!get_option('wpavatar_enable_cravatar', 1)) {
+ 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;
+}
+
+register_activation_hook(__FILE__, function() {
+ 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');
+
+ wp_mkdir_p(WPAVATAR_CACHE_DIR);
+
+ $index_file = rtrim(WPAVATAR_CACHE_DIR, '/\\') . '/index.php';
+ if (!file_exists($index_file)) {
+ @file_put_contents($index_file, '\n";
+ $htaccess_content .= "ExpiresActive On\n";
+ $htaccess_content .= "ExpiresByType image/jpeg \"access plus 1 week\"\n";
+ $htaccess_content .= "\n";
+ @file_put_contents($htaccess_file, $htaccess_content);
+ }
+
+ if (is_multisite()) {
+ add_site_option('wpavatar_network_enabled', 1);
+ add_site_option('wpavatar_network_cdn_type', 'cravatar_route');
+ add_site_option('wpavatar_network_cravatar_route', 'cravatar.com');
+ }
+
+ if (!wp_next_scheduled('wpavatar_purge_cache')) {
+ wp_schedule_event(time(), 'daily', 'wpavatar_purge_cache');
+ }
+});
+
+register_deactivation_hook(__FILE__, function() {
+ wp_clear_scheduled_hook('wpavatar_purge_cache');
+});