From b5e9fc686e12467a85e904a861ea96e681ad4c01 Mon Sep 17 00:00:00 2001 From: feibisi Date: Sun, 5 Oct 2025 11:08:40 +0800 Subject: [PATCH 01/19] Add caching, config, and exception handler classes Introduce WPCC_Conversion_Cache for conversion result caching, WPCC_Config for unified plugin configuration management, and WPCC_Exception_Handler for robust exception handling. Update MediaWiki and OpenCC converters to utilize the new caching layer, improve frontend language switcher accessibility and i18n, and add block compatibility for deprecated block output. Refactor admin class to use modern PHP syntax and improve type safety. --- assets/js/blocks-frontend.js | 42 +- assets/js/wpcc-block-compat.js | 78 ++ assets/js/wpcc-block-script-ok.js | 14 +- includes/admin/wp-chinese-converter-admin.php | 37 +- includes/blocks/blocks-init.php | 29 +- .../blocks/build/language-switcher/index.js | 9 +- includes/core/class-conversion-cache.php | 270 +++++++ includes/core/class-mediawiki-converter.php | 197 ++++- includes/core/class-opencc-converter.php | 54 +- includes/core/class-wpcc-config.php | 267 +++++++ .../core/class-wpcc-exception-handler.php | 295 +++++++ includes/core/class-wpcc-extension.php | 321 ++++++++ includes/core/class-wpcc-main.php | 717 ++++++++++++++++++ includes/core/class-wpcc-utils.php | 213 ++++++ includes/modules/wpcc-rest-api.php | 8 +- includes/wpcc-admin.php | 86 ++- includes/wpcc-core.php | 193 +++-- wp-chinese-converter.php | 59 +- 18 files changed, 2703 insertions(+), 186 deletions(-) create mode 100644 assets/js/wpcc-block-compat.js create mode 100644 includes/core/class-conversion-cache.php create mode 100644 includes/core/class-wpcc-config.php create mode 100644 includes/core/class-wpcc-exception-handler.php create mode 100644 includes/core/class-wpcc-extension.php create mode 100644 includes/core/class-wpcc-main.php create mode 100644 includes/core/class-wpcc-utils.php diff --git a/assets/js/blocks-frontend.js b/assets/js/blocks-frontend.js index 9ee5463..ee90571 100644 --- a/assets/js/blocks-frontend.js +++ b/assets/js/blocks-frontend.js @@ -75,7 +75,9 @@ function renderLanguageSwitcher( }; const target = openInNewWindow ? ' target="_blank"' : ""; - const noConversionText = customNoConversionLabel || "不转换"; + const rel = openInNewWindow ? ' rel="noopener noreferrer"' : ""; + const strings = (typeof wpccFrontendSettings !== 'undefined' && wpccFrontendSettings.strings) ? wpccFrontendSettings.strings : {}; + const noConversionText = customNoConversionLabel || strings.noConversionLabel || "不转换"; let html = ""; @@ -85,22 +87,24 @@ function renderLanguageSwitcher( if (showNoConversion) { const isActive = !currentLang || currentLang === ""; html += ` - ${noConversionText} + ${noConversionText} `; } availableLanguages.forEach(function (lang) { const isActive = currentLang === lang.code; const label = getLanguageLabel(lang); + const ariaCurrent = isActive ? ' aria-current="page"' : ''; html += ` - ${label} + ${label} `; }); html += ""; } else { + const ariaLabel = (typeof strings !== 'undefined' && strings.languageSelectLabel) ? strings.languageSelectLabel : '选择语言'; html = - '`; if (showNoConversion) { const selected = !currentLang || currentLang === "" ? "selected" : ""; @@ -172,13 +176,16 @@ function renderConversionStatus(container, showIcon, displayFormat) { let html = ""; + const strings = (typeof wpccFrontendSettings !== 'undefined' && wpccFrontendSettings.strings) ? wpccFrontendSettings.strings : {}; + const currentPrefix = strings.currentLanguagePrefix || '当前语言:'; + let statusText = ""; switch (displayFormat) { case "badge": statusText = langInfo.label; break; case "text": - statusText = `当前语言:${langInfo.label}`; + statusText = `${currentPrefix}${langInfo.label}`; break; case "minimal": statusText = langInfo.name; @@ -187,6 +194,9 @@ function renderConversionStatus(container, showIcon, displayFormat) { statusText = langInfo.label; } + if (showIcon) { + html += ``; + } html += `${statusText}`; container.innerHTML = html; @@ -239,6 +249,11 @@ function getAvailableLanguages() { } function getCurrentLanguage() { + // 优先使用服务端注入的当前语言(如果存在) + if (typeof wpcc_target_lang !== "undefined" && wpcc_target_lang) { + return wpcc_target_lang; + } + const urlParams = new URLSearchParams(window.location.search); const variant = urlParams.get("variant"); @@ -266,8 +281,23 @@ function getLanguageInfo(langCode) { } function getLanguageUrl(langCode) { - const currentUrl = new URL(window.location.href); + // 优先使用服务端注入的 URL 映射(如可用) + try { + if (typeof wpcc_langs_urls === "object" && wpcc_langs_urls) { + if (!langCode || langCode === "") { + if (typeof wpcc_noconversion_url === "string" && wpcc_noconversion_url) { + return wpcc_noconversion_url; + } + } else if (wpcc_langs_urls[langCode]) { + return wpcc_langs_urls[langCode]; + } + } + } catch (e) { + // 忽略映射读取错误,回退到查询参数模式 + } + // 回退:使用查询参数模式 + const currentUrl = new URL(window.location.href); if (langCode) { currentUrl.searchParams.set("variant", langCode); } else { diff --git a/assets/js/wpcc-block-compat.js b/assets/js/wpcc-block-compat.js new file mode 100644 index 0000000..4a94b47 --- /dev/null +++ b/assets/js/wpcc-block-compat.js @@ -0,0 +1,78 @@ +(function(){ + if (!window.wp || !wp.blocks || !wp.blockEditor || !wp.element || !wp.domReady) return; + const el = wp.element.createElement; + const useBlockProps = wp.blockEditor.useBlockProps; + + // Deprecated saver for Traditional Chinese placeholder (語言切換器) + function saveLanguageSwitcherDeprecated(props){ + const { + displayStyle, + showNoConversion, + alignment, + enabledLanguages, + buttonSize, + customNoConversionLabel, + sortOrder, + openInNewWindow, + showLanguageCode, + customLabels, + } = props.attributes; + + let finalEnabledLanguages = Array.isArray(enabledLanguages) ? enabledLanguages : []; + if (typeof wpccBlockSettings !== 'undefined' && Array.isArray(wpccBlockSettings.enabledLanguages)) { + const base = wpccBlockSettings.enabledLanguages; + const filtered = finalEnabledLanguages.filter(code => base.includes(code)); + finalEnabledLanguages = filtered.length ? filtered : base; + } + + const blockProps = useBlockProps.save({ + className: `wpcc-language-switcher wpcc-${displayStyle} wpcc-align-${alignment} wpcc-size-${buttonSize}` + }); + + return el('div', blockProps, + el('div', { + className: 'wpcc-switcher-placeholder', + 'data-display-style': displayStyle, + 'data-show-no-conversion': !!showNoConversion, + 'data-enabled-languages': JSON.stringify(finalEnabledLanguages), + 'data-button-size': buttonSize, + 'data-custom-no-conversion-label': customNoConversionLabel || '', + 'data-sort-order': sortOrder, + 'data-open-in-new-window': !!openInNewWindow, + 'data-show-language-code': !!showLanguageCode, + 'data-custom-labels': JSON.stringify(customLabels || {}) + }, '語言切換器') + ); + } + + function saveConversionStatusDeprecated(props){ + const { showIcon, displayFormat } = props.attributes; + const blockProps = useBlockProps.save({ + className: `wpcc-conversion-status wpcc-format-${displayFormat}` + }); + return el('div', blockProps, + el('div', { + className: 'wpcc-status-container', + 'data-show-icon': !!showIcon, + 'data-display-format': displayFormat + }, '轉換狀態指示器') + ); + } + + wp.domReady(function(){ + const { getBlockType, unregisterBlockType, registerBlockType } = wp.blocks; + + function extendDeprecated(name, saveFn){ + const type = getBlockType(name); + if (!type) return; + const deprecated = Array.isArray(type.deprecated) ? type.deprecated.slice() : []; + deprecated.push({ save: saveFn }); + const settings = Object.assign({}, type, { deprecated }); + unregisterBlockType(name); + registerBlockType(name, settings); + } + + extendDeprecated('wpcc/language-switcher', saveLanguageSwitcherDeprecated); + extendDeprecated('wpcc/conversion-status', saveConversionStatusDeprecated); + }); +})(); diff --git a/assets/js/wpcc-block-script-ok.js b/assets/js/wpcc-block-script-ok.js index 5b5926f..38a05b7 100755 --- a/assets/js/wpcc-block-script-ok.js +++ b/assets/js/wpcc-block-script-ok.js @@ -1,14 +1,14 @@ -let wpc_switcher_use_permalink_type +var wpc_switcher_use_permalink_type; if (typeof wpc_switcher_use_permalink !== 'undefined') { - wpc_switcher_use_permalink_type = wpc_switcher_use_permalink['type'] + wpc_switcher_use_permalink_type = wpc_switcher_use_permalink['type']; } function wpccRedirectToPage($event) { - if (typeof WPCSVariant === 'undefined') return ; - return WPCSVariant.wpccRedirectToPage($event) + if (typeof WPCSVariant === 'undefined') return; + return WPCSVariant.wpccRedirectToPage($event); } function wpccRedirectToVariant(variantValue) { - if (typeof WPCSVariant === 'undefined') return ; - return WPCSVariant.wpccRedirectToVariant(variantValue) -} \ No newline at end of file + if (typeof WPCSVariant === 'undefined') return; + return WPCSVariant.wpccRedirectToVariant(variantValue); +} diff --git a/includes/admin/wp-chinese-converter-admin.php b/includes/admin/wp-chinese-converter-admin.php index f9572e4..18f8f52 100755 --- a/includes/admin/wp-chinese-converter-admin.php +++ b/includes/admin/wp-chinese-converter-admin.php @@ -2,22 +2,19 @@ class wpcc_Admin { - var $base = ""; - var $is_submitted = false; - var $is_success = false; - var $is_error = false; - var $message = ""; - var $options = false; - var $langs = false; - var $url = ""; - var $admin_lang = false; + private string $base = ""; + private bool $is_submitted = false; + private bool $is_success = false; + private bool $is_error = false; + private string $message = ""; + private $options = false; + private $langs = false; + private string $url = ""; + private $admin_lang = false; - function wpcc_Admin() - { - return $this->__construct(); - } + // 移除旧式构造函数,只使用 __construct - function clean_invalid_langs() + private function clean_invalid_langs(): void { global $wpcc_langs; if ( @@ -34,7 +31,7 @@ class wpcc_Admin } } - function __construct() + public function __construct() { global $wpcc_options, $wpcc_langs, $wpcc_modules; if (function_exists("wpcc_init_languages")) { @@ -110,7 +107,7 @@ class wpcc_Admin ]); } - function action_links($links, $file) + public function action_links(array $links, string $file): array { if ($file == $this->base . "wp-chinese-converter.php") { $links[] = @@ -120,7 +117,7 @@ class wpcc_Admin return $links; } - function is_gutenberg_active() + public function is_gutenberg_active(): bool { // 检查是否启用了Gutenberg编辑器 if (function_exists("use_block_editor_for_post_type")) { @@ -133,7 +130,7 @@ class wpcc_Admin function_exists("wp_enqueue_block_editor_assets"); } - function install_cache_module() + public function install_cache_module(): bool { global $wpcc_options; @@ -172,7 +169,7 @@ class wpcc_Admin return $ret; } - function uninstall_cache_module() + public function uninstall_cache_module(): bool { return unlink( WP_PLUGIN_DIR . @@ -180,7 +177,7 @@ class wpcc_Admin ); } - function get_cache_status() + public function get_cache_status(): int { if (!function_exists("wp_cache_is_enabled")) { return 0; diff --git a/includes/blocks/blocks-init.php b/includes/blocks/blocks-init.php index 0b3fc47..9924ce9 100644 --- a/includes/blocks/blocks-init.php +++ b/includes/blocks/blocks-init.php @@ -27,7 +27,8 @@ class WPCC_Blocks { private function register_single_block( $block_name ) { $block_path = plugin_dir_path( __FILE__ ) . 'build/' . $block_name; - if ( file_exists( $block_path . '/index.js' ) ) { + // 更符合规范的做法:检测 block.json 是否存在 + if ( file_exists( $block_path . '/block.json' ) ) { register_block_type( $block_path ); } } @@ -45,6 +46,15 @@ class WPCC_Blocks { '1.0.0' ); + // 兼容层:为编辑器注册旧占位文本的 deprecated 版本,避免校验失败 + wp_enqueue_script( + 'wpcc-block-compat', + plugins_url( 'assets/js/wpcc-block-compat.js', dirname( dirname( __FILE__ ) ) ), + array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-hooks' ), + '1.0.0', + true + ); + global $wpcc_options; $enabled_languages = $wpcc_options['wpcc_used_langs'] ?? array(); @@ -62,7 +72,12 @@ class WPCC_Blocks { 'zh-sg' => $wpcc_options['sgtip'] ?? '马新', 'zh-jp' => $wpcc_options['jptip'] ?? '日式' ), - 'noConversionLabel' => $wpcc_options['nctip'] ?? '不转换' + 'noConversionLabel' => $wpcc_options['nctip'] ?? '不转换', + 'strings' => array( + 'noConversionLabel' => $wpcc_options['nctip'] ?? __('不转换', 'wp-chinese-converter'), + 'currentLanguagePrefix' => __('当前语言:', 'wp-chinese-converter'), + 'languageSelectLabel' => __('选择语言', 'wp-chinese-converter') + ) ) ); } @@ -73,6 +88,9 @@ class WPCC_Blocks { wp_register_script( 'wpcc-variant', plugins_url( 'assets/dist/wpcc-variant.umd.js', dirname( dirname( __FILE__ ) ) ), array(), '1.1.0' ); } + // 为前端状态指示器的 dashicons 图标提供样式支持 + wp_enqueue_style( 'dashicons' ); + wp_enqueue_style( 'wpcc-blocks-frontend', plugins_url( 'assets/css/blocks-frontend.css', dirname( dirname( __FILE__ ) ) ), @@ -105,7 +123,12 @@ class WPCC_Blocks { 'zh-sg' => $wpcc_options['sgtip'] ?? '马新', 'zh-jp' => $wpcc_options['jptip'] ?? '日式' ), - 'noConversionLabel' => $wpcc_options['nctip'] ?? '不转换' + 'noConversionLabel' => $wpcc_options['nctip'] ?? '不转换', + 'strings' => array( + 'noConversionLabel' => $wpcc_options['nctip'] ?? __('不转换', 'wp-chinese-converter'), + 'currentLanguagePrefix' => __('当前语言:', 'wp-chinese-converter'), + 'languageSelectLabel' => __('选择语言', 'wp-chinese-converter') + ) ) ); } diff --git a/includes/blocks/build/language-switcher/index.js b/includes/blocks/build/language-switcher/index.js index dcb54e2..520f025 100644 --- a/includes/blocks/build/language-switcher/index.js +++ b/includes/blocks/build/language-switcher/index.js @@ -246,13 +246,8 @@ function sortLanguages(languages, sortOrder, showCurrentFirst) { customLabels }=attributes; + // Keep save output stable, do not depend on runtime settings let finalEnabledLanguages = enabledLanguages; - if (typeof wpccBlockSettings !== 'undefined' && wpccBlockSettings.enabledLanguages) { - finalEnabledLanguages = enabledLanguages.filter(lang => wpccBlockSettings.enabledLanguages.includes(lang)); - if (finalEnabledLanguages.length === 0) { - finalEnabledLanguages = wpccBlockSettings.enabledLanguages; - } - } const blockProps=a.useBlockProps.save({ className:`wpcc-language-switcher wpcc-${displayStyle} wpcc-align-${alignment} wpcc-size-${buttonSize}` @@ -270,7 +265,7 @@ function sortLanguages(languages, sortOrder, showCurrentFirst) { "data-open-in-new-window":openInNewWindow, "data-show-language-code":showLanguageCode, "data-custom-labels":JSON.stringify(customLabels) - },(0,n.__)("语言切换器","wp-chinese-converter")) + },"") ); } });})(); \ No newline at end of file diff --git a/includes/core/class-conversion-cache.php b/includes/core/class-conversion-cache.php new file mode 100644 index 0000000..fe95e26 --- /dev/null +++ b/includes/core/class-conversion-cache.php @@ -0,0 +1,270 @@ + 0, + 'misses' => 0, + 'sets' => 0 + ); + + /** + * 最大内存缓存条目数 + * + * @var int + */ + private static $max_memory_cache_size = 1000; + + /** + * 缓存过期时间(秒) + * + * @var int + */ + private static $cache_expire_time = 3600; // 1小时 + + /** + * 获取缓存的转换结果 + * + * @param string $text 原始文本 + * @param string $variant 目标语言变体 + * @return string|null 缓存的转换结果,如果不存在返回null + */ + public static function get_cached_conversion( $text, $variant ) { + if ( empty( $text ) || empty( $variant ) ) { + return null; + } + + $cache_key = self::generate_cache_key( $text, $variant ); + + // 先检查内存缓存 + if ( isset( self::$memory_cache[ $cache_key ] ) ) { + $cached_data = self::$memory_cache[ $cache_key ]; + if ( $cached_data['expires'] > time() ) { + self::$cache_stats['hits']++; + return $cached_data['result']; + } else { + // 清理过期的内存缓存 + unset( self::$memory_cache[ $cache_key ] ); + } + } + + // 检查WordPress对象缓存 + $cached_result = wp_cache_get( $cache_key, 'wpcc_conversion' ); + if ( $cached_result !== false ) { + // 同时存入内存缓存以加速后续访问 + self::set_memory_cache( $cache_key, $cached_result ); + self::$cache_stats['hits']++; + return $cached_result; + } + + self::$cache_stats['misses']++; + return null; + } + + /** + * 设置转换结果缓存 + * + * @param string $text 原始文本 + * @param string $variant 目标语言变体 + * @param string $result 转换结果 + * @return bool 是否设置成功 + */ + public static function set_cached_conversion( $text, $variant, $result ) { + if ( empty( $text ) || empty( $variant ) || empty( $result ) ) { + return false; + } + + $cache_key = self::generate_cache_key( $text, $variant ); + + // 设置内存缓存 + self::set_memory_cache( $cache_key, $result ); + + // 设置WordPress对象缓存 + wp_cache_set( $cache_key, $result, 'wpcc_conversion', self::$cache_expire_time ); + + self::$cache_stats['sets']++; + return true; + } + + /** + * 批量获取转换缓存 + * + * @param array $texts_variants 文本和变体的数组 [[text, variant], ...] + * @return array 缓存结果数组,键为cache_key,值为转换结果 + */ + public static function get_cached_conversions_batch( $texts_variants ) { + $results = array(); + + foreach ( $texts_variants as $item ) { + if ( ! is_array( $item ) || count( $item ) < 2 ) { + continue; + } + + list( $text, $variant ) = $item; + $cache_key = self::generate_cache_key( $text, $variant ); + $cached_result = self::get_cached_conversion( $text, $variant ); + + if ( $cached_result !== null ) { + $results[ $cache_key ] = $cached_result; + } + } + + return $results; + } + + /** + * 批量设置转换缓存 + * + * @param array $conversions 转换结果数组 [[text, variant, result], ...] + * @return int 成功设置的缓存数量 + */ + public static function set_cached_conversions_batch( $conversions ) { + $count = 0; + + foreach ( $conversions as $conversion ) { + if ( ! is_array( $conversion ) || count( $conversion ) < 3 ) { + continue; + } + + list( $text, $variant, $result ) = $conversion; + if ( self::set_cached_conversion( $text, $variant, $result ) ) { + $count++; + } + } + + return $count; + } + + /** + * 生成缓存键 + * + * @param string $text 原始文本 + * @param string $variant 目标语言变体 + * @return string 缓存键 + */ + private static function generate_cache_key( $text, $variant ) { + // 使用文本和变体的哈希值作为缓存键,确保唯一性 + return 'wpcc_' . md5( $text . '|' . $variant ); + } + + /** + * 设置内存缓存 + * + * @param string $cache_key 缓存键 + * @param string $result 转换结果 + */ + private static function set_memory_cache( $cache_key, $result ) { + // 如果内存缓存已满,移除最旧的条目 + if ( count( self::$memory_cache ) >= self::$max_memory_cache_size ) { + $oldest_key = array_key_first( self::$memory_cache ); + unset( self::$memory_cache[ $oldest_key ] ); + } + + self::$memory_cache[ $cache_key ] = array( + 'result' => $result, + 'expires' => time() + self::$cache_expire_time + ); + } + + /** + * 清理过期的内存缓存 + */ + public static function cleanup_expired_memory_cache() { + $current_time = time(); + $expired_keys = array(); + + foreach ( self::$memory_cache as $key => $data ) { + if ( $data['expires'] <= $current_time ) { + $expired_keys[] = $key; + } + } + + foreach ( $expired_keys as $key ) { + unset( self::$memory_cache[ $key ] ); + } + + return count( $expired_keys ); + } + + /** + * 清空所有缓存 + */ + public static function clear_all_cache() { + // 清空内存缓存 + self::$memory_cache = array(); + + // 清空WordPress对象缓存 + wp_cache_flush_group( 'wpcc_conversion' ); + + // 重置统计信息 + self::$cache_stats = array( + 'hits' => 0, + 'misses' => 0, + 'sets' => 0 + ); + } + + /** + * 获取缓存统计信息 + * + * @return array 缓存统计信息 + */ + public static function get_cache_stats() { + $stats = self::$cache_stats; + $stats['memory_cache_size'] = count( self::$memory_cache ); + $stats['hit_rate'] = $stats['hits'] + $stats['misses'] > 0 + ? round( $stats['hits'] / ( $stats['hits'] + $stats['misses'] ) * 100, 2 ) + : 0; + + return $stats; + } + + /** + * 设置缓存配置 + * + * @param array $config 配置数组 + */ + public static function set_cache_config( $config ) { + if ( isset( $config['max_memory_cache_size'] ) ) { + self::$max_memory_cache_size = (int) $config['max_memory_cache_size']; + } + + if ( isset( $config['cache_expire_time'] ) ) { + self::$cache_expire_time = (int) $config['cache_expire_time']; + } + } + + /** + * 获取缓存配置 + * + * @return array 当前缓存配置 + */ + public static function get_cache_config() { + return array( + 'max_memory_cache_size' => self::$max_memory_cache_size, + 'cache_expire_time' => self::$cache_expire_time + ); + } +} \ No newline at end of file diff --git a/includes/core/class-mediawiki-converter.php b/includes/core/class-mediawiki-converter.php index 564716b..65dee37 100644 --- a/includes/core/class-mediawiki-converter.php +++ b/includes/core/class-mediawiki-converter.php @@ -9,30 +9,50 @@ require_once dirname( __FILE__ ) . '/interface-converter.php'; class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface { private $conversion_tables_loaded = false; + private static $loaded_tables = array(); + private $lazy_load_enabled = true; public function convert( $text, $target_variant ) { if ( empty( $text ) ) { return $text; } - $this->ensure_conversion_tables_loaded(); + // 检查缓存 + $cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant ); + if ( $cached_result !== null ) { + return $cached_result; + } + $this->ensure_conversion_tables_loaded( $target_variant ); + + $result = $text; switch ( $target_variant ) { case 'zh-hans': - return $this->convert_to_hans( $text ); + $result = $this->convert_to_hans( $text ); + break; case 'zh-hant': - return $this->convert_to_hant( $text ); + $result = $this->convert_to_hant( $text ); + break; case 'zh-cn': - return $this->convert_to_cn( $text ); + $result = $this->convert_to_cn( $text ); + break; case 'zh-tw': - return $this->convert_to_tw( $text ); + $result = $this->convert_to_tw( $text ); + break; case 'zh-hk': - return $this->convert_to_hk( $text ); + $result = $this->convert_to_hk( $text ); + break; case 'zh-sg': - return $this->convert_to_sg( $text ); - default: - return $text; + $result = $this->convert_to_sg( $text ); + break; } + + // 缓存转换结果 + if ( $result !== $text ) { + WPCC_Conversion_Cache::set_cached_conversion( $text, $target_variant, $result ); + } + + return $result; } public function get_supported_variants() { @@ -44,27 +64,166 @@ class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface { } public function get_engine_info() { + $memory_usage = $this->get_memory_usage_info(); + return array( 'name' => 'MediaWiki', 'version' => '1.23.5', - 'description' => '基于 MediaWiki 的字符映射转换引擎', + 'description' => '基于MediaWiki的字符映射转换引擎', 'features' => array( '字符级精确映射', '快速转换速度', '良好的兼容性', - '支持多地区变体' + '支持多地区变体', + '按需加载优化' ), - 'memory_usage' => '约 1.5MB (转换表加载后)', - 'conversion_type' => 'character_mapping' + 'memory_usage' => $memory_usage, + 'conversion_type' => 'character_mapping', + 'lazy_load_enabled' => $this->lazy_load_enabled, + 'loaded_tables' => array_keys( array_filter( self::$loaded_tables ) ) ); } + public function get_memory_usage_info() { + $loaded_count = count( array_filter( self::$loaded_tables ) ); + $total_count = count( $this->get_supported_variants() ); + + if ( $this->lazy_load_enabled ) { + $estimated_usage = $loaded_count * 0.25; // MB per table approximately + return sprintf( '约 %.1fMB (%d/%d 表已加载, 按需加载开启)', $estimated_usage, $loaded_count, $total_count ); + } else { + return '约 1.5MB (所有转换表已加载)'; + } + } + + public function enable_lazy_loading( $enable = true ) { + $this->lazy_load_enabled = $enable; + } + + public function is_lazy_loading_enabled() { + return $this->lazy_load_enabled; + } + + public static function get_loaded_tables_status() { + return self::$loaded_tables; + } + + public static function unload_table( $variant ) { + if ( isset( self::$loaded_tables[ $variant ] ) ) { + unset( self::$loaded_tables[ $variant ] ); + + // 清理对应的全局变量(这里只能标记,PHP不能真正释放全局变量内存) + return true; + } + return false; + } + public function is_available() { $conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php'; return file_exists( $conversion_file ); } - private function ensure_conversion_tables_loaded() { + private function ensure_conversion_tables_loaded( $target_variant = null ) { + if ( $this->lazy_load_enabled && $target_variant ) { + // 按需加载特定语言变体的转换表 + $this->ensure_specific_table_loaded( $target_variant ); + } else { + // 传统方式:加载所有转换表 + $this->load_all_conversion_tables(); + } + } + + private function ensure_specific_table_loaded( $target_variant ) { + if ( isset( self::$loaded_tables[ $target_variant ] ) ) { + return; // 已经加载 + } + + $conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php'; + if ( ! file_exists( $conversion_file ) ) { + return; + } + + // 只加载需要的转换表 + global $zh2Hans, $zh2Hant, $zh2CN, $zh2TW, $zh2HK, $zh2SG; + + switch ( $target_variant ) { + case 'zh-hans': + if ( ! isset( $zh2Hans ) ) { + $this->load_conversion_table_partial( 'zh2Hans' ); + } + self::$loaded_tables['zh-hans'] = true; + break; + + case 'zh-hant': + if ( ! isset( $zh2Hant ) ) { + $this->load_conversion_table_partial( 'zh2Hant' ); + } + self::$loaded_tables['zh-hant'] = true; + break; + + case 'zh-cn': + if ( ! isset( $zh2CN ) || ! isset( $zh2Hans ) ) { + $this->load_conversion_table_partial( array( 'zh2CN', 'zh2Hans' ) ); + } + self::$loaded_tables['zh-cn'] = true; + break; + + case 'zh-tw': + if ( ! isset( $zh2TW ) || ! isset( $zh2Hant ) ) { + $this->load_conversion_table_partial( array( 'zh2TW', 'zh2Hant' ) ); + } + self::$loaded_tables['zh-tw'] = true; + break; + + case 'zh-hk': + if ( ! isset( $zh2HK ) || ! isset( $zh2Hant ) ) { + $this->load_conversion_table_partial( array( 'zh2HK', 'zh2Hant' ) ); + } + self::$loaded_tables['zh-hk'] = true; + break; + + case 'zh-sg': + if ( ! isset( $zh2SG ) || ! isset( $zh2Hans ) ) { + $this->load_conversion_table_partial( array( 'zh2SG', 'zh2Hans' ) ); + } + self::$loaded_tables['zh-sg'] = true; + break; + + default: + $this->load_all_conversion_tables(); + break; + } + } + + private function load_conversion_table_partial( $table_names ) { + $conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php'; + + if ( ! is_array( $table_names ) ) { + $table_names = array( $table_names ); + } + + // 读取文件内容并解析指定的转换表 + $file_content = file_get_contents( $conversion_file ); + + foreach ( $table_names as $table_name ) { + $pattern = '/\$' . preg_quote( $table_name, '/' ) . '\s*=\s*array\s*\((.*?)\);/s'; + if ( preg_match( $pattern, $file_content, $matches ) ) { + $table_code = '$' . $table_name . ' = array(' . $matches[1] . ');'; + eval( $table_code ); + + // 将变量设置为全局 + $GLOBALS[ $table_name ] = ${$table_name}; + } + } + + // 加载额外转换文件 + $extra_conversion_file = WP_CONTENT_DIR . '/extra_zhconversion.php'; + if ( file_exists( $extra_conversion_file ) ) { + require_once $extra_conversion_file; + } + } + + private function load_all_conversion_tables() { if ( $this->conversion_tables_loaded ) { return; } @@ -80,6 +239,16 @@ class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface { } } + // 标记所有表为已加载 + self::$loaded_tables = array( + 'zh-hans' => true, + 'zh-hant' => true, + 'zh-cn' => true, + 'zh-tw' => true, + 'zh-hk' => true, + 'zh-sg' => true + ); + $this->conversion_tables_loaded = true; } diff --git a/includes/core/class-opencc-converter.php b/includes/core/class-opencc-converter.php index 573d953..8842971 100644 --- a/includes/core/class-opencc-converter.php +++ b/includes/core/class-opencc-converter.php @@ -80,8 +80,60 @@ class WPCC_OpenCC_Converter implements WPCC_Converter_Interface { return $texts; } + if ( ! is_array( $texts ) ) { + return $texts; + } + + // 检查缓存 + $cached_results = array(); + $uncached_texts = array(); + $uncached_indices = array(); + + foreach ( $texts as $index => $text ) { + $cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant ); + if ( $cached_result !== null ) { + $cached_results[ $index ] = $cached_result; + } else { + $uncached_texts[] = $text; + $uncached_indices[] = $index; + } + } + + // 如果所有内容都在缓存中,直接返回 + if ( empty( $uncached_texts ) ) { + $results = array(); + foreach ( $texts as $index => $text ) { + $results[] = $cached_results[ $index ]; + } + return $results; + } + try { - return OpenCC::convert( $texts, $this->strategy_map[ $target_variant ] ); + // 转换未缓存的内容 + $converted_texts = OpenCC::convert( $uncached_texts, $this->strategy_map[ $target_variant ] ); + + // 合并结果并缓存新转换的内容 + $results = array(); + $uncached_index = 0; + + foreach ( $texts as $index => $original_text ) { + if ( isset( $cached_results[ $index ] ) ) { + $results[] = $cached_results[ $index ]; + } else { + $converted_text = is_array( $converted_texts ) ? $converted_texts[ $uncached_index ] : $converted_texts; + $results[] = $converted_text; + + // 缓存转换结果 + if ( $converted_text !== $original_text ) { + WPCC_Conversion_Cache::set_cached_conversion( $original_text, $target_variant, $converted_text ); + } + + $uncached_index++; + } + } + + return $results; + } catch ( Exception $e ) { error_log( 'WPCC OpenCC Batch Conversion Error: ' . $e->getMessage() ); return $texts; diff --git a/includes/core/class-wpcc-config.php b/includes/core/class-wpcc-config.php new file mode 100644 index 0000000..c3787f9 --- /dev/null +++ b/includes/core/class-wpcc-config.php @@ -0,0 +1,267 @@ +load_options(); + $this->init_languages(); + } + + public static function get_instance(): self { + if ( null === self::$instance ) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * 加载插件选项 + */ + private function load_options(): void { + $this->options = get_wpcc_option( 'wpcc_options' ); + if ( empty( $this->options ) ) { + $this->options = $this->get_default_options(); + } + } + + /** + * 获取默认配置 + */ + private function get_default_options(): array { + return [ + 'wpcc_search_conversion' => 1, + 'wpcc_used_langs' => [ + 'zh-hans', + 'zh-hant', + 'zh-cn', + 'zh-hk', + 'zh-sg', + 'zh-tw', + ], + 'wpcc_browser_redirect' => 0, + 'wpcc_auto_language_recong' => 0, + 'wpcc_flag_option' => 1, + 'wpcc_use_cookie_variant' => 0, + 'wpcc_use_fullpage_conversion' => 1, + 'wpcco_use_sitemap' => 1, + 'wpcc_trackback_plugin_author' => 0, + 'wpcc_add_author_link' => 0, + 'wpcc_use_permalink' => 0, + 'wpcc_no_conversion_tag' => '', + 'wpcc_no_conversion_ja' => 0, + 'wpcc_no_conversion_qtag' => 0, + 'wpcc_enable_post_conversion' => 0, + 'wpcc_post_conversion_target' => 'zh-cn', + 'wpcc_engine' => 'opencc', + 'nctip' => '', + ]; + } + + /** + * 初始化语言配置 + */ + private function init_languages(): void { + $this->languages = [ + 'zh-cn' => [ 'zhconversion_cn', 'cntip', __( '简体中文', 'wp-chinese-converter' ), 'zh-CN' ], + 'zh-tw' => [ 'zhconversion_tw', 'twtip', __( '台灣正體', 'wp-chinese-converter' ), 'zh-TW' ], + 'zh-hk' => [ 'zhconversion_hk', 'hktip', __( '港澳繁體', 'wp-chinese-converter' ), 'zh-HK' ], + 'zh-hans' => [ 'zhconversion_hans', 'hanstip', __( '简体中文', 'wp-chinese-converter' ), 'zh-Hans' ], + 'zh-hant' => [ 'zhconversion_hant', 'hanttip', __( '繁体中文', 'wp-chinese-converter' ), 'zh-Hant' ], + 'zh-sg' => [ 'zhconversion_sg', 'sgtip', __( '马新简体', 'wp-chinese-converter' ), 'zh-SG' ], + 'zh-jp' => [ 'zhconversion_jp', 'jptip', __( '日式汉字', 'wp-chinese-converter' ), 'zh-JP' ], + ]; + } + + /** + * 获取配置选项 + */ + public function get_option( string $key, $default = null ) { + return $this->options[$key] ?? $default; + } + + /** + * 设置配置选项 + */ + public function set_option( string $key, $value ): void { + $this->options[$key] = $value; + } + + /** + * 更新配置选项到数据库 + */ + public function save_options(): bool { + return update_wpcc_option( 'wpcc_options', $this->options ); + } + + /** + * 获取所有配置选项 + */ + public function get_all_options(): array { + return $this->options; + } + + /** + * 获取语言配置 + */ + public function get_languages(): array { + return $this->languages; + } + + /** + * 获取特定语言配置 + */ + public function get_language( string $lang_code ): ?array { + return $this->languages[$lang_code] ?? null; + } + + /** + * 设置目标语言 + */ + public function set_target_lang( string $lang ): void { + $this->target_lang = $lang; + } + + /** + * 获取目标语言 + */ + public function get_target_lang(): string { + return $this->target_lang; + } + + /** + * 设置无转换URL + */ + public function set_noconversion_url( string $url ): void { + $this->noconversion_url = $url; + } + + /** + * 获取无转换URL + */ + public function get_noconversion_url(): string { + return $this->noconversion_url; + } + + /** + * 设置语言URL映射 + */ + public function set_langs_urls( array $urls ): void { + $this->langs_urls = $urls; + } + + /** + * 获取语言URL映射 + */ + public function get_langs_urls(): array { + return $this->langs_urls; + } + + /** + * 设置重定向标志 + */ + public function set_redirect_to( bool $redirect ): void { + $this->redirect_to = $redirect; + } + + /** + * 获取重定向标志 + */ + public function get_redirect_to(): bool { + return $this->redirect_to; + } + + /** + * 设置直接转换标志 + */ + public function set_direct_conversion_flag( bool $flag ): void { + $this->direct_conversion_flag = $flag; + } + + /** + * 获取直接转换标志 + */ + public function get_direct_conversion_flag(): bool { + return $this->direct_conversion_flag; + } + + /** + * 添加调试数据 + */ + public function add_debug_data( string $key, $data ): void { + if ( defined( 'wpcc_DEBUG' ) && wpcc_DEBUG ) { + $this->debug_data[$key] = $data; + } + } + + /** + * 获取调试数据 + */ + public function get_debug_data(): array { + return $this->debug_data; + } + + /** + * 检查是否启用了指定功能 + */ + public function is_feature_enabled( string $feature ): bool { + return (bool) $this->get_option( $feature, false ); + } + + /** + * 获取启用的语言列表 + */ + public function get_enabled_languages(): array { + return $this->get_option( 'wpcc_used_langs', [] ); + } + + /** + * 检查语言是否启用 + */ + public function is_language_enabled( string $lang_code ): bool { + return in_array( $lang_code, $this->get_enabled_languages(), true ); + } + + /** + * 获取转换引擎 + */ + public function get_conversion_engine(): string { + return $this->get_option( 'wpcc_engine', 'opencc' ); + } + + /** + * 验证配置完整性 + */ + public function validate_config(): array { + $errors = []; + + if ( empty( $this->get_enabled_languages() ) ) { + $errors[] = 'No languages enabled'; + } + + $engine = $this->get_conversion_engine(); + if ( ! in_array( $engine, ['opencc', 'mediawiki'] ) ) { + $errors[] = 'Invalid conversion engine: ' . $engine; + } + + return $errors; + } +} \ No newline at end of file diff --git a/includes/core/class-wpcc-exception-handler.php b/includes/core/class-wpcc-exception-handler.php new file mode 100644 index 0000000..cfb681a --- /dev/null +++ b/includes/core/class-wpcc-exception-handler.php @@ -0,0 +1,295 @@ +error_code = $error_code; + $this->context = $context; + } + + public function get_error_code(): string { + return $this->error_code; + } + + public function get_context(): array { + return $this->context; + } +} + +/** + * 转换异常 + */ +class WPCC_Conversion_Exception extends WPCC_Exception { + public function __construct( string $message, string $variant = '', string $text_preview = '', ?Throwable $previous = null ) { + $context = [ + 'variant' => $variant, + 'text_preview' => mb_substr( $text_preview, 0, 50 ) . ( mb_strlen( $text_preview ) > 50 ? '...' : '' ) + ]; + + parent::__construct( $message, 'CONVERSION_ERROR', $context, 0, $previous ); + } +} + +/** + * 缓存异常 + */ +class WPCC_Cache_Exception extends WPCC_Exception { + public function __construct( string $message, string $cache_key = '', ?Throwable $previous = null ) { + $context = [ 'cache_key' => $cache_key ]; + parent::__construct( $message, 'CACHE_ERROR', $context, 0, $previous ); + } +} + +/** + * 配置异常 + */ +class WPCC_Config_Exception extends WPCC_Exception { + public function __construct( string $message, string $config_key = '', $config_value = null, ?Throwable $previous = null ) { + $context = [ + 'config_key' => $config_key, + 'config_value' => is_scalar( $config_value ) ? $config_value : gettype( $config_value ) + ]; + parent::__construct( $message, 'CONFIG_ERROR', $context, 0, $previous ); + } +} + +/** + * WPCC异常处理器 + */ +final class WPCC_Exception_Handler { + + private static $error_counts = []; + private static $max_errors_per_type = 10; + private static $error_log_enabled = true; + private static $fallback_strategies = []; + + /** + * 初始化异常处理器 + */ + public static function init(): void { + // 注册默认的降级策略 + self::register_fallback_strategy( 'CONVERSION_ERROR', [ self::class, 'conversion_fallback' ] ); + self::register_fallback_strategy( 'CACHE_ERROR', [ self::class, 'cache_fallback' ] ); + self::register_fallback_strategy( 'CONFIG_ERROR', [ self::class, 'config_fallback' ] ); + } + + /** + * 安全地执行操作,带异常处理 + */ + public static function safe_execute( callable $operation, $fallback_value = null, string $operation_name = 'unknown' ) { + try { + return $operation(); + } catch ( WPCC_Exception $e ) { + return self::handle_wpcc_exception( $e, $fallback_value, $operation_name ); + } catch ( Throwable $e ) { + return self::handle_generic_exception( $e, $fallback_value, $operation_name ); + } + } + + /** + * 处理WPCC特定异常 + */ + public static function handle_wpcc_exception( WPCC_Exception $exception, $fallback_value = null, string $operation_name = 'unknown' ) { + $error_code = $exception->get_error_code(); + + // 记录错误 + self::log_error( $exception, $operation_name ); + + // 检查错误频率限制 + if ( self::should_suppress_error( $error_code ) ) { + return $fallback_value; + } + + // 尝试执行降级策略 + if ( isset( self::$fallback_strategies[ $error_code ] ) ) { + try { + return call_user_func( self::$fallback_strategies[ $error_code ], $exception, $fallback_value ); + } catch ( Throwable $fallback_error ) { + self::log_error( $fallback_error, "fallback_for_{$operation_name}" ); + } + } + + return $fallback_value; + } + + /** + * 处理通用异常 + */ + public static function handle_generic_exception( Throwable $exception, $fallback_value = null, string $operation_name = 'unknown' ) { + self::log_error( $exception, $operation_name ); + + // 对于严重错误,可能需要通知管理员 + if ( $exception instanceof Error || $exception instanceof ParseError ) { + self::notify_admin_of_critical_error( $exception, $operation_name ); + } + + return $fallback_value; + } + + /** + * 注册降级策略 + */ + public static function register_fallback_strategy( string $error_code, callable $strategy ): void { + self::$fallback_strategies[ $error_code ] = $strategy; + } + + /** + * 转换异常的降级策略 + */ + public static function conversion_fallback( WPCC_Conversion_Exception $exception, $fallback_value = null ) { + $context = $exception->get_context(); + $original_text = $context['text_preview'] ?? ''; + + // 如果有原始文本,返回原文本而不是null + if ( ! empty( $original_text ) && $original_text !== '...' ) { + return $original_text; + } + + return $fallback_value; + } + + /** + * 缓存异常的降级策略 + */ + public static function cache_fallback( WPCC_Cache_Exception $exception, $fallback_value = null ) { + // 缓存问题时,继续执行但不使用缓存 + return $fallback_value; + } + + /** + * 配置异常的降级策略 + */ + public static function config_fallback( WPCC_Config_Exception $exception, $fallback_value = null ) { + $context = $exception->get_context(); + $config_key = $context['config_key'] ?? ''; + + // 返回默认配置值 + $defaults = [ + 'wpcc_engine' => 'opencc', + 'wpcc_use_fullpage_conversion' => 1, + 'wpcc_search_conversion' => 1, + 'wpcc_used_langs' => [ 'zh-cn', 'zh-tw' ] + ]; + + return $defaults[ $config_key ] ?? $fallback_value; + } + + /** + * 记录错误 + */ + private static function log_error( Throwable $exception, string $operation_name ): void { + if ( ! self::$error_log_enabled ) { + return; + } + + $error_key = get_class( $exception ); + self::$error_counts[ $error_key ] = ( self::$error_counts[ $error_key ] ?? 0 ) + 1; + + $log_message = sprintf( + 'WPCC Error in %s: %s [%s:%d] Context: %s', + $operation_name, + $exception->getMessage(), + basename( $exception->getFile() ), + $exception->getLine(), + $exception instanceof WPCC_Exception ? json_encode( $exception->get_context() ) : 'N/A' + ); + + error_log( $log_message ); + + // 在调试模式下记录堆栈跟踪 + if ( WP_DEBUG && defined( 'WPCC_DEBUG_TRACE' ) && WPCC_DEBUG_TRACE ) { + error_log( 'WPCC Error Stack Trace: ' . $exception->getTraceAsString() ); + } + } + + /** + * 检查是否应该抑制错误(防止日志洪水) + */ + private static function should_suppress_error( string $error_code ): bool { + $error_key = "wpcc_error_{$error_code}"; + $count = self::$error_counts[ $error_key ] ?? 0; + + return $count > self::$max_errors_per_type; + } + + /** + * 通知管理员关键错误 + */ + private static function notify_admin_of_critical_error( Throwable $exception, string $operation_name ): void { + // 防止发送过多邮件 + $notification_key = 'wpcc_critical_error_' . md5( $exception->getMessage() ); + $last_notification = get_transient( $notification_key ); + + if ( $last_notification ) { + return; + } + + // 设置12小时的冷却期 + set_transient( $notification_key, time(), 12 * HOUR_IN_SECONDS ); + + $admin_email = get_option( 'admin_email' ); + if ( ! $admin_email ) { + return; + } + + $subject = sprintf( 'WP Chinese Converter Critical Error on %s', get_bloginfo( 'name' ) ); + $message = sprintf( + "A critical error occurred in WP Chinese Converter:\n\nOperation: %s\nError: %s\nFile: %s:%d\n\nPlease check your error logs for more details.\n\nTime: %s", + $operation_name, + $exception->getMessage(), + $exception->getFile(), + $exception->getLine(), + current_time( 'mysql' ) + ); + + wp_mail( $admin_email, $subject, $message ); + } + + /** + * 获取错误统计 + */ + public static function get_error_stats(): array { + return [ + 'error_counts' => self::$error_counts, + 'total_errors' => array_sum( self::$error_counts ), + 'error_log_enabled' => self::$error_log_enabled, + 'max_errors_per_type' => self::$max_errors_per_type + ]; + } + + /** + * 重置错误计数 + */ + public static function reset_error_counts(): void { + self::$error_counts = []; + } + + /** + * 设置错误记录状态 + */ + public static function set_error_logging( bool $enabled ): void { + self::$error_log_enabled = $enabled; + } + + /** + * 设置每种错误类型的最大记录数 + */ + public static function set_max_errors_per_type( int $max ): void { + self::$max_errors_per_type = max( 1, $max ); + } +} + +// 初始化异常处理器 +WPCC_Exception_Handler::init(); \ No newline at end of file diff --git a/includes/core/class-wpcc-extension.php b/includes/core/class-wpcc-extension.php new file mode 100644 index 0000000..52497ec --- /dev/null +++ b/includes/core/class-wpcc-extension.php @@ -0,0 +1,321 @@ +extension_slug = $this->get_extension_slug(); + $this->check_core_compatibility(); + $this->init_license_system(); + + parent::__construct(); + } + + /** + * 获取扩展唯一标识符 + */ + abstract protected function get_extension_slug(): string; + + /** + * 检查核心插件兼容性 + */ + private function check_core_compatibility(): bool { + if ( ! defined( 'wpcc_VERSION' ) ) { + add_action( 'admin_notices', [ $this, 'core_plugin_missing_notice' ] ); + return false; + } + + if ( version_compare( wpcc_VERSION, $this->required_core_version, '<' ) ) { + add_action( 'admin_notices', [ $this, 'core_plugin_outdated_notice' ] ); + return false; + } + + return true; + } + + /** + * 初始化许可证系统 + */ + private function init_license_system(): void { + if ( ! $this->is_premium ) { + return; + } + + $this->license_key = get_option( $this->extension_slug . '_license_key', '' ); + $this->license_status = get_option( $this->extension_slug . '_license_status', 'inactive' ); + + add_action( 'admin_init', [ $this, 'check_license_status' ] ); + add_action( 'wp_ajax_' . $this->extension_slug . '_activate_license', [ $this, 'activate_license' ] ); + add_action( 'wp_ajax_' . $this->extension_slug . '_deactivate_license', [ $this, 'deactivate_license' ] ); + } + + /** + * 检查许可证状态 + */ + public function check_license_status(): void { + if ( empty( $this->license_key ) ) { + return; + } + + // 每天检查一次许可证状态 + $last_check = get_option( $this->extension_slug . '_license_last_check', 0 ); + if ( time() - $last_check < DAY_IN_SECONDS ) { + return; + } + + $response = wp_remote_post( $this->update_server_url . '/api/license/check', [ + 'body' => [ + 'license_key' => $this->license_key, + 'product_slug' => $this->extension_slug, + 'site_url' => home_url() + ], + 'timeout' => 15 + ] ); + + if ( is_wp_error( $response ) ) { + return; + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( isset( $body['status'] ) ) { + $this->license_status = $body['status']; + update_option( $this->extension_slug . '_license_status', $this->license_status ); + } + + update_option( $this->extension_slug . '_license_last_check', time() ); + } + + /** + * 激活许可证 + */ + public function activate_license(): void { + if ( ! check_ajax_referer( $this->extension_slug . '_license_nonce', 'nonce' ) ) { + wp_die( 'Security check failed' ); + } + + $license_key = sanitize_text_field( $_POST['license_key'] ?? '' ); + if ( empty( $license_key ) ) { + wp_send_json_error( 'License key is required' ); + } + + $response = wp_remote_post( $this->update_server_url . '/api/license/activate', [ + 'body' => [ + 'license_key' => $license_key, + 'product_slug' => $this->extension_slug, + 'site_url' => home_url() + ] + ] ); + + if ( is_wp_error( $response ) ) { + wp_send_json_error( $response->get_error_message() ); + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( isset( $body['success'] ) && $body['success'] ) { + update_option( $this->extension_slug . '_license_key', $license_key ); + update_option( $this->extension_slug . '_license_status', 'active' ); + $this->license_key = $license_key; + $this->license_status = 'active'; + wp_send_json_success( 'License activated successfully' ); + } else { + wp_send_json_error( $body['message'] ?? 'License activation failed' ); + } + } + + /** + * 停用许可证 + */ + public function deactivate_license(): void { + if ( ! check_ajax_referer( $this->extension_slug . '_license_nonce', 'nonce' ) ) { + wp_die( 'Security check failed' ); + } + + wp_remote_post( $this->update_server_url . '/api/license/deactivate', [ + 'body' => [ + 'license_key' => $this->license_key, + 'product_slug' => $this->extension_slug, + 'site_url' => home_url() + ] + ] ); + + delete_option( $this->extension_slug . '_license_key' ); + delete_option( $this->extension_slug . '_license_status' ); + $this->license_key = ''; + $this->license_status = 'inactive'; + + wp_send_json_success( 'License deactivated successfully' ); + } + + /** + * 是否已授权 + */ + public function is_licensed(): bool { + return $this->license_status === 'active' || ! $this->is_premium; + } + + /** + * 获取许可证信息 + */ + public function get_license_info(): array { + return [ + 'key' => $this->license_key, + 'status' => $this->license_status, + 'is_premium' => $this->is_premium, + 'pricing_tier' => $this->pricing_tier + ]; + } + + /** + * 渲染许可证设置界面 + */ + public function render_license_settings(): string { + if ( ! $this->is_premium ) { + return ''; + } + + ob_start(); + ?> +
+

