From f43da1c7641319eb95f6746317265097ab90c3fb Mon Sep 17 00:00:00 2001 From: feibisi Date: Sun, 5 Oct 2025 23:18:45 +0800 Subject: [PATCH 01/15] 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 02/15] 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 04/15] 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 05/15] 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 06/15] 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 07/15] 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 08/15] 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 09/15] 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;" - : ""; ?>"> +
+ +
+