get_name() ); ?> License

+ + license_status === 'active' ): ?> +
+

✅ License is active

+
+ + +
+

⚠️ Please enter your license key to activate this extension

+
+ + + +
+ + + +
+

+ get_name() ); ?> requires + WP Chinese Converter to be installed and activated. +

+
+ +
+

+ get_name() ); ?> requires + WP Chinese Converter version required_core_version ); ?> or higher. + Current version: +

+
+ is_licensed(); + } + + /** + * 获取扩展状态(包含许可证信息) + */ + public function get_status(): array { + $status = parent::get_status(); + $status['license'] = $this->get_license_info(); + return $status; + } +} \ No newline at end of file diff --git a/includes/core/class-wpcc-main.php b/includes/core/class-wpcc-main.php new file mode 100644 index 0000000..89ec156 --- /dev/null +++ b/includes/core/class-wpcc-main.php @@ -0,0 +1,717 @@ +config = WPCC_Config::get_instance(); + $this->converter_factory = new WPCC_Converter_Factory(); + $this->module_manager = WPCC_Module_Manager::get_instance(); + $this->cache = new WPCC_Conversion_Cache(); + + $this->init_hooks(); + } + + public static function get_instance(): self { + if ( null === self::$instance ) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * 初始化WordPress钩子 + */ + private function init_hooks(): void { + add_action( 'init', [ $this, 'init' ], 1 ); + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); + add_filter( 'query_vars', [ $this, 'add_query_vars' ] ); + add_action( 'parse_request', [ $this, 'parse_request' ] ); + add_action( 'template_redirect', [ $this, 'template_redirect' ], -100 ); + + // 调试模式钩子 + if ( WP_DEBUG || ( defined( 'wpcc_DEBUG' ) && wpcc_DEBUG ) ) { + add_action( 'init', [ $this, 'flush_rewrite_rules' ] ); + add_action( 'wp_footer', [ $this, 'debug_output' ] ); + } + } + + /** + * 插件初始化 + */ + public function init(): void { + // 验证配置 + $config_errors = $this->config->validate_config(); + if ( ! empty( $config_errors ) ) { + $this->handle_config_errors( $config_errors ); + return; + } + + // 初始化模块 + $this->init_modules(); + + // 设置重写规则 + if ( $this->config->is_feature_enabled( 'wpcc_use_permalink' ) ) { + $this->setup_rewrite_rules(); + } + + // 处理评论提交 + $this->handle_comment_submission(); + + // 修复首页显示问题 + if ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) ) { + add_action( 'parse_query', [ $this, 'fix_homepage_query' ] ); + } + + // 注册区块渲染过滤器 + add_filter( 'render_block', [ $this, 'render_no_conversion_block' ], 5, 2 ); + } + + /** + * 处理配置错误 + */ + private function handle_config_errors( array $errors ): void { + if ( is_admin() ) { + foreach ( $errors as $error ) { + add_action( 'admin_notices', function() use ( $error ) { + printf( + '

WP Chinese Converter: %s

', + esc_html( $error ) + ); + }); + } + } + + // 记录错误日志 + error_log( 'WPCC Config Errors: ' . implode( ', ', $errors ) ); + } + + /** + * 初始化模块 + */ + private function init_modules(): void { + $modules_dir = dirname( dirname( __FILE__ ) ) . '/modules/'; + + // 注册核心模块 + $this->module_manager->register_module( 'WPCC_Cache_Addon', $modules_dir . 'wpcc-cache-addon.php' ); + $this->module_manager->register_module( 'WPCC_Network', $modules_dir . 'wpcc-network.php' ); + $this->module_manager->register_module( 'WPCC_Rest_Api', $modules_dir . 'wpcc-rest-api.php' ); + $this->module_manager->register_module( 'WPCC_Modern_Cache', $modules_dir . 'wpcc-modern-cache.php' ); + $this->module_manager->register_module( 'WPCC_SEO_Enhancement', $modules_dir . 'wpcc-seo-enhancement.php' ); + + // 自动发现并加载模块 + $this->module_manager->auto_discover_modules(); + } + + /** + * 加载脚本和样式 + */ + public function enqueue_scripts(): void { + wp_register_script( + 'wpcc-variant', + plugins_url( '/assets/dist/wpcc-variant.umd.js', dirname( dirname( __FILE__ ) ) ), + [], + wpcc_VERSION + ); + + wp_register_script( + 'wpcc-block-script', + plugins_url( '/assets/js/wpcc-block-script-ok.js', dirname( dirname( __FILE__ ) ) ), + [ 'wp-blocks', 'wp-element', 'wpcc-variant' ], + wpcc_VERSION + ); + + wp_enqueue_script( [ 'wpcc-variant', 'wpcc-block-script' ] ); + + wp_localize_script( 'wpcc-block-script', 'wpcc_config', [ + 'use_permalink' => $this->config->get_option( 'wpcc_use_permalink', 0 ), + 'target_lang' => $this->config->get_target_lang(), + 'enabled_languages' => $this->config->get_enabled_languages(), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wpcc_nonce' ) + ]); + } + + /** + * 添加查询变量 + */ + public function add_query_vars( array $vars ): array { + $vars[] = 'variant'; + return $vars; + } + + /** + * 解析请求 + */ + public function parse_request( $wp ): void { + if ( is_robots() ) { + return; + } + + if ( is_404() ) { + $this->config->set_noconversion_url( home_url( '/' ) ); + $this->config->set_target_lang( '' ); + return; + } + + // 设置无转换URL + $noconversion_url = $this->get_noconversion_url(); + $this->config->set_noconversion_url( $noconversion_url ); + + // 获取目标语言 + $target_lang = $this->determine_target_language( $wp ); + $this->config->set_target_lang( $target_lang ); + + // 处理搜索转换 + $this->handle_search_conversion(); + + // 设置Cookie + $this->handle_language_cookie( $target_lang ); + } + + /** + * 确定目标语言 + */ + private function determine_target_language( $wp ): string { + $request_lang = $wp->query_vars['variant'] ?? ''; + $cookie_lang = $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ?? ''; + + // 检查URL参数中的语言 + if ( $request_lang && $this->config->is_language_enabled( $request_lang ) ) { + return $request_lang; + } + + // 处理特殊的'zh'重定向 + if ( $request_lang === 'zh' && ! is_admin() ) { + $this->handle_zh_redirect(); + return ''; + } + + // 浏览器语言检测 + if ( $this->config->is_feature_enabled( 'wpcc_browser_redirect' ) ) { + $browser_lang = $this->detect_browser_language(); + if ( $browser_lang ) { + return $browser_lang; + } + } + + // Cookie语言偏好 + if ( $this->config->is_feature_enabled( 'wpcc_use_cookie_variant' ) && $cookie_lang ) { + if ( $this->config->is_language_enabled( $cookie_lang ) ) { + return $cookie_lang; + } + } + + return ''; + } + + /** + * 处理'zh'重定向 + */ + private function handle_zh_redirect(): void { + $cookie_key = 'wpcc_variant_' . COOKIEHASH; + + if ( $this->config->is_feature_enabled( 'wpcc_use_cookie_variant' ) ) { + setcookie( $cookie_key, 'zh', time() + 30000000, COOKIEPATH, COOKIE_DOMAIN ); + } else { + setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', 0, COOKIEPATH, COOKIE_DOMAIN ); + } + + wp_redirect( $this->config->get_noconversion_url() ); + exit; + } + + /** + * 检测浏览器语言 + */ + private function detect_browser_language(): string { + $accept_language = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''; + if ( empty( $accept_language ) ) { + return ''; + } + + return wpcc_get_prefered_language( + $accept_language, + $this->config->get_enabled_languages(), + $this->config->get_option( 'wpcc_auto_language_recong', 0 ) + ) ?: ''; + } + + /** + * 处理语言Cookie + */ + private function handle_language_cookie( string $target_lang ): void { + if ( ! $target_lang || ! $this->config->is_feature_enabled( 'wpcc_use_cookie_variant' ) ) { + return; + } + + $cookie_key = 'wpcc_variant_' . COOKIEHASH; + $current_cookie = $_COOKIE[$cookie_key] ?? ''; + + if ( $current_cookie !== $target_lang ) { + setcookie( $cookie_key, $target_lang, time() + 30000000, COOKIEPATH, COOKIE_DOMAIN ); + } + } + + /** + * 处理搜索转换 + */ + private function handle_search_conversion(): void { + $search_option = $this->config->get_option( 'wpcc_search_conversion', 0 ); + $target_lang = $this->config->get_target_lang(); + + if ( $search_option === 2 || ( $target_lang && $search_option === 1 ) ) { + add_filter( 'posts_where', [ $this, 'filter_search_query' ], 100 ); + add_filter( 'posts_distinct', function() { return 'DISTINCT'; } ); + } + } + + /** + * 模板重定向处理 + */ + public function template_redirect(): void { + $this->set_language_urls(); + + // 处理重定向 + if ( ! is_404() && $this->config->get_redirect_to() && ! is_admin() ) { + $redirect_url = $this->config->get_langs_urls()[ $this->config->get_redirect_to() ]; + setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', 0, COOKIEPATH, COOKIE_DOMAIN ); + wp_redirect( $redirect_url, 302 ); + exit; + } + + $target_lang = $this->config->get_target_lang(); + if ( ! $target_lang ) { + return; + } + + // 添加评论表单语言参数 + add_action( 'comment_form', [ $this, 'add_comment_form_variant' ] ); + + // 执行转换 + $this->do_conversion(); + } + + /** + * 设置各语言版本的URL + */ + private function set_language_urls(): void { + $langs_urls = []; + $noconversion_url = $this->config->get_noconversion_url(); + $use_permalink = $this->config->is_feature_enabled( 'wpcc_use_permalink' ); + + foreach ( $this->config->get_enabled_languages() as $lang ) { + if ( $noconversion_url === home_url( '/' ) && $use_permalink ) { + $langs_urls[$lang] = $noconversion_url . $lang . '/'; + } else { + $langs_urls[$lang] = $this->convert_link( $noconversion_url, $lang ); + } + } + + $this->config->set_langs_urls( $langs_urls ); + } + + /** + * 执行转换 + */ + private function do_conversion(): void { + // 加载转换表 + $this->load_conversion_table(); + + // 输出头部信息 + add_action( 'wp_head', [ $this, 'output_header' ] ); + + if ( ! $this->config->get_direct_conversion_flag() ) { + // 移除默认canonical并添加自定义的 + remove_action( 'wp_head', 'rel_canonical' ); + add_action( 'wp_head', [ $this, 'output_canonical' ] ); + + // 添加链接转换过滤器 + $this->add_link_conversion_filters(); + } + + // 内容转换过滤器 + $this->add_content_conversion_filters(); + + // 全页面转换模式 + if ( $this->config->is_feature_enabled( 'wpcc_use_fullpage_conversion' ) ) { + ob_start( [ $this, 'full_page_conversion_callback' ] ); + } + } + + /** + * 获取配置实例 + */ + public function get_config(): WPCC_Config { + return $this->config; + } + + /** + * 获取转换器工厂 + */ + public function get_converter_factory(): WPCC_Converter_Factory { + return $this->converter_factory; + } + + /** + * 获取模块管理器 + */ + public function get_module_manager(): WPCC_Module_Manager { + return $this->module_manager; + } + + /** + * 获取缓存实例 + */ + public function get_cache(): WPCC_Conversion_Cache { + return $this->cache; + } + + /** + * 调试输出 + */ + public function debug_output(): void { + if ( ! ( WP_DEBUG || ( defined( 'wpcc_DEBUG' ) && wpcc_DEBUG ) ) ) { + return; + } + + $debug_data = $this->config->get_debug_data(); + if ( empty( $debug_data ) ) { + return; + } + + echo ''; + echo ''; + } + + /** + * 刷新重写规则(调试模式) + */ + public function flush_rewrite_rules(): void { + global $wp_rewrite; + $wp_rewrite->flush_rules(); + } + + // 这里添加其他必要的私有方法... + + /** + * 获取无转换URL + */ + private function get_noconversion_url(): string { + $enabled_langs = $this->config->get_enabled_languages(); + $reg = implode( '|', $enabled_langs ); + + $protocol = is_ssl() ? 'https://' : 'http://'; + $host = $_SERVER['HTTP_HOST'] ?? ''; + $uri = $_SERVER['REQUEST_URI'] ?? ''; + + $tmp = trim( strtolower( remove_query_arg( 'variant', $protocol . $host . $uri ) ) ); + + if ( preg_match( '/^(.*)\\/(' . $reg . '|zh|zh-reset)(\\/.*)?$/', $tmp, $matches ) ) { + $tmp = user_trailingslashit( trailingslashit( $matches[1] ) . ltrim( $matches[3] ?? '', '/' ) ); + if ( $tmp === home_url() ) { + $tmp .= '/'; + } + } + + return $tmp; + } + + /** + * 设置重写规则 + */ + private function setup_rewrite_rules(): void { + add_filter( 'rewrite_rules_array', [ $this, 'modify_rewrite_rules' ] ); + } + + /** + * 修改重写规则 + */ + public function modify_rewrite_rules( array $rules ): array { + $enabled_langs = $this->config->get_enabled_languages(); + $reg = implode( '|', $enabled_langs ); + $rules2 = []; + + $use_permalink = $this->config->get_option( 'wpcc_use_permalink', 0 ); + + if ( $use_permalink == 1 ) { + foreach ( $rules as $key => $value ) { + if ( strpos( $key, 'trackback' ) !== false || strpos( $key, 'print' ) !== false || strpos( $value, 'lang=' ) !== false ) { + continue; + } + if ( substr( $key, -3 ) == '/?$' ) { + if ( ! preg_match_all( '/\\$matches\\[(\\d+)\\]/', $value, $matches, PREG_PATTERN_ORDER ) ) { + continue; + } + $number = count( $matches[0] ) + 1; + $rules2[ substr( $key, 0, -3 ) . '/(' . $reg . '|zh|zh-reset)/?$' ] = $value . '&variant=$matches[' . $number . ']'; + } + } + } else { + foreach ( $rules as $key => $value ) { + if ( strpos( $key, 'trackback' ) !== false || strpos( $key, 'print' ) !== false || strpos( $value, 'lang=' ) !== false ) { + continue; + } + if ( substr( $key, -3 ) == '/?$' ) { + $rules2[ '(' . $reg . '|zh|zh-reset)/' . $key ] = preg_replace_callback( '/\\$matches\\[(\\d+)\\]/', [ $this, 'permalink_preg_callback' ], $value ) . '&variant=$matches[1]'; + } + } + } + + $rules2[ '^(' . $reg . '|zh|zh-reset)/?$' ] = 'index.php?variant=$matches[1]'; + return array_merge( $rules2, $rules ); + } + + /** + * URL重写回调函数 + */ + private function permalink_preg_callback( array $matches ): string { + return '$matches[' . ( intval( $matches[1] ) + 1 ) . ']'; + } + + /** + * 处理评论提交 + */ + private function handle_comment_submission(): void { + if ( ( isset( $_SERVER['PHP_SELF'] ) && ( strpos( $_SERVER['PHP_SELF'], 'wp-comments-post.php' ) !== false + || strpos( $_SERVER['PHP_SELF'], 'ajax-comments.php' ) !== false + || strpos( $_SERVER['PHP_SELF'], 'comments-ajax.php' ) !== false ) + ) && + isset( $_SERVER["REQUEST_METHOD"] ) && $_SERVER["REQUEST_METHOD"] == "POST" && + isset( $_POST['variant'] ) && ! empty( $_POST['variant'] ) && $this->config->is_language_enabled( $_POST['variant'] ) + ) { + $this->config->set_target_lang( sanitize_text_field( $_POST['variant'] ) ); + $this->do_conversion(); + } + } + + /** + * 修复首页查询 + */ + public function fix_homepage_query( $wp_query ) { + $qv = &$wp_query->query_vars; + + if ( $wp_query->is_home && 'page' == get_option( 'show_on_front' ) && get_option( 'page_on_front' ) ) { + $_query = wp_parse_args( $wp_query->query ); + if ( isset( $_query['pagename'] ) && '' == $_query['pagename'] ) { + unset( $_query['pagename'] ); + } + if ( empty( $_query ) || ! array_diff( array_keys( $_query ), [ + 'preview', 'page', 'paged', 'cpage', 'variant' + ] ) ) { + $wp_query->is_page = true; + $wp_query->is_home = false; + $qv['page_id'] = get_option( 'page_on_front' ); + if ( ! empty( $qv['paged'] ) ) { + $qv['page'] = $qv['paged']; + unset( $qv['paged'] ); + } + } + } + + return $wp_query; + } + + /** + * 渲染不转换区块 + */ + public function render_no_conversion_block( string $block_content, array $block ): string { + if ( isset( $block['blockName'] ) && $block['blockName'] === 'wpcc/no-conversion' ) { + $unique_id = uniqid(); + + $pattern = '/]*class="[^"]*wpcc-no-conversion-content[^"]*"[^>]*>(.*?)<\\/div>/s'; + + $replacement = function ( $matches ) use ( $unique_id ) { + $content = $matches[1]; + return '
' . $content . '
'; + }; + + $block_content = preg_replace_callback( $pattern, $replacement, $block_content ); + } + + return $block_content; + } + + /** + * 添加评论表单语言参数 + */ + public function add_comment_form_variant(): void { + $target_lang = $this->config->get_target_lang(); + if ( $target_lang ) { + echo ''; + } + } + + /** + * 转换链接 + */ + private function convert_link( string $link, string $variant ): string { + static $wp_home; + if ( empty( $wp_home ) ) { + $wp_home = home_url(); + } + + if ( str_contains( $link, $variant ) ) { + return $link; + } + + if ( str_contains( $link, '?' ) || ! $this->config->is_feature_enabled( 'wpcc_use_permalink' ) ) { + return add_query_arg( 'variant', $variant, $link ); + } + + if ( $this->config->get_option( 'wpcc_use_permalink' ) == 1 ) { + return user_trailingslashit( trailingslashit( $link ) . $variant ); + } + + return str_replace( $wp_home, "$wp_home/$variant", $link ); + } + + /** + * 加载转换表 + */ + private function load_conversion_table(): void { + // 这个方法会调用原有的全局函数来保持兼容性 + if ( function_exists( 'wpcc_load_conversion_table' ) ) { + wpcc_load_conversion_table(); + } + } + + /** + * 添加链接转换过滤器 + */ + private function add_link_conversion_filters(): void { + add_filter( 'post_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'month_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'day_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'year_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'page_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'tag_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'author_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'category_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'feed_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'attachment_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'search_feed_link', [ $this, 'filter_link_conversion' ] ); + } + + /** + * 过滤链接转换 + */ + public function filter_link_conversion( string $link ): string { + return $this->convert_link( $link, $this->config->get_target_lang() ); + } + + /** + * 添加内容转换过滤器 + */ + private function add_content_conversion_filters(): void { + add_filter( 'the_content', 'zhconversion2', 20 ); + add_filter( 'the_content_rss', 'zhconversion2', 20 ); + add_filter( 'the_excerpt', 'zhconversion2', 20 ); + add_filter( 'the_excerpt_rss', 'zhconversion2', 20 ); + add_filter( 'the_title', 'zhconversion' ); + add_filter( 'comment_text', 'zhconversion' ); + add_filter( 'bloginfo', 'zhconversion' ); + add_filter( 'the_tags', 'zhconversion_deep' ); + add_filter( 'term_links-post_tag', 'zhconversion_deep' ); + add_filter( 'wp_tag_cloud', 'zhconversion' ); + add_filter( 'the_category', 'zhconversion' ); + add_filter( 'list_cats', 'zhconversion' ); + add_filter( 'category_description', 'zhconversion' ); + add_filter( 'single_cat_title', 'zhconversion' ); + add_filter( 'single_post_title', 'zhconversion' ); + add_filter( 'bloginfo_rss', 'zhconversion' ); + add_filter( 'the_title_rss', 'zhconversion' ); + add_filter( 'comment_text_rss', 'zhconversion' ); + } + + /** + * 输出头部信息 + */ + public function output_header(): void { + $target_lang = $this->config->get_target_lang(); + $noconversion_url = $this->config->get_noconversion_url(); + $langs_urls = $this->config->get_langs_urls(); + + echo "\n" . ''; + + $script_data = [ + 'wpcc_target_lang' => $target_lang ? esc_js( $target_lang ) : '', + 'wpcc_noconversion_url' => $noconversion_url ? esc_url( $noconversion_url ) : '', + 'wpcc_langs_urls' => [] + ]; + + if ( is_array( $langs_urls ) ) { + foreach ( $langs_urls as $key => $value ) { + $safe_key = preg_match( '/^[a-z-]+$/', $key ) ? $key : ''; + if ( $safe_key && filter_var( $value, FILTER_VALIDATE_URL ) ) { + $script_data['wpcc_langs_urls'][ $safe_key ] = esc_url( $value ); + } + } + } + + echo ''; + } + + /** + * 输出canonical链接 + */ + public function output_canonical(): void { + if ( ! is_singular() ) { + return; + } + global $wp_the_query; + if ( ! $id = $wp_the_query->get_queried_object_id() ) { + return; + } + $link = get_permalink( $id ); + // 移除语言参数 + $link = remove_query_arg( 'variant', $link ); + echo "\n"; + } + + /** + * 全页面转换回调 + */ + public function full_page_conversion_callback( string $buffer ): string { + $target_lang = $this->config->get_target_lang(); + if ( $target_lang && ! $this->config->get_direct_conversion_flag() ) { + $home_url = $this->convert_link( home_url( '/' ), $target_lang ); + $home_pattern = preg_quote( esc_url( home_url( '' ) ), '|' ); + $buffer = preg_replace( '|(]*?href=([\'"]]))' . $home_pattern . '/?(\2[^<>]*?>)|', '${1}' . esc_url( $home_url ) . '${3}', $buffer ); + } + return zhconversion2( $buffer ) . "\n" . ''; + } + + /** + * 过滤搜索查询 + */ + public function filter_search_query( string $where ): string { + // 调用原有的全局函数来保持兼容性 + if ( function_exists( 'wpcc_filter_search_rule' ) ) { + return wpcc_filter_search_rule( $where ); + } + return $where; + } +} diff --git a/includes/core/class-wpcc-utils.php b/includes/core/class-wpcc-utils.php new file mode 100644 index 0000000..eb6b929 --- /dev/null +++ b/includes/core/class-wpcc-utils.php @@ -0,0 +1,213 @@ + $traditional_count ) { + return 'simplified'; + } elseif ( $traditional_count > $simplified_count ) { + return 'traditional'; + } + + return 'mixed'; + } + + /** + * 格式化内存使用量 + */ + public static function format_memory_usage( int $bytes ): string { + $units = ['B', 'KB', 'MB', 'GB']; + $factor = floor( ( strlen( (string) $bytes ) - 1 ) / 3 ); + + return sprintf( "%.1f%s", $bytes / pow( 1024, $factor ), $units[ $factor ] ?? 'TB' ); + } + + /** + * 生成缓存键 + */ + public static function generate_cache_key( string ...$parts ): string { + $key = implode( '|', array_filter( $parts ) ); + return 'wpcc_' . md5( $key ); + } + + /** + * 安全的字符串截取(支持多字节字符) + */ + public static function safe_substr( string $str, int $start, ?int $length = null ): string { + return mb_substr( $str, $start, $length, 'UTF-8' ); + } + + /** + * 清理和验证用户输入 + */ + public static function sanitize_language_input( $input ): array { + if ( ! is_array( $input ) ) { + return []; + } + + return array_filter( + array_map( 'sanitize_text_field', $input ), + [ self::class, 'is_valid_language_code' ] + ); + } + + /** + * 获取当前请求的语言偏好 + */ + public static function get_request_language_preference(): ?string { + // 从 URL 参数获取 + $url_lang = $_GET['variant'] ?? null; + if ( $url_lang && self::is_valid_language_code( $url_lang ) ) { + return $url_lang; + } + + // 从 Cookie 获取 + $cookie_lang = $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ?? null; + if ( $cookie_lang && self::is_valid_language_code( $cookie_lang ) ) { + return $cookie_lang; + } + + // 从浏览器 Accept-Language 头获取 + $accept_lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''; + if ( $accept_lang ) { + return self::parse_accept_language( $accept_lang ); + } + + return null; + } + + /** + * 解析 Accept-Language 头 + */ + private static function parse_accept_language( string $accept_language ): ?string { + $supported_langs = [ + 'zh-cn' => ['zh-cn', 'zh-hans', 'zh'], + 'zh-tw' => ['zh-tw', 'zh-hant'], + 'zh-hk' => ['zh-hk'], + 'zh-sg' => ['zh-sg'] + ]; + + // 解析语言偏好 + preg_match_all( '/([a-z]{2,3}(?:-[a-z]{2,4})?)\s*(?:;\s*q\s*=\s*([\d.]+))?/i', + $accept_language, $matches, PREG_SET_ORDER ); + + $preferences = []; + foreach ( $matches as $match ) { + $lang = strtolower( $match[1] ); + $quality = isset( $match[2] ) ? (float) $match[2] : 1.0; + $preferences[ $lang ] = $quality; + } + + // 按质量值排序 + arsort( $preferences ); + + // 匹配支持的语言 + foreach ( $preferences as $lang => $quality ) { + foreach ( $supported_langs as $supported => $aliases ) { + if ( in_array( $lang, $aliases, true ) ) { + return $supported; + } + } + } + + return null; + } + + /** + * 记录性能指标 + */ + public static function log_performance_metric( string $operation, float $duration, array $context = [] ): void { + if ( ! WP_DEBUG || ! defined( 'WPCC_LOG_PERFORMANCE' ) || ! WPCC_LOG_PERFORMANCE ) { + return; + } + + $log_entry = sprintf( + 'WPCC Performance: %s took %.4fs %s', + $operation, + $duration, + $context ? '(' . json_encode( $context ) . ')' : '' + ); + + error_log( $log_entry ); + } + + /** + * 执行带性能监控的操作 + */ + public static function with_performance_monitoring( string $operation, callable $callback, array $context = [] ) { + $start_time = microtime( true ); + + try { + $result = $callback(); + return $result; + } finally { + $duration = microtime( true ) - $start_time; + self::log_performance_metric( $operation, $duration, $context ); + } + } +} \ No newline at end of file diff --git a/includes/modules/wpcc-rest-api.php b/includes/modules/wpcc-rest-api.php index bf068cf..a335c7a 100644 --- a/includes/modules/wpcc-rest-api.php +++ b/includes/modules/wpcc-rest-api.php @@ -29,8 +29,14 @@ class WPCC_Rest_Api extends WPCC_Abstract_Module { private function check_rest_api_availability() { return class_exists( 'WP_REST_Server' ) && function_exists( 'rest_ensure_response' ); } - + /** + * 确保API密钥存在 + */ + public function ensure_api_key_exists() { + // 这个方法可以用来初始化API相关的设置 + // 目前为空实现,保持兼容性 + } public function register_routes() { register_rest_route( $this->namespace, '/convert', array( diff --git a/includes/wpcc-admin.php b/includes/wpcc-admin.php index c2c5744..72e61fd 100644 --- a/includes/wpcc-admin.php +++ b/includes/wpcc-admin.php @@ -111,34 +111,90 @@ function wpcc_init_post_conversion() { $wpcc_options = get_wpcc_option( 'wpcc_options' ); } - error_log( 'WPCC DEBUG: wpcc_init_post_conversion called' ); - error_log( 'WPCC DEBUG: wpcc_options = ' . print_r( $wpcc_options, true ) ); - if ( ! empty( $wpcc_options['wpcc_enable_post_conversion'] ) ) { $target_lang = $wpcc_options['wpcc_post_conversion_target'] ?? 'zh-cn'; - error_log( 'WPCC DEBUG: Adding filters for post conversion, target_lang = ' . $target_lang ); - + /** + * 在保存时安全地转换块内容: + * - 跳过 WPCC 自有区块(wpcc/*),避免破坏区块占位和结构 + * - 仅转换非 WPCC 区块的纯文本片段(innerContent 字符串等) + */ add_filter( 'content_save_pre', function ( $content ) use ( $target_lang ) { - error_log( 'WPCC DEBUG: content_save_pre filter triggered, content length = ' . strlen( $content ) ); - $converted = zhconversion( $content, $target_lang ); - error_log( 'WPCC DEBUG: content converted from ' . substr( $content, 0, 50 ) . ' to ' . substr( $converted, 0, 50 ) ); - return $converted; + if ( empty( $content ) ) { + return $content; + } + try { + return wpcc_convert_post_content_safely( $content, $target_lang ); + } catch ( Exception $e ) { + error_log( 'WPCC Content Conversion Error: ' . $e->getMessage() ); + return $content; + } } ); add_filter( 'title_save_pre', function ( $title ) use ( $target_lang ) { - error_log( 'WPCC DEBUG: title_save_pre filter triggered, title = ' . $title ); - $converted = zhconversion( $title, $target_lang ); - error_log( 'WPCC DEBUG: title converted from ' . $title . ' to ' . $converted ); - return $converted; + if ( empty( $title ) ) { + return $title; + } + try { + return zhconversion( $title, $target_lang ); + } catch ( Exception $e ) { + error_log( 'WPCC Title Conversion Error: ' . $e->getMessage() ); + return $title; + } } ); add_action( 'add_meta_boxes', 'wpcc_add_conversion_meta_box' ); - } else { - error_log( 'WPCC DEBUG: Post conversion is disabled' ); } } +/** + * 使用区块解析安全转换文章内容,仅转换非 WPCC 区块的纯文本 + */ +function wpcc_convert_post_content_safely( $content, $target_lang ) { + if ( ! function_exists( 'parse_blocks' ) || ! function_exists( 'serialize_blocks' ) ) { + // 回退:无法解析区块时,使用整体转换(可能导致占位被转换) + return zhconversion( $content, $target_lang ); + } + + $blocks = parse_blocks( $content ); + $converted = wpcc_convert_blocks_array_safely( $blocks, $target_lang ); + return serialize_blocks( $converted ); +} + +function wpcc_convert_blocks_array_safely( $blocks, $target_lang ) { + foreach ( $blocks as &$block ) { + $name = isset( $block['blockName'] ) ? (string) $block['blockName'] : ''; + + // 跳过 WPCC 自有区块,避免转换其内部占位与结构 + if ( substr( $name, 0, 5 ) === 'wpcc/' ) { + // 递归处理其子块(如不希望转换子块,也可以直接 continue) + if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) { + $block['innerBlocks'] = wpcc_convert_blocks_array_safely( $block['innerBlocks'], $target_lang ); + } + continue; + } + + // 转换非 WPCC 区块的纯文本内容 + if ( isset( $block['innerContent'] ) && is_array( $block['innerContent'] ) ) { + foreach ( $block['innerContent'] as $i => $piece ) { + if ( is_string( $piece ) && $piece !== '' ) { + $block['innerContent'][ $i ] = zhconversion( $piece, $target_lang ); + } + } + } + + if ( isset( $block['innerHTML'] ) && is_string( $block['innerHTML'] ) && $block['innerHTML'] !== '' ) { + $block['innerHTML'] = zhconversion( $block['innerHTML'], $target_lang ); + } + + // 递归处理子块 + if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) { + $block['innerBlocks'] = wpcc_convert_blocks_array_safely( $block['innerBlocks'], $target_lang ); + } + } + return $blocks; +} + /** * 添加转换设置元框 */ diff --git a/includes/wpcc-core.php b/includes/wpcc-core.php index 135ea74..f23f0d9 100644 --- a/includes/wpcc-core.php +++ b/includes/wpcc-core.php @@ -20,19 +20,19 @@ $wpcc_langs = array(); /** * 初始化语言配置 */ -function wpcc_init_languages() { +function wpcc_init_languages(): void { global $wpcc_langs; if ( empty( $wpcc_langs ) ) { - $wpcc_langs = array( - 'zh-cn' => array( 'zhconversion_cn', 'cntip', __( '简体中文', 'wp-chinese-converter' ), 'zh-CN' ), - 'zh-tw' => array( 'zhconversion_tw', 'twtip', __( '台灣正體', 'wp-chinese-converter' ), 'zh-TW' ), - 'zh-hk' => array( 'zhconversion_hk', 'hktip', __( '港澳繁體', 'wp-chinese-converter' ), 'zh-HK' ), - 'zh-hans' => array( 'zhconversion_hans', 'hanstip', __( '简体中文', 'wp-chinese-converter' ), 'zh-Hans' ), - 'zh-hant' => array( 'zhconversion_hant', 'hanttip', __( '繁体中文', 'wp-chinese-converter' ), 'zh-Hant' ), - 'zh-sg' => array( 'zhconversion_sg', 'sgtip', __( '马新简体', 'wp-chinese-converter' ), 'zh-SG' ), - 'zh-jp' => array( 'zhconversion_jp', 'jptip', __( '日式汉字', 'wp-chinese-converter' ), 'zh-JP' ), - ); + $wpcc_langs = [ + 'zh-cn' => [ 'zhconversion_cn', 'cntip', __( '简体中文', 'wp-chinese-converter' ), 'zh-CN' ], + 'zh-tw' => [ 'zhconversion_tw', 'twtip', __( '台灣正體', 'wp-chinese-converter' ), 'zh-TW' ], + 'zh-hk' => [ 'zhconversion_hk', 'hktip', __( '港澳繁體', 'wp-chinese-converter' ), 'zh-HK' ], + 'zh-hans' => [ 'zhconversion_hans', 'hanstip', __( '简体中文', 'wp-chinese-converter' ), 'zh-Hans' ], + 'zh-hant' => [ 'zhconversion_hant', 'hanttip', __( '繁体中文', 'wp-chinese-converter' ), 'zh-Hant' ], + 'zh-sg' => [ 'zhconversion_sg', 'sgtip', __( '马新简体', 'wp-chinese-converter' ), 'zh-SG' ], + 'zh-jp' => [ 'zhconversion_jp', 'jptip', __( '日式汉字', 'wp-chinese-converter' ), 'zh-JP' ], + ]; } } @@ -329,7 +329,7 @@ function _wpcc_permalink_preg_callback( $matches ) { /** * 核心转换函数 */ -function zhconversion( $str, $variant = null ) { +function zhconversion( ?string $str, ?string $variant = null ): string { global $wpcc_options, $wpcc_langs; wpcc_init_languages(); @@ -349,13 +349,27 @@ function zhconversion( $str, $variant = null ) { return $str; } - try { - $converter = WPCC_Converter_Factory::get_converter(); - return $converter->convert( $str, $variant ); - } catch ( Exception $e ) { - error_log( 'WPCC Conversion Error: ' . $e->getMessage() ); - return $str; + // 检查缓存 + $cached_result = WPCC_Conversion_Cache::get_cached_conversion( $str, $variant ); + if ( $cached_result !== null ) { + return $cached_result; } + + return WPCC_Exception_Handler::safe_execute( + function() use ( $str, $variant ) { + $converter = WPCC_Converter_Factory::get_converter(); + $result = $converter->convert( $str, $variant ); + + // 将结果存入缓存 + if ( $result !== $str ) { // 只缓存真正有变化的转换 + WPCC_Conversion_Cache::set_cached_conversion( $str, $variant, $result ); + } + + return $result; + }, + $str, // 降级值:返回原文本 + "zhconversion_{$variant}" + ); } /** @@ -383,9 +397,9 @@ function zhconversion2( $str, $variant = null ) { /** * 各种特定语言的转换函数 */ -function zhconversion_hant( $str ) { +function zhconversion_hant( ?string $str ): string { if ( $str === null || $str === '' ) { - return $str; + return $str ?? ''; } try { $converter = WPCC_Converter_Factory::get_converter(); @@ -573,7 +587,8 @@ function wpcc_output_navi( $args = '', $isReturn = false ) { if ( $wpcc_target_lang ) { $tip = zhconversion( $tip ); } - $output .= ' ' . esc_html( $tip ) . '' . "\n"; + $safe_key = esc_attr( $key ); + $output .= ' ' . esc_html( $tip ) . '' . "\n"; } } else if ( $wpcc_translate_type == 1 ) { $checkSelected = function ( $selected_lang ) use ( $wpcc_target_lang ) { @@ -589,7 +604,8 @@ function wpcc_output_navi( $args = '', $isReturn = false ) { if ( $wpcc_target_lang ) { $tip = zhconversion( $tip ); } - $output .= sprintf( '', $key, ( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ), $key, $checkSelected( $key ), esc_html( $tip ) ); + $safe_key = esc_attr( $key ); + $output .= sprintf( '', $safe_key, esc_attr( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ), $safe_key, $checkSelected( $key ), esc_html( $tip ) ); } $output .= sprintf( '' ); } @@ -629,7 +645,7 @@ function wpcc_output_navi2() { /** * 短码处理函数 */ -function wp_chinese_converter_shortcode() { +function wp_chinese_converter_shortcode(): string { set_wpcc_langs_urls(); return wpcc_output_navi( '', true ); } @@ -638,29 +654,30 @@ function wp_chinese_converter_shortcode() { * 小部件类 */ class wpcc_Widget extends WP_Widget { - function __construct() { - parent::__construct( 'widget_wpcc', 'WP Chinese Converter', array( + public function __construct() { + parent::__construct( 'widget_wpcc', 'WP Chinese Converter', [ 'classname' => 'widget_wpcc', 'description' => 'WP Chinese Converter Widget' - ) ); + ] ); } - function widget( $args, $instance ) { + public function widget( $args, $instance ): void { extract( $args ); $title = apply_filters( 'widget_title', $instance['title'] ?? '' ); echo $before_widget; if ( $title ) { - echo $before_title . $title . $after_title; + echo $before_title . esc_html( $title ) . $after_title; } - wpcc_output_navi( isset( $instance['args'] ) ? $instance['args'] : '' ); + $widget_args = isset( $instance['args'] ) ? sanitize_text_field( $instance['args'] ) : ''; + wpcc_output_navi( $widget_args ); echo $after_widget; } - function update( $new_instance, $old_instance ) { + public function update( $new_instance, $old_instance ): array { return $new_instance; } - function form( $instance ) { + public function form( $instance ): void { $title = isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : ''; $args = isset( $instance['args'] ) ? esc_attr( $instance['args'] ) : ''; ?> @@ -683,7 +700,7 @@ class wpcc_Widget extends WP_Widget { /** * 获取浏览器首选语言 */ -function wpcc_get_prefered_language( $accept_languages, $target_langs, $flag = 0 ) { +function wpcc_get_prefered_language( string $accept_languages, array $target_langs, int $flag = 0 ): string|false { $langs = array(); preg_match_all( '/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $accept_languages, $lang_parse ); @@ -733,7 +750,7 @@ function wpcc_get_prefered_language( $accept_languages, $target_langs, $flag = 0 /** * 判断是否为搜索引擎访问 */ -function wpcc_is_robot() { +function wpcc_is_robot(): bool { if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) { return true; } @@ -776,43 +793,71 @@ function wpcc_apply_filter_search_rule() { */ function wpcc_filter_search_rule( $where ) { global $wp_query, $wpdb; - if ( empty( $wp_query->query_vars['s'] ) ) { + + if ( empty( $wp_query->query_vars['s'] ) || empty( $wp_query->query_vars['search_terms'] ) ) { return $where; } - if ( ! preg_match( "/^([" . chr( 228 ) . "-" . chr( 233 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}){1}/", $wp_query->query_vars['s'] ) && ! preg_match( "/([" . chr( 228 ) . "-" . chr( 233 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}){1}$/", $wp_query->query_vars['s'] ) && ! preg_match( "/([" . chr( 228 ) . "-" . chr( 233 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}){2,}/", $wp_query->query_vars['s'] ) ) { + + // 检查是否包含中文字符 + if ( ! preg_match( '/[\x{4e00}-\x{9fff}]+/u', $wp_query->query_vars['s'] ) ) { return $where; } wpcc_load_conversion_table(); - $placeholder = '%'; - if ( method_exists( $wpdb, 'placeholder_escape' ) ) { - $placeholder = $wpdb->placeholder_escape( "%" ); - } - $sql = ''; - $and1 = ''; - $original = ''; - foreach ( $wp_query->query_vars['search_terms'] as $value ) { - $value = addslashes_gpc( $value ); - $original .= "{$and1}(($wpdb->posts.post_title LIKE '{$placeholder}{$value}{$placeholder}') OR ($wpdb->posts.post_excerpt LIKE '{$placeholder}{$value}{$placeholder}') OR ($wpdb->posts.post_content LIKE '{$placeholder}{$value}{$placeholder}'))"; - $valuea = zhconversion_all( $value ); - $valuea[] = $value; - $sql .= "{$and1}( "; - $or2 = ''; - foreach ( $valuea as $v ) { - $sql .= "{$or2}( " . $wpdb->prefix . "posts.post_title LIKE '{$placeholder}" . $v . "{$placeholder}') "; - $sql .= " OR ( " . $wpdb->prefix . "posts.post_content LIKE '{$placeholder}" . $v . "{$placeholder}') "; - $sql .= " OR ( " . $wpdb->prefix . "posts.post_excerpt LIKE '{$placeholder}" . $v . "{$placeholder}') "; - $or2 = ' OR '; + $sql_parts = array(); + $original_parts = array(); + + foreach ( $wp_query->query_vars['search_terms'] as $term ) { + // 安全处理搜索词 + $safe_term = sanitize_text_field( $term ); + if ( empty( $safe_term ) ) { + continue; + } + + // 构建原始搜索条件(用于替换) + $original_condition = $wpdb->prepare( + "(({$wpdb->posts}.post_title LIKE %s) OR ({$wpdb->posts}.post_excerpt LIKE %s) OR ({$wpdb->posts}.post_content LIKE %s))", + '%' . $wpdb->esc_like( $safe_term ) . '%', + '%' . $wpdb->esc_like( $safe_term ) . '%', + '%' . $wpdb->esc_like( $safe_term ) . '%' + ); + $original_parts[] = $original_condition; + + // 获取转换后的变体 + $variants = zhconversion_all( $safe_term ); + $variants[] = $safe_term; // 包含原始词 + $variants = array_unique( array_filter( $variants ) ); + + $variant_conditions = array(); + foreach ( $variants as $variant ) { + $safe_variant = sanitize_text_field( $variant ); + if ( empty( $safe_variant ) ) { + continue; + } + + $variant_conditions[] = $wpdb->prepare( + "({$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_content LIKE %s OR {$wpdb->posts}.post_excerpt LIKE %s)", + '%' . $wpdb->esc_like( $safe_variant ) . '%', + '%' . $wpdb->esc_like( $safe_variant ) . '%', + '%' . $wpdb->esc_like( $safe_variant ) . '%' + ); + } + + if ( ! empty( $variant_conditions ) ) { + $sql_parts[] = '(' . implode( ' OR ', $variant_conditions ) . ')'; } - $sql .= ' ) '; - $and1 = ' AND '; } - if ( empty( $sql ) ) { + if ( empty( $sql_parts ) || empty( $original_parts ) ) { return $where; } - $where = preg_replace( '/' . preg_quote( $original, '/' ) . '/', $sql, $where, 1 ); + + // 安全地替换原始查询 + $original_pattern = implode( ' AND ', $original_parts ); + $replacement_sql = implode( ' AND ', $sql_parts ); + + $where = str_replace( $original_pattern, $replacement_sql, $where ); return $where; } @@ -913,17 +958,31 @@ function wpcc_do_conversion() { */ function wpcc_header() { global $wpcc_target_lang, $wpcc_langs_urls, $wpcc_noconversion_url, $wpcc_direct_conversion_flag; - echo "\n" . ''; - echo "'; + + echo ''; if ( ! $wpcc_direct_conversion_flag ) { wp_enqueue_script( 'wpcc-search-js', wpcc_DIR_URL . 'assets/js/search-variant.min.js', array(), '1.1', false ); diff --git a/wp-chinese-converter.php b/wp-chinese-converter.php index b5d39dc..c7c3485 100755 --- a/wp-chinese-converter.php +++ b/wp-chinese-converter.php @@ -60,8 +60,13 @@ use Overtrue\PHPOpenCC\OpenCC; use Overtrue\PHPOpenCC\Strategy; // 载入核心依赖 +require_once dirname(__FILE__) . "/includes/core/class-wpcc-exception-handler.php"; +require_once dirname(__FILE__) . "/includes/core/class-wpcc-utils.php"; require_once dirname(__FILE__) . "/includes/core/class-converter-factory.php"; require_once dirname(__FILE__) . "/includes/core/class-module-manager.php"; +require_once dirname(__FILE__) . "/includes/core/class-conversion-cache.php"; +require_once dirname(__FILE__) . "/includes/core/class-wpcc-config.php"; +require_once dirname(__FILE__) . "/includes/core/class-wpcc-main.php"; require_once dirname(__FILE__) . "/includes/blocks/blocks-init.php"; // 加载JS和CSS资源 @@ -88,37 +93,12 @@ function wpcc_add_global_js() ]); } -// 获取插件配置 -$wpcc_options = get_wpcc_option("wpcc_options"); -if (empty($wpcc_options)) { - $wpcc_options = [ - "wpcc_search_conversion" => 1, - "wpcc_used_langs" => [ - "zh-hans", - "zh-hant", - "zh-cn", - "zh-hk", - "zh-sg", - "zh-tw", - ], - "wpcc_browser_redirect" => 0, - "wpcc_auto_language_recong" => 0, - "wpcc_flag_option" => 1, - "wpcc_use_cookie_variant" => 0, - "wpcc_use_fullpage_conversion" => 1, - "wpcco_use_sitemap" => 1, - "wpcc_trackback_plugin_author" => 0, - "wpcc_add_author_link" => 0, - "wpcc_use_permalink" => 0, - "wpcc_no_conversion_tag" => "", - "wpcc_no_conversion_ja" => 0, - "wpcc_no_conversion_qtag" => 0, - "wpcc_enable_post_conversion" => 0, - "wpcc_post_conversion_target" => "zh-cn", - "wpcc_engine" => "opencc", // alternative: mediawiki - "nctip" => "", - ]; -} +// 初始化现代化架构 +$wpcc_main = WPCC_Main::get_instance(); +$wpcc_config = $wpcc_main->get_config(); + +// 保持向后兼容性 +$wpcc_options = $wpcc_config->get_all_options(); // 加载站点地图模块 $modules_dir = __DIR__ . "/includes/modules/"; @@ -132,7 +112,7 @@ if ( is_array($wpcc_options) && is_array($wpcc_options["wpcc_used_langs"]) ) { - // 加载核心功能模块 + // 加载遗留的核心功能模块(为了向后兼容) require_once dirname(__FILE__) . "/includes/wpcc-core.php"; // 加载管理后台模块(仅在后台时加载) @@ -140,19 +120,8 @@ if ( require_once dirname(__FILE__) . "/includes/wpcc-admin.php"; } - // 注册基础钩子 - add_action("wp_enqueue_scripts", "wpcc_add_global_js"); - add_filter("query_vars", "wpcc_insert_query_vars"); - add_action("init", "wpcc_init"); - - // 调试模式下的重写规则刷新 - if (WP_DEBUG || (defined("wpcc_DEBUG") && wpcc_DEBUG == true)) { - add_action("init", function () { - global $wp_rewrite; - $wp_rewrite->flush_rules(); - }); - add_action("wp_footer", "wpcc_debug"); - } + // 新架构已经处理了大部分初始化;移除旧版全局脚本入列以避免重复/冲突 + // add_action("wp_enqueue_scripts", "wpcc_add_global_js"); } /** From a39504560772bba04612401ff59a05388648eac1 Mon Sep 17 00:00:00 2001 From: feibisi Date: Sun, 5 Oct 2025 11:42:45 +0800 Subject: [PATCH 02/19] Update README.md --- README.md | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 197 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e356fd3..2a79b78 100755 --- a/README.md +++ b/README.md @@ -1,33 +1,215 @@ -# 关于 WPCC.NET 中文简繁转换插件 +# WP Chinese Converter (WPCC) 文派简繁转换(WP Chinese Converter),简称 WPCC,是完全符合阅读习惯和 SEO 优化的 WordPress 网站中文繁简转换解决方案。 ![avatar](https://img.feibisi.com/2021/03/wpchinese-switcher-banner-1544x500-1.png) -## 关于简繁转换 +## 核心特性 -WPCC 是一个 WordPress 插件,用于将网站中的中文内容转换为简体中文或繁体中文。它基于 0 项目的转换引擎,提供了简单而强大的简繁转换功能。 +### 现代化架构 (2025重构) +- **面向对象设计**: 采用单例模式和依赖注入的现代PHP架构 +- **模块化系统**: 支持功能模块的动态加载和管理 +- **向后兼容**: 保持与旧版本配置和功能的兼容性 -### 技术特性 +### 多语言转换支持 +- **转换引擎**: + - 基于 PHP OpenCC 1.2.1 版本的高精度转换 + - MediaWiki 转换引擎支持 +- **支持地区**: + - `zh-cn` / `zh-hans`: 简体中文(中国大陆) + - `zh-tw` / `zh-hant`: 繁体中文(台湾) + - `zh-hk`: 港澳繁体中文 + - `zh-sg`: 马新简体中文 + - `zh-jp`: 日式汉字 +- **转换策略**: 词汇级别转换、异体字转换、地区习惯用词转换 -- **转换引擎**: 基于 PHP OpenCC 1.2.1 版本 -- **转换策略**: 支持词汇级别的转换、异体字转换和地区习惯用词转换 -- **支持地区**: 中国大陆、台湾、香港、日本新字体 -- **性能优化**: 支持字典预加载和缓存机制 +### Gutenberg 区块支持 +- **语言切换器区块**: 可视化语言切换按钮 +- **转换状态区块**: 显示当前语言状态 +- **不转换区块**: 指定不需要转换的内容区域 +- **现代编辑器体验**: 完全兼容WordPress 5.0+区块编辑器 -目前我们正在构建更多地域词义词源项目—— OpenCC.org ,并首要为 WPCC 提供支持,可以实现更好的原生中文简繁阅读体验。 +### 性能优化 +- **智能缓存系统**: 支持多种缓存策略(WordPress原生缓存、现代缓存系统) +- **内存优化**: 字典预加载和按需加载机制 +- **转换缓存**: 避免重复转换相同内容 +- **全页面转换**: 可选的服务端全页面转换模式 +### SEO 优化功能 +- **Canonical URL**: 自动生成规范链接,避免重复内容问题 +- **Hreflang 支持**: 自动添加语言版本链接标记 +- **URL 结构优化**: 支持伪静态和查询参数两种URL模式 +- **搜索增强**: 支持搜索关键词的简繁转换 +- **站点地图兼容**: 自动生成多语言版本的站点地图 ->此项目旧版本 WPChinese Switcher 分叉于原 WP Chinese Conversion 中文简繁转换器免费插件,感谢原作者 Ono Oogami 提供了此工具。由于原插件已经多年无更新,无维护及无支持回复,旧版本插件在新的 PHP 版本中也有多处错误函数警告。在简单修复了几个 bug 后越发认识到,这对于中文用户网站来说确实是非常好且实用的简繁双语解决方案,现借由文派开源重启项目,以便为 WordPress 中文用户提供长期更新和支持。 +### 高级功能 +- **浏览器语言检测**: 根据访客浏览器语言自动切换 +- **Cookie 记忆**: 记住用户的语言偏好设置 +- **多站点支持**: 完全兼容 WordPress 多站点模式 +- **REST API**: 提供完整的 REST API 接口 +- **AJAX 转换**: 支持动态内容的实时转换 ->为了避免与原始插件名称冲突,故新版本重命名为 WPChinese Switcher ——文派中文切换器或简繁切换器。 +### 兼容性 +- **WordPress 版本**: WordPress 5.0+ (推荐 6.0+) +- **PHP 版本**: PHP 7.4+ (推荐 8.0+) +- **数据库**: MySQL 5.7+ 或 MariaDB 10.2+ +- **缓存插件**: 兼容 WP Super Cache, W3 Total Cache 等 -## 品牌重塑 +## 系统要求 -2025 年为了满足更多自定义开发要求,文派开源开源团队对此项目进行重写和品牌重塑,正式命名为“文派简繁切换器(WP Chinese Converter),简称 WPCC。” +- WordPress 5.0 或更高版本 +- PHP 7.4 或更高版本(推荐 PHP 8.0+) +- MySQL 5.7+ 或 MariaDB 10.2+ +- 服务器内存至少 128MB(用于加载转换字典) -由于历史代码太过混乱,维护负担过重,且不再适用于 WordPress 最新区块编辑器项目,故后续版本不再与过完任何旧版本兼容。 +## 安装配置 + +### 自动安装 +1. 在 WordPress 后台导航到 `插件 > 安装插件` +2. 搜索 "WP Chinese Converter" 或 "WPCC" +3. 点击 `现在安装`,然后激活插件 +4. 在 `设置 > WP Chinese Converter` 中配置转换选项 + +### 手动安装 +1. 下载插件压缩包 +2. 解压到 `/wp-content/plugins/wp-chinese-converter/` 目录 +3. 在 WordPress 后台激活插件 +4. 配置插件设置 + +## 主要设置选项 + +### 基本设置 +- **启用语言**: 选择要启用的语言版本 +- **转换模式**: 选择使用的转换引擎(OpenCC 或 MediaWiki) +- **URL 模式**: 选择伪静态或查询参数模式 +- **默认语言**: 设置网站的默认语言版本 + +### 高级设置 +- **缓存设置**: 配置转换缓存策略 +- **SEO 优化**: 启用 canonical URL 和 hreflang 支持 +- **浏览器检测**: 启用自动语言检测 +- **Cookie 记忆**: 启用用户语言偏好记忆 + +## 使用方法 + +### Gutenberg 区块使用 +1. 在页面编辑器中点击 `+` 添加区块 +2. 搜索 "WP Chinese Converter" 相关区块 +3. 选择所需区块并配置选项: + - **语言切换器**: 设置按钮样式和显示语言 + - **转换状态**: 显示当前页面语言状态 + - **不转换**: 指定不需要转换的内容区域 + +### 短代码支持 +```php +// 显示语言切换器 +[wpcc_language_switcher] + +// 显示转换状态 +[wpcc_conversion_status] + +// 不转换指定内容 +[wpcc_no_conversion]不转换的内容[/wpcc_no_conversion] +``` + +### PHP 函数调用 +```php +// 获取转换后的内容 + + +// 获取当前语言 + + +// 获取语言切换链接 + +``` + +## 开发者接口 + +### 过滤器钩子 +```php +// 自定义转换文本 +add_filter('wpcc_convert_text', 'custom_conversion', 10, 3); + +// 修改语言标签 +add_filter('wpcc_language_labels', 'custom_labels'); + +// 自定义缓存时间 +add_filter('wpcc_cache_expire', 'custom_cache_time'); +``` + +### 动作钩子 +```php +// 转换前执行 +add_action('wpcc_before_conversion', 'before_convert'); + +// 转换后执行 +add_action('wpcc_after_conversion', 'after_convert'); +``` + +## 故障排除 + +### 常见问题 + +**Q: 插件激活后页面显示空白?** +A: 请检查 PHP 内存限制,建议至少 128MB。可以在 `wp-config.php` 中添加 `define('WP_MEMORY_LIMIT', '256M');` + +**Q: 转换后的内容不正确?** +A: 请检查选择的转换引擎和目标语言是否正确。OpenCC 引擎适合日常使用,MediaWiki 引擎更严格。 + +**Q: 缓存不生效?** +A: 请检查 WordPress 缓存配置,或尝试清除转换缓存:`设置 > WP Chinese Converter > 高级设置 > 清除缓存` + +**Q: URL 重写不工作?** +A: 请确保服务器的 mod_rewrite 模块已启用,并重新保存固定链接设置。 + +## 性能优化建议 + +1. **启用缓存**: 使用 WordPress 缓存插件(如 WP Super Cache) +2. **PHP OPcache**: 启用 PHP OPcache 以提高字典加载速度 +3. **CDN 集成**: 使用 CDN 加速静态资源加载 +4. **按需加载**: 仅在需要的页面启用转换功能 + +## 贡献指南 + +欢迎提交 Issue 和 Pull Request! + +1. Fork 本仓库 +2. 创建功能分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 开启 Pull Request + +## 许可证 + +本插件采用 GPLv3 或更高版本许可证。详见 [LICENSE](LICENSE) 文件。 + +## 支持与反馈 + +- **官方网站**: https://wpcc.net +- **GitHub 仓库**: https://github.com/wpcc-net/wp-chinese-converter +- **技术支持**: https://wpcc.net/support +- **Bug 报告**: https://github.com/wpcc-net/wp-chinese-converter/issues --- -**Copyright © 2025 · WPCC.NET , All Rights Reserved. 文派 (广州) 科技有限公司;** +## 版本历史 + +### v1.3.0 (2025年重构版本) +- 完全重写插件架构,采用现代化 OOP 设计 +- 新增 Gutenberg 区块支持 +- 优化性能和内存使用 +- 增强 SEO 优化功能 +- 完善多站点支持 +- 新增 REST API 接口 + +### v1.2.x (历史版本) +- 基于原 WP Chinese Conversion 的改进版本 +- 修复 PHP 8.x 兼容性问题 +- 基础的简繁转换功能 + +> **历史说明**: 此项目分叉于原 WP Chinese Conversion 中文简繁转换器免费插件,感谢原作者 Ono Oogami 提供的基础框架。由于原插件多年未更新,文派开源团队于 2025 年进行完全重写和品牌重塑,为 WordPress 中文用户提供长期更新和技术支持。 + +--- + +**Copyright © 2025 · WPCC.NET , All Rights Reserved. 文派 (广州) 科技有限公司** From 837b659fa9aa2a3eb25fd8cc495ad7a6fd8a63bd Mon Sep 17 00:00:00 2001 From: feibisi Date: Sun, 5 Oct 2025 11:44:40 +0800 Subject: [PATCH 03/19] Add settings reset and presets, update defaults Introduces a settings reset feature with a new 'WPCC_Presets' class for managing and applying configuration presets. Updates default options for language tags, conversion exclusions, and other settings to improve usability and consistency. The settings page now includes a 'Reset Options' button, and the admin logic handles resetting to factory defaults securely. --- includes/admin/wp-chinese-converter-admin.php | 80 +++++++- includes/core/class-wpcc-config.php | 57 +++-- includes/core/class-wpcc-presets.php | 194 ++++++++++++++++++ includes/wpcc-settings-page.php | 7 +- wp-chinese-converter.php | 1 + 5 files changed, 309 insertions(+), 30 deletions(-) create mode 100644 includes/core/class-wpcc-presets.php diff --git a/includes/admin/wp-chinese-converter-admin.php b/includes/admin/wp-chinese-converter-admin.php index 18f8f52..7dbb8d6 100755 --- a/includes/admin/wp-chinese-converter-admin.php +++ b/includes/admin/wp-chinese-converter-admin.php @@ -45,21 +45,54 @@ class wpcc_Admin $this->options = $wpcc_options; if (empty($this->options)) { $this->options = [ + // 语言与标签 + "wpcc_used_langs" => ["zh-cn", "zh-tw"], + "cntip" => "简体", + "twtip" => "繁体", + "hktip" => "港澳", + "hanstip" => "简体", + "hanttip" => "繁体", + "sgtip" => "马新", + "jptip" => "日式", + + // 引擎与转换 + "wpcc_engine" => "mediawiki", "wpcc_search_conversion" => 1, - "wpcc_used_langs" => ["zh-cn", "zh-tw", "zh-hk"], + "wpcc_use_fullpage_conversion" => 0, + + // 浏览器与 Cookie "wpcc_browser_redirect" => 0, "wpcc_auto_language_recong" => 0, - "wpcc_flag_option" => 1, - "wpcc_use_cookie_variant" => 0, - "wpcc_use_fullpage_conversion" => 1, - "wpcco_use_sitemap" => 1, - "wpcc_trackback_plugin_author" => 0, - "wpcc_add_author_link" => 0, - "wpcc_use_permalink" => 0, - "wpcc_no_conversion_tag" => "", + "wpcc_use_cookie_variant" => 1, + + // 不转换 +"wpcc_no_conversion_tag" => "pre,code,pre.wp-block-code,pre.wp-block-preformatted,script,noscript,style,kbd,samp", "wpcc_no_conversion_ja" => 0, "wpcc_no_conversion_qtag" => 0, - "wpcc_engine" => "opencc", + + // 发表时转换 + "wpcc_enable_post_conversion" => 0, + "wpcc_post_conversion_target" => "zh-cn", + + // URL 与站点地图 + "wpcc_use_permalink" => 0, + "wpcco_use_sitemap" => 0, + "wpcco_sitemap_post_type" => "post,page", + + // SEO + "wpcc_enable_hreflang_tags" => 1, + "wpcc_hreflang_x_default" => "zh-cn", + "wpcc_enable_schema_conversion" => 1, + "wpcc_enable_meta_conversion" => 1, + + // 其他 + "wpcc_flag_option" => 1, + "wpcc_trackback_plugin_author" => 0, + "wpcc_add_author_link" => 0, + "wpcc_translate_type" => 0, + "nctip" => "", + "wpcc_enable_cache_addon" => 1, + "wpcc_enable_network_module" => 0, ]; } @@ -232,6 +265,31 @@ class wpcc_Admin return; } + // 处理“重置为默认设置” + if (!empty($_POST['wpcc_reset_defaults']) && current_user_can('manage_options')) { + if (check_admin_referer('wpcc_reset_defaults', 'wpcc_reset_nonce')) { + $ok = false; + if (class_exists('WPCC_Presets')) { + // 利用工厂默认预设恢复默认 + $ok = WPCC_Presets::apply_preset('factory_default'); + } + if ($ok) { + $this->options = get_wpcc_option('wpcc_options', []); + $this->is_submitted = true; + $this->is_success = true; + $this->message .= __('已重置为默认设置。', 'wp-chinese-converter'); + } else { + $this->is_submitted = true; + $this->is_error = true; + $this->message .= __('重置失败。', 'wp-chinese-converter'); + } + } else { + $this->is_submitted = true; + $this->is_error = true; + $this->message .= __('安全验证失败。', 'wp-chinese-converter'); + } + } + if (!empty($_POST["toggle_cache"])) { if ($this->get_cache_status() == 1) { $result = $this->install_cache_module(); @@ -270,7 +328,7 @@ class wpcc_Admin } } - if (!empty($_POST["wpcco_submitted"])) { + if (!empty($_POST["wpcco_submitted"]) && empty($_POST['wpcc_reset_defaults'])) { $this->is_submitted = true; $this->process(); diff --git a/includes/core/class-wpcc-config.php b/includes/core/class-wpcc-config.php index c3787f9..8620371 100644 --- a/includes/core/class-wpcc-config.php +++ b/includes/core/class-wpcc-config.php @@ -48,31 +48,54 @@ class WPCC_Config { */ private function get_default_options(): array { return [ + // 语言与标签 + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + 'cntip' => '简体', + 'twtip' => '繁体', + 'hktip' => '港澳', + 'hanstip' => '简体', + 'hanttip' => '繁体', + 'sgtip' => '马新', + 'jptip' => '日式', + + // 引擎与转换 + 'wpcc_engine' => 'mediawiki', 'wpcc_search_conversion' => 1, - 'wpcc_used_langs' => [ - 'zh-hans', - 'zh-hant', - 'zh-cn', - 'zh-hk', - 'zh-sg', - 'zh-tw', - ], + 'wpcc_use_fullpage_conversion' => 0, + + // 浏览器与 Cookie 'wpcc_browser_redirect' => 0, 'wpcc_auto_language_recong' => 0, - 'wpcc_flag_option' => 1, - 'wpcc_use_cookie_variant' => 0, - 'wpcc_use_fullpage_conversion' => 1, - 'wpcco_use_sitemap' => 1, - 'wpcc_trackback_plugin_author' => 0, - 'wpcc_add_author_link' => 0, - 'wpcc_use_permalink' => 0, - 'wpcc_no_conversion_tag' => '', + 'wpcc_use_cookie_variant' => 1, + + // 不转换 +'wpcc_no_conversion_tag' => 'pre,code,pre.wp-block-code,pre.wp-block-preformatted,script,noscript,style,kbd,samp', 'wpcc_no_conversion_ja' => 0, 'wpcc_no_conversion_qtag' => 0, + + // 发表时转换 'wpcc_enable_post_conversion' => 0, 'wpcc_post_conversion_target' => 'zh-cn', - 'wpcc_engine' => 'opencc', + + // URL 与站点地图 + 'wpcc_use_permalink' => 0, + 'wpcco_use_sitemap' => 0, + 'wpcco_sitemap_post_type' => 'post,page', + + // SEO + 'wpcc_enable_hreflang_tags' => 1, + 'wpcc_hreflang_x_default' => 'zh-cn', + 'wpcc_enable_schema_conversion' => 1, + 'wpcc_enable_meta_conversion' => 1, + + // 其他 + 'wpcc_flag_option' => 1, + 'wpcc_trackback_plugin_author' => 0, + 'wpcc_add_author_link' => 0, + 'wpcc_translate_type' => 0, 'nctip' => '', + 'wpcc_enable_cache_addon' => 1, + 'wpcc_enable_network_module' => 0, ]; } diff --git a/includes/core/class-wpcc-presets.php b/includes/core/class-wpcc-presets.php new file mode 100644 index 0000000..f68c5fc --- /dev/null +++ b/includes/core/class-wpcc-presets.php @@ -0,0 +1,194 @@ + ['zh-cn','zh-tw'], + 'cntip' => '简体', + 'twtip' => '繁体', + 'hktip' => '港澳', + 'hanstip' => '简体', + 'hanttip' => '繁体', + 'sgtip' => '马新', + 'jptip' => '日式', + 'wpcc_translate_type' => 0, + 'wpcc_engine' => 'opencc', + 'wpcc_search_conversion' => 1, + 'wpcc_use_fullpage_conversion' => 0, + 'wpcc_no_conversion_qtag' => 0, + 'wpcc_enable_post_conversion' => 0, + 'wpcc_post_conversion_target' => 'zh-cn', + 'wpcc_no_conversion_tag' => 'pre,code,wp-block-code', + 'wpcc_no_conversion_ja' => 1, + 'nctip' => '', + 'wpcc_browser_redirect' => 0, + 'wpcc_auto_language_recong' => 0, + 'wpcc_use_cookie_variant' => 2, + 'wpcc_use_permalink' => 0, + 'wpcco_use_sitemap' => 0, + 'wpcco_sitemap_post_type' => 'post,page', + 'wpcc_enable_cache_addon' => 1, + 'wpcc_enable_network_module' => 0, + 'wpcc_enable_hreflang_tags' => 1, + 'wpcc_hreflang_x_default' => 'zh-cn', + 'wpcc_enable_schema_conversion' => 1, + 'wpcc_enable_meta_conversion' => 1, + ]; + + return [ + // 恢复到插件的“工厂默认值”(更广覆盖的默认语言集与基础选项) +'factory_default' => [ + 'label' => __( '恢复默认', 'wp-chinese-converter' ), + 'options' => [ + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + 'cntip' => '简体', + 'twtip' => '繁体', + 'hktip' => '港澳', + 'hanstip' => '简体', + 'hanttip' => '繁体', + 'sgtip' => '马新', + 'jptip' => '日式', + + 'wpcc_engine' => 'mediawiki', + 'wpcc_search_conversion' => 1, + + 'wpcc_browser_redirect' => 0, + 'wpcc_auto_language_recong' => 0, + 'wpcc_use_cookie_variant' => 1, + + 'wpcc_use_fullpage_conversion' => 0, + +'wpcc_no_conversion_tag' => 'pre,code,pre.wp-block-code,pre.wp-block-preformatted,script,noscript,style,kbd,samp', + 'wpcc_no_conversion_ja' => 0, + 'wpcc_no_conversion_qtag' => 0, + + 'wpcc_enable_post_conversion' => 0, + 'wpcc_post_conversion_target' => 'zh-cn', + + 'wpcc_use_permalink' => 0, + 'wpcco_use_sitemap' => 0, + 'wpcco_sitemap_post_type' => 'post,page', + + 'wpcc_enable_hreflang_tags' => 1, + 'wpcc_hreflang_x_default' => 'zh-cn', + 'wpcc_enable_schema_conversion' => 1, + 'wpcc_enable_meta_conversion' => 1, + + 'wpcc_enable_cache_addon' => 1, + 'wpcc_enable_network_module' => 0, + + 'wpcc_translate_type' => 0, + 'nctip' => '', + 'wpcc_flag_option' => 1, + 'wpcc_trackback_plugin_author' => 0, + 'wpcc_add_author_link' => 0, + ], + ], + + // 内容/资讯站 + 'content_site' => [ + 'label' => __( '内容站', 'wp-chinese-converter' ), + 'options' => array_merge( $base, [ + 'wpcc_engine' => 'opencc', + 'wpcc_search_conversion' => 2, + 'wpcco_use_sitemap' => 1, + 'wpcc_use_fullpage_conversion' => 0, + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + ]), + ], + + // 企业站 + 'corporate' => [ + 'label' => __( '企业站', 'wp-chinese-converter' ), + 'options' => array_merge( $base, [ + 'wpcc_engine' => 'mediawiki', + 'wpcc_search_conversion' => 0, + 'wpcco_use_sitemap' => 1, + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + ]), + ], + + // 技术文档/知识库 + 'docs' => [ + 'label' => __( '技术文档', 'wp-chinese-converter' ), + 'options' => array_merge( $base, [ + 'wpcc_engine' => 'opencc', + 'wpcc_search_conversion' => 1, + 'wpcc_use_fullpage_conversion' => 0, + 'wpcc_no_conversion_tag' => 'pre,code,wp-block-code,kbd,samp', + 'wpcc_no_conversion_ja' => 1, + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + ]), + ], + + // 高性能/兼容优先 + 'performance' => [ + 'label' => __( '高性能', 'wp-chinese-converter' ), + 'options' => array_merge( $base, [ + 'wpcc_engine' => 'mediawiki', + 'wpcc_search_conversion' => 0, + 'wpcc_use_fullpage_conversion' => 0, + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + ]), + ], + ]; + } + + /** + * 应用预设 + */ + public static function apply_preset( string $slug ): bool { + $presets = self::get_presets(); + if ( ! isset( $presets[ $slug ] ) ) { + return false; + } + + $new = $presets[ $slug ]['options']; + $current = get_wpcc_option( 'wpcc_options', [] ); + if ( ! is_array( $current ) ) { + $current = []; + } + + // 合并:仅覆盖预设中出现的键,其他用户自定义保持不变 + $merged = array_merge( $current, $new ); + + $before_permalink = isset( $current['wpcc_use_permalink'] ) ? intval( $current['wpcc_use_permalink'] ) : 0; + $after_permalink = isset( $merged['wpcc_use_permalink'] ) ? intval( $merged['wpcc_use_permalink'] ) : 0; + + $ok = update_wpcc_option( 'wpcc_options', $merged ); + + // 若固定链接设定发生变化,则刷新重写规则 + if ( $before_permalink !== $after_permalink ) { + if ( function_exists( 'flush_rewrite_rules' ) ) { + flush_rewrite_rules(); + } + } + + return (bool) $ok; + } + + /** + * 获取可用于UI的预设清单(slug => label) + */ + public static function get_preset_labels(): array { + $labels = []; + foreach ( self::get_presets() as $slug => $preset ) { + $labels[ $slug ] = $preset['label'] ?? $slug; + } + return $labels; + } +} diff --git a/includes/wpcc-settings-page.php b/includes/wpcc-settings-page.php index e8b97d5..1412958 100644 --- a/includes/wpcc-settings-page.php +++ b/includes/wpcc-settings-page.php @@ -471,9 +471,9 @@ $active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'basic'; +placeholder="">options['wpcc_no_conversion_tag'] ?? 'pre,code,pre.wp-block-code,pre.wp-block-preformatted'); ?>

- pre,code,wp-block-code + pre,code,pre.wp-block-code,pre.wp-block-preformatted

@@ -654,6 +654,8 @@ $active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'basic';
+ +
@@ -789,6 +791,7 @@ $active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'basic'; + diff --git a/wp-chinese-converter.php b/wp-chinese-converter.php index c7c3485..773b346 100755 --- a/wp-chinese-converter.php +++ b/wp-chinese-converter.php @@ -66,6 +66,7 @@ require_once dirname(__FILE__) . "/includes/core/class-converter-factory.php"; require_once dirname(__FILE__) . "/includes/core/class-module-manager.php"; require_once dirname(__FILE__) . "/includes/core/class-conversion-cache.php"; require_once dirname(__FILE__) . "/includes/core/class-wpcc-config.php"; +require_once dirname(__FILE__) . "/includes/core/class-wpcc-presets.php"; require_once dirname(__FILE__) . "/includes/core/class-wpcc-main.php"; require_once dirname(__FILE__) . "/includes/blocks/blocks-init.php"; From 5a7813a7a01cfa7dffe9d81fe3faa9bb31b8a7dc Mon Sep 17 00:00:00 2001 From: feibisi Date: Sun, 5 Oct 2025 11:48:54 +0800 Subject: [PATCH 04/19] Bump version to 1.4.0 Updated plugin version in header and constant from 1.3.0 to 1.4.0 to reflect new release. --- wp-chinese-converter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wp-chinese-converter.php b/wp-chinese-converter.php index 773b346..4dcac12 100755 --- a/wp-chinese-converter.php +++ b/wp-chinese-converter.php @@ -6,7 +6,7 @@ * Author URI: https://wpcc.net * Text Domain: wp-chinese-converter * Domain Path: /languages - * Version: 1.3.0 + * Version: 1.4.0 * License: GPLv3 or later * License URI: http://www.gnu.org/licenses/gpl-3.0.html */ @@ -25,7 +25,7 @@ // 基础常量定义 define("wpcc_DEBUG", false); -define("wpcc_VERSION", "1.3.0"); +define("wpcc_VERSION", "1.4.0"); // 插件URL常量 if (defined("WP_PLUGIN_URL")) { From f43da1c7641319eb95f6746317265097ab90c3fb Mon Sep 17 00:00:00 2001 From: feibisi Date: Sun, 5 Oct 2025 23:18:45 +0800 Subject: [PATCH 05/19] Revert lazy loading and update default no-conversion tags This commit removes the lazy loading optimization from the MediaWiki converter, reverting to always loading all conversion tables for stability. It also changes the default value of 'wpcc_no_conversion_tag' to an empty string across config, presets, and admin, and updates global variable synchronization for backward compatibility. Additionally, OpenCC converter now pre-processes dictionaries for improved performance and skips conversion for non-Chinese text. The plugin version is reverted from 1.4.0 to 1.3.0. --- includes/admin/wp-chinese-converter-admin.php | 3 +- includes/core/class-mediawiki-converter.php | 204 +++--------------- includes/core/class-opencc-converter.php | 116 ++++++++-- includes/core/class-wpcc-config.php | 18 +- includes/core/class-wpcc-main.php | 6 + includes/core/class-wpcc-presets.php | 2 +- includes/wpcc-core.php | 2 + includes/wpcc-settings-page.php | 8 +- wp-chinese-converter.php | 4 +- 9 files changed, 148 insertions(+), 215 deletions(-) diff --git a/includes/admin/wp-chinese-converter-admin.php b/includes/admin/wp-chinese-converter-admin.php index 7dbb8d6..2f8bc27 100755 --- a/includes/admin/wp-chinese-converter-admin.php +++ b/includes/admin/wp-chinese-converter-admin.php @@ -66,7 +66,7 @@ class wpcc_Admin "wpcc_use_cookie_variant" => 1, // 不转换 -"wpcc_no_conversion_tag" => "pre,code,pre.wp-block-code,pre.wp-block-preformatted,script,noscript,style,kbd,samp", +"wpcc_no_conversion_tag" => "", "wpcc_no_conversion_ja" => 0, "wpcc_no_conversion_qtag" => 0, @@ -85,6 +85,7 @@ class wpcc_Admin "wpcc_enable_schema_conversion" => 1, "wpcc_enable_meta_conversion" => 1, + // 其他 "wpcc_flag_option" => 1, "wpcc_trackback_plugin_author" => 0, diff --git a/includes/core/class-mediawiki-converter.php b/includes/core/class-mediawiki-converter.php index 65dee37..7819cc2 100644 --- a/includes/core/class-mediawiki-converter.php +++ b/includes/core/class-mediawiki-converter.php @@ -7,24 +7,22 @@ if ( ! defined( 'ABSPATH' ) ) { require_once dirname( __FILE__ ) . '/interface-converter.php'; class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface { - private $conversion_tables_loaded = false; - private static $loaded_tables = array(); - private $lazy_load_enabled = true; - + public function convert( $text, $target_variant ) { if ( empty( $text ) ) { return $text; } - + // 检查缓存 $cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant ); if ( $cached_result !== null ) { return $cached_result; } - - $this->ensure_conversion_tables_loaded( $target_variant ); - + + // 回滚:始终加载所有转换表 + $this->load_all_conversion_tables(); + $result = $text; switch ( $target_variant ) { case 'zh-hans': @@ -46,26 +44,24 @@ class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface { $result = $this->convert_to_sg( $text ); break; } - + // 缓存转换结果 if ( $result !== $text ) { WPCC_Conversion_Cache::set_cached_conversion( $text, $target_variant, $result ); } - + return $result; } - + public function get_supported_variants() { return array( 'zh-hans', 'zh-hant', 'zh-cn', 'zh-tw', 'zh-hk', 'zh-sg' ); } - + public function get_engine_name() { return 'mediawiki'; } - + public function get_engine_info() { - $memory_usage = $this->get_memory_usage_info(); - return array( 'name' => 'MediaWiki', 'version' => '1.23.5', @@ -74,211 +70,61 @@ class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface { '字符级精确映射', '快速转换速度', '良好的兼容性', - '支持多地区变体', - '按需加载优化' + '支持多地区变体' ), - 'memory_usage' => $memory_usage, - 'conversion_type' => 'character_mapping', - 'lazy_load_enabled' => $this->lazy_load_enabled, - 'loaded_tables' => array_keys( array_filter( self::$loaded_tables ) ) + 'conversion_type' => 'character_mapping' ); } - - public function get_memory_usage_info() { - $loaded_count = count( array_filter( self::$loaded_tables ) ); - $total_count = count( $this->get_supported_variants() ); - - if ( $this->lazy_load_enabled ) { - $estimated_usage = $loaded_count * 0.25; // MB per table approximately - return sprintf( '约 %.1fMB (%d/%d 表已加载, 按需加载开启)', $estimated_usage, $loaded_count, $total_count ); - } else { - return '约 1.5MB (所有转换表已加载)'; - } - } - - public function enable_lazy_loading( $enable = true ) { - $this->lazy_load_enabled = $enable; - } - - public function is_lazy_loading_enabled() { - return $this->lazy_load_enabled; - } - - public static function get_loaded_tables_status() { - return self::$loaded_tables; - } - - public static function unload_table( $variant ) { - if ( isset( self::$loaded_tables[ $variant ] ) ) { - unset( self::$loaded_tables[ $variant ] ); - - // 清理对应的全局变量(这里只能标记,PHP不能真正释放全局变量内存) - return true; - } - return false; - } - + public function is_available() { $conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php'; return file_exists( $conversion_file ); } - - private function ensure_conversion_tables_loaded( $target_variant = null ) { - if ( $this->lazy_load_enabled && $target_variant ) { - // 按需加载特定语言变体的转换表 - $this->ensure_specific_table_loaded( $target_variant ); - } else { - // 传统方式:加载所有转换表 - $this->load_all_conversion_tables(); - } - } - - private function ensure_specific_table_loaded( $target_variant ) { - if ( isset( self::$loaded_tables[ $target_variant ] ) ) { - return; // 已经加载 - } - - $conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php'; - if ( ! file_exists( $conversion_file ) ) { - return; - } - - // 只加载需要的转换表 - global $zh2Hans, $zh2Hant, $zh2CN, $zh2TW, $zh2HK, $zh2SG; - - switch ( $target_variant ) { - case 'zh-hans': - if ( ! isset( $zh2Hans ) ) { - $this->load_conversion_table_partial( 'zh2Hans' ); - } - self::$loaded_tables['zh-hans'] = true; - break; - - case 'zh-hant': - if ( ! isset( $zh2Hant ) ) { - $this->load_conversion_table_partial( 'zh2Hant' ); - } - self::$loaded_tables['zh-hant'] = true; - break; - - case 'zh-cn': - if ( ! isset( $zh2CN ) || ! isset( $zh2Hans ) ) { - $this->load_conversion_table_partial( array( 'zh2CN', 'zh2Hans' ) ); - } - self::$loaded_tables['zh-cn'] = true; - break; - - case 'zh-tw': - if ( ! isset( $zh2TW ) || ! isset( $zh2Hant ) ) { - $this->load_conversion_table_partial( array( 'zh2TW', 'zh2Hant' ) ); - } - self::$loaded_tables['zh-tw'] = true; - break; - - case 'zh-hk': - if ( ! isset( $zh2HK ) || ! isset( $zh2Hant ) ) { - $this->load_conversion_table_partial( array( 'zh2HK', 'zh2Hant' ) ); - } - self::$loaded_tables['zh-hk'] = true; - break; - - case 'zh-sg': - if ( ! isset( $zh2SG ) || ! isset( $zh2Hans ) ) { - $this->load_conversion_table_partial( array( 'zh2SG', 'zh2Hans' ) ); - } - self::$loaded_tables['zh-sg'] = true; - break; - - default: - $this->load_all_conversion_tables(); - break; - } - } - - private function load_conversion_table_partial( $table_names ) { - $conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php'; - - if ( ! is_array( $table_names ) ) { - $table_names = array( $table_names ); - } - - // 读取文件内容并解析指定的转换表 - $file_content = file_get_contents( $conversion_file ); - - foreach ( $table_names as $table_name ) { - $pattern = '/\$' . preg_quote( $table_name, '/' ) . '\s*=\s*array\s*\((.*?)\);/s'; - if ( preg_match( $pattern, $file_content, $matches ) ) { - $table_code = '$' . $table_name . ' = array(' . $matches[1] . ');'; - eval( $table_code ); - - // 将变量设置为全局 - $GLOBALS[ $table_name ] = ${$table_name}; - } - } - - // 加载额外转换文件 - $extra_conversion_file = WP_CONTENT_DIR . '/extra_zhconversion.php'; - if ( file_exists( $extra_conversion_file ) ) { - require_once $extra_conversion_file; - } - } - + private function load_all_conversion_tables() { if ( $this->conversion_tables_loaded ) { return; } - - global $zh2Hans, $zh2Hant, $zh2CN, $zh2TW, $zh2HK, $zh2SG; - + + global $zh2Hans; if ( ! isset( $zh2Hans ) ) { require_once dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php'; - $extra_conversion_file = WP_CONTENT_DIR . '/extra_zhconversion.php'; if ( file_exists( $extra_conversion_file ) ) { require_once $extra_conversion_file; } } - - // 标记所有表为已加载 - self::$loaded_tables = array( - 'zh-hans' => true, - 'zh-hant' => true, - 'zh-cn' => true, - 'zh-tw' => true, - 'zh-hk' => true, - 'zh-sg' => true - ); - + $this->conversion_tables_loaded = true; } - + private function convert_to_hans( $text ) { global $zh2Hans; return strtr( $text, $zh2Hans ); } - + private function convert_to_hant( $text ) { global $zh2Hant; return strtr( $text, $zh2Hant ); } - + private function convert_to_cn( $text ) { global $zh2Hans, $zh2CN; return strtr( strtr( $text, $zh2CN ), $zh2Hans ); } - + private function convert_to_tw( $text ) { global $zh2Hant, $zh2TW; return strtr( strtr( $text, $zh2TW ), $zh2Hant ); } - + private function convert_to_hk( $text ) { global $zh2Hant, $zh2HK; return strtr( strtr( $text, $zh2HK ), $zh2Hant ); } - + private function convert_to_sg( $text ) { global $zh2Hans, $zh2SG; return strtr( strtr( $text, $zh2SG ), $zh2Hans ); } -} \ No newline at end of file +} diff --git a/includes/core/class-opencc-converter.php b/includes/core/class-opencc-converter.php index 8842971..54fd00e 100644 --- a/includes/core/class-opencc-converter.php +++ b/includes/core/class-opencc-converter.php @@ -8,9 +8,60 @@ require_once dirname( __FILE__ ) . '/interface-converter.php'; use Overtrue\PHPOpenCC\OpenCC; use Overtrue\PHPOpenCC\Strategy; +use Overtrue\PHPOpenCC\Dictionary; class WPCC_OpenCC_Converter implements WPCC_Converter_Interface { - + /** + * 预处理并排序后的字典缓存(按策略) + * @var array>> 每个策略对应若干个字典映射,保持原有顺序 + */ + private $prepared = []; + + /** + * 是否包含中文字符的快速检测(避免不必要的 OpenCC 调用) + */ + private function contains_chinese($text) { + return is_string($text) && preg_match('/[\x{4e00}-\x{9fff}]/u', $text) === 1; + } + + /** + * 获取预处理后的字典(已展开并按键长度降序排序),仅在本请求内执行一次 + */ + private function get_prepared_dictionaries(string $strategy): array { + if (isset($this->prepared[$strategy])) { + return $this->prepared[$strategy]; + } + + $sets = Dictionary::get($strategy); // 与 upstream 一致的字典结构 + $prepared = []; + + foreach ($sets as $dictionary) { + // 可能是分组数组(数组的数组),需要先展平 + if (is_array(reset($dictionary))) { + $flat = []; + foreach ($dictionary as $dict) { + $flat = array_merge($flat, $dict); + } + // 按键长度降序排序,优先匹配长词 + uksort($flat, function ($a, $b) { + return mb_strlen($b) <=> mb_strlen($a); + }); + $prepared[] = $flat; + continue; + } + + // 普通单字典 + $flat = $dictionary; + uksort($flat, function ($a, $b) { + return mb_strlen($b) <=> mb_strlen($a); + }); + $prepared[] = $flat; + } + + $this->prepared[$strategy] = $prepared; + return $prepared; + } + private $strategy_map = array( 'zh-hans' => Strategy::TRADITIONAL_TO_SIMPLIFIED, 'zh-hant' => Strategy::SIMPLIFIED_TO_TRADITIONAL, @@ -26,17 +77,29 @@ class WPCC_OpenCC_Converter implements WPCC_Converter_Interface { return $text; } - if ( ! $this->is_available() ) { - throw new Exception( 'OpenCC library is not available' ); - } + // 快速路径:非中文内容不做转换 + if ( ! $this->contains_chinese( $text ) ) { + return $text; + } + + if ( ! $this->is_available() ) { + throw new Exception( 'OpenCC library is not available' ); + } if ( ! isset( $this->strategy_map[ $target_variant ] ) ) { return $text; } - try { - return OpenCC::convert( $text, $this->strategy_map[ $target_variant ] ); - } catch ( Exception $e ) { + try { + // 使用预处理字典避免每次调用时重复排序 + $strategy = $this->strategy_map[ $target_variant ]; + $dictionaries = $this->get_prepared_dictionaries($strategy); + $output = $text; + foreach ($dictionaries as $dict) { + $output = strtr($output, $dict); + } + return $output; + } catch ( Exception $e ) { error_log( 'WPCC OpenCC Conversion Error: ' . $e->getMessage() ); return $text; } @@ -89,15 +152,18 @@ class WPCC_OpenCC_Converter implements WPCC_Converter_Interface { $uncached_texts = array(); $uncached_indices = array(); - foreach ( $texts as $index => $text ) { - $cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant ); - if ( $cached_result !== null ) { - $cached_results[ $index ] = $cached_result; - } else { - $uncached_texts[] = $text; - $uncached_indices[] = $index; - } - } + foreach ( $texts as $index => $text ) { + $cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant ); + if ( $cached_result !== null ) { + $cached_results[ $index ] = $cached_result; + } else if ( ! $this->contains_chinese( $text ) ) { + // 非中文内容:无需调用 OpenCC,直接作为“已处理”内容返回 + $cached_results[ $index ] = $text; + } else { + $uncached_texts[] = $text; + $uncached_indices[] = $index; + } + } // 如果所有内容都在缓存中,直接返回 if ( empty( $uncached_texts ) ) { @@ -108,11 +174,19 @@ class WPCC_OpenCC_Converter implements WPCC_Converter_Interface { return $results; } - try { - // 转换未缓存的内容 - $converted_texts = OpenCC::convert( $uncached_texts, $this->strategy_map[ $target_variant ] ); - - // 合并结果并缓存新转换的内容 + try { + // 使用预处理字典一次性转换,避免每次排序 + $strategy = $this->strategy_map[ $target_variant ]; + $dictionaries = $this->get_prepared_dictionaries($strategy); + + $converted_texts = $uncached_texts; + foreach ($dictionaries as $dict) { + $converted_texts = array_map(function ($str) use ($dict) { + return strtr($str, $dict); + }, $converted_texts); + } + + // 合并结果并缓存新转换的内容 $results = array(); $uncached_index = 0; diff --git a/includes/core/class-wpcc-config.php b/includes/core/class-wpcc-config.php index 8620371..89867a4 100644 --- a/includes/core/class-wpcc-config.php +++ b/includes/core/class-wpcc-config.php @@ -69,7 +69,7 @@ class WPCC_Config { 'wpcc_use_cookie_variant' => 1, // 不转换 -'wpcc_no_conversion_tag' => 'pre,code,pre.wp-block-code,pre.wp-block-preformatted,script,noscript,style,kbd,samp', +'wpcc_no_conversion_tag' => '', 'wpcc_no_conversion_ja' => 0, 'wpcc_no_conversion_qtag' => 0, @@ -88,6 +88,7 @@ class WPCC_Config { 'wpcc_enable_schema_conversion' => 1, 'wpcc_enable_meta_conversion' => 1, + // 其他 'wpcc_flag_option' => 1, 'wpcc_trackback_plugin_author' => 0, @@ -103,14 +104,15 @@ class WPCC_Config { * 初始化语言配置 */ private function init_languages(): void { + // 注意:为避免在 init 之前触发 _load_textdomain_just_in_time,这里不调用 __(),仅存储原始中文标签。 $this->languages = [ - 'zh-cn' => [ 'zhconversion_cn', 'cntip', __( '简体中文', 'wp-chinese-converter' ), 'zh-CN' ], - 'zh-tw' => [ 'zhconversion_tw', 'twtip', __( '台灣正體', 'wp-chinese-converter' ), 'zh-TW' ], - 'zh-hk' => [ 'zhconversion_hk', 'hktip', __( '港澳繁體', 'wp-chinese-converter' ), 'zh-HK' ], - 'zh-hans' => [ 'zhconversion_hans', 'hanstip', __( '简体中文', 'wp-chinese-converter' ), 'zh-Hans' ], - 'zh-hant' => [ 'zhconversion_hant', 'hanttip', __( '繁体中文', 'wp-chinese-converter' ), 'zh-Hant' ], - 'zh-sg' => [ 'zhconversion_sg', 'sgtip', __( '马新简体', 'wp-chinese-converter' ), 'zh-SG' ], - 'zh-jp' => [ 'zhconversion_jp', 'jptip', __( '日式汉字', 'wp-chinese-converter' ), 'zh-JP' ], + 'zh-cn' => [ 'zhconversion_cn', 'cntip', '简体中文', 'zh-CN' ], + 'zh-tw' => [ 'zhconversion_tw', 'twtip', '台灣正體', 'zh-TW' ], + 'zh-hk' => [ 'zhconversion_hk', 'hktip', '港澳繁體', 'zh-HK' ], + 'zh-hans' => [ 'zhconversion_hans', 'hanstip', '简体中文', 'zh-Hans' ], + 'zh-hant' => [ 'zhconversion_hant', 'hanttip', '繁体中文', 'zh-Hant' ], + 'zh-sg' => [ 'zhconversion_sg', 'sgtip', '马新简体', 'zh-SG' ], + 'zh-jp' => [ 'zhconversion_jp', 'jptip', '日式汉字', 'zh-JP' ], ]; } diff --git a/includes/core/class-wpcc-main.php b/includes/core/class-wpcc-main.php index 89ec156..fd1fa16 100644 --- a/includes/core/class-wpcc-main.php +++ b/includes/core/class-wpcc-main.php @@ -171,10 +171,14 @@ class WPCC_Main { // 设置无转换URL $noconversion_url = $this->get_noconversion_url(); $this->config->set_noconversion_url( $noconversion_url ); + // 向后兼容:同步全局变量 + $GLOBALS['wpcc_noconversion_url'] = $noconversion_url; // 获取目标语言 $target_lang = $this->determine_target_language( $wp ); $this->config->set_target_lang( $target_lang ); + // 向后兼容:同步全局变量 + $GLOBALS['wpcc_target_lang'] = $target_lang; // 处理搜索转换 $this->handle_search_conversion(); @@ -323,6 +327,8 @@ class WPCC_Main { } $this->config->set_langs_urls( $langs_urls ); + // 向后兼容:同步全局变量 + $GLOBALS['wpcc_langs_urls'] = $langs_urls; } /** diff --git a/includes/core/class-wpcc-presets.php b/includes/core/class-wpcc-presets.php index f68c5fc..285f7be 100644 --- a/includes/core/class-wpcc-presets.php +++ b/includes/core/class-wpcc-presets.php @@ -72,7 +72,7 @@ final class WPCC_Presets { 'wpcc_use_fullpage_conversion' => 0, -'wpcc_no_conversion_tag' => 'pre,code,pre.wp-block-code,pre.wp-block-preformatted,script,noscript,style,kbd,samp', +'wpcc_no_conversion_tag' => '', 'wpcc_no_conversion_ja' => 0, 'wpcc_no_conversion_qtag' => 0, diff --git a/includes/wpcc-core.php b/includes/wpcc-core.php index f23f0d9..c89502f 100644 --- a/includes/wpcc-core.php +++ b/includes/wpcc-core.php @@ -892,6 +892,7 @@ function wpcc_do_conversion() { add_action( 'wp_head', 'wpcc_header' ); + if ( ! $wpcc_direct_conversion_flag ) { remove_action( 'wp_head', 'rel_canonical' ); add_action( 'wp_head', 'wpcc_rel_canonical' ); @@ -1055,6 +1056,7 @@ function wpcc_id() { return $_wpcc_id ++; } + /** * 修复链接转换 */ diff --git a/includes/wpcc-settings-page.php b/includes/wpcc-settings-page.php index 1412958..95cbbb7 100644 --- a/includes/wpcc-settings-page.php +++ b/includes/wpcc-settings-page.php @@ -471,9 +471,9 @@ $active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'basic'; + placeholder="">options['wpcc_no_conversion_tag'] ?? 'pre,code,pre.wp-block-code,pre.wp-block-preformatted'); ?>

- pre,code,pre.wp-block-code,pre.wp-block-preformatted + pre,code,pre.wp-block-code,pre.wp-block-preformatted

@@ -652,6 +652,7 @@ placeholder=" +
@@ -665,6 +666,7 @@ placeholder="

+

@@ -876,7 +878,7 @@ jQuery(document).ready(function($) { var jpOption = $('#zh-jp-option'); var jpCheckbox = jpOption.find('input[type="checkbox"]'); var jpInput = jpOption.find('input[type="text"]'); - + if (engine === 'opencc') { // OpenCC 引擎支持日式汉字 jpCheckbox.prop('disabled', false); diff --git a/wp-chinese-converter.php b/wp-chinese-converter.php index 4dcac12..773b346 100755 --- a/wp-chinese-converter.php +++ b/wp-chinese-converter.php @@ -6,7 +6,7 @@ * Author URI: https://wpcc.net * Text Domain: wp-chinese-converter * Domain Path: /languages - * Version: 1.4.0 + * Version: 1.3.0 * License: GPLv3 or later * License URI: http://www.gnu.org/licenses/gpl-3.0.html */ @@ -25,7 +25,7 @@ // 基础常量定义 define("wpcc_DEBUG", false); -define("wpcc_VERSION", "1.4.0"); +define("wpcc_VERSION", "1.3.0"); // 插件URL常量 if (defined("WP_PLUGIN_URL")) { From 54ebb8cd91aacfc608660d528dc2026d7057bb32 Mon Sep 17 00:00:00 2001 From: feibisi Date: Sun, 5 Oct 2025 23:24:08 +0800 Subject: [PATCH 06/19] Add [wpcc_nc] shortcode and improve no-conversion handling Introduces [wpcc_nc] and [wpcs_nc] shortcodes for marking no-conversion regions, ensuring compatibility with editors that strip HTML comments. Refactors block and content protection logic to use persistent wrappers and data attributes, improving reliability against HTML compressors. Updates quicktag button to insert the new shortcode and revises related UI descriptions. --- includes/core/class-wpcc-main.php | 11 +---- includes/wpcc-admin.php | 6 +-- includes/wpcc-core.php | 75 ++++++++++++++++++++++++++----- includes/wpcc-settings-page.php | 6 +-- 4 files changed, 72 insertions(+), 26 deletions(-) diff --git a/includes/core/class-wpcc-main.php b/includes/core/class-wpcc-main.php index fd1fa16..e61c091 100644 --- a/includes/core/class-wpcc-main.php +++ b/includes/core/class-wpcc-main.php @@ -540,17 +540,8 @@ class WPCC_Main { public function render_no_conversion_block( string $block_content, array $block ): string { if ( isset( $block['blockName'] ) && $block['blockName'] === 'wpcc/no-conversion' ) { $unique_id = uniqid(); - - $pattern = '/]*class="[^"]*wpcc-no-conversion-content[^"]*"[^>]*>(.*?)<\\/div>/s'; - - $replacement = function ( $matches ) use ( $unique_id ) { - $content = $matches[1]; - return '
' . $content . '
'; - }; - - $block_content = preg_replace_callback( $pattern, $replacement, $block_content ); + return '' . $block_content . ''; } - return $block_content; } diff --git a/includes/wpcc-admin.php b/includes/wpcc-admin.php index 72e61fd..93ef4d0 100644 --- a/includes/wpcc-admin.php +++ b/includes/wpcc-admin.php @@ -80,17 +80,17 @@ function wpcc_activate() { */ function wpcc_appthemes_add_quicktags() { global $wpcc_options; - if ( ! empty( $wpcc_options ) && ! empty( $wpcc_options['wpcc_no_conversion_qtag'] ) && wp_script_is( 'quicktags', 'enqueued' ) ) { + if ( ! empty( $wpcc_options ) && ! empty( $wpcc_options['wpcc_no_conversion_qtag'] ) ) { ?> + 网络默认键 的映射 + $map = [ + "wpcc_used_langs" => "wpcc_default_used_langs", + "cntip" => "wpcc_default_cntip", + "twtip" => "wpcc_default_twtip", + "hktip" => "wpcc_default_hktip", + "hanstip" => "wpcc_default_hanstip", + "hanttip" => "wpcc_default_hanttip", + "sgtip" => "wpcc_default_sgtip", + "jptip" => "wpcc_default_jptip", + "nctip" => "wpcc_default_nctip", + "wpcc_flag_option" => "wpcc_default_flag_option", + // 扩展语言模块(单站点键为 wpcc_show_more_langs) + "wpcc_show_more_langs" => + "wpcc_default_enable_extended_langs", + "wpcc_engine" => "wpcc_default_engine", + "wpcc_search_conversion" => + "wpcc_default_search_conversion", + "wpcc_use_fullpage_conversion" => + "wpcc_default_use_fullpage_conversion", + // 快速标签(单站点键为 wpcc_no_conversion_qtag) + "wpcc_no_conversion_qtag" => + "wpcc_default_no_conversion_qtag", + "wpcc_enable_post_conversion" => + "wpcc_default_enable_post_conversion", + "wpcc_post_conversion_target" => + "wpcc_default_post_conversion_target", + "wpcc_use_permalink" => "wpcc_default_use_permalink", + "wpcco_sitemap_post_type" => + "wpcc_default_sitemap_post_type", + "wpcco_use_sitemap" => "wpcc_default_use_sitemap", + "wpcc_browser_redirect" => "wpcc_default_browser_redirect", + "wpcc_auto_language_recong" => + "wpcc_default_auto_language_recong", + "wpcc_use_cookie_variant" => + "wpcc_default_use_cookie_variant", + "wpcc_no_conversion_tag" => + "wpcc_default_no_conversion_tag", + "wpcc_no_conversion_ja" => "wpcc_default_no_conversion_ja", + "wpcc_hreflang_x_default" => + "wpcc_default_hreflang_x_default", + "wpcc_enable_hreflang_tags" => + "wpcc_default_enable_hreflang_tags", + "wpcc_enable_hreflang_x_default" => + "wpcc_default_enable_hreflang_x_default", + "wpcc_enable_schema_conversion" => + "wpcc_default_enable_schema_conversion", + "wpcc_enable_meta_conversion" => + "wpcc_default_enable_meta_conversion", + "wpcc_enable_cache_addon" => + "wpcc_default_enable_cache_addon", + "wpcc_enable_network_module" => + "wpcc_default_enable_network_module", + ]; + + foreach ($controlled_options as $local_key) { + if (isset($map[$local_key])) { + $network_key = $map[$local_key]; + $sync_settings[$network_key] = get_site_option( + $network_key, + ); + } + } + + // 仅当有需要同步的键时才应用 + if (!empty($sync_settings)) { + self::apply_settings_to_all_sites($sync_settings); + } + } + } + + $redirect_url = add_query_arg( + [ + "page" => "wpcc-network", + "updated" => "true", + ], + network_admin_url("settings.php"), + ); + + if (!empty($current_tab)) { + $redirect_url = add_query_arg("tab", $current_tab, $redirect_url); + } + + wp_redirect($redirect_url); + exit(); + } + + public static function display_sites_status() + { + $sites = get_sites(["number" => 50]); + + if (empty($sites)) { + echo "

没有找到站点。

"; + return; + } + + echo '
'; + echo ""; + echo ""; + + foreach ($sites as $site) { + switch_to_blog($site->blog_id); + + $site_options = get_option("wpcc_options", []); + $engine = $site_options["wpcc_engine"] ?? "mediawiki"; + $languages = $site_options["wpcc_used_langs"] ?? []; + $active = is_plugin_active( + "wp-chinese-converter/wp-chinese-converter.php", + ); + + echo ""; + echo '"; + echo ""; + echo ""; + echo ""; + echo ""; + + restore_current_blog(); + } + + echo "
站点转换引擎启用语言状态
' . + esc_html(get_bloginfo("name")) . + "" . esc_html($engine) . "" . esc_html(implode(", ", $languages)) . "" . ($active ? "✅ 激活" : "❌ 未激活") . "
"; + } + + public static function export_settings() + { + if (!current_user_can("manage_network_options")) { + wp_die("您没有足够权限执行此操作。"); + return; + } + + check_admin_referer("wpcc_export_settings"); + + $export_data = []; + + if (isset($_POST["export_network_settings"])) { + $export_data["network_settings"] = [ + "wpcc_network_enabled" => get_site_option( + "wpcc_network_enabled", + 0, + ), + "wpcc_network_enforce" => get_site_option( + "wpcc_network_enforce", + 0, + ), + ]; + } + + if (isset($_POST["export_basic_settings"])) { + $export_data["basic_settings"] = [ + "wpcc_default_used_langs" => get_site_option( + "wpcc_default_used_langs", + ["zh-cn", "zh-tw"], + ), + "wpcc_default_cntip" => get_site_option( + "wpcc_default_cntip", + "简体", + ), + "wpcc_default_twtip" => get_site_option( + "wpcc_default_twtip", + "繁体", + ), + "wpcc_default_hktip" => get_site_option( + "wpcc_default_hktip", + "港澳", + ), + "wpcc_default_hanstip" => get_site_option( + "wpcc_default_hanstip", + "简体", + ), + "wpcc_default_hanttip" => get_site_option( + "wpcc_default_hanttip", + "繁体", + ), + "wpcc_default_sgtip" => get_site_option( + "wpcc_default_sgtip", + "马新", + ), + "wpcc_default_jptip" => get_site_option( + "wpcc_default_jptip", + "日式", + ), + "wpcc_default_nctip" => get_site_option( + "wpcc_default_nctip", + "简体", + ), + "wpcc_default_flag_option" => get_site_option( + "wpcc_default_flag_option", + 1, + ), + "wpcc_default_enable_extended_langs" => get_site_option( + "wpcc_default_enable_extended_langs", + 1, + ), + "wpcc_default_engine" => get_site_option( + "wpcc_default_engine", + "opencc", + ), + "wpcc_default_search_conversion" => get_site_option( + "wpcc_default_search_conversion", + 1, + ), + "wpcc_default_use_fullpage_conversion" => get_site_option( + "wpcc_default_use_fullpage_conversion", + 1, + ), + "wpcc_default_enable_quicktags" => get_site_option( + "wpcc_default_enable_quicktags", + 1, + ), + "wpcc_default_enable_post_conversion" => get_site_option( + "wpcc_default_enable_post_conversion", + 0, + ), + "wpcc_default_use_permalink" => get_site_option( + "wpcc_default_use_permalink", + 0, + ), + "wpcc_default_sitemap_post_type" => get_site_option( + "wpcc_default_sitemap_post_type", + "post,page", + ), + "wpcc_default_use_sitemap" => get_site_option( + "wpcc_default_use_sitemap", + 1, + ), + "wpcc_default_browser_redirect" => get_site_option( + "wpcc_default_browser_redirect", + 0, + ), + "wpcc_default_use_cookie_variant" => get_site_option( + "wpcc_default_use_cookie_variant", + 0, + ), + "wpcc_default_no_conversion_tag" => get_site_option( + "wpcc_default_no_conversion_tag", + "", + ), + "wpcc_default_no_conversion_ja" => get_site_option( + "wpcc_default_no_conversion_ja", + 0, + ), + "wpcc_default_hreflang_x_default" => get_site_option( + "wpcc_default_hreflang_x_default", + "zh-cn", + ), + "wpcc_default_enable_hreflang_x_default" => get_site_option( + "wpcc_default_enable_hreflang_x_default", + 1, + ), + "wpcc_default_enable_hreflang_tags" => get_site_option( + "wpcc_default_enable_hreflang_tags", + 1, + ), + "wpcc_default_enable_schema_conversion" => get_site_option( + "wpcc_default_enable_schema_conversion", + 1, + ), + "wpcc_default_enable_meta_conversion" => get_site_option( + "wpcc_default_enable_meta_conversion", + 1, + ), + "wpcc_default_enable_cache_addon" => get_site_option( + "wpcc_default_enable_cache_addon", + 1, + ), + "wpcc_default_enable_network_module" => get_site_option( + "wpcc_default_enable_network_module", + 1, + ), + ]; + } + + if (isset($_POST["export_controlled_options"])) { + $export_data["controlled_options"] = [ + "wpcc_network_controlled_options" => get_site_option( + "wpcc_network_controlled_options", + self::$default_controlled_options, + ), + ]; + } + + $export_data["export_info"] = [ + "plugin_version" => wpcc_VERSION, + "export_date" => current_time("mysql"), + "site_url" => network_site_url(), + ]; + + $filename = "wpcc-network-settings-" . date("Y-m-d-H-i-s") . ".json"; + + header("Content-Type: application/json"); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + header("Cache-Control: no-cache, must-revalidate"); + header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); + + echo json_encode( + $export_data, + JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE, + ); + exit(); + } + + public static function import_settings() + { + if (!current_user_can("manage_network_options")) { + wp_die("您没有足够权限执行此操作。"); + return; + } + + check_admin_referer("wpcc_import_settings"); + + if ( + !isset($_FILES["import_file"]) || + $_FILES["import_file"]["error"] !== UPLOAD_ERR_OK + ) { + wp_die("文件上传失败,请重试。"); + return; + } + + $file_content = file_get_contents($_FILES["import_file"]["tmp_name"]); + $import_data = json_decode($file_content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + wp_die("无效的JSON文件,请检查文件格式。"); + return; + } + + if ( + isset($_POST["import_network_settings"]) && + isset($import_data["network_settings"]) + ) { + foreach ($import_data["network_settings"] as $key => $value) { + update_site_option($key, $value); + } + } + + if ( + isset($_POST["import_basic_settings"]) && + isset($import_data["basic_settings"]) + ) { + foreach ($import_data["basic_settings"] as $key => $value) { + update_site_option($key, $value); + } + } + + if ( + isset($_POST["import_controlled_options"]) && + isset($import_data["controlled_options"]) + ) { + foreach ($import_data["controlled_options"] as $key => $value) { + update_site_option($key, $value); + } + } + + if ( + isset($_POST["apply_to_existing_sites"]) && + isset($import_data["basic_settings"]) + ) { + self::apply_settings_to_all_sites($import_data["basic_settings"]); + } + + $redirect_url = add_query_arg( + [ + "page" => "wpcc-network", + "tab" => "import_export", + "imported" => "true", + ], + network_admin_url("settings.php"), + ); + + wp_redirect($redirect_url); + exit(); + } + + public static function reset_network_settings() + { + if (!current_user_can("manage_network_options")) { + wp_die("您没有足够权限执行此操作。"); + return; + } + + check_admin_referer("wpcc_reset_network_settings"); + + delete_site_option("wpcc_network_enabled"); + delete_site_option("wpcc_network_enforce"); + delete_site_option("wpcc_default_engine"); + delete_site_option("wpcc_allow_site_override"); + delete_site_option("wpcc_default_languages"); + delete_site_option("wpcc_default_search_conversion"); + delete_site_option("wpcc_default_use_fullpage_conversion"); + delete_site_option("wpcc_default_enable_hreflang_tags"); + delete_site_option("wpcc_default_enable_schema_conversion"); + delete_site_option("wpcc_default_enable_meta_conversion"); + delete_site_option("wpcc_network_controlled_options"); + + $redirect_url = add_query_arg( + [ + "page" => "wpcc-network", + "tab" => "import_export", + "reset" => "true", + ], + network_admin_url("settings.php"), + ); + + wp_redirect($redirect_url); + exit(); + } + + private static function apply_settings_to_all_sites($settings) + { + $sites = get_sites(); + + // 获取网络主站的固定链接结构,作为必要时的回填模板 + $main_site_id = function_exists('get_main_site_id') ? get_main_site_id() : (is_multisite() ? get_network()->site_id : get_current_blog_id()); + $network_permalink_structure = ''; + if ($main_site_id) { + switch_to_blog($main_site_id); + $network_permalink_structure = (string) get_option('permalink_structure', ''); + restore_current_blog(); + } + + foreach ($sites as $site) { + switch_to_blog($site->blog_id); + + $current_options = get_option('wpcc_options', array()); + + if (isset($settings["wpcc_default_used_langs"])) { + $current_options["wpcc_used_langs"] = + $settings["wpcc_default_used_langs"]; + } + if (isset($settings["wpcc_default_cntip"])) { + $current_options["cntip"] = $settings["wpcc_default_cntip"]; + } + if (isset($settings["wpcc_default_twtip"])) { + $current_options["twtip"] = $settings["wpcc_default_twtip"]; + } + if (isset($settings["wpcc_default_hktip"])) { + $current_options["hktip"] = $settings["wpcc_default_hktip"]; + } + if (isset($settings["wpcc_default_hanstip"])) { + $current_options["hanstip"] = $settings["wpcc_default_hanstip"]; + } + if (isset($settings["wpcc_default_hanttip"])) { + $current_options["hanttip"] = $settings["wpcc_default_hanttip"]; + } + if (isset($settings["wpcc_default_sgtip"])) { + $current_options["sgtip"] = $settings["wpcc_default_sgtip"]; + } + if (isset($settings["wpcc_default_jptip"])) { + $current_options["jptip"] = $settings["wpcc_default_jptip"]; + } + if (isset($settings["wpcc_default_nctip"])) { + $current_options["nctip"] = $settings["wpcc_default_nctip"]; + } + if (isset($settings["wpcc_default_flag_option"])) { + $current_options["wpcc_flag_option"] = + $settings["wpcc_default_flag_option"]; + } + if (isset($settings["wpcc_default_enable_extended_langs"])) { + $current_options["wpcc_show_more_langs"] = + $settings["wpcc_default_enable_extended_langs"]; + } + if (isset($settings["wpcc_default_engine"])) { + $current_options["wpcc_engine"] = + $settings["wpcc_default_engine"]; + } + if (isset($settings["wpcc_default_search_conversion"])) { + $current_options["wpcc_search_conversion"] = + $settings["wpcc_default_search_conversion"]; + } + if (isset($settings["wpcc_default_use_fullpage_conversion"])) { + $current_options["wpcc_use_fullpage_conversion"] = + $settings["wpcc_default_use_fullpage_conversion"]; + } + if (isset($settings["wpcc_default_enable_hreflang_tags"])) { + $current_options["wpcc_enable_hreflang_tags"] = + $settings["wpcc_default_enable_hreflang_tags"]; + } + if (isset($settings["wpcc_default_enable_schema_conversion"])) { + $current_options["wpcc_enable_schema_conversion"] = + $settings["wpcc_default_enable_schema_conversion"]; + } + if (isset($settings["wpcc_default_enable_meta_conversion"])) { + $current_options["wpcc_enable_meta_conversion"] = + $settings["wpcc_default_enable_meta_conversion"]; + } + if (isset($settings["wpcc_default_no_conversion_qtag"])) { + $current_options["wpcc_no_conversion_qtag"] = + $settings["wpcc_default_no_conversion_qtag"]; + } + if (isset($settings["wpcc_default_enable_post_conversion"])) { + $current_options["wpcc_enable_post_conversion"] = + $settings["wpcc_default_enable_post_conversion"]; + } + if (isset($settings["wpcc_default_post_conversion_target"])) { + $current_options["wpcc_post_conversion_target"] = + $settings["wpcc_default_post_conversion_target"]; + } + if (isset($settings["wpcc_default_use_permalink"])) { + $current_options["wpcc_use_permalink"] = + $settings["wpcc_default_use_permalink"]; + } + if (isset($settings["wpcc_default_sitemap_post_type"])) { + $current_options["wpcco_sitemap_post_type"] = + $settings["wpcc_default_sitemap_post_type"]; + } + if (isset($settings["wpcc_default_use_sitemap"])) { + $current_options["wpcco_use_sitemap"] = + $settings["wpcc_default_use_sitemap"]; + } + if (isset($settings["wpcc_default_browser_redirect"])) { + $current_options["wpcc_browser_redirect"] = + $settings["wpcc_default_browser_redirect"]; + } + if (isset($settings["wpcc_default_use_cookie_variant"])) { + $current_options["wpcc_use_cookie_variant"] = + $settings["wpcc_default_use_cookie_variant"]; + } + if (isset($settings["wpcc_default_auto_language_recong"])) { + $current_options["wpcc_auto_language_recong"] = + $settings["wpcc_default_auto_language_recong"]; + } + if (isset($settings["wpcc_default_no_conversion_tag"])) { + $current_options["wpcc_no_conversion_tag"] = + $settings["wpcc_default_no_conversion_tag"]; + } + if (isset($settings["wpcc_default_no_conversion_ja"])) { + $current_options["wpcc_no_conversion_ja"] = + $settings["wpcc_default_no_conversion_ja"]; + } + if (isset($settings["wpcc_default_hreflang_x_default"])) { + $current_options["wpcc_hreflang_x_default"] = + $settings["wpcc_default_hreflang_x_default"]; + } + if (isset($settings["wpcc_default_enable_cache_addon"])) { + $current_options["wpcc_enable_cache_addon"] = + $settings["wpcc_default_enable_cache_addon"]; + } + if (isset($settings["wpcc_default_enable_network_module"])) { + $current_options["wpcc_enable_network_module"] = + $settings["wpcc_default_enable_network_module"]; + } + + update_option('wpcc_options', $current_options); + + // 若子站启用了插件的固定链接模式,但 WP 仍是默认朴素结构,则回填网络主站的固定链接结构,避免 404 + $site_permastruct = (string) get_option('permalink_structure', ''); + $wpcc_use_permalink = isset($current_options['wpcc_use_permalink']) ? (int) $current_options['wpcc_use_permalink'] : 0; + if ($wpcc_use_permalink !== 0 && empty($site_permastruct) && !empty($network_permalink_structure)) { + update_option('permalink_structure', $network_permalink_structure); + } + + // 确保在网络同步后,为每个子站自动刷新重写规则,避免新语言路径 404 + if (function_exists('flush_rewrite_rules')) { + // 将子站的最新选项注入全局,供重写规则过滤器使用 + global $wpcc_options, $wp_rewrite; + $wpcc_options = $current_options; + + if (!has_filter('rewrite_rules_array', 'wpcc_rewrite_rules')) { + add_filter('rewrite_rules_array', 'wpcc_rewrite_rules'); + } + + // 重新初始化并强制刷新(hard) + if (isset($wp_rewrite) && is_object($wp_rewrite)) { + $wp_rewrite->init(); + } + flush_rewrite_rules(true); + } + + restore_current_blog(); + } + } + + public static function bulk_reset_sites() + { + if (!current_user_can("manage_network_options")) { + wp_die("您没有足够权限执行此操作。"); + return; + } + + check_admin_referer("wpcc_bulk_reset_sites"); + + if (!isset($_POST["wpcc_bulk_reset_confirm"])) { + wp_die("请确认批量重置操作。"); + return; + } + + $sites = get_sites(); + $reset_count = 0; + + foreach ($sites as $site) { + switch_to_blog($site->blog_id); + + // 删除站点的 WPCC 设置 + $deleted = delete_option("wpcc_options"); + if ($deleted) { + $reset_count++; + } + + restore_current_blog(); + } + + $redirect_url = add_query_arg( + [ + "page" => "wpcc-network", + "tab" => "tools", + "bulk_reset" => "true", + "reset_count" => $reset_count, + ], + network_admin_url("settings.php"), + ); + + wp_redirect($redirect_url); + exit(); + } +} diff --git a/includes/wpcc-admin.php b/includes/wpcc-admin.php index 93ef4d0..14458e8 100644 --- a/includes/wpcc-admin.php +++ b/includes/wpcc-admin.php @@ -1,14 +1,101 @@ id, 'wpcc' ) === false ) { + return; + } + + global $wpcc_options; + if ( empty( $wpcc_options['wpcc_use_permalink'] ) ) { + return; + } + + // 检查是否存在语言规则 + $rules = get_option( 'rewrite_rules', [] ); + $enabled_langs = $wpcc_options['wpcc_used_langs'] ?? []; + if ( empty( $enabled_langs ) ) { + return; + } + + $reg = implode( '|', $enabled_langs ); + $expected = '^(' . $reg . '|zh|zh-reset)/?$'; + $has_rule = false; + + foreach ( $rules as $regex => $query ) { + if ( $regex === $expected && strpos( $query, 'variant=' ) !== false ) { + $has_rule = true; + break; + } + } + + if ( ! $has_rule ) { + echo '
'; + echo '

WP Chinese Converter: 检测到语言重写规则可能未正确配置,这可能导致语言主页404错误。 '; + echo '刷新重写规则

'; + echo '
'; + + // 添加JavaScript + echo ''; + } +} + // 防止直接访问 if ( ! defined( 'ABSPATH' ) ) { exit; @@ -19,7 +106,7 @@ if ( ! defined( 'ABSPATH' ) ) { */ function wpcc_admin_init() { global $wpcc_admin; - require_once __DIR__ . '/admin/wp-chinese-converter-admin.php'; + require_once __DIR__ . '/admin/class-wpcc-admin.php'; $wpcc_admin = new wpcc_Admin(); add_filter( 'plugin_action_links', array( $wpcc_admin, 'action_links' ), 10, 2 ); } @@ -86,11 +173,11 @@ function wpcc_appthemes_add_quicktags() { //', '', null, 'WP Chinese Converter: Insert no-convert markers', 120); + QTags.addButton('eg_wpcc_nc', 'wpcc_NC', '[wpcc_nc]', '[/wpcc_nc]', null, 'WP Chinese Converter: Insert no-convert markers', 120); } }, 100); } @@ -211,31 +298,13 @@ function wpcc_add_conversion_meta_box() { /** * 获取语言模块配置 + * 使用中心化的语言配置管理 */ function wpcc_get_language_config() { global $wpcc_options; - $default_names = array( - 'zh-cn' => '中国大陆', - 'zh-tw' => '台湾正体', - 'zh-hk' => '港澳繁体', - 'zh-hans' => '简体中文', - 'zh-hant' => '繁体中文', - 'zh-sg' => '马新简体', - 'zh-jp' => '日式汉字' - ); - - $custom_names = array( - 'zh-cn' => $wpcc_options['cntip'] ?? $default_names['zh-cn'], - 'zh-tw' => $wpcc_options['twtip'] ?? $default_names['zh-tw'], - 'zh-hk' => $wpcc_options['hktip'] ?? $default_names['zh-hk'], - 'zh-hans' => $wpcc_options['hanstip'] ?? $default_names['zh-hans'], - 'zh-hant' => $wpcc_options['hanttip'] ?? $default_names['zh-hant'], - 'zh-sg' => $wpcc_options['sgtip'] ?? $default_names['zh-sg'], - 'zh-jp' => $wpcc_options['jptip'] ?? $default_names['zh-jp'] - ); - - return $custom_names; + // 使用中心化的语言配置 + return WPCC_Language_Config::get_custom_names( $wpcc_options ); } /** @@ -361,8 +430,10 @@ function my_ajax_clear_cache_handler() { // 注册管理钩子 +// 注意:网络管理菜单现在由 wpcc-network-settings.php 处理 if ( is_multisite() && wpcc_mobile_exist( 'network' ) ) { - add_action( 'network_admin_menu', 'wpcc_admin_init' ); + // add_action( 'network_admin_menu', 'wpcc_admin_init' ); // 已被新的网络设置模块替代 + add_action( 'admin_menu', 'wpcc_admin_init' ); } else { add_action( 'admin_menu', 'wpcc_admin_init' ); } @@ -373,6 +444,31 @@ register_activation_hook( dirname( __DIR__ ) . '/wp-chinese-converter.php', 'wpc // 注册编辑器增强钩子 add_action( 'admin_print_footer_scripts', 'wpcc_appthemes_add_quicktags' ); +// TinyMCE 可视化编辑器按钮(仅在开启“快速标签”选项时添加按钮;短代码始终可用) +function wpcc_register_tinymce_plugin( $plugins ) { + global $wpcc_options; + if ( empty( $wpcc_options ) ) { + $wpcc_options = get_wpcc_option( 'wpcc_options' ); + } + // 仅当设置开启时添加按钮脚本 + if ( ! empty( $wpcc_options['wpcc_no_conversion_qtag'] ) ) { + $plugins['wpcc_nc'] = wpcc_DIR_URL . 'assets/js/tinymce-wpcc-nc.js'; + } + return $plugins; +} +function wpcc_add_tinymce_button( $buttons ) { + global $wpcc_options; + if ( empty( $wpcc_options ) ) { + $wpcc_options = get_wpcc_option( 'wpcc_options' ); + } + if ( ! empty( $wpcc_options['wpcc_no_conversion_qtag'] ) ) { + $buttons[] = 'wpcc_nc'; + } + return $buttons; +} +add_filter( 'mce_external_plugins', 'wpcc_register_tinymce_plugin' ); +add_filter( 'mce_buttons', 'wpcc_add_tinymce_button' ); + // 注册文章转换钩子 add_action( 'init', 'wpcc_init_post_conversion' ); diff --git a/includes/wpcc-core.php b/includes/wpcc-core.php index 709cec2..2edde8f 100644 --- a/includes/wpcc-core.php +++ b/includes/wpcc-core.php @@ -6,7 +6,7 @@ * 包含所有前台转换相关功能 * * @package WPChineseConverter - * @version 1.2.0 + * @version 1.4 */ // 防止直接访问 @@ -28,20 +28,14 @@ add_shortcode( 'wpcs_nc', function( $atts, $content = '' ) { /** * 初始化语言配置 + * 使用中心化的语言配置管理 */ function wpcc_init_languages(): void { global $wpcc_langs; if ( empty( $wpcc_langs ) ) { - $wpcc_langs = [ - 'zh-cn' => [ 'zhconversion_cn', 'cntip', __( '简体中文', 'wp-chinese-converter' ), 'zh-CN' ], - 'zh-tw' => [ 'zhconversion_tw', 'twtip', __( '台灣正體', 'wp-chinese-converter' ), 'zh-TW' ], - 'zh-hk' => [ 'zhconversion_hk', 'hktip', __( '港澳繁體', 'wp-chinese-converter' ), 'zh-HK' ], - 'zh-hans' => [ 'zhconversion_hans', 'hanstip', __( '简体中文', 'wp-chinese-converter' ), 'zh-Hans' ], - 'zh-hant' => [ 'zhconversion_hant', 'hanttip', __( '繁体中文', 'wp-chinese-converter' ), 'zh-Hant' ], - 'zh-sg' => [ 'zhconversion_sg', 'sgtip', __( '马新简体', 'wp-chinese-converter' ), 'zh-SG' ], - 'zh-jp' => [ 'zhconversion_jp', 'jptip', __( '日式汉字', 'wp-chinese-converter' ), 'zh-JP' ], - ]; + // 使用中心化的语言配置 + $wpcc_langs = WPCC_Language_Config::get_all_languages(); } } @@ -49,6 +43,17 @@ function wpcc_init_languages(): void { * 插件核心初始化 */ function wpcc_init() { + // 当新版核心 WPCC_Main 存在时,仍需要确保重写规则正确设置 + if ( class_exists( 'WPCC_Main' ) ) { + // 确保重写规则被正确设置(新版本的重写规则可能有问题) + global $wpcc_options; + if ( isset( $wpcc_options['wpcc_use_permalink'] ) && $wpcc_options['wpcc_use_permalink'] != 0 ) { + if ( ! has_filter( 'rewrite_rules_array', 'wpcc_rewrite_rules' ) ) { + add_filter( 'rewrite_rules_array', 'wpcc_rewrite_rules' ); + } + } + return; + } global $wpcc_options, $wp_rewrite; if ( isset( $wpcc_options['wpcc_use_permalink'] ) && $wpcc_options['wpcc_use_permalink'] != 0 && empty( $wp_rewrite->permalink_structure ) ) { @@ -61,17 +66,23 @@ function wpcc_init() { } // 处理评论提交的转换 - if ( ( isset( $_SERVER['PHP_SELF'] ) && ( strpos( $_SERVER['PHP_SELF'], 'wp-comments-post.php' ) !== false - || strpos( $_SERVER['PHP_SELF'], 'ajax-comments.php' ) !== false - || strpos( $_SERVER['PHP_SELF'], 'comments-ajax.php' ) !== false ) + $php_self = isset( $_SERVER['PHP_SELF'] ) ? sanitize_text_field( wp_unslash( $_SERVER['PHP_SELF'] ) ) : ''; + $request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : ''; + + if ( ( $php_self && ( strpos( $php_self, 'wp-comments-post.php' ) !== false + || strpos( $php_self, 'ajax-comments.php' ) !== false + || strpos( $php_self, 'comments-ajax.php' ) !== false ) ) && - isset( $_SERVER["REQUEST_METHOD"] ) && $_SERVER["REQUEST_METHOD"] == "POST" && - isset( $_POST['variant'] ) && ! empty( $_POST['variant'] ) && in_array( $_POST['variant'], $wpcc_options['wpcc_used_langs'] ) + $request_method === 'POST' && + isset( $_POST['variant'] ) && ! empty( $_POST['variant'] ) ) { - global $wpcc_target_lang; - $wpcc_target_lang = $_POST['variant']; - wpcc_do_conversion(); - return; + $variant = sanitize_text_field( wp_unslash( $_POST['variant'] ) ); + if ( in_array( $variant, $wpcc_options['wpcc_used_langs'], true ) ) { + global $wpcc_target_lang; + $wpcc_target_lang = $variant; + wpcc_do_conversion(); + return; + } } // 修复首页显示Page时的问题 @@ -143,10 +154,10 @@ function wpcc_parse_query( $query ) { return; } - $request_lang = isset( $query->query_vars['variant'] ) ? $query->query_vars['variant'] : ''; - $cookie_lang = isset( $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ) ? $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] : ''; + $request_lang = isset( $query->query_vars['variant'] ) ? sanitize_text_field( $query->query_vars['variant'] ) : ''; + $cookie_lang = isset( $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ) ) : ''; - if ( $request_lang && in_array( $request_lang, $wpcc_options['wpcc_used_langs'] ) ) { + if ( $request_lang && in_array( $request_lang, $wpcc_options['wpcc_used_langs'], true ) ) { $wpcc_target_lang = $request_lang; } else { $wpcc_target_lang = false; @@ -212,11 +223,15 @@ function wpcc_template_redirect() { */ function set_wpcc_langs_urls() { global $wpcc_langs_urls, $wpcc_options, $wpcc_noconversion_url; - + if ( ! $wpcc_langs_urls ) { - if ( $wpcc_noconversion_url == get_option( 'home' ) . '/' && $wpcc_options['wpcc_use_permalink'] ) { + $permalinks_enabled = (string) get_option( 'permalink_structure' ) !== ''; + $style = (int) ( $wpcc_options['wpcc_use_permalink'] ?? 0 ); + $style_effective = $permalinks_enabled ? $style : 0; + + if ( $wpcc_noconversion_url == get_option( 'home' ) . '/' && $style_effective ) { foreach ( $wpcc_options['wpcc_used_langs'] as $value ) { - $wpcc_langs_urls[ $value ] = $wpcc_noconversion_url . $value . '/'; + $wpcc_langs_urls[ $value ] = trailingslashit( $wpcc_noconversion_url . $value ); } } else { foreach ( $wpcc_options['wpcc_used_langs'] as $value ) { @@ -231,11 +246,13 @@ function set_wpcc_langs_urls() { */ function wpcc_get_noconversion_url() { global $wpcc_options; - $reg = implode( '|', $wpcc_options['wpcc_used_langs'] ); - $tmp = ( is_ssl() ? 'https://' : 'http://' ) . - ( isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' ) . - ( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' ); - $tmp = trim( strtolower( remove_query_arg( 'variant', $tmp ) ) ); + $reg = implode( '|', array_map( 'preg_quote', $wpcc_options['wpcc_used_langs'] ) ); + + $protocol = is_ssl() ? 'https://' : 'http://'; + $host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; + $uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; + + $tmp = trim( strtolower( remove_query_arg( 'variant', $protocol . $host . $uri ) ) ); if ( preg_match( '/^(.*)\/(' . $reg . '|zh|zh-reset)(\/.*)?$/', $tmp, $matches ) ) { $tmp = user_trailingslashit( trailingslashit( $matches[1] ) . ltrim( $matches[3] ?? '', '/' ) ); @@ -252,44 +269,67 @@ function wpcc_get_noconversion_url() { */ function wpcc_link_conversion( $link, $variant = null ) { global $wpcc_options; - + static $wpcc_wp_home; if ( empty( $wpcc_wp_home ) ) { $wpcc_wp_home = home_url(); } - + if ( $variant === null ) { $variant = $GLOBALS['wpcc_target_lang']; } if ( $variant == false ) { return $link; } - if ( str_contains( $link, $variant ) ) { - return $link; + + $style = (int) ( $wpcc_options['wpcc_use_permalink'] ?? 0 ); + $permalinks_enabled = (string) get_option( 'permalink_structure' ) !== ''; + + // Split path and query + $qpos = strpos( $link, '?' ); + $path = $qpos !== false ? substr( $link, 0, $qpos ) : $link; + $qs = $qpos !== false ? substr( $link, $qpos ) : ''; + + // Detect existing variant in path; if present, strip duplicate query param if any + $enabled = isset( $wpcc_options['wpcc_used_langs'] ) && is_array( $wpcc_options['wpcc_used_langs'] ) ? $wpcc_options['wpcc_used_langs'] : []; + $variant_regex = '#/(?:' . implode( '|', array_map( 'preg_quote', $enabled ) ) . '|zh|zh-reset)(/|$)#i'; + $path_only = parse_url( $path, PHP_URL_PATH ); + if ( $path_only === null ) { $path_only = $path; } + if ( preg_match( $variant_regex, $path_only ) ) { + if ( $qpos !== false ) { + $qs = preg_replace( '/([?&])variant=[^&]*(&|$)/', '$1', $qs ); + $qs = rtrim( $qs, '?&' ); + if ( $qs && $qs[0] !== '?' ) { $qs = '?' . ltrim( $qs, '?' ); } + } + return $path . $qs; } - - if ( str_contains( $link, '?' ) || ! ($wpcc_options['wpcc_use_permalink'] ?? 0) ) { + + // 当 WP 未启用固定链接时,强制使用查询参数,避免 /zh-xx/ 404 + if ( ! $permalinks_enabled || $style === 0 ) { return add_query_arg( 'variant', $variant, $link ); } - - if ( $wpcc_options['wpcc_use_permalink'] == 1 ) { - return user_trailingslashit( trailingslashit( $link ) . $variant ); + + if ( $style === 1 ) { + // suffix style + return user_trailingslashit( trailingslashit( $path ) . $variant ) . $qs; } + + // prefix style (2) if ( is_multisite() && wpcc_mobile_exist( 'network' ) ) { $sites = get_sites(); foreach ( $sites as $site ) { if ( '/' == $site->path ) { continue; } - $path = str_replace( '/', '', $site->path ); - $sub_url = "$site->domain/$path"; - if ( str_contains( $link, $sub_url ) ) { - return str_replace( $sub_url, "$sub_url/$variant", $link ); + $path_seg = str_replace( '/', '', $site->path ); + $sub_url = "$site->domain/$path_seg"; + if ( str_contains( $path, $sub_url ) ) { + return str_replace( $sub_url, "$sub_url/$variant", $path ) . $qs; } } } - - return str_replace( $wpcc_wp_home, "$wpcc_wp_home/$variant", $link ); + + return str_replace( $wpcc_wp_home, "$wpcc_wp_home/$variant", $path ) . $qs; } /** @@ -404,7 +444,6 @@ function zhconversion2( $str, $variant = null ) { $str = wpcc_protect_no_conversion_blocks( $str ); return limit_zhconversion( $str, $wpcc_langs[ $variant ][0] ); - return limit_zhconversion( $str, $wpcc_langs[ $variant ][0] ); } /** @@ -513,8 +552,10 @@ function zhconversion_deep( $value ) { * 兜底保护 - 为“不转换内容”区块添加注释标记 */ function wpcc_protect_no_conversion_blocks( $str ) { - // 已有保护则跳过 -if ( preg_match( '//', $str ) ) { + // 若内容中不存在可识别的不转换占位(data 属性或指定类),则无需处理 + $has_data_attr = preg_match( '/data-wpcc-no-conversion=("|\')true\1/i', $str ); + $has_nc_class = preg_match( '/wpcc-no-conversion-(content|wrapper)/i', $str ); + if ( ! $has_data_attr && ! $has_nc_class ) { return $str; } @@ -532,10 +573,10 @@ if ( preg_match( '//', $str ) ) { }; // 优先:包裹 .wpcc-no-conversion-content 内部 - $str = preg_replace_callback( '/(]*class=\"[^\"]*wpcc-no-conversion-content[^\"]*\"[^>]*>)([\s\S]*?)(<\/div>)/i', $wrap_inner_callback, $str ); + $str = preg_replace_callback( '/(]*class="[^"]*wpcc-no-conversion-content[^"]*"[^>]*>)([\s\S]*?)(<\/div>)/i', $wrap_inner_callback, $str ); // 兜底:包裹外层 wrapper 或 data 属性 - $str = preg_replace_callback( '/]*class=\"[^\"]*wpcc-no-conversion-wrapper[^\"]*\"[^>]*>[\s\S]*?<\/div>/i', $wrap_whole_callback, $str ); - $str = preg_replace_callback( '/<[^>]*data-wpcc-no-conversion=\"true\"[^>]*>[\s\S]*?<\/[^>]+>/i', $wrap_whole_callback, $str ); + $str = preg_replace_callback( '/]*class="[^"]*wpcc-no-conversion-wrapper[^"]*"[^>]*>[\s\S]*?<\/div>/i', $wrap_whole_callback, $str ); + $str = preg_replace_callback( '/<[^>]*data-wpcc-no-conversion="true"[^>]*>[\s\S]*?<\/[a-zA-Z0-9]+>/i', $wrap_whole_callback, $str ); return $str; } @@ -545,7 +586,7 @@ if ( preg_match( '//', $str ) ) { */ function limit_zhconversion( $str, $function ) { -if ( $m = preg_split( '/()(.*?)()/s', $str, - 1, PREG_SPLIT_DELIM_CAPTURE ) ) { +if ( $m = preg_split( '/()(.*?)()/s', $str, - 1, PREG_SPLIT_DELIM_CAPTURE ) ) { $r = ''; $count = 0; foreach ( $m as $v ) { @@ -593,73 +634,75 @@ function wpcc_output_navi( $args = '', $isReturn = false ) { return; } + // 计算“不转换”标签 if ( ! empty( $wpcc_options['nctip'] ) ) { $noconverttip = $wpcc_options['nctip']; } else { $locale = str_replace( '_', '-', strtolower( get_locale() ) ); - if ( in_array( $locale, array( 'zh-hant', 'zh-tw', 'zh-hk', 'zh-mo' ) ) ) { - $noconverttip = '不转换'; - } else { - $noconverttip = '不转换'; - } + $noconverttip = in_array( $locale, array( 'zh-hant', 'zh-tw', 'zh-hk', 'zh-mo' ) ) ? '不转换' : '不转换'; } if ( $wpcc_target_lang ) { $noconverttip = zhconversion( $noconverttip ); } - if ( ( $wpcc_options['wpcc_browser_redirect'] == 2 || $wpcc_options['wpcc_use_cookie_variant'] == 2 ) && - $wpcc_target_lang - ) { - $default_url = wpcc_link_conversion( $wpcc_noconversion_url, 'zh' ); - if ( $wpcc_options['wpcc_use_permalink'] != 0 && is_home() && ! is_paged() ) { + // 计算“不转换”链接(必要时注入 zh 哨兵以覆盖浏览器/Cookie 策略) + if ( ( ! empty($wpcc_options['wpcc_browser_redirect']) && $wpcc_options['wpcc_browser_redirect'] == 2 ) || + ( ! empty($wpcc_options['wpcc_use_cookie_variant']) && $wpcc_options['wpcc_use_cookie_variant'] == 2 ) ) { + $default_url = $wpcc_target_lang ? wpcc_link_conversion( $wpcc_noconversion_url, 'zh' ) : $wpcc_noconversion_url; + if ( ! empty($wpcc_options['wpcc_use_permalink']) && is_home() && ! is_paged() ) { $default_url = trailingslashit( $default_url ); } } else { $default_url = $wpcc_noconversion_url; } - $wpcc_translate_type = $wpcc_options['wpcc_translate_type'] ?? 0; + // 展示形式:优先使用新字段 wpcc_translate_type,兼容旧字段 wpcc_flag_option + $wpcc_translate_type = $wpcc_options['wpcc_translate_type'] ?? ($wpcc_options['wpcc_flag_option'] ?? 0); - $output = "\n" . '
' . "\n"; - if ( $wpcc_translate_type == 0 ) { - $output .= ' ' . esc_html( $noconverttip ) . '' . "\n"; + $html = "\n" . '
' . "\n"; + + // 统一语义:1 = 平铺,0 = 下拉 + if ( $wpcc_translate_type == 1 ) { + $__nofollow = ( preg_match( '/\/zh\//i', $default_url ) || preg_match( '/(?:[?&])variant=zh(?:&|$)/i', $default_url ) ) ? ' rel="nofollow"' : ''; + $html .= '' . esc_html( $noconverttip ) . '' . "\n"; foreach ( $wpcc_langs_urls as $key => $value ) { if ( !isset( $wpcc_langs[ $key ] ) || !isset( $wpcc_langs[ $key ][1] ) || !isset( $wpcc_langs[ $key ][2] ) ) { continue; } - $tip = ! empty( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) ? esc_html( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) : $wpcc_langs[ $key ][2]; + $tip = ! empty( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) ? $wpcc_options[ $wpcc_langs[ $key ][1] ] : $wpcc_langs[ $key ][2]; if ( $wpcc_target_lang ) { $tip = zhconversion( $tip ); } $safe_key = esc_attr( $key ); - $output .= ' ' . esc_html( $tip ) . '' . "\n"; + $html .= '' . esc_html( $tip ) . '' . "\n"; } - } else if ( $wpcc_translate_type == 1 ) { + } else if ( $wpcc_translate_type == 0 ) { $checkSelected = function ( $selected_lang ) use ( $wpcc_target_lang ) { - return $selected_lang == $wpcc_target_lang ? "selected" : ""; + return $selected_lang == $wpcc_target_lang ? 'selected' : ''; }; - $output .= sprintf( ''; + $html .= ''; foreach ( $wpcc_langs_urls as $key => $value ) { if ( !isset( $wpcc_langs[ $key ] ) || !isset( $wpcc_langs[ $key ][1] ) || !isset( $wpcc_langs[ $key ][2] ) ) { continue; } - $tip = ! empty( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) ? esc_html( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) : $wpcc_langs[ $key ][2]; + $tip = ! empty( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) ? $wpcc_options[ $wpcc_langs[ $key ][1] ] : $wpcc_langs[ $key ][2]; if ( $wpcc_target_lang ) { $tip = zhconversion( $tip ); } $safe_key = esc_attr( $key ); - $output .= sprintf( '', $safe_key, esc_attr( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ), $safe_key, $checkSelected( $key ), esc_html( $tip ) ); + $html .= ''; } - $output .= sprintf( '' ); + $html .= ''; } - $output .= '
' . "\n"; + + $html .= '
' . "\n"; if ( ! $echo || $isReturn ) { - return $output; + return $html; } - echo $output; + echo $html; } /** @@ -668,23 +711,23 @@ function wpcc_output_navi( $args = '', $isReturn = false ) { function wpcc_output_navi2() { global $wpcc_target_lang, $wpcc_noconversion_url, $wpcc_langs_urls, $wpcc_options; - if ( ( $wpcc_options['wpcc_browser_redirect'] == 2 || $wpcc_options['wpcc_use_cookie_variant'] == 2 ) && - $wpcc_target_lang - ) { + if ( ( ! empty($wpcc_options['wpcc_browser_redirect']) && $wpcc_options['wpcc_browser_redirect'] == 2 ) && $wpcc_target_lang || + ( ! empty($wpcc_options['wpcc_use_cookie_variant']) && $wpcc_options['wpcc_use_cookie_variant'] == 2 ) && $wpcc_target_lang ) { $default_url = wpcc_link_conversion( $wpcc_noconversion_url, 'zh' ); - if ( $wpcc_options['wpcc_use_permalink'] != 0 && is_home() && ! is_paged() ) { + if ( ! empty($wpcc_options['wpcc_use_permalink']) && is_home() && ! is_paged() ) { $default_url = trailingslashit( $default_url ); } } else { $default_url = $wpcc_noconversion_url; } - $output = "\n" . '
' . "\n"; - $output .= ' ' . esc_html( '不转换' ) . '' . "\n"; - $output .= ' ' . esc_html( '大陆简体' ) . '' . "\n"; - $output .= ' ' . esc_html( '台湾正体' ) . '' . "\n"; - $output .= '
' . "\n"; - echo $output; + $html = "\n" . '
' . "\n"; + $__nofollow2 = ( preg_match( '/\/zh\//i', $default_url ) || preg_match( '/(?:[?&])variant=zh(?:&|$)/i', $default_url ) ) ? ' rel="nofollow"' : ''; + $html .= '' . esc_html( '不转换' ) . '' . "\n"; + $html .= '' . esc_html( '大陆简体' ) . '' . "\n"; + $html .= '' . esc_html( '台湾正体' ) . '' . "\n"; + $html .= '
' . "\n"; + echo $html; } /** @@ -932,6 +975,10 @@ function wpcc_load_conversion_table() { * 执行转换 */ function wpcc_do_conversion() { + // 当新版核心 WPCC_Main 存在时,跳过旧版全页面转换与过滤器,避免重复执行 + if ( class_exists( 'WPCC_Main' ) ) { + return; + } global $wpcc_direct_conversion_flag, $wpcc_options; wpcc_load_conversion_table(); @@ -1035,7 +1082,7 @@ function wpcc_header() { echo ''; if ( ! $wpcc_direct_conversion_flag ) { - wp_enqueue_script( 'wpcc-search-js', wpcc_DIR_URL . 'assets/js/search-variant.min.js', array(), '1.1', false ); +wp_enqueue_script( 'wpcc-search-js', wpcc_DIR_URL . 'assets/js/search-variant.min.js', array(), wpcc_VERSION, false ); } if ( $wpcc_direct_conversion_flag || @@ -1065,7 +1112,7 @@ function wpcc_wrap_nc_markers( $content ) { if ( empty( $content ) || strpos( $content, 'wpc' ) === false ) { return $content; } - $pattern = '/([\s\S]*?)/i'; + $pattern = '/([\\s\\S]*?)/i'; return preg_replace_callback( $pattern, function( $m ) { $inner = $m[2]; return '' . $inner . ''; @@ -1246,14 +1293,33 @@ function wpcc_body_class( $classes ) { * 语言属性过滤器 */ function wpcc_locale( $output, $doctype = 'html' ) { - global $wpcc_target_lang, $wpcc_langs; + global $wpcc_target_lang, $wpcc_langs, $wp; wpcc_init_languages(); $lang = get_bloginfo( 'language' ); - if ( $wpcc_target_lang && strpos( $lang, 'zh-' ) === 0 && isset( $wpcc_langs[ $wpcc_target_lang ] ) && isset( $wpcc_langs[ $wpcc_target_lang ][3] ) ) { + + // 仅在“确实处于变体页面”时才覆盖 + $variant_from_query = ''; + if ( isset( $wp->query_vars['variant'] ) ) { + $variant_from_query = sanitize_text_field( $wp->query_vars['variant'] ); + } + $variant_from_path = ''; + $path = isset( $wp->request ) ? $wp->request : ''; + if ( $path && preg_match( '/^(zh-cn|zh-tw|zh-hk|zh-hans|zh-hant|zh-sg|zh|zh-reset)\b/i', $path, $m ) ) { + $variant_from_path = strtolower( $m[1] ); + } + $is_variant_request = ( $variant_from_query !== '' ) || ( $variant_from_path !== '' ); + + if ( + $wpcc_target_lang && + $is_variant_request && + strpos( $lang, 'zh-' ) === 0 && + isset( $wpcc_langs[ $wpcc_target_lang ] ) && + isset( $wpcc_langs[ $wpcc_target_lang ][3] ) + ) { $lang = $wpcc_langs[ $wpcc_target_lang ][3]; - $output = preg_replace( '/lang="[^"]+"/', "lang=\"{$lang}\"", $output ); + $output = preg_replace( '/lang=\"[^\"]+\"/', "lang=\"{$lang}\"", $output ); } return $output; } @@ -1277,7 +1343,7 @@ function wpcc_init_modules() { $module_manager = WPCC_Module_Manager::get_instance(); $module_manager->register_module( 'WPCC_Cache_Addon', dirname( __FILE__ ) . '/../modules/wpcc-cache-addon.php' ); - $module_manager->register_module( 'WPCC_Network', dirname( __FILE__ ) . '/../modules/wpcc-network.php' ); + // $module_manager->register_module( 'WPCC_Network', dirname( __FILE__ ) . '/../modules/wpcc-network.php' ); // 已被新的网络设置模块替代 $module_manager->register_module( 'WPCC_Rest_Api', dirname( __FILE__ ) . '/../modules/wpcc-rest-api.php' ); $module_manager->register_module( 'WPCC_Modern_Cache', dirname( __FILE__ ) . '/../modules/wpcc-modern-cache.php' ); $module_manager->register_module( 'WPCC_SEO_Enhancement', dirname( __FILE__ ) . '/../modules/wpcc-seo-enhancement.php' ); @@ -1323,6 +1389,33 @@ add_shortcode( 'wp-chinese-converter', 'wp_chinese_converter_shortcode' ); add_filter( "body_class", "wpcc_body_class" ); add_filter( 'language_attributes', 'wpcc_locale' ); +// 兜底层兜底:在未启用新内核时,显式将根级变体规则置于顶端,避免 /zh-xx/ 404 +if ( ! class_exists( 'WPCC_Main' ) ) { + add_action( 'init', function() { + global $wpcc_options; + if ( empty( $wpcc_options ) || empty( $wpcc_options['wpcc_use_permalink'] ) ) { + return; + } + $enabled = isset( $wpcc_options['wpcc_used_langs'] ) && is_array( $wpcc_options['wpcc_used_langs'] ) ? $wpcc_options['wpcc_used_langs'] : []; + if ( empty( $enabled ) ) { + return; + } + $reg = implode( '|', array_map( 'preg_quote', $enabled ) ); + if ( function_exists( 'add_rewrite_rule' ) ) { + // 多次尝试添加规则,确保优先级 + add_rewrite_rule( '^(' . $reg . '|zh|zh-reset)/?$', 'index.php?variant=$matches[1]', 'top' ); + + // 添加钩子确保规则被正确处理 + add_filter( 'rewrite_rules_array', function( $rules ) use ( $reg ) { + $new_rules = []; + $root_rule = '^(' . $reg . '|zh|zh-reset)/?$'; + $new_rules[ $root_rule ] = 'index.php?variant=$matches[1]'; + return array_merge( $new_rules, $rules ); + }, 1 ); // 最高优先级 + } + }, 1 ); +} + // 初始化小部件 if ( ! empty( $wpcc_options ) && is_array( $wpcc_options ) && is_array( $wpcc_options['wpcc_used_langs'] ) ) { add_action( 'widgets_init', function () { diff --git a/includes/wpcc-settings-page.php b/includes/wpcc-settings-page.php index ce9c79b..4052835 100644 --- a/includes/wpcc-settings-page.php +++ b/includes/wpcc-settings-page.php @@ -1,18 +1,122 @@ "语言模块", + "cntip" => "简体中文显示名", + "twtip" => "台湾正体显示名", + "hktip" => "港澳繁体显示名", + "hanstip" => "简体中文(国际)显示名", + "hanttip" => "繁体中文(国际)显示名", + "sgtip" => "马新简体显示名", + "jptip" => "日式汉字显示名", + "nctip" => '"不转换"标签显示名', + "wpcc_flag_option" => "展示形式", + "wpcc_show_more_langs" => "扩展语言模块", + "wpcc_engine" => "转换引擎", + "wpcc_search_conversion" => "搜索转换", + "wpcc_use_fullpage_conversion" => "全页面转换", + "wpcc_no_conversion_qtag" => "编辑器快速标签", + "wpcc_enable_post_conversion" => "发表时转换", + "wpcc_post_conversion_target" => "发表时转换目标语言", + "wpcc_use_permalink" => "URL链接格式", + "wpcco_sitemap_post_type" => "站点地图内容类型", + "wpcco_use_sitemap" => "多语言站点地图", + "wpcc_browser_redirect" => "浏览器语言检测", + "wpcc_auto_language_recong" => "语系内通用", + "wpcc_use_cookie_variant" => "Cookie偏好记忆", + "wpcc_no_conversion_tag" => "标签排除", + "wpcc_no_conversion_ja" => "日语内容排除", + "wpcc_hreflang_x_default" => "SEO默认语言", + "wpcc_enable_hreflang_tags" => "hreflang标签", + "wpcc_enable_hreflang_x_default" => "x-default 标签", + "wpcc_enable_schema_conversion" => "Schema数据转换", + "wpcc_enable_meta_conversion" => "SEO元数据转换", + "wpcc_enable_cache_addon" => "缓存插件兼容", + "wpcc_allow_uninstall" => "允许插件卸载", + ]; + + $notice = + '
'; + + // 简洁的单行显示 + if (count($controlled_options) == 1) { + $label = + $option_labels[$controlled_options[0]] ?? + $controlled_options[0]; + $notice .= + '

' . + esc_html($label) . + " 由网络管理员控制

"; + } else { + $notice .= + '

以下 ' . + count($controlled_options) . + " 项设置由网络管理员控制:

"; + foreach ($controlled_options as $option) { + $label = $option_labels[$option] ?? $option; + $notice .= + '

' . + esc_html($label) . + "

"; + } + } + + $notice .= + '

以上选项由网络管理员控制,您的更改将不会生效。

'; + $notice .= "
"; + return $notice; + } + return ""; +}; ?>

- + - + - + - +

@@ -29,199 +133,548 @@ $active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'basic';
- -
-
-

-

+
"> +

+

-

+ + +

+
- - + + ">↗
-
-
-
- -
- - + + ">↗ - " class="regular-text wpcc-input" - placeholder="" /> -

+ placeholder="" + /> +

- - + + ">↗ + options["wpcc_translate_type"] ?? ($this->options["wpcc_flag_option"] ?? 1); ?>
-

+

- - + + ">↗ -
-

+

+
- - + + ">↗ -
- $engine_info): ?> -
-

+ $engine_info + ): ?> +
"> +

@@ -229,568 +682,1395 @@ $active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'basic';
- - + + ">↗ - > + - - -

+ +

- - + + ">↗ -
-

+

+
- - + + ">↗ -

- + + +

在经典编辑器工具栏中添加"wpcc_NC"按钮。区块编辑器可直接用 [wpcc_nc] 简码,示例:[wpcc_nc]文派墨图,文风笔笙[/wpcc_nc]

- - + + ">↗ -
-

+

+ - + - +
- - + + ">↗ -

- +

- - + + ">↗ - " class="regular-text wpcc-input" - placeholder="" /> + placeholder="" + />

- +

- - + + ">↗ -
-

+

+
- - + + ">↗ - > + - - -
-
- - + + ">↗ - > + - -

- +

-

+

+
- - + + ">↗ - +

- pre,code,pre.wp-block-code,pre.wp-block-preformatted + pre,code,pre.wp-block-code,pre.wp-block-preformatted

- - + + ">↗ -
-

+

+ + + + +
- - + + ">↗ -
- - + - - options['wpcc_enable_hreflang_x_default']) ? $this->options['wpcc_enable_hreflang_x_default'] : 1, 1); ?> + /> + + + +

+
+ + ">↗ + +

- +

- - + + ">↗ -
- - + + ">↗ -
-

+

+ - - - - - - +
- - + + ">↗ -
- - - - -

- - - - - - -

-
- - - + + +
-
-

-

+
"> +

+

+ + + $tools_section_options = ["wpcc_enable_cache_addon"]; + $has_controlled_tools = false; + foreach ($tools_section_options as $option) { + if ($is_network_controlled($option)) { + $has_controlled_tools = true; + break; + } + } + if ($has_controlled_tools) { + echo $get_network_notice($tools_section_options); + } + ?> + +

- - + + ">↗ get_cache_status(); if ($cache_status == 0) { - echo '

' . __("未使用WP Super Cache插件", "wp-chinese-converter") . '

'; + echo '

' . + __( + "未使用WP Super Cache插件", + "wp-chinese-converter", + ) . + "

"; } elseif ($cache_status == 1) { - echo '

' . __("未安装", "wp-chinese-converter") . '

'; + echo '

' . + __("未安装", "wp-chinese-converter") . + "

"; } elseif ($cache_status == 2) { - echo '

' . __("已安装", "wp-chinese-converter") . '

'; + echo '

' . + __("已安装", "wp-chinese-converter") . + "

"; } ?> - + 0): ?>
- + - +
-

+

- - + + ">↗ 'WP Super Cache', - 'wp_rocket' => 'WP Rocket', - 'litespeed_cache' => 'LiteSpeed Cache', - 'w3_total_cache' => 'W3 Total Cache', - 'wp_fastest_cache' => 'WP Fastest Cache', - 'autoptimize' => 'Autoptimize', - 'jetpack_boost' => 'Jetpack Boost' - ); - - $active_cache_plugins = array(); - - if (function_exists('wp_cache_is_enabled') || function_exists('wp_super_cache_init')) { - $active_cache_plugins['wp_super_cache'] = 'WP Super Cache'; + $plugin_names = [ + "wp_super_cache" => "WP Super Cache", + "wp_rocket" => "WP Rocket", + "litespeed_cache" => "LiteSpeed Cache", + "w3_total_cache" => "W3 Total Cache", + "wp_fastest_cache" => "WP Fastest Cache", + "autoptimize" => "Autoptimize", + "jetpack_boost" => "Jetpack Boost", + ]; + $active_cache_plugins = []; + if ( + function_exists("wp_cache_is_enabled") || + function_exists("wp_super_cache_init") + ) { + $active_cache_plugins["wp_super_cache"] = + "WP Super Cache"; } - if (function_exists('rocket_clean_domain')) { - $active_cache_plugins['wp_rocket'] = 'WP Rocket'; + if (function_exists("rocket_clean_domain")) { + $active_cache_plugins["wp_rocket"] = + "WP Rocket"; } - if (class_exists('LiteSpeed\Core')) { - $active_cache_plugins['litespeed_cache'] = 'LiteSpeed Cache'; + if (class_exists("LiteSpeed\Core")) { + $active_cache_plugins["litespeed_cache"] = + "LiteSpeed Cache"; } - if (function_exists('w3tc_flush_all')) { - $active_cache_plugins['w3_total_cache'] = 'W3 Total Cache'; + if (function_exists("w3tc_flush_all")) { + $active_cache_plugins["w3_total_cache"] = + "W3 Total Cache"; } - if (class_exists('WpFastestCache')) { - $active_cache_plugins['wp_fastest_cache'] = 'WP Fastest Cache'; + if (class_exists("WpFastestCache")) { + $active_cache_plugins["wp_fastest_cache"] = + "WP Fastest Cache"; } - if (class_exists('autoptimizeMain') || function_exists('autoptimize_autoload')) { - $active_cache_plugins['autoptimize'] = 'Autoptimize'; + if ( + class_exists("autoptimizeMain") || + function_exists("autoptimize_autoload") + ) { + $active_cache_plugins["autoptimize"] = + "Autoptimize"; } - if (defined('JETPACK_BOOST_VERSION') || class_exists('Automattic\Jetpack_Boost\Jetpack_Boost')) { - $active_cache_plugins['jetpack_boost'] = 'Jetpack Boost'; + if ( + defined("JETPACK_BOOST_VERSION") || + class_exists( + "Automattic\Jetpack_Boost\Jetpack_Boost", + ) + ) { + $active_cache_plugins["jetpack_boost"] = + "Jetpack Boost"; } - if (!empty($active_cache_plugins)) { echo '
'; - echo '' . __('检测到的活跃缓存插件:', 'wp-chinese-converter') . '
'; - foreach ($active_cache_plugins as $plugin => $name) { - echo '✓ ' . esc_html($name) . ''; + echo '' . + __( + "检测到的活跃缓存插件:", + "wp-chinese-converter", + ) . + "
"; + foreach ( + $active_cache_plugins + as $plugin => $name + ) { + echo '✓ ' . + esc_html($name) . + ""; } - echo '
'; - - $cache_addon_enabled = isset($this->options['wpcc_enable_cache_addon']) ? $this->options['wpcc_enable_cache_addon'] : 1; + echo ""; + $cache_addon_enabled = isset( + $this->options["wpcc_enable_cache_addon"], + ) + ? $this->options["wpcc_enable_cache_addon"] + : 1; if ($cache_addon_enabled) { echo '

✓ 缓存兼容模块已启用,语言切换时将自动清除相关缓存。

'; } else { echo '

⚠ 缓存兼容模块已禁用,请在基础设置中启用"缓存插件兼容"。

'; } } else { - echo '

' . __('未检测到活跃的缓存插件。', 'wp-chinese-converter') . '

'; + echo '

' . + __( + "未检测到活跃的缓存插件。", + "wp-chinese-converter", + ) . + "

"; } ?>
- +
- + + + + + + +
- - + + ">↗ -

- -

- -
- - -
- + +
+

+ + 插件卸载功能由网络管理员控制 +

+

+ 如需卸载插件,请联系网络管理员。" target="_blank" style="color: #856404;">访问网络设置 +

- + +

+ +

+ +
+ + +
+ +
+
+
+ + ">↗ + + get_network_statistics(); + echo '
'; + echo "" . + __("网络统计:", "wp-chinese-converter") . + "
"; + echo "总站点数: " . + esc_html($stats["total_sites"]) . + "
"; + echo "激活站点数: " . + esc_html($stats["active_sites"]) . + "
"; + echo "MediaWiki引擎: " . + esc_html($stats["engines"]["mediawiki"]) . + " 个站点
"; + echo "OpenCC引擎: " . + esc_html($stats["engines"]["opencc"]) . + " 个站点
"; + if (!empty($stats["languages"])) { + echo "语言使用情况: "; + $lang_stats = []; + foreach ( + $stats["languages"] + as $lang => $count + ) { + $lang_stats[] = + $lang . "(" . $count . ")"; + } + echo esc_html(implode(", ", $lang_stats)); + } + echo "
"; + } + if ( + method_exists( + $network_module, + "display_sites_status", + ) + ) { + echo '
'; + $network_module->display_sites_status(); + echo "
"; + } + ?> +

+
@@ -807,7 +2087,7 @@ jQuery(document).ready(function($) { $('#post-conversion-options').hide(); } }); - + // 控制浏览器重定向相关选项的显示/隐藏 $('select[name="wpcco_browser_redirect"]').change(function() { if ($(this).val() == '0') { @@ -816,19 +2096,19 @@ jQuery(document).ready(function($) { $('.browser-redirect-dependent').show(); } }); - + // 控制扩展语言模块的显示/隐藏 function toggleExtendedLanguages() { var showMoreLangs = $('#wpcc_show_more_langs').is(':checked'); var extendedSection = $('.wpcc-extended-languages'); - + if (showMoreLangs) { extendedSection.show(); } else { extendedSection.hide(); } } - + // 检测是否使用Gutenberg编辑器 function isGutenbergActive() { // 检查WordPress是否启用了Gutenberg编辑器 @@ -836,42 +2116,54 @@ jQuery(document).ready(function($) { if (typeof wpccAdminData !== 'undefined' && wpccAdminData.isGutenbergActive !== undefined) { return wpccAdminData.isGutenbergActive; } - + // 备用检测方法:检查是否存在Gutenberg相关的全局变量 - return (typeof wp !== 'undefined' && wp.blocks) || + return (typeof wp !== 'undefined' && wp.blocks) || document.body.classList.contains('block-editor-page') || document.querySelector('.block-editor') !== null || (typeof window.wp !== 'undefined' && window.wp.blockEditor); } - + // 控制编辑器增强选项的启用/禁用 function toggleEditorEnhancementOptions() { var isGutenberg = isGutenbergActive(); var quicktagsOption = $('input[name="wpcc_no_conversion_qtag"]').closest('tr'); var postConversionOption = $('input[name="wpcc_enable_post_conversion"]').closest('tr'); - + if (isGutenberg) { // Gutenberg环境下只禁用快速标签功能,保留发表时转换功能 quicktagsOption.find('.wpcc-switch').addClass('wpcc-disabled'); quicktagsOption.find('input[type="checkbox"]').prop('disabled', true); -quicktagsOption.find('.description').html('
'); - +quicktagsOption.find('.description').html(''); + // 发表时转换功能在区块编辑器下保持可用 postConversionOption.find('.wpcc-switch').removeClass('wpcc-disabled'); postConversionOption.find('input[type="checkbox"]').prop('disabled', false); - postConversionOption.find('.description').html(''); + postConversionOption.find('.description').html(''); } else { // 经典编辑器环境下启用所有编辑器增强功能 quicktagsOption.find('.wpcc-switch').removeClass('wpcc-disabled'); quicktagsOption.find('input[type="checkbox"]').prop('disabled', false); -quicktagsOption.find('.description').html(''); - +quicktagsOption.find('.description').html(''); + postConversionOption.find('.wpcc-switch').removeClass('wpcc-disabled'); postConversionOption.find('input[type="checkbox"]').prop('disabled', false); - postConversionOption.find('.description').html(''); + postConversionOption.find('.description').html(''); } } - + // 控制引擎相关选项的启用/禁用 function toggleEngineRelatedOptions() { var engine = $('select[name="wpcc_engine"]').val(); @@ -891,20 +2183,29 @@ quicktagsOption.find('.description').html(' 默认语言行显示/隐藏 + function toggleSiteXDefault() { + var enabled = $('input[name="wpcc_enable_hreflang_x_default"]').is(':checked'); + $('#wpcc-site-x-default-options').toggle(enabled); + } + $('input[name="wpcc_enable_hreflang_x_default"]').on('change', toggleSiteXDefault); + // 页面加载时检查 toggleExtendedLanguages(); toggleEngineRelatedOptions(); toggleEditorEnhancementOptions(); - + toggleSiteXDefault(); + // 显示更多语言选项改变时 $('#wpcc_show_more_langs').change(function() { toggleExtendedLanguages(); }); - + // 转换引擎改变时检查 $('select[name="wpcc_engine"]').change(function() { toggleEngineRelatedOptions(); }); }); - \ No newline at end of file + diff --git a/wp-chinese-converter.php b/wp-chinese-converter.php index 773b346..6887e4d 100755 --- a/wp-chinese-converter.php +++ b/wp-chinese-converter.php @@ -6,7 +6,7 @@ * Author URI: https://wpcc.net * Text Domain: wp-chinese-converter * Domain Path: /languages - * Version: 1.3.0 + * Version: 1.4 * License: GPLv3 or later * License URI: http://www.gnu.org/licenses/gpl-3.0.html */ @@ -25,7 +25,7 @@ // 基础常量定义 define("wpcc_DEBUG", false); -define("wpcc_VERSION", "1.3.0"); +define("wpcc_VERSION", "1.4"); // 插件URL常量 if (defined("WP_PLUGIN_URL")) { @@ -60,11 +60,14 @@ use Overtrue\PHPOpenCC\OpenCC; use Overtrue\PHPOpenCC\Strategy; // 载入核心依赖 -require_once dirname(__FILE__) . "/includes/core/class-wpcc-exception-handler.php"; +require_once dirname(__FILE__) . + "/includes/core/class-wpcc-exception-handler.php"; require_once dirname(__FILE__) . "/includes/core/class-wpcc-utils.php"; -require_once dirname(__FILE__) . "/includes/core/class-converter-factory.php"; -require_once dirname(__FILE__) . "/includes/core/class-module-manager.php"; -require_once dirname(__FILE__) . "/includes/core/class-conversion-cache.php"; +require_once dirname(__FILE__) . + "/includes/core/class-wpcc-language-config.php"; +require_once dirname(__FILE__) . "/includes/core/class-wpcc-converter-factory.php"; +require_once dirname(__FILE__) . "/includes/core/class-wpcc-module-manager.php"; +require_once dirname(__FILE__) . "/includes/core/class-wpcc-conversion-cache.php"; require_once dirname(__FILE__) . "/includes/core/class-wpcc-config.php"; require_once dirname(__FILE__) . "/includes/core/class-wpcc-presets.php"; require_once dirname(__FILE__) . "/includes/core/class-wpcc-main.php"; @@ -79,18 +82,22 @@ function wpcc_add_global_js() "wpcc-variant", plugins_url("/assets/dist/wpcc-variant.umd.js", __FILE__), [], - "1.1.0", + wpcc_VERSION, ); wp_register_script( "wpcc-block-script-ok", plugins_url("/assets/js/wpcc-block-script-ok.js", __FILE__), ["wp-blocks", "wp-element", "wpcc-variant"], - "1.3.0", + wpcc_VERSION, ); wp_enqueue_script(["wpcc-variant", "wpcc-block-script-ok"]); + $use_permalink_type = 0; + if (is_array($wpcc_options) && isset($wpcc_options["wpcc_use_permalink"])) { + $use_permalink_type = (int) $wpcc_options["wpcc_use_permalink"]; + } wp_localize_script("wpcc-block-script-ok", "wpc_switcher_use_permalink", [ - "type" => $wpcc_options["wpcc_use_permalink"], + "type" => $use_permalink_type, ]); } @@ -107,6 +114,16 @@ if (file_exists($modules_dir . "wpcc-sitemap.php")) { require_once $modules_dir . "wpcc-sitemap.php"; } +// 加载网络设置模块 +if ( + is_multisite() && + file_exists(__DIR__ . "/includes/network/wpcc-network-settings.php") +) { + require_once __DIR__ . "/includes/network/wpcc-network-settings.php"; + add_action("init", ["WPCC_Network_Settings", "init"]); +} + + // 容错处理 - 只有配置正确时才加载功能 if ( $wpcc_options != false && @@ -116,13 +133,16 @@ if ( // 加载遗留的核心功能模块(为了向后兼容) require_once dirname(__FILE__) . "/includes/wpcc-core.php"; + // 确保核心初始化与查询变量已注册(保证变体参数与重写规则生效) + if (!has_action("init", "wpcc_init")) { + add_action("init", "wpcc_init"); + } + add_filter("query_vars", "wpcc_insert_query_vars"); + // 加载管理后台模块(仅在后台时加载) if (is_admin()) { require_once dirname(__FILE__) . "/includes/wpcc-admin.php"; } - - // 新架构已经处理了大部分初始化;移除旧版全局脚本入列以避免重复/冲突 - // add_action("wp_enqueue_scripts", "wpcc_add_global_js"); } /** @@ -139,11 +159,18 @@ function wpcc_mobile_exist($name) */ function get_wpcc_option($option_name, $default = false) { - if (is_multisite() && wpcc_mobile_exist("network")) { - return get_site_option($option_name, $default); - } else { - return get_option($option_name, $default); + // 使用现代化的配置管理器 + global $wpcc_main; + if ($wpcc_main && method_exists($wpcc_main, "get_config")) { + $config = $wpcc_main->get_config(); + if ($option_name === "wpcc_options") { + return $config->get_all_options(); + } + return $config->get_option($option_name, $default); } + + // 向后兼容 + return get_option($option_name, $default); } /** @@ -151,9 +178,7 @@ function get_wpcc_option($option_name, $default = false) */ function update_wpcc_option($option_name, $option_value) { - if (is_multisite() && wpcc_mobile_exist("network")) { - return update_site_option($option_name, $option_value); - } else { - return update_option($option_name, $option_value); - } + // 始终将站点级设置保存到当前站点的 options; + // 网络级设置在网络设置模块中使用 update_site_option 专门处理。 + return update_option($option_name, $option_value); } From 86d8dbeaf52705821555c3267d01ba2909c19d46 Mon Sep 17 00:00:00 2001 From: feibisi Date: Sat, 11 Oct 2025 22:59:08 +0800 Subject: [PATCH 08/19] Update README.md --- README.md | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2a79b78..4e9bfb8 100755 --- a/README.md +++ b/README.md @@ -101,15 +101,14 @@ - **不转换**: 指定不需要转换的内容区域 ### 短代码支持 -```php -// 显示语言切换器 -[wpcc_language_switcher] - -// 显示转换状态 -[wpcc_conversion_status] - -// 不转换指定内容 -[wpcc_no_conversion]不转换的内容[/wpcc_no_conversion] +- 语言切换器(老短代码,平铺/下拉由“展示形式”设置控制) +``` +[wp-chinese-converter] +``` +- 不转换内容(编辑器无法保留注释时作为稳健占位): +``` +[wpcc_nc]不转换的内容[/wpcc_nc] +[wpcs_nc]不转换的内容[/wpcs_nc] ``` ### PHP 函数调用 @@ -147,6 +146,25 @@ add_action('wpcc_before_conversion', 'before_convert'); add_action('wpcc_after_conversion', 'after_convert'); ``` +## 链接与重写规则行为说明(重要) + +- 链接格式与固定链接的关系 + - 当 WordPress 启用了固定链接,且“URL 链接格式”选择了“前缀/后缀”时:生成 /zh-xx/ 样式链接(或 …/zh-xx/) + - 当 WordPress 未启用固定链接(或环境未正确应用 rewrite 规则)时:自动回退为 ?variant=zh-xx,避免 404 +- 首页根级变体访问行为 + - 访问 /zh/ 或 /zh-reset/:作为“哨兵”回到不转换首页(https://example.com/),并设置 zh 偏好以覆盖浏览器/Cookie 策略 + - 访问 /zh-xx/(如 /zh-tw/、/zh-hk/):统一 302 跳转到首页,避免首页重复内容与 404 +- zh 哨兵与 rel="nofollow" + - 仅当“不转换”链接为覆盖策略而携带 zh 哨兵(URL 含 /zh/ 或 ?variant=zh)时自动添加 rel="nofollow" + - 当不需要哨兵(直接是原始 URL)时,不加 nofollow,避免影响站内权重传递 + +## 兼容层与新版内核 + +- 新内核(WPCC_Main / WPCC_Config 等)统一管理重写、变体解析、链接构造、注入脚本等; +- 为了兼容历史主题/插件/短代码调用,保留了 includes/wpcc-core.php 中的“老函数”(如 wpcc_link_conversion、set_wpcc_langs_urls、wpcc_output_navi、短代码 [wp-chinese-converter] 等); +- 老函数的行为已与新内核对齐(例如固定链接未启用时自动降级为 ?variant=xx),确保前后端一致; +- 建议新项目优先使用区块与新内核能力;对生态依赖的老接口,后续会以 @deprecated 标注与迁移指引逐步过渡。 + ## 故障排除 ### 常见问题 @@ -161,7 +179,7 @@ A: 请检查选择的转换引擎和目标语言是否正确。OpenCC 引擎适 A: 请检查 WordPress 缓存配置,或尝试清除转换缓存:`设置 > WP Chinese Converter > 高级设置 > 清除缓存` **Q: URL 重写不工作?** -A: 请确保服务器的 mod_rewrite 模块已启用,并重新保存固定链接设置。 +A: 请确保 WordPress 固定链接已启用并“保存更改”一次;服务器需正确支持 rewrite(如 Nginx/Apache 规则)。插件在未启用固定链接时会自动回退为 ?variant=xx。 ## 性能优化建议 @@ -195,6 +213,13 @@ A: 请确保服务器的 mod_rewrite 模块已启用,并重新保存固定链 ## 版本历史 +### v1.4.x (2025年稳定版补丁) +- 统一“展示形式”数值语义:1=平铺,0=下拉;修复短代码展示反向问题 +- 单站模式:当 WordPress 未启用固定链接时,链接自动降级为 ?variant=xx,避免 /zh-xx/ 404 +- 首页根级变体:/zh/ 与 /zh-xx/ 统一 302 到首页;/zh/ 同时设置 zh 偏好覆盖浏览器/Cookie 策略 +- 前端切换器:仅在携带 zh 哨兵时为“不转换”链接添加 rel="nofollow",并与新窗口 noopener noreferrer 兼容 +- 统一文件命名风格:核心类重命名为 class-wpcc-*.php;保留兼容层但与新内核对齐 + ### v1.3.0 (2025年重构版本) - 完全重写插件架构,采用现代化 OOP 设计 - 新增 Gutenberg 区块支持 From c50e16121b930b7d11596a7e04e7116ca364beaa Mon Sep 17 00:00:00 2001 From: feibisi Date: Sat, 11 Oct 2025 23:11:13 +0800 Subject: [PATCH 09/19] Release version 1.5.0 and update module versions Bump core and all module versions to 1.5.0, update required core version in extension class, and add changelog for version 1.5.0. This release includes bug fixes for single-site mode, improvements to language switcher and SEO rules, unified permalink and rewrite strategies, and codebase cleanup for consistency. --- changelog.txt | 27 +++++++++++++++++++++++ includes/core/class-wpcc-extension.php | 2 +- includes/modules/wpcc-cache-addon.php | 2 +- includes/modules/wpcc-modern-cache.php | 2 +- includes/modules/wpcc-network.php | 2 +- includes/modules/wpcc-rest-api.php | 2 +- includes/modules/wpcc-seo-enhancement.php | 2 +- wp-chinese-converter.php | 4 ++-- 8 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 changelog.txt diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..c0382fe --- /dev/null +++ b/changelog.txt @@ -0,0 +1,27 @@ +WP Chinese Converter (WPCC) — 更新日志 / Changelog + +Version 1.5.0 (2025-10-11) +- 修复 单站模式下“展示形式”反向的问题:统一语义为 1=平铺、0=下拉,短代码 [wp-chinese-converter] 与区块表现一致 +- 修复 单站设置保存问题:确保语言模块、全页面转换、展示形式等选项在单站模式可正确保存 +- 优化 zh 哨兵与 rel="nofollow" 规则:仅当“不转换”链接为覆盖策略而携带 zh 哨兵(/zh/ 或 ?variant=zh)时添加 nofollow;新窗口时追加 noopener noreferrer +- 优化 前端语言切换器:在变体页为“不转换”注入 zh 哨兵;缓存破除(基于文件 mtime 的版本号) +- 链接与重写 统一策略: + * WordPress 未启用固定链接时,所有语言链接自动回退为 ?variant=xx,避免 /zh-xx/ 404 + * 首页根级变体(/zh/、/zh-xx/)统一 302 到首页;/zh/ 同时设置 zh 偏好 +- 内核一致性:老函数(includes/wpcc-core.php)与新内核(WPCC_Main)策略对齐(固定链接检测、链接降级) +- 架构与清理:统一核心类文件命名为 class-wpcc-*.php;移除旧 url fix 入口;保留兼容层并与新内核对齐 +- 文档:更新 README,补充链接/重写行为说明与兼容策略 + +Version 1.4.x (2025) +- 统一 SEO 行为:在不需要 zh 哨兵时不添加 nofollow,避免权重传递受影响 +- 改善 区块与脚本的加载与配置注入,优化多语言切换体验 +- 多站点:网络设置与子站策略协调,增强稳定性 + +Version 1.3.0 (2025 重构) +- 完整重写插件架构为 OOP:WPCC_Main / WPCC_Config / 模块管理器等 +- 新增 Gutenberg 区块(语言切换器、转换状态、不转换区块) +- 优化 性能与内存占用;OpenCC 与 MediaWiki 双引擎;转换缓存 +- 增强 SEO(canonical、hreflang、schema/meta 转换)与 REST API 支持 + +历史说明 +- 本项目分叉自 WP Chinese Conversion,2025 年起由文派团队(WPCC.NET)进行长期维护与重构。 diff --git a/includes/core/class-wpcc-extension.php b/includes/core/class-wpcc-extension.php index 7f4efd1..b471551 100644 --- a/includes/core/class-wpcc-extension.php +++ b/includes/core/class-wpcc-extension.php @@ -18,7 +18,7 @@ abstract class WPCC_Extension extends WPCC_Abstract_Module { protected $update_server_url; protected $extension_slug; protected $is_premium = true; -protected $required_core_version = '1.4'; +protected $required_core_version = '1.5'; protected $pricing_tier = 'pro'; // free, pro, enterprise, ultimate public function __construct() { diff --git a/includes/modules/wpcc-cache-addon.php b/includes/modules/wpcc-cache-addon.php index 4582f03..8f9e84e 100755 --- a/includes/modules/wpcc-cache-addon.php +++ b/includes/modules/wpcc-cache-addon.php @@ -10,7 +10,7 @@ class WPCC_Cache_Addon extends WPCC_Abstract_Module { public function init() { $this->name = 'Cache Addon'; - $this->version = '1.4'; +$this->version = '1.5'; $this->description = '缓存插件兼容性模块,支持多种缓存插件'; $this->dependencies = array(); diff --git a/includes/modules/wpcc-modern-cache.php b/includes/modules/wpcc-modern-cache.php index cb88a6f..bb89b02 100644 --- a/includes/modules/wpcc-modern-cache.php +++ b/includes/modules/wpcc-modern-cache.php @@ -13,7 +13,7 @@ class WPCC_Modern_Cache extends WPCC_Abstract_Module { public function init() { $this->name = 'Modern Cache'; - $this->version = '1.4'; +$this->version = '1.5'; $this->description = '现代缓存模块,支持对象缓存、Redis、Memcached'; $this->dependencies = array(); diff --git a/includes/modules/wpcc-network.php b/includes/modules/wpcc-network.php index 529b4bf..d18d2c6 100755 --- a/includes/modules/wpcc-network.php +++ b/includes/modules/wpcc-network.php @@ -10,7 +10,7 @@ class WPCC_Network extends WPCC_Abstract_Module { public function init() { $this->name = 'Network Multisite'; - $this->version = '1.4'; +$this->version = '1.5'; $this->description = '多站点网络支持模块'; $this->dependencies = array( array( 'type' => 'function', 'name' => 'is_multisite' ) diff --git a/includes/modules/wpcc-rest-api.php b/includes/modules/wpcc-rest-api.php index 135f12f..e5bce0b 100644 --- a/includes/modules/wpcc-rest-api.php +++ b/includes/modules/wpcc-rest-api.php @@ -13,7 +13,7 @@ class WPCC_Rest_Api extends WPCC_Abstract_Module { public function init() { $this->name = 'REST API'; - $this->version = '1.4'; +$this->version = '1.5'; $this->description = 'REST API 接口模块,提供转换服务的 API 端点'; $this->dependencies = array( array( 'type' => 'class', 'name' => 'WP_REST_Server' ), diff --git a/includes/modules/wpcc-seo-enhancement.php b/includes/modules/wpcc-seo-enhancement.php index 48fe3ff..ef6244f 100644 --- a/includes/modules/wpcc-seo-enhancement.php +++ b/includes/modules/wpcc-seo-enhancement.php @@ -13,7 +13,7 @@ class WPCC_SEO_Enhancement extends WPCC_Abstract_Module { public function init() { $this->name = 'SEO Enhancement'; - $this->version = '1.4'; +$this->version = '1.5'; $this->description = 'SEO插件增强模块,支持Yoast SEO、RankMath、AIOSEO等主流SEO插件'; $this->dependencies = array(); diff --git a/wp-chinese-converter.php b/wp-chinese-converter.php index 6887e4d..0278c57 100755 --- a/wp-chinese-converter.php +++ b/wp-chinese-converter.php @@ -6,7 +6,7 @@ * Author URI: https://wpcc.net * Text Domain: wp-chinese-converter * Domain Path: /languages - * Version: 1.4 +* Version: 1.5 * License: GPLv3 or later * License URI: http://www.gnu.org/licenses/gpl-3.0.html */ @@ -25,7 +25,7 @@ // 基础常量定义 define("wpcc_DEBUG", false); -define("wpcc_VERSION", "1.4"); +define("wpcc_VERSION", "1.5"); // 插件URL常量 if (defined("WP_PLUGIN_URL")) { From 039f3aa91a85c83e9b0e44e66f4e8c29b8971184 Mon Sep 17 00:00:00 2001 From: feibisi Date: Sun, 12 Oct 2025 00:20:32 +0800 Subject: [PATCH 10/19] Improve zh variant URL handling and homepage mapping Refines regex patterns in blocks-frontend.js to better match zh and its variants. Updates admin option handling for backward compatibility with no-conversion tag fields. In WPCC_Main, adds logic to map root-level zh variant paths to the static homepage to prevent 404s, avoids unnecessary redirects for root-level variants, and enhances redirect cancellation to prevent incorrect normalization between variant and non-variant paths. --- assets/js/blocks-frontend.js | 17 +++++----- includes/admin/class-wpcc-admin.php | 4 ++- includes/core/class-wpcc-main.php | 49 ++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/assets/js/blocks-frontend.js b/assets/js/blocks-frontend.js index 36a6c12..5bf39c0 100644 --- a/assets/js/blocks-frontend.js +++ b/assets/js/blocks-frontend.js @@ -263,7 +263,7 @@ function getCurrentLanguage() { return variant; } - const pathMatch = window.location.pathname.match(/\/(zh-[a-z]+)\//); + const pathMatch = window.location.pathname.match(/\/(zh(?:-[a-z]+)?)\//i); if (pathMatch) { return pathMatch[1]; } @@ -301,7 +301,7 @@ function getLanguageUrl(langCode) { // 回退:使用查询参数模式(并避免重复:/zh-xx/ 与 ?variant=zh-xx 共存) const currentUrl = new URL(window.location.href); - const pathMatch = currentUrl.pathname.match(/^\/(zh-[a-z]+)(\b|\/)/i); + const pathMatch = currentUrl.pathname.match(/^\/(zh(?:-[a-z]+)?)(\b|\/)/i); if (langCode) { // 若当前已处于同一变体路径,则仅移除冗余的 variant 参数,返回干净的漂亮链接 @@ -331,7 +331,7 @@ function getNoConversionUrl() { const u = new URL(window.location.href); // 去除 variant 查询与路径前缀 u.searchParams.delete("variant"); - u.pathname = u.pathname.replace(/^\/(zh-[a-z]+)(\/?)/i, "/"); + u.pathname = u.pathname.replace(/^\/(zh(?:-[a-z]+)?)(\/?)/i, "/"); return u.toString(); })(); @@ -351,8 +351,8 @@ function getNoConversionUrl() { const u = new URL(href, window.location.origin); const path = u.pathname; // 前缀: /zh-xx/... 后缀: .../zh-xx/ - if (/^\/(zh-[a-z]+)(\/|$)/i.test(path)) return 'prefix'; - if (/(\/)(zh-[a-z]+)\/?$/i.test(path)) return 'suffix'; + if (/^\/(zh(?:-[a-z]+)?)(\/|$)/i.test(path)) return 'prefix'; + if (/(\/)(zh(?:-[a-z]+)?)\/?$/i.test(path)) return 'suffix'; } } } catch (e) {} @@ -373,8 +373,11 @@ function getNoConversionUrl() { } // prefix // 将路径改为 /zh/ + 原路径 - u.pathname = u.pathname.replace(/^\/+/, '/'); - u.pathname = '/zh' + (u.pathname.startsWith('/') ? '' : '/') + u.pathname; + u.pathname = u.pathname.replace(/^\/+/, '/'); + // 避免在已是 /zh/ 的情况下重复添加 /zh + if (!/^\/(zh)(\/|$)/i.test(u.pathname)) { + u.pathname = '/zh' + (u.pathname.startsWith('/') ? '' : '/') + u.pathname; + } // 合并多余斜杠 u.pathname = u.pathname.replace(/\/{2,}/g, '/'); return u.toString(); diff --git a/includes/admin/class-wpcc-admin.php b/includes/admin/class-wpcc-admin.php index e77fb77..aa411b4 100755 --- a/includes/admin/class-wpcc-admin.php +++ b/includes/admin/class-wpcc-admin.php @@ -462,7 +462,9 @@ class wpcc_Admin // 文本字段和下拉选择框 $text_fields = [ "wpcc_translate_type" => "wpcc_translate_type", + // 同时兼容两种命名(历史与现用):wpcco_no_conversion_tag 与 wpcc_no_conversion_tag "wpcco_no_conversion_tag" => "wpcc_no_conversion_tag", + "wpcc_no_conversion_tag" => "wpcc_no_conversion_tag", "wpcco_no_conversion_tip" => "nctip", "wpcc_engine" => "wpcc_engine", "wpcco_search_conversion" => "wpcc_search_conversion", @@ -480,7 +482,7 @@ class wpcc_Admin foreach ($text_fields as $post_field => $option_field) { if (isset($_POST[$post_field])) { - if ($post_field === "wpcco_no_conversion_tag") { + if ($post_field === "wpcco_no_conversion_tag" || $post_field === "wpcc_no_conversion_tag") { $options[$option_field] = trim( $_POST[$post_field], " \t\n\r\0\x0B,|", diff --git a/includes/core/class-wpcc-main.php b/includes/core/class-wpcc-main.php index 7848f80..9074c42 100644 --- a/includes/core/class-wpcc-main.php +++ b/includes/core/class-wpcc-main.php @@ -200,6 +200,21 @@ class WPCC_Main { // 向后兼容:同步全局变量 $GLOBALS['wpcc_target_lang'] = $target_lang; + // 若首页为静态页面且请求为根级变体路径,则直接映射到首页,避免 404 + if ( get_option( 'show_on_front' ) === 'page' && get_option( 'page_on_front' ) ) { + $req_path = isset( $wp->request ) ? trim( (string) $wp->request, '/' ) : ''; + if ( $req_path !== '' ) { + $enabled = $this->config->get_enabled_languages(); + if ( ! empty( $enabled ) ) { + $pattern = '/^(?:' . implode( '|', array_map( 'preg_quote', $enabled ) ) . '|zh|zh-reset)$/i'; + if ( preg_match( $pattern, $req_path ) ) { + $wp->query_vars['page_id'] = (int) get_option( 'page_on_front' ); + unset( $wp->query_vars['pagename'], $wp->query_vars['name'] ); + } + } + } + } + // 处理搜索转换 $this->handle_search_conversion(); @@ -323,15 +338,21 @@ class WPCC_Main { $enabled = $this->config->get_enabled_languages(); $pattern = '/^(?:' . implode( '|', array_map( 'preg_quote', $enabled ) ) . '|zh|zh-reset)$/i'; if ( preg_match( $pattern, $req ) ) { + // 禁用本次自动重定向(避免将根级变体再次重定向到其它 URL) + if ( method_exists( $this->config, 'set_redirect_to' ) ) { + $this->config->set_redirect_to( false ); + } $v = strtolower( $req ); - // zh 哨兵:回到不转换首页(并设置 zh 偏好) + + // 仅 zh/zh-reset 使用哨兵重定向到不转换首页;其它根级变体在当前 URL 下渲染(避免缓存都命中 /) if ( $v === 'zh' || $v === 'zh-reset' ) { $this->handle_zh_redirect(); - return; // handle_zh_redirect 内部会 exit + return; } - // 其他语言:统一跳转到站点首页,避免首页 404 或重复内容 - wp_redirect( home_url( '/' ), 302 ); - exit; + + // 在根级变体 URL 下渲染首页:由下游 pre_get_posts/filter_request_vars 修正查询为首页 + // 注入头部脚本供前端使用 + add_action( 'wp_head', [ $this, 'output_header' ] ); } } } @@ -942,14 +963,28 @@ class WPCC_Main { */ public function cancel_incorrect_redirect( $redirect_to, $redirect_from ) { if ( ! is_string( $redirect_to ) || ! is_string( $redirect_from ) ) { return $redirect_to; } - if ( preg_match( '/^.*\/(zh-tw|zh-cn|zh-sg|zh-hant|zh-hans|zh-my|zh-mo|zh-hk|zh|zh-reset)\/?.+$/i', $redirect_to ) ) { + + // 如果来源是变体路径(根级或包含变体段),阻止将其规范化到非变体路径(例如首页 /) + if ( preg_match( '/\/(zh-tw|zh-cn|zh-sg|zh-hant|zh-hans|zh-my|zh-mo|zh-hk|zh|zh-reset)(\/|$)/i', $redirect_from ) ) { + // 允许仅修正末尾斜杠 global $wp_rewrite; if ( ( $wp_rewrite && $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) != '/' ) || ( $wp_rewrite && ! $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) == '/' ) ) { return user_trailingslashit( $redirect_from ); } - return false; // 阻止错误跳转 + return false; // 阻止从变体路径跳到非变体路径 } + + // 如果目标是变体路径,确保不因斜杠规范导致错误跳转 + if ( preg_match( '/\/(zh-tw|zh-cn|zh-sg|zh-hant|zh-hans|zh-my|zh-mo|zh-hk|zh|zh-reset)(\/|$)/i', $redirect_to ) ) { + global $wp_rewrite; + if ( ( $wp_rewrite && $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) != '/' ) || + ( $wp_rewrite && ! $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) == '/' ) ) { + return user_trailingslashit( $redirect_from ); + } + return false; // 阻止多余跳转 + } + return $redirect_to; } From 54b872015a98089c8c85110398bfcff7c368a398 Mon Sep 17 00:00:00 2001 From: feibisi Date: Thu, 23 Oct 2025 16:38:52 +0800 Subject: [PATCH 11/19] Update language switcher button styles Adjusted padding and removed border-radius from .wpcc-link for a more compact appearance. Improved mobile layout for .wpcc-horizontal-switcher by adding flex display and align-items: stretch in media queries. --- assets/css/blocks-frontend.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/css/blocks-frontend.css b/assets/css/blocks-frontend.css index aaae025..58ec63d 100644 --- a/assets/css/blocks-frontend.css +++ b/assets/css/blocks-frontend.css @@ -26,12 +26,11 @@ .wpcc-lang-item .wpcc-link { display: inline-block; - padding: 4px 10px; + padding: 4px 8px; background: #0073aa; color: #fff; text-decoration: none; border: 1px solid #0073aa; - border-radius: 5px; font-size: 13px; } @@ -112,6 +111,8 @@ @media (max-width: 768px) { .wpcc-horizontal-switcher { + display: flex; + align-items: stretch; flex-direction: column; align-items: stretch; gap: 6px; From f4720ecedb50fcadf17b283b0b87cdc1f3d7a14d Mon Sep 17 00:00:00 2001 From: feibisi Date: Sat, 25 Oct 2025 05:11:55 +0800 Subject: [PATCH 12/19] Update blocks-frontend.css --- assets/css/blocks-frontend.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/css/blocks-frontend.css b/assets/css/blocks-frontend.css index 58ec63d..22d0833 100644 --- a/assets/css/blocks-frontend.css +++ b/assets/css/blocks-frontend.css @@ -114,7 +114,8 @@ display: flex; align-items: stretch; flex-direction: column; - align-items: stretch; + flex-direction: row; + flex-wrap: nowrap; gap: 6px; } From 6c9407cbd55e4be6d958fc6e69d8fecc33fbb9e7 Mon Sep 17 00:00:00 2001 From: feibisi Date: Sun, 26 Oct 2025 15:20:25 +0800 Subject: [PATCH 13/19] Enhance language variant handling and sitemap support This update improves dynamic language variant handling across URL, cookie, and redirect logic, including dynamic regex generation for enabled languages. It adds a 'first visit no conversion' option to both network and site settings, updates admin UI labels to Chinese, and refines the sitemap module to generate rules and output only for enabled languages. Cookie setting now uses secure attributes, and sitemap URLs in the UI reflect the actual enabled language set. Several regexes and logic paths are now dynamically generated based on the current language configuration, improving flexibility and correctness. --- assets/admin/admin.css | 1 + includes/admin/class-wpcc-admin.php | 9 +- includes/blocks/blocks-init.php | 2 +- .../core/class-wpcc-exception-handler.php | 10 +- includes/core/class-wpcc-main.php | 46 ++++- includes/modules/wpcc-sitemap.php | 190 ++++++++++++++---- includes/network/wpcc-network-settings.php | 65 ++++-- includes/wpcc-core.php | 59 ++++-- includes/wpcc-settings-page.php | 54 ++--- wp-chinese-converter.php | 4 +- 10 files changed, 337 insertions(+), 103 deletions(-) diff --git a/assets/admin/admin.css b/assets/admin/admin.css index 97cd029..843491d 100644 --- a/assets/admin/admin.css +++ b/assets/admin/admin.css @@ -322,6 +322,7 @@ .wpcc-disabled * { opacity: 0.6 !important; cursor: not-allowed !important; + pointer-events: none !important; } .wpcc-disabled .wpcc-slider { diff --git a/includes/admin/class-wpcc-admin.php b/includes/admin/class-wpcc-admin.php index aa411b4..cf41f58 100755 --- a/includes/admin/class-wpcc-admin.php +++ b/includes/admin/class-wpcc-admin.php @@ -122,8 +122,8 @@ class wpcc_Admin // 网络管理界面菜单(实际网络设置页面由 WPCC_Network_Settings 提供,此处保持入口一致) add_submenu_page( "settings.php", - "WP Chinese Converter", - "WP Chinese Converter", + "文派译词", + "文派译词", "manage_network_options", $page_slug, [&$this, "display_options"], @@ -133,8 +133,8 @@ class wpcc_Admin $network_enforced = is_multisite() ? (int) get_site_option('wpcc_network_enforce', 0) : 0; if (!$network_enforced) { add_options_page( - "WP Chinese Converter", - "WP Chinese Converter", + "文派译词", + "译词", "manage_options", $page_slug, [&$this, "display_options"], @@ -453,6 +453,7 @@ class wpcc_Admin "wpcc_no_conversion_qtag" => "wpcc_no_conversion_qtag", "wpcc_no_conversion_ja" => "wpcc_no_conversion_ja", "wpcc_enable_post_conversion" => "wpcc_enable_post_conversion", + "wpcc_first_visit_default" => "wpcc_first_visit_default", ]; foreach ($checkbox_fields as $post_field => $option_field) { diff --git a/includes/blocks/blocks-init.php b/includes/blocks/blocks-init.php index 4b05902..221d6f6 100644 --- a/includes/blocks/blocks-init.php +++ b/includes/blocks/blocks-init.php @@ -59,7 +59,7 @@ wpcc_VERSION, $enabled_languages = $wpcc_options['wpcc_used_langs'] ?? array(); wp_localize_script( - 'wp-blocks', + 'wpcc-block-compat', 'wpccBlockSettings', array( 'enabledLanguages' => $enabled_languages, diff --git a/includes/core/class-wpcc-exception-handler.php b/includes/core/class-wpcc-exception-handler.php index cfb681a..07b7b10 100644 --- a/includes/core/class-wpcc-exception-handler.php +++ b/includes/core/class-wpcc-exception-handler.php @@ -194,8 +194,14 @@ final class WPCC_Exception_Handler { return; } - $error_key = get_class( $exception ); - self::$error_counts[ $error_key ] = ( self::$error_counts[ $error_key ] ?? 0 ) + 1; + // 统一异常计数键,保证与 should_suppress_error 使用的键一致 + if ( $exception instanceof WPCC_Exception ) { + $ekey = 'wpcc_error_' . $exception->get_error_code(); + self::$error_counts[ $ekey ] = ( self::$error_counts[ $ekey ] ?? 0 ) + 1; + } else { + $ekey = get_class( $exception ); + self::$error_counts[ $ekey ] = ( self::$error_counts[ $ekey ] ?? 0 ) + 1; + } $log_message = sprintf( 'WPCC Error in %s: %s [%s:%d] Context: %s', diff --git a/includes/core/class-wpcc-main.php b/includes/core/class-wpcc-main.php index 9074c42..f28984f 100644 --- a/includes/core/class-wpcc-main.php +++ b/includes/core/class-wpcc-main.php @@ -270,9 +270,23 @@ class WPCC_Main { $cookie_key = 'wpcc_variant_' . COOKIEHASH; if ( $this->config->is_feature_enabled( 'wpcc_use_cookie_variant' ) ) { - setcookie( $cookie_key, 'zh', time() + 30000000, COOKIEPATH, COOKIE_DOMAIN ); + setcookie( $cookie_key, 'zh', [ + 'expires' => time() + 30000000, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ] ); } else { - setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', 0, COOKIEPATH, COOKIE_DOMAIN ); + setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', [ + 'expires' => 0, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ] ); } wp_redirect( $this->config->get_noconversion_url() ); @@ -307,7 +321,14 @@ class WPCC_Main { $current_cookie = $_COOKIE[$cookie_key] ?? ''; if ( $current_cookie !== $target_lang ) { - setcookie( $cookie_key, $target_lang, time() + 30000000, COOKIEPATH, COOKIE_DOMAIN ); + setcookie( $cookie_key, $target_lang, [ + 'expires' => time() + 30000000, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ] ); } } @@ -360,7 +381,14 @@ class WPCC_Main { // 处理重定向 if ( ! is_404() && $this->config->get_redirect_to() && ! is_admin() ) { $redirect_url = $this->config->get_langs_urls()[ $this->config->get_redirect_to() ]; - setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', 0, COOKIEPATH, COOKIE_DOMAIN ); + setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', [ + 'expires' => 0, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ] ); wp_redirect( $redirect_url, 302 ); exit; } @@ -964,8 +992,12 @@ class WPCC_Main { public function cancel_incorrect_redirect( $redirect_to, $redirect_from ) { if ( ! is_string( $redirect_to ) || ! is_string( $redirect_from ) ) { return $redirect_to; } + // 动态构建允许的语言前缀集合 + $langs = method_exists( 'WPCC_Language_Config', 'get_valid_language_codes' ) ? WPCC_Language_Config::get_valid_language_codes() : [ 'zh-cn','zh-tw','zh-hk','zh-hans','zh-hant','zh-sg','zh-jp' ]; + $reg = implode( '|', array_map( 'preg_quote', $langs ) ); + // 如果来源是变体路径(根级或包含变体段),阻止将其规范化到非变体路径(例如首页 /) - if ( preg_match( '/\/(zh-tw|zh-cn|zh-sg|zh-hant|zh-hans|zh-my|zh-mo|zh-hk|zh|zh-reset)(\/|$)/i', $redirect_from ) ) { + if ( preg_match( '/\/(' . $reg . '|zh|zh-reset)(\/|$)/i', $redirect_from ) ) { // 允许仅修正末尾斜杠 global $wp_rewrite; if ( ( $wp_rewrite && $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) != '/' ) || @@ -976,7 +1008,7 @@ class WPCC_Main { } // 如果目标是变体路径,确保不因斜杠规范导致错误跳转 - if ( preg_match( '/\/(zh-tw|zh-cn|zh-sg|zh-hant|zh-hans|zh-my|zh-mo|zh-hk|zh|zh-reset)(\/|$)/i', $redirect_to ) ) { + if ( preg_match( '/\/(' . $reg . '|zh|zh-reset)(\/|$)/i', $redirect_to ) ) { global $wp_rewrite; if ( ( $wp_rewrite && $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) != '/' ) || ( $wp_rewrite && ! $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) == '/' ) ) { @@ -1079,7 +1111,7 @@ class WPCC_Main { if ( $target_lang && ! $this->config->get_direct_conversion_flag() ) { $home_url = $this->convert_link( home_url( '/' ), $target_lang ); $home_pattern = preg_quote( esc_url( home_url( '' ) ), '|' ); - $buffer = preg_replace( '|(]*?href=([\'"]]))' . $home_pattern . '/?(\2[^<>]*?>)|', '${1}' . esc_url( $home_url ) . '${3}', $buffer ); + $buffer = preg_replace( '|(]*?href=([\'\"]))' . $home_pattern . '/?(\2[^<>]*?>)|', '${1}' . esc_url( $home_url ) . '${3}', $buffer ); } return zhconversion2( $buffer ) . "\n" . ''; } diff --git a/includes/modules/wpcc-sitemap.php b/includes/modules/wpcc-sitemap.php index f2103a3..f196708 100755 --- a/includes/modules/wpcc-sitemap.php +++ b/includes/modules/wpcc-sitemap.php @@ -27,10 +27,17 @@ function wpcc_sitemap_setup() { function wpcc_sitemap_rewrite_rules( $rules ) { + global $wpcc_options; + $langs = isset($wpcc_options['wpcc_used_langs']) && is_array($wpcc_options['wpcc_used_langs']) ? $wpcc_options['wpcc_used_langs'] : array('zh-cn','zh-tw'); + $langs = array_values(array_unique(array_filter($langs))); + $pattern = implode('|', array_map('preg_quote', $langs)); + $new_rules = array(); - $new_rules['^(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)/wp-sitemap\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]'; - $new_rules['^(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)/sitemap\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]'; - $new_rules['^sitemap-(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)-(\d+)\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]&wpcc_sitemap_page=$matches[2]'; + if ( $pattern !== '' ) { + $new_rules['^(' . $pattern . ')/wp-sitemap\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]'; + $new_rules['^(' . $pattern . ')/sitemap\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]'; + $new_rules['^sitemap-(' . $pattern . ')-(\d+)\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]&wpcc_sitemap_page=$matches[2]'; + } return array_merge( $new_rules, $rules ); } @@ -40,46 +47,149 @@ function custom_sitemap_query_vars( $vars ) { return $vars; } -function custom_sitemap_template_redirect() { - $uri = $_SERVER['REQUEST_URI']; +/** + * 专门用于站点地图的URL转换函数 + * 与wpcc_link_conversion不同,这个函数会强制转换URL到指定语言,即使原URL已包含语言变体 + */ +function wpcc_sitemap_link_conversion( $link, $variant ) { + global $wpcc_options; - $lang = ''; - if ( preg_match( '/\/(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)\/sitemap\.xml\/?$/', $uri, $matches ) ) { - $lang = $matches[1]; - } elseif ( preg_match( '/\/(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)\/wp-sitemap\.xml\/?$/', $uri, $matches ) ) { - $lang = $matches[1]; + if ( empty( $variant ) || empty( $link ) ) { + return $link; } - if ( ! empty( $lang ) ) { - header( 'Content-Type: application/xml; charset=utf-8' ); - header( 'HTTP/1.1 200 OK' ); - - $content = generate_sitemap_index( $lang ); - - if ( ! empty( $content ) ) { - echo $content; - } else { - echo 'Sitemap not found'; + // 获取原始URL(去除所有语言变体) + $original_link = wpcc_remove_language_from_url( $link ); + + $style = (int) ( $wpcc_options['wpcc_use_permalink'] ?? 0 ); + $permalinks_enabled = (string) get_option( 'permalink_structure' ) !== ''; + + // 当 WP 未启用固定链接时,使用查询参数 + if ( ! $permalinks_enabled || $style === 0 ) { + return add_query_arg( 'variant', $variant, $original_link ); + } + + // Split path and query + $qpos = strpos( $original_link, '?' ); + $path = $qpos !== false ? substr( $original_link, 0, $qpos ) : $original_link; + $qs = $qpos !== false ? substr( $original_link, $qpos ) : ''; + + if ( $style === 1 ) { + // suffix style: /postname/zh-xx/ + return user_trailingslashit( trailingslashit( $path ) . $variant ) . $qs; + } + + // prefix style (2): /zh-xx/postname/ + if ( is_multisite() && wpcc_mobile_exist( 'network' ) ) { + $sites = get_sites(); + foreach ( $sites as $site ) { + if ( '/' == $site->path ) { + continue; + } + $path_seg = str_replace( '/', '', $site->path ); + $sub_url = "$site->domain/$path_seg"; + if ( str_contains( $path, $sub_url ) ) { + return str_replace( $sub_url, "$sub_url/$variant", $path ) . $qs; + } + } + } + + // 默认前缀样式 + $home_url = home_url(); + return str_replace( $home_url, $home_url . '/' . $variant, $original_link ); +} + +/** + * 从URL中移除语言变体 + */ +function wpcc_remove_language_from_url( $url ) { + global $wpcc_options; + + if ( empty( $url ) ) { + return $url; + } + + // 获取启用的语言列表 + $enabled = isset( $wpcc_options['wpcc_used_langs'] ) && is_array( $wpcc_options['wpcc_used_langs'] ) ? $wpcc_options['wpcc_used_langs'] : []; + if ( empty( $enabled ) ) { + return $url; + } + + // 移除查询参数中的variant + $url = remove_query_arg( 'variant', $url ); + + // 创建语言变体的正则表达式 + $reg = implode( '|', array_map( 'preg_quote', $enabled ) ); + $variant_regex = '/\/(' . $reg . '|zh|zh-reset)(\/|$)/i'; + + // 移除路径中的语言变体 + $parsed = parse_url( $url ); + if ( isset( $parsed['path'] ) ) { + $path = $parsed['path']; + $path = preg_replace( $variant_regex, '/', $path ); + $path = rtrim( $path, '/' ); + if ( empty( $path ) ) { + $path = '/'; } - exit; + // 重建URL + $scheme = isset( $parsed['scheme'] ) ? $parsed['scheme'] . '://' : ''; + $host = $parsed['host'] ?? ''; + $port = isset( $parsed['port'] ) ? ':' . $parsed['port'] : ''; + $query = isset( $parsed['query'] ) ? '?' . $parsed['query'] : ''; + $fragment = isset( $parsed['fragment'] ) ? '#' . $parsed['fragment'] : ''; + + $url = $scheme . $host . $port . $path . $query . $fragment; } - if ( preg_match( '/\/sitemap-(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)-(\d+)\.xml\/?$/', $uri, $matches ) ) { - $lang = $matches[1]; - $page = (int) $matches[2]; - + return $url; +} + +function custom_sitemap_template_redirect() { + $uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : ''; + + // 动态语言模式:仅允许已启用语言 + $enabled = isset($GLOBALS['wpcc_options']['wpcc_used_langs']) && is_array($GLOBALS['wpcc_options']['wpcc_used_langs']) ? $GLOBALS['wpcc_options']['wpcc_used_langs'] : array('zh-cn','zh-tw'); + $enabled = array_values(array_unique(array_filter($enabled))); + $pat = implode('|', array_map('preg_quote', $enabled)); + + $lang = ''; + if ( $pat !== '' && preg_match( '#/(' . $pat . ')/sitemap\.xml/?$#i', $uri, $matches ) ) { + $lang = strtolower($matches[1]); + } elseif ( $pat !== '' && preg_match( '#/(' . $pat . ')/wp-sitemap\.xml/?$#i', $uri, $matches ) ) { + $lang = strtolower($matches[1]); + } + + if ( $lang !== '' ) { + if ( ! in_array( $lang, $enabled, true ) ) { + status_header( 404 ); + exit; + } + header( 'Content-Type: application/xml; charset=utf-8' ); + header( 'HTTP/1.1 200 OK' ); + + $content = generate_sitemap_index( $lang ); + echo $content !== '' ? $content : 'Sitemap not found'; + exit; + } + + if ( $pat !== '' && preg_match( '#/sitemap-(' . $pat . ')-(\d+)\.xml/?$#i', $uri, $matches ) ) { + $lang = strtolower($matches[1]); + $page = (int) $matches[2]; + if ( ! in_array( $lang, $enabled, true ) ) { + status_header( 404 ); + exit; + } header( 'Content-Type: application/xml; charset=utf-8' ); header( 'HTTP/1.1 200 OK' ); - echo generate_paged_sitemap_content( $lang, $page ); exit; } - - if ( preg_match( '/\/sitemap-style\.xsl\/?$/', $uri ) ) { + + if ( preg_match( '#/sitemap-style\.xsl/?$#i', $uri ) ) { header( 'Content-Type: text/xsl; charset=utf-8' ); header( 'HTTP/1.1 200 OK' ); - + echo generate_sitemap_styles(); exit; } @@ -87,9 +197,15 @@ function custom_sitemap_template_redirect() { function generate_sitemap_index( string $lang ) { global $wpdb, $wpcc_options; - + + // 仅允许启用语言 + $enabled = isset($wpcc_options['wpcc_used_langs']) && is_array($wpcc_options['wpcc_used_langs']) ? $wpcc_options['wpcc_used_langs'] : array('zh-cn','zh-tw'); + if ( ! in_array( $lang, $enabled, true ) ) { + return ''; + } + $max_urls_per_sitemap = 1000; - + if ( empty( $wpcc_options['wpcco_sitemap_post_type'] ) ) { $post_type = 'post,page'; } else { @@ -126,9 +242,15 @@ function generate_sitemap_index( string $lang ) { function generate_paged_sitemap_content( string $lang, int $page ) { global $wpcc_options; - + + // 仅允许启用语言 + $enabled = isset($wpcc_options['wpcc_used_langs']) && is_array($wpcc_options['wpcc_used_langs']) ? $wpcc_options['wpcc_used_langs'] : array('zh-cn','zh-tw'); + if ( ! in_array( $lang, $enabled, true ) ) { + return ''; + } + $max_urls_per_sitemap = 1000; - + if ( empty( $wpcc_options['wpcco_sitemap_post_type'] ) ) { $post_type = 'post,page'; } else { @@ -155,7 +277,7 @@ function generate_paged_sitemap_content( string $lang, int $page ) { $postdate = explode( " ", $post->post_modified ); $sitemap .= ''; - $sitemap .= '' . wpcc_link_conversion( get_permalink( $post->ID ), $lang ) . ''; + $sitemap .= '' . wpcc_sitemap_link_conversion( get_permalink( $post->ID ), $lang ) . ''; $sitemap .= '' . $postdate[0] . ''; $sitemap .= 'weekly'; $sitemap .= '0.6'; diff --git a/includes/network/wpcc-network-settings.php b/includes/network/wpcc-network-settings.php index e14e3a6..dd64f82 100644 --- a/includes/network/wpcc-network-settings.php +++ b/includes/network/wpcc-network-settings.php @@ -35,6 +35,7 @@ class WPCC_Network_Settings "wpcc_browser_redirect", "wpcc_auto_language_recong", "wpcc_use_cookie_variant", + "wpcc_first_visit_default", // 过滤 "wpcc_no_conversion_tag", "wpcc_no_conversion_ja", @@ -95,8 +96,8 @@ class WPCC_Network_Settings { add_submenu_page( "settings.php", - "WP Chinese Converter 网络设置", - "WP Chinese Converter", + "文派译词 网络设置", + "文派译词", "manage_network_options", "wpcc-network", [__CLASS__, "render_network_page"], @@ -370,6 +371,8 @@ class WPCC_Network_Settings "语系内通用", "wpcc_use_cookie_variant" => "Cookie偏好记忆", + "wpcc_first_visit_default" => + "首次访问不转换", "wpcc_no_conversion_tag" => "标签排除", "wpcc_no_conversion_ja" => "日语内容排除", @@ -432,10 +435,11 @@ class WPCC_Network_Settings ], "detection" => [ "title" => "智能检测", - "options" => [ + "options" => [ "wpcc_browser_redirect", "wpcc_auto_language_recong", "wpcc_use_cookie_variant", + "wpcc_first_visit_default", ], ], "filter" => [ @@ -998,7 +1002,12 @@ class WPCC_Network_Settings 启用多语言网站地图 -

网站地图访问地址:/zh-tw/sitemap.xml/

+ +

网站地图访问地址://sitemap.xml/

@@ -1009,14 +1018,14 @@ class WPCC_Network_Settings 浏览器语言 /> + + 首次访问不转换(保持站点默认语言) + +

开启后,首次访问根路径时不根据浏览器语言自动转换,避免首页内容与默认语言不一致;用户选择语言后仍按其它策略生效。

+ + Cookie偏好 " id="post-conversion-options" class=""> + : ""; ?>" > -
" style="margin-top: 10px; options["wpcc_browser_redirect"] ?? - 0) == -0 - ? "display: none;" - : ""; ?>"> +
+ +
+