Compare commits

...

19 commits
v1.3.0 ... main

Author SHA1 Message Date
feibisi
a7a6b1bd18 Add .gitignore to exclude .DS_Store files
Added a .gitignore file to prevent tracking of macOS .DS_Store files in the assets directory.
2025-11-07 21:13:30 +08:00
feibisi
2e42b5bb83 Refactor block styles registration and update frontend CSS
Moved block styles registration to a dedicated method hooked to 'init', allowing block.json to reference registered styles. Updated frontend CSS for language switcher and conversion status for improved appearance and consistency, and removed visual styles from non-conversion blocks on the frontend.
2025-11-07 20:21:18 +08:00
feibisi
85cd30a5b1 Skip conversion for AJAX, REST, and WooCommerce AJAX requests
Adds checks to bypass language/content conversion for AJAX, REST API, and WooCommerce AJAX requests to prevent interference with JSON or fragment responses. Also excludes WooCommerce critical pages (checkout, cart, account) from conversion to avoid breaking their functionality.
2025-11-07 17:23:06 +08:00
feibisi
cede6d1b02 Refactor and enhance language variant URL handling
Major refactor of WPCC_Main to improve and expand support for language variant URL handling, including custom rewrite rules, homepage and canonical fixes, and robust link conversion for different permalink styles. Adds new methods for rewrite rule management, homepage query correction, and canonical redirect cancellation. Also improves menu and logo link handling, and enhances content and link conversion filters. Minor fix in wpcc-core.php to check config existence before using options.
2025-11-07 16:35:13 +08:00
feibisi
f15abc6e30 Refactor and enhance language variant handling
Refactored core logic for language variant support, consolidating and simplifying rewrite rules, link conversion, and request handling. Improved AJAX/REST detection to skip conversion for non-HTML responses, unified link and menu conversion filters, and restructured homepage and comment handling for better reliability. The changes also streamline the addition of language variants to URLs and ensure correct behavior for canonical redirects and rewrite rule flushing.
2025-10-26 16:52:43 +08:00
feibisi
1a89f9ec27 Add taxonomy sitemap support for multiple languages
This update extends the sitemap functionality to include taxonomy (category, tag, etc.) sitemaps for each enabled language. It introduces new rewrite rules, query vars, and handlers for taxonomy sitemaps, generates taxonomy sitemap index entries, and provides a function to output taxonomy sitemap XML with pagination. The default language set now includes 'zh-hk' in addition to 'zh-cn' and 'zh-tw'.
2025-10-26 16:01:26 +08:00
feibisi
6c9407cbd5 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.
2025-10-26 15:20:25 +08:00
feibisi
f4720ecedb Update blocks-frontend.css 2025-10-25 05:11:55 +08:00
feibisi
54b872015a 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.
2025-10-23 16:38:52 +08:00
feibisi
039f3aa91a 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.
2025-10-12 00:20:32 +08:00
feibisi
c50e16121b 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.
2025-10-11 23:11:13 +08:00
feibisi
86d8dbeaf5 Update README.md 2025-10-11 22:59:08 +08:00
feibisi
a6157c75f4 Add network control and language config enhancements
Introduces network-controlled options for multisite environments, including enforcement and UI disabling for network-managed settings. Centralizes language configuration in a new WPCC_Language_Config class, improves admin and frontend JS/CSS for network and disabled state UI, updates block asset versioning, and refactors/renames core classes for consistency. Also adds a TinyMCE plugin for no-conversion shortcodes and enhances option handling for better compatibility and reliability.
2025-10-11 22:56:32 +08:00
feibisi
54ebb8cd91 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.
2025-10-05 23:24:08 +08:00
feibisi
f43da1c764 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.
2025-10-05 23:18:45 +08:00
feibisi
5a7813a7a0 Bump version to 1.4.0
Updated plugin version in header and constant from 1.3.0 to 1.4.0 to reflect new release.
2025-10-05 11:48:54 +08:00
feibisi
837b659fa9 Add settings reset and presets, update defaults
Introduces a settings reset feature with a new 'WPCC_Presets' class for managing and applying configuration presets. Updates default options for language tags, conversion exclusions, and other settings to improve usability and consistency. The settings page now includes a 'Reset Options' button, and the admin logic handles resetting to factory defaults securely.
2025-10-05 11:44:40 +08:00
feibisi
a395045607 Update README.md 2025-10-05 11:42:45 +08:00
feibisi
b5e9fc686e Add caching, config, and exception handler classes
Introduce WPCC_Conversion_Cache for conversion result caching, WPCC_Config for unified plugin configuration management, and WPCC_Exception_Handler for robust exception handling. Update MediaWiki and OpenCC converters to utilize the new caching layer, improve frontend language switcher accessibility and i18n, and add block compatibility for deprecated block output. Refactor admin class to use modern PHP syntax and improve type safety.
2025-10-05 11:08:40 +08:00
38 changed files with 10240 additions and 1089 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
assets/.DS_Store

237
README.md
View file

@ -1,33 +1,240 @@
# 关于 WPCC.NET 中文简繁转换插件
# WP Chinese Converter (WPCC)

文派简繁转换WP Chinese Converter简称 WPCC是完全符合阅读习惯和 SEO 优化的 WordPress 网站中文繁简转换解决方案。

![avatar](https://img.feibisi.com/2021/03/wpchinese-switcher-banner-1544x500-1.png)

## 关于简繁转换
## 核心特性

WPCC 是一个 WordPress 插件,用于将网站中的中文内容转换为简体中文或繁体中文。它基于 <mcreference link="https://github.com/overtrue/php-opencc" index="0">0</mcreference> 项目的转换引擎,提供了简单而强大的简繁转换功能。
### 现代化架构 (2025重构)
- **面向对象设计**: 采用单例模式和依赖注入的现代PHP架构
- **模块化系统**: 支持功能模块的动态加载和管理
- **向后兼容**: 保持与旧版本配置和功能的兼容性

### 技术特性
### 多语言转换支持
- **转换引擎**:
- 基于 PHP OpenCC 1.2.1 版本的高精度转换
- MediaWiki 转换引擎支持
- **支持地区**:
- `zh-cn` / `zh-hans`: 简体中文(中国大陆)
- `zh-tw` / `zh-hant`: 繁体中文(台湾)
- `zh-hk`: 港澳繁体中文
- `zh-sg`: 马新简体中文
- `zh-jp`: 日式汉字
- **转换策略**: 词汇级别转换、异体字转换、地区习惯用词转换

- **转换引擎**: 基于 PHP OpenCC 1.2.1 版本
- **转换策略**: 支持词汇级别的转换、异体字转换和地区习惯用词转换
- **支持地区**: 中国大陆、台湾、香港、日本新字体
- **性能优化**: 支持字典预加载和缓存机制
### Gutenberg 区块支持
- **语言切换器区块**: 可视化语言切换按钮
- **转换状态区块**: 显示当前语言状态
- **不转换区块**: 指定不需要转换的内容区域
- **现代编辑器体验**: 完全兼容WordPress 5.0+区块编辑器

目前我们正在构建更多地域词义词源项目—— OpenCC.org ,并首要为 WPCC 提供支持,可以实现更好的原生中文简繁阅读体验。
### 性能优化
- **智能缓存系统**: 支持多种缓存策略WordPress原生缓存、现代缓存系统
- **内存优化**: 字典预加载和按需加载机制
- **转换缓存**: 避免重复转换相同内容
- **全页面转换**: 可选的服务端全页面转换模式

### SEO 优化功能
- **Canonical URL**: 自动生成规范链接,避免重复内容问题
- **Hreflang 支持**: 自动添加语言版本链接标记
- **URL 结构优化**: 支持伪静态和查询参数两种URL模式
- **搜索增强**: 支持搜索关键词的简繁转换
- **站点地图兼容**: 自动生成多语言版本的站点地图

>此项目旧版本 WPChinese Switcher 分叉于原 WP Chinese Conversion 中文简繁转换器免费插件,感谢原作者 Ono Oogami 提供了此工具。由于原插件已经多年无更新,无维护及无支持回复,旧版本插件在新的 PHP 版本中也有多处错误函数警告。在简单修复了几个 bug 后越发认识到,这对于中文用户网站来说确实是非常好且实用的简繁双语解决方案,现借由文派开源重启项目,以便为 WordPress 中文用户提供长期更新和支持。
### 高级功能
- **浏览器语言检测**: 根据访客浏览器语言自动切换
- **Cookie 记忆**: 记住用户的语言偏好设置
- **多站点支持**: 完全兼容 WordPress 多站点模式
- **REST API**: 提供完整的 REST API 接口
- **AJAX 转换**: 支持动态内容的实时转换

>为了避免与原始插件名称冲突,故新版本重命名为 WPChinese Switcher ——文派中文切换器或简繁切换器。
### 兼容性
- **WordPress 版本**: WordPress 5.0+ (推荐 6.0+
- **PHP 版本**: PHP 7.4+ (推荐 8.0+
- **数据库**: MySQL 5.7+ 或 MariaDB 10.2+
- **缓存插件**: 兼容 WP Super Cache, W3 Total Cache 等

## 品牌重塑
## 系统要求

2025 年为了满足更多自定义开发要求文派开源开源团队对此项目进行重写和品牌重塑正式命名为“文派简繁切换器WP Chinese Converter简称 WPCC。”
- WordPress 5.0 或更高版本
- PHP 7.4 或更高版本(推荐 PHP 8.0+
- MySQL 5.7+ 或 MariaDB 10.2+
- 服务器内存至少 128MB用于加载转换字典

由于历史代码太过混乱,维护负担过重,且不再适用于 WordPress 最新区块编辑器项目,故后续版本不再与过完任何旧版本兼容。
## 安装配置

### 自动安装
1. 在 WordPress 后台导航到 `插件 > 安装插件`
2. 搜索 "WP Chinese Converter" 或 "WPCC"
3. 点击 `现在安装`,然后激活插件
4. 在 `设置 > WP Chinese Converter` 中配置转换选项

### 手动安装
1. 下载插件压缩包
2. 解压到 `/wp-content/plugins/wp-chinese-converter/` 目录
3. 在 WordPress 后台激活插件
4. 配置插件设置

## 主要设置选项

### 基本设置
- **启用语言**: 选择要启用的语言版本
- **转换模式**: 选择使用的转换引擎OpenCC 或 MediaWiki
- **URL 模式**: 选择伪静态或查询参数模式
- **默认语言**: 设置网站的默认语言版本

### 高级设置
- **缓存设置**: 配置转换缓存策略
- **SEO 优化**: 启用 canonical URL 和 hreflang 支持
- **浏览器检测**: 启用自动语言检测
- **Cookie 记忆**: 启用用户语言偏好记忆

## 使用方法

### Gutenberg 区块使用
1. 在页面编辑器中点击 `+` 添加区块
2. 搜索 "WP Chinese Converter" 相关区块
3. 选择所需区块并配置选项:
- **语言切换器**: 设置按钮样式和显示语言
- **转换状态**: 显示当前页面语言状态
- **不转换**: 指定不需要转换的内容区域

### 短代码支持
- 语言切换器(老短代码,平铺/下拉由“展示形式”设置控制)
```
[wp-chinese-converter]
```
- 不转换内容(编辑器无法保留注释时作为稳健占位):
```
[wpcc_nc]不转换的内容[/wpcc_nc]
[wpcs_nc]不转换的内容[/wpcs_nc]
```

### PHP 函数调用
```php
// 获取转换后的内容
<?php echo wpcc_convert_text('要转换的文本', 'zh-tw'); ?>

// 获取当前语言
<?php $current_lang = wpcc_get_current_language(); ?>

// 获取语言切换链接
<?php $switch_url = wpcc_get_switch_url('zh-cn'); ?>
```

## 开发者接口

### 过滤器钩子
```php
// 自定义转换文本
add_filter('wpcc_convert_text', 'custom_conversion', 10, 3);

// 修改语言标签
add_filter('wpcc_language_labels', 'custom_labels');

// 自定义缓存时间
add_filter('wpcc_cache_expire', 'custom_cache_time');
```

### 动作钩子
```php
// 转换前执行
add_action('wpcc_before_conversion', 'before_convert');

// 转换后执行
add_action('wpcc_after_conversion', 'after_convert');
```

## 链接与重写规则行为说明(重要)

- 链接格式与固定链接的关系
- 当 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 标注与迁移指引逐步过渡。

## 故障排除

### 常见问题

**Q: 插件激活后页面显示空白?**
A: 请检查 PHP 内存限制,建议至少 128MB。可以在 `wp-config.php` 中添加 `define('WP_MEMORY_LIMIT', '256M');`

**Q: 转换后的内容不正确?**
A: 请检查选择的转换引擎和目标语言是否正确。OpenCC 引擎适合日常使用MediaWiki 引擎更严格。

**Q: 缓存不生效?**
A: 请检查 WordPress 缓存配置,或尝试清除转换缓存:`设置 > WP Chinese Converter > 高级设置 > 清除缓存`

**Q: URL 重写不工作?**
A: 请确保 WordPress 固定链接已启用并“保存更改”一次;服务器需正确支持 rewrite如 Nginx/Apache 规则)。插件在未启用固定链接时会自动回退为 ?variant=xx。

## 性能优化建议

1. **启用缓存**: 使用 WordPress 缓存插件(如 WP Super Cache
2. **PHP OPcache**: 启用 PHP OPcache 以提高字典加载速度
3. **CDN 集成**: 使用 CDN 加速静态资源加载
4. **按需加载**: 仅在需要的页面启用转换功能

## 贡献指南

欢迎提交 Issue 和 Pull Request

1. Fork 本仓库
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request

## 许可证

本插件采用 GPLv3 或更高版本许可证。详见 [LICENSE](LICENSE) 文件。

## 支持与反馈

- **官方网站**: https://wpcc.net
- **GitHub 仓库**: https://github.com/wpcc-net/wp-chinese-converter
- **技术支持**: https://wpcc.net/support
- **Bug 报告**: https://github.com/wpcc-net/wp-chinese-converter/issues

---

**Copyright © 2025 · WPCC.NET , All Rights Reserved. 文派 (广州) 科技有限公司;**
## 版本历史

### v1.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 区块支持
- 优化性能和内存使用
- 增强 SEO 优化功能
- 完善多站点支持
- 新增 REST API 接口

### v1.2.x (历史版本)
- 基于原 WP Chinese Conversion 的改进版本
- 修复 PHP 8.x 兼容性问题
- 基础的简繁转换功能

> **历史说明**: 此项目分叉于原 WP Chinese Conversion 中文简繁转换器免费插件,感谢原作者 Ono Oogami 提供的基础框架。由于原插件多年未更新,文派开源团队于 2025 年进行完全重写和品牌重塑,为 WordPress 中文用户提供长期更新和技术支持。

---

**Copyright © 2025 · WPCC.NET , All Rights Reserved. 文派 (广州) 科技有限公司**

BIN
assets/.DS_Store vendored

Binary file not shown.

View file

@ -317,6 +317,30 @@
max-width: 120px;
}

/* Disabled state styling for network-controlled or engine-restricted options */
.wpcc-disabled,
.wpcc-disabled * {
opacity: 0.6 !important;
cursor: not-allowed !important;
pointer-events: none !important;
}

.wpcc-disabled .wpcc-slider {
background-color: #e0e0e0 !important;
}

.wpcc-disabled .wpcc-slider:before {
background-color: #f5f5f5 !important;
}

input[disabled] + .wpcc-slider {
background-color: #e0e0e0 !important;
}

input[disabled] + .wpcc-slider:before {
background-color: #f5f5f5 !important;
}

@media (max-width: 768px) {
.wpcc-sync-tabs {
flex-direction: column;
@ -556,3 +580,193 @@
text-decoration: none;
}

/* 网络管理提示样式 */
.wpcc-network-notice {
margin: 10px 0 20px 0;
padding: 12px 15px;
background: #fff3cd;
border-left: 4px solid #ffc107;
border-radius: 4px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
font-size: 13px;
line-height: 1.4;
}

.wpcc-network-notice p {
margin: 0;
color: #856404;
}

.wpcc-network-notice p:not(:last-child) {
margin-bottom: 8px;
}

.wpcc-network-notice .dashicons {
font-size: 14px;
width: 14px;
height: 14px;
vertical-align: text-top;
margin-right: 4px;
color: #856404;
}

.wpcc-network-notice a {
color: #856404;
text-decoration: underline;
}

.wpcc-network-notice a:hover {
color: #533f03;
text-decoration: none;
}

.wpcc-network-notice strong {
font-weight: 600;
color: #533f03;
}

.wpcc-network-notice em {
font-style: normal;
opacity: 0.9;
}

/* 响应式设计 */
@media (max-width: 768px) {
.wpcc-network-notice {
margin: 15px 0;
padding: 10px 12px;
font-size: 12px;
}
.wpcc-network-notice .dashicons {
font-size: 12px;
width: 12px;
height: 12px;
}
}

/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
.wpcc-network-notice {
background: #2c2611;
border-left-color: #d4a017;
}
.wpcc-network-notice p,
.wpcc-network-notice .dashicons {
color: #f4d03f;
}
.wpcc-network-notice strong {
color: #f7dc6f;
}
.wpcc-network-notice a {
color: #f4d03f;
}
.wpcc-network-notice a:hover {
color: #f7dc6f;
}
}/* Disab
led 字段样式 */
.wpcc-switch.wpcc-disabled {
opacity: 0.6;
pointer-events: none;
cursor: not-allowed;
}

.wpcc-switch.wpcc-disabled .wpcc-slider {
background-color: #ddd;
cursor: not-allowed;
}

.wpcc-switch.wpcc-disabled .wpcc-switch-label {
color: #999;
cursor: not-allowed;
}

.wpcc-radio input[disabled] + .wpcc-radio-label {
color: #999;
cursor: not-allowed;
}

input[disabled].wpcc-input,
select[disabled].wpcc-select,
textarea[disabled].wpcc-textarea {
background-color: #f5f5f5;
color: #999;
cursor: not-allowed;
opacity: 0.6;
}



/* 网络控
制禁用状态样式 */
.wpcc-switch.wpcc-disabled {
opacity: 0.6;
pointer-events: none !important;
cursor: not-allowed !important;
}

.wpcc-switch.wpcc-disabled input[type="checkbox"] {
pointer-events: none !important;
cursor: not-allowed !important;
}

.wpcc-switch.wpcc-disabled .wpcc-slider {
background-color: #ddd !important;
cursor: not-allowed !important;
pointer-events: none !important;
}

.wpcc-switch.wpcc-disabled .wpcc-switch-label {
color: #999 !important;
cursor: not-allowed !important;
pointer-events: none !important;
}

.wpcc-switch.wpcc-disabled input[type="checkbox"]:checked + .wpcc-slider {
background-color: #ccc !important;
}

.wpcc-switch.wpcc-disabled input[type="checkbox"]:checked + .wpcc-slider:before {
background-color: #f0f0f0 !important;
}

/* 网络控制区域样式 */
.wpcc-network-controlled {
opacity: 0.6;
pointer-events: none !important;
}

.wpcc-network-controlled select,
.wpcc-network-controlled input,
.wpcc-network-controlled textarea {
background-color: #f5f5f5 !important;
color: #999 !important;
cursor: not-allowed !important;
pointer-events: none !important;
}

/* 网络控制提示样式 */
.wpcc-network-notice {
margin: 10px 0;
padding: 10px 12px;
background: #fff3cd;
border-left: 4px solid #ffc107;
border-radius: 4px;
}

.wpcc-network-notice p {
margin: 0;
color: #856404;
}

.wpcc-network-notice .dashicons {
font-size: 16px;
width: 16px;
height: 16px;
vertical-align: text-top;
}

View file

@ -24,11 +24,12 @@

.wpcc-lang-item .wpcc-link {
display: inline-block;
padding: 4px 8px;
padding: 4px 10px;
background: #f0f0f0;
color: #333;
text-decoration: none;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 12px;
}


View file

@ -26,46 +26,62 @@

.wpcc-lang-item .wpcc-link {
display: inline-block;
padding: 4px 8px;
background: #0073aa;
color: #fff;
padding: 4px 10px;
background: #f0f0f0;
color: #333;
text-decoration: none;
border: 1px solid #0073aa;
font-size: 13px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 12px;
transition: background-color 0.2s ease, border-color 0.2s ease;
}

.wpcc-lang-item .wpcc-link:hover {
background: #e0e0e0;
border-color: #ccc;
}

.wpcc-lang-item.wpcc-current .wpcc-link {
background: #0073aa;
color: #fff;
border-color: #0073aa;
}

.wpcc-lang-item.wpcc-current .wpcc-link:hover {
background: #005a87;
border-color: #005a87;
}

.wpcc-lang-item.wpcc-current .wpcc-link {
background: #00a32a;
border-color: #00a32a;
}

.wpcc-lang-item.wpcc-no-conversion .wpcc-link {
background: #666;
border-color: #666;
background: #f9f9f9;
color: #666;
border-color: #ddd;
}

.wpcc-lang-item.wpcc-no-conversion .wpcc-link:hover {
background: #555;
border-color: #555;
background: #f0f0f0;
border-color: #ccc;
}

.wpcc-dropdown-switcher {
padding: 6px 8px;
border: 1px solid #ddd;
background: #fff;
font-size: 13px;
font-size: 12px;
cursor: pointer;
min-width: 150px;
border-radius: 5px;
transition: border-color 0.2s ease;
}

.wpcc-dropdown-switcher:hover {
border-color: #bbb;
}

.wpcc-dropdown-switcher:focus {
outline: 1px solid #0073aa;
outline-offset: 1px;
outline: 2px solid #0073aa;
outline-offset: 0;
border-color: #0073aa;
}

.wpcc-conversion-status {
@ -76,11 +92,12 @@
.wpcc-status-container {
display: flex;
align-items: center;
gap: 4px;
gap: 6px;
}

.wpcc-status-text {
font-size: 13px;
font-size: 12px;
color: #333;
}

.wpcc-conversion-status.wpcc-format-badge .wpcc-status-text {
@ -93,7 +110,7 @@

.wpcc-conversion-status.wpcc-format-text .wpcc-status-text {
color: #333;
font-size: 13px;
font-size: 12px;
}

.wpcc-conversion-status.wpcc-format-minimal .wpcc-status-text {
@ -101,18 +118,30 @@
color: #666;
}

/* 不转换区块在前台不显示任何视觉样式 */
.wpcc-no-conversion-wrapper {
position: relative;
/* 仅保留功能性标记,无视觉样式 */
}

.wpcc-no-conversion-header {
display: none; /* 前台隐藏标题 */
}

.wpcc-label {
display: none; /* 前台隐藏标签 */
}

.wpcc-no-conversion-content {
position: relative;
/* 内容正常显示,无额外样式 */
}

@media (max-width: 768px) {
.wpcc-horizontal-switcher {
flex-direction: column;
display: flex;
align-items: stretch;
flex-direction: column;
flex-direction: row;
flex-wrap: nowrap;
gap: 6px;
}

@ -157,13 +186,14 @@
min-width: 180px;
}

.wpcc-no-conversion-wrapper.wpcc-border-solid {
border-style: solid;
}

/* 前台所有不转换区块的边框变体都不显示 */
.wpcc-no-conversion-wrapper.wpcc-border-solid,
.wpcc-no-conversion-wrapper.wpcc-border-dashed,
.wpcc-no-conversion-wrapper.wpcc-border-none {
border: none;
background: transparent;
padding: 0;
margin: 0;
}

.wpcc-conversion-status.wpcc-clickable {

View file

@ -75,7 +75,9 @@ function renderLanguageSwitcher(
};

const target = openInNewWindow ? ' target="_blank"' : "";
const noConversionText = customNoConversionLabel || "不转换";
const rel = openInNewWindow ? ' rel="noopener noreferrer"' : "";
const strings = (typeof wpccFrontendSettings !== 'undefined' && wpccFrontendSettings.strings) ? wpccFrontendSettings.strings : {};
const noConversionText = customNoConversionLabel || strings.noConversionLabel || "不转换";

let html = "";

@ -84,23 +86,27 @@ function renderLanguageSwitcher(

if (showNoConversion) {
const isActive = !currentLang || currentLang === "";
const noConvUrl = getNoConversionUrl();
const relForNoConv = buildRelAttribute(noConvUrl, openInNewWindow);
html += `<span class="wpcc-lang-item wpcc-no-conversion ${isActive ? "wpcc-current" : ""}">
<a href="${getLanguageUrl("")}" class="wpcc-link"${target}>${noConversionText}</a>
<a href="${noConvUrl}" class="wpcc-link"${target}${relForNoConv}>${noConversionText}</a>
</span>`;
}

availableLanguages.forEach(function (lang) {
const isActive = currentLang === lang.code;
const label = getLanguageLabel(lang);
const ariaCurrent = isActive ? ' aria-current="page"' : '';
html += `<span class="wpcc-lang-item ${isActive ? "wpcc-current" : ""}">
<a href="${getLanguageUrl(lang.code)}" class="wpcc-link" title="${lang.label}"${target}>${label}</a>
<a href="${getLanguageUrl(lang.code)}" class="wpcc-link" title="${lang.label}"${target}${rel}${ariaCurrent}>${label}</a>
</span>`;
});

html += "</div>";
} else {
const ariaLabel = (typeof strings !== 'undefined' && strings.languageSelectLabel) ? strings.languageSelectLabel : '选择语言';
html =
'<select class="wpcc-dropdown-switcher" onchange="handleLanguageChange(this.value, this)">';
`<select class="wpcc-dropdown-switcher" aria-label="${ariaLabel}" onchange="handleLanguageChange(this.value, this)">`;

if (showNoConversion) {
const selected = !currentLang || currentLang === "" ? "selected" : "";
@ -172,13 +178,16 @@ function renderConversionStatus(container, showIcon, displayFormat) {

let html = "";

const strings = (typeof wpccFrontendSettings !== 'undefined' && wpccFrontendSettings.strings) ? wpccFrontendSettings.strings : {};
const currentPrefix = strings.currentLanguagePrefix || '当前语言:';

let statusText = "";
switch (displayFormat) {
case "badge":
statusText = langInfo.label;
break;
case "text":
statusText = `当前语言:${langInfo.label}`;
statusText = `${currentPrefix}${langInfo.label}`;
break;
case "minimal":
statusText = langInfo.name;
@ -187,6 +196,9 @@ function renderConversionStatus(container, showIcon, displayFormat) {
statusText = langInfo.label;
}

if (showIcon) {
html += `<span class="dashicons dashicons-translation" aria-hidden="true"></span>`;
}
html += `<span class="wpcc-status-text">${statusText}</span>`;

container.innerHTML = html;
@ -239,6 +251,11 @@ function getAvailableLanguages() {
}

function getCurrentLanguage() {
// 优先使用服务端注入的当前语言(如果存在)
if (typeof wpcc_target_lang !== "undefined" && wpcc_target_lang) {
return wpcc_target_lang;
}

const urlParams = new URLSearchParams(window.location.search);
const variant = urlParams.get("variant");

@ -246,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];
}
@ -266,15 +283,123 @@ function getLanguageInfo(langCode) {
}

function getLanguageUrl(langCode) {
const currentUrl = new URL(window.location.href);

if (langCode) {
currentUrl.searchParams.set("variant", langCode);
} else {
currentUrl.searchParams.delete("variant");
// 优先使用服务端注入的 URL 映射(如可用)
try {
if (typeof wpcc_langs_urls === "object" && wpcc_langs_urls) {
if (langCode && wpcc_langs_urls[langCode]) {
return wpcc_langs_urls[langCode];
}
if (!langCode || langCode === "") {
if (typeof wpcc_noconversion_url === "string" && wpcc_noconversion_url) {
return wpcc_noconversion_url;
}
}
}
} catch (e) {
// 忽略映射读取错误,回退到查询参数模式
}

return currentUrl.toString();
// 回退:使用查询参数模式(并避免重复:/zh-xx/ 与 ?variant=zh-xx 共存)
const currentUrl = new URL(window.location.href);
const pathMatch = currentUrl.pathname.match(/^\/(zh(?:-[a-z]+)?)(\b|\/)/i);

if (langCode) {
// 若当前已处于同一变体路径,则仅移除冗余的 variant 参数,返回干净的漂亮链接
if (pathMatch && pathMatch[1].toLowerCase() === langCode.toLowerCase()) {
currentUrl.searchParams.delete("variant");
return currentUrl.toString();
}
currentUrl.searchParams.set("variant", langCode);
return currentUrl.toString();
} else {
// 不转换:尽量去除语言段与冗余参数
currentUrl.searchParams.delete("variant");
if (pathMatch) {
// 去掉开头的 /zh-xx 段,回到原始路径
currentUrl.pathname = currentUrl.pathname.replace(/^\/(zh-[a-z]+)(\/?)/i, "/");
}
return currentUrl.toString();
}
}

// 构建“不转换”链接:在变体页面时注入 zh 哨兵以覆盖浏览器/Cookie 策略
function getNoConversionUrl() {
// 基于服务端注入的原始 URL 获取基础地址
let baseUrl = (typeof wpcc_noconversion_url === "string" && wpcc_noconversion_url)
? wpcc_noconversion_url
: (function() {
const u = new URL(window.location.href);
// 去除 variant 查询与路径前缀
u.searchParams.delete("variant");
u.pathname = u.pathname.replace(/^\/(zh(?:-[a-z]+)?)(\/?)/i, "/");
return u.toString();
})();

const currentLang = getCurrentLanguage();
if (!currentLang) {
return baseUrl; // 已经是不转换
}

// 检测站点使用的链接风格:查询参数 / 后缀 / 前缀
const detectStyle = () => {
try {
if (typeof wpcc_langs_urls === 'object' && wpcc_langs_urls) {
for (const k in wpcc_langs_urls) {
if (!Object.prototype.hasOwnProperty.call(wpcc_langs_urls, k)) continue;
const href = String(wpcc_langs_urls[k] || '');
if (/([?&])variant=zh-[a-z]+/i.test(href)) return 'query';
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';
}
}
} catch (e) {}
return 'query';
};

const style = detectStyle();
try {
const u = new URL(baseUrl);
if (style === 'query') {
u.searchParams.set('variant', 'zh');
return u.toString();
}
if (style === 'suffix') {
// 确保末尾带 /zh/
u.pathname = u.pathname.replace(/\/$/, '') + '/zh/';
return u.toString();
}
// prefix
// 将路径改为 /zh/ + 原路径
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();
} catch (e) {
// 回退:总是可用的查询参数
try {
const u2 = new URL(baseUrl);
u2.searchParams.set('variant', 'zh');
return u2.toString();
} catch (_e) {
return baseUrl;
}
}
}

// 根据链接是否包含 zh 哨兵决定 rel且兼容新窗口的 noopener noreferrer
function buildRelAttribute(url, openInNewWindow) {
const hasZhSentinel = /\/(?:zh)(?:\b|\/)/i.test(url) || /(?:[?&])variant=zh(?:&|$)/i.test(url);
let relParts = [];
if (hasZhSentinel) relParts.push('nofollow');
if (openInNewWindow) relParts.push('noopener', 'noreferrer');
return relParts.length ? ` rel="${Array.from(new Set(relParts)).join(' ')}"` : '';
}

function handleLanguageChange(langCode, selectElement) {

View file

@ -0,0 +1,44 @@
(function() {
if (typeof window.tinymce === 'undefined') {
return;
}

tinymce.PluginManager.add('wpcc_nc', function(editor, url) {
function insertShortcode() {
var sel = editor.selection ? editor.selection.getContent({ format: 'text' }) : '';
var before = '[wpcc_nc]';
var after = '[/wpcc_nc]';
var content = before + (sel || '') + after;
editor.insertContent(content);
}

// TinyMCE 5+ API
if (editor.ui && editor.ui.registry && editor.ui.registry.addButton) {
editor.ui.registry.addButton('wpcc_nc', {
text: 'wpcc_NC',
tooltip: '插入不转换包裹 [wpcc_nc]...[/wpcc_nc]',
onAction: insertShortcode
});
editor.ui.registry.addMenuItem('wpcc_nc', {
text: '插入不转换包裹',
onAction: insertShortcode
});
} else if (editor.addButton) {
// TinyMCE 4 API (WordPress Classic Editor)
editor.addButton('wpcc_nc', {
text: 'wpcc_NC',
tooltip: '插入不转换包裹 [wpcc_nc]...[/wpcc_nc]',
onclick: insertShortcode
});
}

return {
getMetadata: function () {
return {
name: 'WPCC No-Conversion Shortcode Helper',
url: 'https://wpcc.net'
};
}
};
});
})();

View file

@ -0,0 +1,78 @@
(function(){
if (!window.wp || !wp.blocks || !wp.blockEditor || !wp.element || !wp.domReady) return;
const el = wp.element.createElement;
const useBlockProps = wp.blockEditor.useBlockProps;

// Deprecated saver for Traditional Chinese placeholder (語言切換器)
function saveLanguageSwitcherDeprecated(props){
const {
displayStyle,
showNoConversion,
alignment,
enabledLanguages,
buttonSize,
customNoConversionLabel,
sortOrder,
openInNewWindow,
showLanguageCode,
customLabels,
} = props.attributes;

let finalEnabledLanguages = Array.isArray(enabledLanguages) ? enabledLanguages : [];
if (typeof wpccBlockSettings !== 'undefined' && Array.isArray(wpccBlockSettings.enabledLanguages)) {
const base = wpccBlockSettings.enabledLanguages;
const filtered = finalEnabledLanguages.filter(code => base.includes(code));
finalEnabledLanguages = filtered.length ? filtered : base;
}

const blockProps = useBlockProps.save({
className: `wpcc-language-switcher wpcc-${displayStyle} wpcc-align-${alignment} wpcc-size-${buttonSize}`
});

return el('div', blockProps,
el('div', {
className: 'wpcc-switcher-placeholder',
'data-display-style': displayStyle,
'data-show-no-conversion': !!showNoConversion,
'data-enabled-languages': JSON.stringify(finalEnabledLanguages),
'data-button-size': buttonSize,
'data-custom-no-conversion-label': customNoConversionLabel || '',
'data-sort-order': sortOrder,
'data-open-in-new-window': !!openInNewWindow,
'data-show-language-code': !!showLanguageCode,
'data-custom-labels': JSON.stringify(customLabels || {})
}, '語言切換器')
);
}

function saveConversionStatusDeprecated(props){
const { showIcon, displayFormat } = props.attributes;
const blockProps = useBlockProps.save({
className: `wpcc-conversion-status wpcc-format-${displayFormat}`
});
return el('div', blockProps,
el('div', {
className: 'wpcc-status-container',
'data-show-icon': !!showIcon,
'data-display-format': displayFormat
}, '轉換狀態指示器')
);
}

wp.domReady(function(){
const { getBlockType, unregisterBlockType, registerBlockType } = wp.blocks;

function extendDeprecated(name, saveFn){
const type = getBlockType(name);
if (!type) return;
const deprecated = Array.isArray(type.deprecated) ? type.deprecated.slice() : [];
deprecated.push({ save: saveFn });
const settings = Object.assign({}, type, { deprecated });
unregisterBlockType(name);
registerBlockType(name, settings);
}

extendDeprecated('wpcc/language-switcher', saveLanguageSwitcherDeprecated);
extendDeprecated('wpcc/conversion-status', saveConversionStatusDeprecated);
});
})();

View file

@ -1,14 +1,14 @@
let wpc_switcher_use_permalink_type
var wpc_switcher_use_permalink_type;
if (typeof wpc_switcher_use_permalink !== 'undefined') {
wpc_switcher_use_permalink_type = wpc_switcher_use_permalink['type']
wpc_switcher_use_permalink_type = wpc_switcher_use_permalink['type'];
}

function wpccRedirectToPage($event) {
if (typeof WPCSVariant === 'undefined') return ;
return WPCSVariant.wpccRedirectToPage($event)
if (typeof WPCSVariant === 'undefined') return;
return WPCSVariant.wpccRedirectToPage($event);
}

function wpccRedirectToVariant(variantValue) {
if (typeof WPCSVariant === 'undefined') return ;
return WPCSVariant.wpccRedirectToVariant(variantValue)
if (typeof WPCSVariant === 'undefined') return;
return WPCSVariant.wpccRedirectToVariant(variantValue);
}

27
changelog.txt Normal file
View file

@ -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 重构)
- 完整重写插件架构为 OOPWPCC_Main / WPCC_Config / 模块管理器等
- 新增 Gutenberg 区块(语言切换器、转换状态、不转换区块)
- 优化 性能与内存占用OpenCC 与 MediaWiki 双引擎;转换缓存
- 增强 SEOcanonical、hreflang、schema/meta 转换)与 REST API 支持

历史说明
- 本项目分叉自 WP Chinese Conversion2025 年起由文派团队WPCC.NET进行长期维护与重构。

View file

@ -2,22 +2,23 @@

class wpcc_Admin
{
var $base = "";
var $is_submitted = false;
var $is_success = false;
var $is_error = false;
var $message = "";
var $options = false;
var $langs = false;
var $url = "";
var $admin_lang = false;
private string $base = "";
private bool $is_submitted = false;
private bool $is_success = false;
private bool $is_error = false;
private string $message = "";
private $options = false;
private $langs = false;
private string $url = "";
private $admin_lang = false;
function wpcc_Admin()
{
return $this->__construct();
}
private array $network_controlled_options = [];
private bool $is_network_enforced = false;
private array $network_options = [];

function clean_invalid_langs()
// 移除旧式构造函数,只使用 __construct

private function clean_invalid_langs(): void
{
global $wpcc_langs;
if (
@ -34,7 +35,7 @@ class wpcc_Admin
}
}

function __construct()
public function __construct()
{
global $wpcc_options, $wpcc_langs, $wpcc_modules;
if (function_exists("wpcc_init_languages")) {
@ -46,27 +47,66 @@ class wpcc_Admin
}
$this->langs = &$wpcc_langs;
$this->options = $wpcc_options;
// 初始化网络控制
$this->init_network_control();
if (empty($this->options)) {
$this->options = [
// 语言与标签
"wpcc_used_langs" => ["zh-cn", "zh-tw"],
"cntip" => "简体",
"twtip" => "繁体",
"hktip" => "港澳",
"hanstip" => "简体",
"hanttip" => "繁体",
"sgtip" => "马新",
"jptip" => "日式",

// 引擎与转换
"wpcc_engine" => "mediawiki",
"wpcc_search_conversion" => 1,
"wpcc_used_langs" => ["zh-cn", "zh-tw", "zh-hk"],
"wpcc_use_fullpage_conversion" => 0,

// 浏览器与 Cookie
"wpcc_browser_redirect" => 0,
"wpcc_auto_language_recong" => 0,
"wpcc_flag_option" => 1,
"wpcc_use_cookie_variant" => 0,
"wpcc_use_fullpage_conversion" => 1,
"wpcco_use_sitemap" => 1,
"wpcc_trackback_plugin_author" => 0,
"wpcc_add_author_link" => 0,
"wpcc_use_permalink" => 0,
"wpcc_no_conversion_tag" => "",
"wpcc_use_cookie_variant" => 1,

// 不转换
"wpcc_no_conversion_tag" => "",
"wpcc_no_conversion_ja" => 0,
"wpcc_no_conversion_qtag" => 0,
"wpcc_engine" => "opencc",

// 发表时转换
"wpcc_enable_post_conversion" => 0,
"wpcc_post_conversion_target" => "zh-cn",

// URL 与站点地图
"wpcc_use_permalink" => 0,
"wpcco_use_sitemap" => 0,
"wpcco_sitemap_post_type" => "post,page",

// SEO
"wpcc_enable_hreflang_tags" => 1,
"wpcc_hreflang_x_default" => "zh-cn",
"wpcc_enable_schema_conversion" => 1,
"wpcc_enable_meta_conversion" => 1,


// 其他
"wpcc_flag_option" => 1,
"wpcc_trackback_plugin_author" => 0,
"wpcc_add_author_link" => 0,
"wpcc_translate_type" => 0,
"nctip" => "",
"wpcc_enable_cache_addon" => 1,
"wpcc_enable_network_module" => 0,
];
}

$this->clean_invalid_langs();

update_wpcc_option("wpcc_options", $this->options);

$this->base = plugin_basename(dirname(__FILE__)) . "/";
@ -78,23 +118,28 @@ class wpcc_Admin
$this->url = admin_url("options-general.php?page=" . $page_slug);
}

if (is_multisite() && wpcc_mobile_exist("network")) {
if (is_multisite() && is_network_admin() && wpcc_mobile_exist("network")) {
// 网络管理界面菜单(实际网络设置页面由 WPCC_Network_Settings 提供,此处保持入口一致)
add_submenu_page(
"settings.php",
"WP Chinese Converter",
"WP Chinese Converter",
"文派译词",
"文派译词",
"manage_network_options",
$page_slug,
[&$this, "display_options"],
);
} else {
add_options_page(
"WP Chinese Converter",
"WP Chinese Converter",
"manage_options",
$page_slug,
[&$this, "display_options"],
);
} elseif (!is_network_admin()) {
// 子站点菜单:当未启用强制网络管理时显示
$network_enforced = is_multisite() ? (int) get_site_option('wpcc_network_enforce', 0) : 0;
if (!$network_enforced) {
add_options_page(
"文派译词",
"译词",
"manage_options",
$page_slug,
[&$this, "display_options"],
);
}
}

wp_enqueue_script("jquery");
@ -110,7 +155,7 @@ class wpcc_Admin
]);
}

function action_links($links, $file)
public function action_links(array $links, string $file): array
{
if ($file == $this->base . "wp-chinese-converter.php") {
$links[] =
@ -120,7 +165,7 @@ class wpcc_Admin
return $links;
}

function is_gutenberg_active()
public function is_gutenberg_active(): bool
{
// 检查是否启用了Gutenberg编辑器
if (function_exists("use_block_editor_for_post_type")) {
@ -133,7 +178,7 @@ class wpcc_Admin
function_exists("wp_enqueue_block_editor_assets");
}

function install_cache_module()
public function install_cache_module(): bool
{
global $wpcc_options;

@ -172,7 +217,7 @@ class wpcc_Admin
return $ret;
}

function uninstall_cache_module()
public function uninstall_cache_module(): bool
{
return unlink(
WP_PLUGIN_DIR .
@ -180,7 +225,7 @@ class wpcc_Admin
);
}

function get_cache_status()
public function get_cache_status(): int
{
if (!function_exists("wp_cache_is_enabled")) {
return 0;
@ -207,7 +252,7 @@ class wpcc_Admin
dirname(dirname(__DIR__)) . "/wp-chinese-converter.php",
) . "assets/admin/admin.css",
[],
"2.0.0",
wpcc_VERSION,
);

wp_enqueue_script(
@ -216,7 +261,7 @@ class wpcc_Admin
dirname(dirname(__DIR__)) . "/wp-chinese-converter.php",
) . "assets/admin/admin.js",
["jquery"],
"2.0.0",
wpcc_VERSION,
true,
);

@ -235,6 +280,31 @@ class wpcc_Admin
return;
}

// 处理“重置为默认设置”
if (!empty($_POST['wpcc_reset_defaults']) && current_user_can('manage_options')) {
if (check_admin_referer('wpcc_reset_defaults', 'wpcc_reset_nonce')) {
$ok = false;
if (class_exists('WPCC_Presets')) {
// 利用工厂默认预设恢复默认
$ok = WPCC_Presets::apply_preset('factory_default');
}
if ($ok) {
$this->options = get_wpcc_option('wpcc_options', []);
$this->is_submitted = true;
$this->is_success = true;
$this->message .= __('已重置为默认设置。', 'wp-chinese-converter');
} else {
$this->is_submitted = true;
$this->is_error = true;
$this->message .= __('重置失败。', 'wp-chinese-converter');
}
} else {
$this->is_submitted = true;
$this->is_error = true;
$this->message .= __('安全验证失败。', 'wp-chinese-converter');
}
}

if (!empty($_POST["toggle_cache"])) {
if ($this->get_cache_status() == 1) {
$result = $this->install_cache_module();
@ -273,7 +343,7 @@ class wpcc_Admin
}
}

if (!empty($_POST["wpcco_submitted"])) {
if (!empty($_POST["wpcco_submitted"]) && empty($_POST['wpcc_reset_defaults'])) {
$this->is_submitted = true;
$this->process();

@ -346,36 +416,44 @@ class wpcc_Admin

// 只更新表单中实际提交的字段

// 语言设置
if (
isset($_POST["wpcco_variant_zh-cn"]) ||
isset($_POST["wpcco_variant_zh-tw"]) ||
isset($_POST["wpcco_variant_zh-hk"])
) {
$langs = [];
if (is_array($this->langs)) {
foreach ($this->langs as $key => $value) {
if (isset($_POST["wpcco_variant_" . $key])) {
$langs[] = $key;
}
// 确保关键字段存在默认值,防止未定义键产生警告
if (!is_array($options)) { $options = []; }
if (!isset($options['wpcc_use_permalink'])) { $options['wpcc_use_permalink'] = 0; }
if (!isset($this->options['wpcc_use_permalink'])) { $this->options['wpcc_use_permalink'] = 0; }
if (!isset($options['wpcc_used_langs']) || !is_array($options['wpcc_used_langs'])) { $options['wpcc_used_langs'] = []; }
if (!isset($this->options['wpcc_used_langs']) || !is_array($this->options['wpcc_used_langs'])) { $this->options['wpcc_used_langs'] = []; }

// 语言设置:总是基于提交的 wpcco_variant_* 重建已启用语言,避免部分站点无法保存
$langs = [];
if (is_array($this->langs)) {
foreach ($this->langs as $key => $value) {
if (isset($_POST["wpcco_variant_" . $key])) {
$langs[] = $key;
}
}
$options["wpcc_used_langs"] = $langs;
}
$options["wpcc_used_langs"] = $langs;

// 复选框字段未选中时不会在POST中出现
$checkbox_fields = [
// 同时兼容两种命名(历史与现用)
"wpcco_use_fullpage_conversion" => "wpcc_use_fullpage_conversion",
"wpcc_use_fullpage_conversion" => "wpcc_use_fullpage_conversion",
"wpcco_use_sitemap" => "wpcco_use_sitemap",
// 支持两种命名历史兼容wpcco_* 与 wpcc_*
"wpcco_auto_language_recong" => "wpcc_auto_language_recong",
"wpcc_auto_language_recong" => "wpcc_auto_language_recong",
"wpcc_enable_cache_addon" => "wpcc_enable_cache_addon",
"wpcc_enable_network_module" => "wpcc_enable_network_module",
"wpcc_enable_hreflang_tags" => "wpcc_enable_hreflang_tags",
"wpcc_enable_hreflang_x_default" => "wpcc_enable_hreflang_x_default",
"wpcc_enable_schema_conversion" => "wpcc_enable_schema_conversion",
"wpcc_enable_meta_conversion" => "wpcc_enable_meta_conversion",
"wpcc_show_more_langs" => "wpcc_show_more_langs",
"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) {
@ -385,13 +463,19 @@ 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",
// 支持两种命名历史兼容wpcco_* 与 wpcc_*
"wpcco_browser_redirect" => "wpcc_browser_redirect",
"wpcc_browser_redirect" => "wpcc_browser_redirect",
"wpcco_use_cookie_variant" => "wpcc_use_cookie_variant",
"wpcc_use_cookie_variant" => "wpcc_use_cookie_variant",
"wpcco_use_permalink" => "wpcc_use_permalink",
"wpcc_use_permalink" => "wpcc_use_permalink",
"wpcco_sitemap_post_type" => "wpcco_sitemap_post_type",
"wpcc_hreflang_x_default" => "wpcc_hreflang_x_default",
"wpcc_post_conversion_target" => "wpcc_post_conversion_target",
@ -399,7 +483,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,|",
@ -426,6 +510,11 @@ class wpcc_Admin
}
}

// 兼容旧字段:当表单提交了 wpcc_flag_option 时,将其映射为 wpcc_translate_type
if (isset($_POST['wpcc_flag_option'])) {
$options['wpcc_translate_type'] = intval($_POST['wpcc_flag_option']);
}

if (is_array($this->langs)) {
foreach ($this->langs as $lang => $value) {
if (
@ -439,6 +528,22 @@ class wpcc_Admin
}
}

// 根据本地“显示更多语言”与引擎裁剪启用语言,防止未启用的扩展语言(如 zh-sg误入站点配置
$enable_more = isset($options['wpcc_show_more_langs']) ? (int)$options['wpcc_show_more_langs'] : 1;
if (! $enable_more) {
$base_langs = array('zh-cn','zh-tw','zh-hk');
$options['wpcc_used_langs'] = array_values(array_intersect($options['wpcc_used_langs'], $base_langs));
}
// 非 OpenCC 引擎移除 zh-jp
$engine = isset($options['wpcc_engine']) ? $options['wpcc_engine'] : 'opencc';
if ($engine !== 'opencc') {
$options['wpcc_used_langs'] = array_values(array_diff($options['wpcc_used_langs'], array('zh-jp')));
}
// 至少保留一种语言
if (empty($options['wpcc_used_langs'])) {
$options['wpcc_used_langs'] = array('zh-cn');
}

if (
$this->get_cache_status() == 2 &&
empty($options["wpcc_browser_redirect"]) &&
@ -450,12 +555,14 @@ class wpcc_Admin
$wpcc_options = $options;
$need_flush_rules = false;

$current_permalink = isset($this->options["wpcc_use_permalink"]) ? (int) $this->options["wpcc_use_permalink"] : 0;
$new_permalink = isset($options["wpcc_use_permalink"]) ? (int) $options["wpcc_use_permalink"] : 0;
$current_langs = isset($this->options["wpcc_used_langs"]) && is_array($this->options["wpcc_used_langs"]) ? $this->options["wpcc_used_langs"] : [];
$new_langs = isset($options["wpcc_used_langs"]) && is_array($options["wpcc_used_langs"]) ? $options["wpcc_used_langs"] : [];

if (
$this->options["wpcc_use_permalink"] !=
$options["wpcc_use_permalink"] ||
($this->options["wpcc_use_permalink"] != 0 &&
$this->options["wpcc_used_langs"] !=
$options["wpcc_used_langs"])
$current_permalink !== $new_permalink ||
($current_permalink !== 0 && $current_langs != $new_langs)
) {
if (!has_filter("rewrite_rules_array", "wpcc_rewrite_rules")) {
add_filter("rewrite_rules_array", "wpcc_rewrite_rules");
@ -478,6 +585,8 @@ class wpcc_Admin
$wp_rewrite->flush_rules();
}

// 网络设置交由 WPCC_Network_Settings 统一管理,此处不再重复保存以避免冲突

update_wpcc_option("wpcc_options", $options);

$this->options = $options;
@ -559,4 +668,71 @@ class wpcc_Admin

wp_send_json_success(["message" => "已优化 {$optimized} 个数据表"]);
}

/**
* 初始化网络控制
*/
private function init_network_control() {
if (!is_multisite()) {
return;
}

// 获取网络控制的选项
$this->network_controlled_options = get_site_option('wpcc_network_controlled_options', []);
$this->is_network_enforced = get_site_option('wpcc_network_enforce', false);
// 如果启用了网络强制模式,获取网络选项值
if ($this->is_network_enforced) {
$this->network_options = get_site_option('wpcc_network_options', []);
$this->apply_network_options();
}
}

/**
* 应用网络选项值
*/
private function apply_network_options() {
foreach ($this->network_controlled_options as $option_name) {
if (isset($this->network_options[$option_name])) {
$this->options[$option_name] = $this->network_options[$option_name];
}
}
}

/**
* 检查选项是否被网络控制
*/
public function is_option_controlled($option_name) {
if (!is_multisite()) {
return false;
}
return in_array($option_name, $this->network_controlled_options);
}

/**
* 获取字段属性(用于禁用网络控制的字段)
*/
public function get_field_attributes($option_name) {
if ($this->is_option_controlled($option_name)) {
return 'disabled="disabled" title="此选项由网络管理员控制"';
}
return '';
}

/**
* 获取网络控制提示
*/
public function get_network_controlled_notice($option_name) {
if ($this->is_option_controlled($option_name)) {
return '<p class="description" style="color: #d63638;"><strong>此选项由网络管理员统一控制,无法在子站点修改。</strong></p>';
}
return '';
}

/**
* 检查是否为网络强制模式
*/
public function is_network_enforced() {
return $this->is_network_enforced;
}
}

View file

@ -8,10 +8,28 @@ class WPCC_Blocks {
public function __construct() {
add_action( 'init', array( $this, 'register_blocks' ) );
add_action( 'init', array( $this, 'register_block_styles' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) );
}

public function register_block_styles() {
// 在 init 阶段注册样式句柄,供 block.json 中的 editorStyle 和 style 字段使用
wp_register_style(
'wpcc-blocks-editor',
plugins_url( 'assets/css/blocks-editor.css', dirname( dirname( __FILE__ ) ) ),
array(),
wpcc_VERSION
);

wp_register_style(
'wpcc-blocks-frontend',
plugins_url( 'assets/css/blocks-frontend.css', dirname( dirname( __FILE__ ) ) ),
array(),
wpcc_VERSION
);
}

public function register_blocks() {
$blocks = array(
'language-switcher',
@ -27,7 +45,8 @@ class WPCC_Blocks {
private function register_single_block( $block_name ) {
$block_path = plugin_dir_path( __FILE__ ) . 'build/' . $block_name;

if ( file_exists( $block_path . '/index.js' ) ) {
// 更符合规范的做法:检测 block.json 是否存在
if ( file_exists( $block_path . '/block.json' ) ) {
register_block_type( $block_path );
}
}
@ -35,21 +54,26 @@ class WPCC_Blocks {
public function enqueue_block_editor_assets() {
// 确保wpcc-variant脚本已加载
if ( ! wp_script_is( 'wpcc-variant', 'registered' ) ) {
wp_register_script( 'wpcc-variant', plugins_url( 'assets/dist/wpcc-variant.umd.js', dirname( dirname( __FILE__ ) ) ), array(), '1.1.0' );
wp_register_script( 'wpcc-variant', plugins_url( 'assets/dist/wpcc-variant.umd.js', dirname( dirname( __FILE__ ) ) ), array(), wpcc_VERSION );
}

wp_enqueue_style(
'wpcc-blocks-editor',
plugins_url( 'assets/css/blocks-editor.css', dirname( dirname( __FILE__ ) ) ),
array(),
'1.0.0'
// 样式已在 init 阶段注册,这里只需要加载即可
wp_enqueue_style( 'wpcc-blocks-editor' );

// 兼容层:为编辑器注册旧占位文本的 deprecated 版本,避免校验失败
wp_enqueue_script(
'wpcc-block-compat',
plugins_url( 'assets/js/wpcc-block-compat.js', dirname( dirname( __FILE__ ) ) ),
array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-hooks' ),
wpcc_VERSION,
true
);

global $wpcc_options;
$enabled_languages = $wpcc_options['wpcc_used_langs'] ?? array();

wp_localize_script(
'wp-blocks',
'wpcc-block-compat',
'wpccBlockSettings',
array(
'enabledLanguages' => $enabled_languages,
@ -62,7 +86,12 @@ class WPCC_Blocks {
'zh-sg' => $wpcc_options['sgtip'] ?? '马新',
'zh-jp' => $wpcc_options['jptip'] ?? '日式'
),
'noConversionLabel' => $wpcc_options['nctip'] ?? '不转换'
'noConversionLabel' => $wpcc_options['nctip'] ?? '不转换',
'strings' => array(
'noConversionLabel' => $wpcc_options['nctip'] ?? __('不转换', 'wp-chinese-converter'),
'currentLanguagePrefix' => __('当前语言:', 'wp-chinese-converter'),
'languageSelectLabel' => __('选择语言', 'wp-chinese-converter')
)
)
);
}
@ -70,21 +99,23 @@ class WPCC_Blocks {
public function enqueue_frontend_assets() {
// 确保wpcc-variant脚本已加载
if ( ! wp_script_is( 'wpcc-variant', 'registered' ) ) {
wp_register_script( 'wpcc-variant', plugins_url( 'assets/dist/wpcc-variant.umd.js', dirname( dirname( __FILE__ ) ) ), array(), '1.1.0' );
wp_register_script( 'wpcc-variant', plugins_url( 'assets/dist/wpcc-variant.umd.js', dirname( dirname( __FILE__ ) ) ), array(), wpcc_VERSION );
}

wp_enqueue_style(
'wpcc-blocks-frontend',
plugins_url( 'assets/css/blocks-frontend.css', dirname( dirname( __FILE__ ) ) ),
array(),
'1.0.0'
);
// 为前端状态指示器的 dashicons 图标提供样式支持
wp_enqueue_style( 'dashicons' );

// 样式已在 init 阶段注册,这里只需要加载即可
wp_enqueue_style( 'wpcc-blocks-frontend' );

// 使用文件修改时间作为版本号,避免浏览器缓存导致逻辑不更新
$blocks_front_js = plugin_dir_path( dirname( __FILE__ ) ) . 'assets/js/blocks-frontend.js';
$blocks_front_ver = file_exists( $blocks_front_js ) ? filemtime( $blocks_front_js ) : wpcc_VERSION;
wp_enqueue_script(
'wpcc-blocks-frontend',
plugins_url( 'assets/js/blocks-frontend.js', dirname( dirname( __FILE__ ) ) ),
array( 'wpcc-variant' ),
'1.0.0',
$blocks_front_ver,
true
);
@ -105,7 +136,12 @@ class WPCC_Blocks {
'zh-sg' => $wpcc_options['sgtip'] ?? '马新',
'zh-jp' => $wpcc_options['jptip'] ?? '日式'
),
'noConversionLabel' => $wpcc_options['nctip'] ?? '不转换'
'noConversionLabel' => $wpcc_options['nctip'] ?? '不转换',
'strings' => array(
'noConversionLabel' => $wpcc_options['nctip'] ?? __('不转换', 'wp-chinese-converter'),
'currentLanguagePrefix' => __('当前语言:', 'wp-chinese-converter'),
'languageSelectLabel' => __('选择语言', 'wp-chinese-converter')
)
)
);
}

View file

@ -246,13 +246,8 @@ function sortLanguages(languages, sortOrder, showCurrentFirst) {
customLabels
}=attributes;
// Keep save output stable, do not depend on runtime settings
let finalEnabledLanguages = enabledLanguages;
if (typeof wpccBlockSettings !== 'undefined' && wpccBlockSettings.enabledLanguages) {
finalEnabledLanguages = enabledLanguages.filter(lang => wpccBlockSettings.enabledLanguages.includes(lang));
if (finalEnabledLanguages.length === 0) {
finalEnabledLanguages = wpccBlockSettings.enabledLanguages;
}
}
const blockProps=a.useBlockProps.save({
className:`wpcc-language-switcher wpcc-${displayStyle} wpcc-align-${alignment} wpcc-size-${buttonSize}`
@ -270,7 +265,7 @@ function sortLanguages(languages, sortOrder, showCurrentFirst) {
"data-open-in-new-window":openInNewWindow,
"data-show-language-code":showLanguageCode,
"data-custom-labels":JSON.stringify(customLabels)
},(0,n.__)("语言切换器","wp-chinese-converter"))
},"")
);
}
});})();

View file

@ -1,107 +0,0 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

require_once dirname( __FILE__ ) . '/interface-converter.php';

use Overtrue\PHPOpenCC\OpenCC;
use Overtrue\PHPOpenCC\Strategy;

class WPCC_OpenCC_Converter implements WPCC_Converter_Interface {
private $strategy_map = array(
'zh-hans' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-hant' => Strategy::SIMPLIFIED_TO_TRADITIONAL,
'zh-cn' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-tw' => Strategy::SIMPLIFIED_TO_TAIWAN_WITH_PHRASE,
'zh-hk' => Strategy::SIMPLIFIED_TO_HONGKONG,
'zh-sg' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-jp' => Strategy::SIMPLIFIED_TO_JAPANESE,
);
public function convert( $text, $target_variant ) {
if ( empty( $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 ) {
error_log( 'WPCC OpenCC Conversion Error: ' . $e->getMessage() );
return $text;
}
}
public function get_supported_variants() {
return array_keys( $this->strategy_map );
}
public function get_engine_name() {
return 'opencc';
}
public function get_engine_info() {
return array(
'name' => 'OpenCC',
'version' => '1.2.1',
'description' => '基于 OpenCC 的智能词汇级转换引擎',
'features' => array(
'词汇级别转换',
'异体字转换',
'地区习惯用词转换',
'智能语境分析',
'支持批量转换'
),
'memory_usage' => '按需加载,内存占用较低',
'conversion_type' => 'vocabulary_based'
);
}
public function is_available() {
return class_exists( 'Overtrue\PHPOpenCC\OpenCC' );
}
public function batch_convert( $texts, $target_variant ) {
if ( ! $this->is_available() ) {
return $texts;
}
if ( ! isset( $this->strategy_map[ $target_variant ] ) ) {
return $texts;
}
try {
return OpenCC::convert( $texts, $this->strategy_map[ $target_variant ] );
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Batch Conversion Error: ' . $e->getMessage() );
return $texts;
}
}
public function get_available_strategies() {
return $this->strategy_map;
}
public function convert_with_strategy( $text, $strategy ) {
if ( empty( $text ) || ! $this->is_available() ) {
return $text;
}
try {
return OpenCC::convert( $text, $strategy );
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Strategy Conversion Error: ' . $e->getMessage() );
return $text;
}
}
}

View file

@ -0,0 +1,416 @@
<?php

if (! defined('ABSPATH')) {
exit;
}

/**
* WPCC配置管理器
*
* 统一管理插件配置,减少全局变量的使用
*/
class WPCC_Config
{

private static ?self $instance = null;
private array $options = [];
private array $languages = [];
private string $target_lang = '';
private string $noconversion_url = '';
private bool $redirect_to = false;
private bool $direct_conversion_flag = false;
private array $langs_urls = [];
private array $debug_data = [];

private function __construct()
{
$this->load_options();
$this->init_languages();
}

public static function get_instance(): self
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}

/**
* 加载插件选项
*/
private function load_options(): void
{
$options = $this->get_wpcc_options();

// 确保返回值是数组类型
if (! is_array($options) || empty($options)) {
$this->options = $this->get_default_options();
} else {
$this->options = $options;
}
}

/**
* 获取WPCC选项考虑网络控制
*/
private function get_wpcc_options(): array
{
// 获取本地选项
$local_options = get_option('wpcc_options', []);

// 如果不是多站点环境,直接返回本地选项
if (! is_multisite() || is_network_admin()) {
return $local_options;
}

// 检查网络控制
if (! class_exists('WPCC_Network_Settings')) {
return $local_options;
}

// 获取网络控制的选项列表
$controlled_options = get_site_option('wpcc_network_controlled_options', []);
if (! is_array($controlled_options)) {
$controlled_options = explode(',', $controlled_options);
}

// 如果没有网络控制选项,返回本地选项
if (empty($controlled_options)) {
return $local_options;
}

// 合并网络控制的选项
$network_options = [];

// 映射网络默认选项到本地选项名
$option_mapping = [
'wpcc_default_used_langs' => 'wpcc_used_langs',
'wpcc_default_cntip' => 'cntip',
'wpcc_default_twtip' => 'twtip',
'wpcc_default_hktip' => 'hktip',
'wpcc_default_hanstip' => 'hanstip',
'wpcc_default_hanttip' => 'hanttip',
'wpcc_default_sgtip' => 'sgtip',
'wpcc_default_jptip' => 'jptip',
'wpcc_default_nctip' => 'nctip',
'wpcc_default_flag_option' => 'wpcc_flag_option',
// Harmonize with single-site option key used in UI
'wpcc_default_enable_extended_langs' => 'wpcc_show_more_langs',
'wpcc_default_engine' => 'wpcc_engine',
'wpcc_default_search_conversion' => 'wpcc_search_conversion',
'wpcc_default_use_fullpage_conversion' => 'wpcc_use_fullpage_conversion',
// Quicktags uses wpcc_no_conversion_qtag in single-site
'wpcc_default_no_conversion_qtag' => 'wpcc_no_conversion_qtag',
'wpcc_default_enable_post_conversion' => 'wpcc_enable_post_conversion',
'wpcc_default_post_conversion_target' => 'wpcc_post_conversion_target',
'wpcc_default_use_permalink' => 'wpcc_use_permalink',
'wpcc_default_sitemap_post_type' => 'wpcco_sitemap_post_type',
'wpcc_default_use_sitemap' => 'wpcco_use_sitemap',
'wpcc_default_browser_redirect' => 'wpcc_browser_redirect',
'wpcc_default_auto_language_recong' => 'wpcc_auto_language_recong',
'wpcc_default_use_cookie_variant' => 'wpcc_use_cookie_variant',
'wpcc_default_no_conversion_tag' => 'wpcc_no_conversion_tag',
'wpcc_default_no_conversion_ja' => 'wpcc_no_conversion_ja',
'wpcc_default_hreflang_x_default' => 'wpcc_hreflang_x_default',
'wpcc_default_enable_hreflang_tags' => 'wpcc_enable_hreflang_tags',
'wpcc_default_enable_hreflang_x_default' => 'wpcc_enable_hreflang_x_default',
'wpcc_default_enable_schema_conversion' => 'wpcc_enable_schema_conversion',
'wpcc_default_enable_meta_conversion' => 'wpcc_enable_meta_conversion',
'wpcc_default_enable_cache_addon' => 'wpcc_enable_cache_addon'
];

// 对于每个被控制的选项,使用网络设置
foreach ($controlled_options as $option_name) {
// 使用映射表查找对应的网络选项名
$network_option_name = null;
// 查找映射表中的对应关系
foreach ($option_mapping as $network_key => $local_key) {
if ($local_key === $option_name) {
$network_option_name = $network_key;
break;
}
}
// 如果映射表中没有找到,使用默认规则
if (!$network_option_name) {
$network_option_name = 'wpcc_default_' . str_replace('wpcc_', '', $option_name);
}

// 获取网络设置值
$network_value = get_site_option($network_option_name);
if ($network_value !== false) {
$network_options[$option_name] = $network_value;
}
}

// 合并选项:网络控制的选项覆盖本地选项
return array_merge($local_options, $network_options);
}

/**
* 获取默认配置
*/
private function get_default_options(): array
{
return [
// 语言与标签
'wpcc_used_langs' => ['zh-cn', 'zh-tw'],
'cntip' => '简体',
'twtip' => '繁体',
'hktip' => '港澳',
'hanstip' => '简体',
'hanttip' => '繁体',
'sgtip' => '马新',
'jptip' => '日式',

// 引擎与转换
'wpcc_engine' => 'mediawiki',
'wpcc_search_conversion' => 1,
'wpcc_use_fullpage_conversion' => 0,

// 浏览器与 Cookie
'wpcc_browser_redirect' => 0,
'wpcc_auto_language_recong' => 0,
'wpcc_use_cookie_variant' => 1,

// 不转换
'wpcc_no_conversion_tag' => '',
'wpcc_no_conversion_ja' => 0,
'wpcc_no_conversion_qtag' => 0,

// 发表时转换
'wpcc_enable_post_conversion' => 0,
'wpcc_post_conversion_target' => 'zh-cn',

// URL 与站点地图
'wpcc_use_permalink' => 0,
'wpcco_use_sitemap' => 0,
'wpcco_sitemap_post_type' => 'post,page',

// SEO
'wpcc_enable_hreflang_tags' => 1,
'wpcc_enable_hreflang_x_default' => 1,
'wpcc_hreflang_x_default' => 'zh-cn',
'wpcc_enable_schema_conversion' => 1,
'wpcc_enable_meta_conversion' => 1,


// 其他
'wpcc_flag_option' => 1,
'wpcc_trackback_plugin_author' => 0,
'wpcc_add_author_link' => 0,
'wpcc_translate_type' => 0,
'nctip' => '',
'wpcc_enable_cache_addon' => 1,
'wpcc_enable_network_module' => 0,
];
}

/**
* 初始化语言配置
*/
private function init_languages(): void
{
// 使用中心化的语言配置
$this->languages = WPCC_Language_Config::get_all_languages();
}

/**
* 获取配置选项
*/
public function get_option(string $key, $default = null)
{
return $this->options[$key] ?? $default;
}

/**
* 设置配置选项
*/
public function set_option(string $key, $value): void
{
$this->options[$key] = $value;
}

/**
* 更新配置选项到数据库
*/
public function save_options(): bool
{
return update_option('wpcc_options', $this->options);
}

/**
* 获取所有配置选项
*/
public function get_all_options(): array
{
return $this->options;
}

/**
* 获取语言配置
*/
public function get_languages(): array
{
return $this->languages;
}

/**
* 获取特定语言配置
*/
public function get_language(string $lang_code): ?array
{
return $this->languages[$lang_code] ?? null;
}

/**
* 设置目标语言
*/
public function set_target_lang(string $lang): void
{
$this->target_lang = $lang;
}

/**
* 获取目标语言
*/
public function get_target_lang(): string
{
return $this->target_lang;
}

/**
* 设置无转换URL
*/
public function set_noconversion_url(string $url): void
{
$this->noconversion_url = $url;
}

/**
* 获取无转换URL
*/
public function get_noconversion_url(): string
{
return $this->noconversion_url;
}

/**
* 设置语言URL映射
*/
public function set_langs_urls(array $urls): void
{
$this->langs_urls = $urls;
}

/**
* 获取语言URL映射
*/
public function get_langs_urls(): array
{
return $this->langs_urls;
}

/**
* 设置重定向标志
*/
public function set_redirect_to(bool $redirect): void
{
$this->redirect_to = $redirect;
}

/**
* 获取重定向标志
*/
public function get_redirect_to(): bool
{
return $this->redirect_to;
}

/**
* 设置直接转换标志
*/
public function set_direct_conversion_flag(bool $flag): void
{
$this->direct_conversion_flag = $flag;
}

/**
* 获取直接转换标志
*/
public function get_direct_conversion_flag(): bool
{
return $this->direct_conversion_flag;
}

/**
* 添加调试数据
*/
public function add_debug_data(string $key, $data): void
{
if (defined('wpcc_DEBUG') && wpcc_DEBUG) {
$this->debug_data[$key] = $data;
}
}

/**
* 获取调试数据
*/
public function get_debug_data(): array
{
return $this->debug_data;
}

/**
* 检查是否启用了指定功能
*/
public function is_feature_enabled(string $feature): bool
{
return (bool) $this->get_option($feature, false);
}

/**
* 获取启用的语言列表
*/
public function get_enabled_languages(): array
{
return $this->get_option('wpcc_used_langs', []);
}

/**
* 检查语言是否启用
*/
public function is_language_enabled(string $lang_code): bool
{
return in_array($lang_code, $this->get_enabled_languages(), true);
}

/**
* 获取转换引擎
*/
public function get_conversion_engine(): string
{
return $this->get_option('wpcc_engine', 'opencc');
}

/**
* 验证配置完整性
*/
public function validate_config(): array
{
$errors = [];

if (empty($this->get_enabled_languages())) {
$errors[] = 'No languages enabled';
}

$engine = $this->get_conversion_engine();
if (! in_array($engine, ['opencc', 'mediawiki'])) {
$errors[] = 'Invalid conversion engine: ' . $engine;
}

return $errors;
}
}

View file

@ -0,0 +1,270 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* WPCC转换缓存管理器
*
* 提供转换结果缓存功能,避免重复转换相同内容
* 支持内存缓存和持久化缓存两种模式
*/
class WPCC_Conversion_Cache {
/**
* 内存缓存数组
*
* @var array
*/
private static $memory_cache = array();
/**
* 缓存统计信息
*
* @var array
*/
private static $cache_stats = array(
'hits' => 0,
'misses' => 0,
'sets' => 0
);
/**
* 最大内存缓存条目数
*
* @var int
*/
private static $max_memory_cache_size = 1000;
/**
* 缓存过期时间(秒)
*
* @var int
*/
private static $cache_expire_time = 3600; // 1小时
/**
* 获取缓存的转换结果
*
* @param string $text 原始文本
* @param string $variant 目标语言变体
* @return string|null 缓存的转换结果如果不存在返回null
*/
public static function get_cached_conversion( $text, $variant ) {
if ( empty( $text ) || empty( $variant ) ) {
return null;
}
$cache_key = self::generate_cache_key( $text, $variant );
// 先检查内存缓存
if ( isset( self::$memory_cache[ $cache_key ] ) ) {
$cached_data = self::$memory_cache[ $cache_key ];
if ( $cached_data['expires'] > time() ) {
self::$cache_stats['hits']++;
return $cached_data['result'];
} else {
// 清理过期的内存缓存
unset( self::$memory_cache[ $cache_key ] );
}
}
// 检查WordPress对象缓存
$cached_result = wp_cache_get( $cache_key, 'wpcc_conversion' );
if ( $cached_result !== false ) {
// 同时存入内存缓存以加速后续访问
self::set_memory_cache( $cache_key, $cached_result );
self::$cache_stats['hits']++;
return $cached_result;
}
self::$cache_stats['misses']++;
return null;
}
/**
* 设置转换结果缓存
*
* @param string $text 原始文本
* @param string $variant 目标语言变体
* @param string $result 转换结果
* @return bool 是否设置成功
*/
public static function set_cached_conversion( $text, $variant, $result ) {
if ( empty( $text ) || empty( $variant ) || empty( $result ) ) {
return false;
}
$cache_key = self::generate_cache_key( $text, $variant );
// 设置内存缓存
self::set_memory_cache( $cache_key, $result );
// 设置WordPress对象缓存
wp_cache_set( $cache_key, $result, 'wpcc_conversion', self::$cache_expire_time );
self::$cache_stats['sets']++;
return true;
}
/**
* 批量获取转换缓存
*
* @param array $texts_variants 文本和变体的数组 [[text, variant], ...]
* @return array 缓存结果数组键为cache_key值为转换结果
*/
public static function get_cached_conversions_batch( $texts_variants ) {
$results = array();
foreach ( $texts_variants as $item ) {
if ( ! is_array( $item ) || count( $item ) < 2 ) {
continue;
}
list( $text, $variant ) = $item;
$cache_key = self::generate_cache_key( $text, $variant );
$cached_result = self::get_cached_conversion( $text, $variant );
if ( $cached_result !== null ) {
$results[ $cache_key ] = $cached_result;
}
}
return $results;
}
/**
* 批量设置转换缓存
*
* @param array $conversions 转换结果数组 [[text, variant, result], ...]
* @return int 成功设置的缓存数量
*/
public static function set_cached_conversions_batch( $conversions ) {
$count = 0;
foreach ( $conversions as $conversion ) {
if ( ! is_array( $conversion ) || count( $conversion ) < 3 ) {
continue;
}
list( $text, $variant, $result ) = $conversion;
if ( self::set_cached_conversion( $text, $variant, $result ) ) {
$count++;
}
}
return $count;
}
/**
* 生成缓存键
*
* @param string $text 原始文本
* @param string $variant 目标语言变体
* @return string 缓存键
*/
private static function generate_cache_key( $text, $variant ) {
// 使用文本和变体的哈希值作为缓存键,确保唯一性
return 'wpcc_' . md5( $text . '|' . $variant );
}
/**
* 设置内存缓存
*
* @param string $cache_key 缓存键
* @param string $result 转换结果
*/
private static function set_memory_cache( $cache_key, $result ) {
// 如果内存缓存已满,移除最旧的条目
if ( count( self::$memory_cache ) >= self::$max_memory_cache_size ) {
$oldest_key = array_key_first( self::$memory_cache );
unset( self::$memory_cache[ $oldest_key ] );
}
self::$memory_cache[ $cache_key ] = array(
'result' => $result,
'expires' => time() + self::$cache_expire_time
);
}
/**
* 清理过期的内存缓存
*/
public static function cleanup_expired_memory_cache() {
$current_time = time();
$expired_keys = array();
foreach ( self::$memory_cache as $key => $data ) {
if ( $data['expires'] <= $current_time ) {
$expired_keys[] = $key;
}
}
foreach ( $expired_keys as $key ) {
unset( self::$memory_cache[ $key ] );
}
return count( $expired_keys );
}
/**
* 清空所有缓存
*/
public static function clear_all_cache() {
// 清空内存缓存
self::$memory_cache = array();
// 清空WordPress对象缓存
wp_cache_flush_group( 'wpcc_conversion' );
// 重置统计信息
self::$cache_stats = array(
'hits' => 0,
'misses' => 0,
'sets' => 0
);
}
/**
* 获取缓存统计信息
*
* @return array 缓存统计信息
*/
public static function get_cache_stats() {
$stats = self::$cache_stats;
$stats['memory_cache_size'] = count( self::$memory_cache );
$stats['hit_rate'] = $stats['hits'] + $stats['misses'] > 0
? round( $stats['hits'] / ( $stats['hits'] + $stats['misses'] ) * 100, 2 )
: 0;
return $stats;
}
/**
* 设置缓存配置
*
* @param array $config 配置数组
*/
public static function set_cache_config( $config ) {
if ( isset( $config['max_memory_cache_size'] ) ) {
self::$max_memory_cache_size = (int) $config['max_memory_cache_size'];
}
if ( isset( $config['cache_expire_time'] ) ) {
self::$cache_expire_time = (int) $config['cache_expire_time'];
}
}
/**
* 获取缓存配置
*
* @return array 当前缓存配置
*/
public static function get_cache_config() {
return array(
'max_memory_cache_size' => self::$max_memory_cache_size,
'cache_expire_time' => self::$cache_expire_time
);
}
}

View file

@ -25,16 +25,16 @@ class WPCC_Converter_Factory {
}
private static function create_converter( $engine ) {
switch ( $engine ) {
case 'opencc':
require_once dirname( __FILE__ ) . '/class-opencc-converter.php';
return new WPCC_OpenCC_Converter();
switch ( $engine ) {
case 'opencc':
require_once dirname( __FILE__ ) . '/class-wpcc-opencc-converter.php';
return new WPCC_OpenCC_Converter();
case 'mediawiki':
default:
require_once dirname( __FILE__ ) . '/class-mediawiki-converter.php';
return new WPCC_MediaWiki_Converter();
}
case 'mediawiki':
default:
require_once dirname( __FILE__ ) . '/class-wpcc-mediawiki-converter.php';
return new WPCC_MediaWiki_Converter();
}
}
public static function get_available_engines() {

View file

@ -0,0 +1,301 @@
<?php

declare(strict_types=1);

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* WPCC异常基类
*/
class WPCC_Exception extends Exception {
protected $error_code;
protected $context;
public function __construct( string $message = '', string $error_code = '', array $context = [], int $code = 0, ?Throwable $previous = null ) {
parent::__construct( $message, $code, $previous );
$this->error_code = $error_code;
$this->context = $context;
}
public function get_error_code(): string {
return $this->error_code;
}
public function get_context(): array {
return $this->context;
}
}

/**
* 转换异常
*/
class WPCC_Conversion_Exception extends WPCC_Exception {
public function __construct( string $message, string $variant = '', string $text_preview = '', ?Throwable $previous = null ) {
$context = [
'variant' => $variant,
'text_preview' => mb_substr( $text_preview, 0, 50 ) . ( mb_strlen( $text_preview ) > 50 ? '...' : '' )
];
parent::__construct( $message, 'CONVERSION_ERROR', $context, 0, $previous );
}
}

/**
* 缓存异常
*/
class WPCC_Cache_Exception extends WPCC_Exception {
public function __construct( string $message, string $cache_key = '', ?Throwable $previous = null ) {
$context = [ 'cache_key' => $cache_key ];
parent::__construct( $message, 'CACHE_ERROR', $context, 0, $previous );
}
}

/**
* 配置异常
*/
class WPCC_Config_Exception extends WPCC_Exception {
public function __construct( string $message, string $config_key = '', $config_value = null, ?Throwable $previous = null ) {
$context = [
'config_key' => $config_key,
'config_value' => is_scalar( $config_value ) ? $config_value : gettype( $config_value )
];
parent::__construct( $message, 'CONFIG_ERROR', $context, 0, $previous );
}
}

/**
* WPCC异常处理器
*/
final class WPCC_Exception_Handler {
private static $error_counts = [];
private static $max_errors_per_type = 10;
private static $error_log_enabled = true;
private static $fallback_strategies = [];
/**
* 初始化异常处理器
*/
public static function init(): void {
// 注册默认的降级策略
self::register_fallback_strategy( 'CONVERSION_ERROR', [ self::class, 'conversion_fallback' ] );
self::register_fallback_strategy( 'CACHE_ERROR', [ self::class, 'cache_fallback' ] );
self::register_fallback_strategy( 'CONFIG_ERROR', [ self::class, 'config_fallback' ] );
}
/**
* 安全地执行操作,带异常处理
*/
public static function safe_execute( callable $operation, $fallback_value = null, string $operation_name = 'unknown' ) {
try {
return $operation();
} catch ( WPCC_Exception $e ) {
return self::handle_wpcc_exception( $e, $fallback_value, $operation_name );
} catch ( Throwable $e ) {
return self::handle_generic_exception( $e, $fallback_value, $operation_name );
}
}
/**
* 处理WPCC特定异常
*/
public static function handle_wpcc_exception( WPCC_Exception $exception, $fallback_value = null, string $operation_name = 'unknown' ) {
$error_code = $exception->get_error_code();
// 记录错误
self::log_error( $exception, $operation_name );
// 检查错误频率限制
if ( self::should_suppress_error( $error_code ) ) {
return $fallback_value;
}
// 尝试执行降级策略
if ( isset( self::$fallback_strategies[ $error_code ] ) ) {
try {
return call_user_func( self::$fallback_strategies[ $error_code ], $exception, $fallback_value );
} catch ( Throwable $fallback_error ) {
self::log_error( $fallback_error, "fallback_for_{$operation_name}" );
}
}
return $fallback_value;
}
/**
* 处理通用异常
*/
public static function handle_generic_exception( Throwable $exception, $fallback_value = null, string $operation_name = 'unknown' ) {
self::log_error( $exception, $operation_name );
// 对于严重错误,可能需要通知管理员
if ( $exception instanceof Error || $exception instanceof ParseError ) {
self::notify_admin_of_critical_error( $exception, $operation_name );
}
return $fallback_value;
}
/**
* 注册降级策略
*/
public static function register_fallback_strategy( string $error_code, callable $strategy ): void {
self::$fallback_strategies[ $error_code ] = $strategy;
}
/**
* 转换异常的降级策略
*/
public static function conversion_fallback( WPCC_Conversion_Exception $exception, $fallback_value = null ) {
$context = $exception->get_context();
$original_text = $context['text_preview'] ?? '';
// 如果有原始文本返回原文本而不是null
if ( ! empty( $original_text ) && $original_text !== '...' ) {
return $original_text;
}
return $fallback_value;
}
/**
* 缓存异常的降级策略
*/
public static function cache_fallback( WPCC_Cache_Exception $exception, $fallback_value = null ) {
// 缓存问题时,继续执行但不使用缓存
return $fallback_value;
}
/**
* 配置异常的降级策略
*/
public static function config_fallback( WPCC_Config_Exception $exception, $fallback_value = null ) {
$context = $exception->get_context();
$config_key = $context['config_key'] ?? '';
// 返回默认配置值
$defaults = [
'wpcc_engine' => 'opencc',
'wpcc_use_fullpage_conversion' => 1,
'wpcc_search_conversion' => 1,
'wpcc_used_langs' => [ 'zh-cn', 'zh-tw' ]
];
return $defaults[ $config_key ] ?? $fallback_value;
}
/**
* 记录错误
*/
private static function log_error( Throwable $exception, string $operation_name ): void {
if ( ! self::$error_log_enabled ) {
return;
}
// 统一异常计数键,保证与 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',
$operation_name,
$exception->getMessage(),
basename( $exception->getFile() ),
$exception->getLine(),
$exception instanceof WPCC_Exception ? json_encode( $exception->get_context() ) : 'N/A'
);
error_log( $log_message );
// 在调试模式下记录堆栈跟踪
if ( WP_DEBUG && defined( 'WPCC_DEBUG_TRACE' ) && WPCC_DEBUG_TRACE ) {
error_log( 'WPCC Error Stack Trace: ' . $exception->getTraceAsString() );
}
}
/**
* 检查是否应该抑制错误(防止日志洪水)
*/
private static function should_suppress_error( string $error_code ): bool {
$error_key = "wpcc_error_{$error_code}";
$count = self::$error_counts[ $error_key ] ?? 0;
return $count > self::$max_errors_per_type;
}
/**
* 通知管理员关键错误
*/
private static function notify_admin_of_critical_error( Throwable $exception, string $operation_name ): void {
// 防止发送过多邮件
$notification_key = 'wpcc_critical_error_' . md5( $exception->getMessage() );
$last_notification = get_transient( $notification_key );
if ( $last_notification ) {
return;
}
// 设置12小时的冷却期
set_transient( $notification_key, time(), 12 * HOUR_IN_SECONDS );
$admin_email = get_option( 'admin_email' );
if ( ! $admin_email ) {
return;
}
$subject = sprintf( 'WP Chinese Converter Critical Error on %s', get_bloginfo( 'name' ) );
$message = sprintf(
"A critical error occurred in WP Chinese Converter:\n\nOperation: %s\nError: %s\nFile: %s:%d\n\nPlease check your error logs for more details.\n\nTime: %s",
$operation_name,
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
current_time( 'mysql' )
);
wp_mail( $admin_email, $subject, $message );
}
/**
* 获取错误统计
*/
public static function get_error_stats(): array {
return [
'error_counts' => self::$error_counts,
'total_errors' => array_sum( self::$error_counts ),
'error_log_enabled' => self::$error_log_enabled,
'max_errors_per_type' => self::$max_errors_per_type
];
}
/**
* 重置错误计数
*/
public static function reset_error_counts(): void {
self::$error_counts = [];
}
/**
* 设置错误记录状态
*/
public static function set_error_logging( bool $enabled ): void {
self::$error_log_enabled = $enabled;
}
/**
* 设置每种错误类型的最大记录数
*/
public static function set_max_errors_per_type( int $max ): void {
self::$max_errors_per_type = max( 1, $max );
}
}

// 初始化异常处理器
WPCC_Exception_Handler::init();

View file

@ -0,0 +1,321 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

require_once dirname( __FILE__ ) . '/abstract-module.php';

/**
* WPCC 扩展基础类
*
* 为第三方扩展提供标准化的基础架构
*/
abstract class WPCC_Extension extends WPCC_Abstract_Module {
protected $license_key;
protected $license_status = 'inactive';
protected $update_server_url;
protected $extension_slug;
protected $is_premium = true;
protected $required_core_version = '1.5';
protected $pricing_tier = 'pro'; // free, pro, enterprise, ultimate
public function __construct() {
$this->extension_slug = $this->get_extension_slug();
$this->check_core_compatibility();
$this->init_license_system();
parent::__construct();
}
/**
* 获取扩展唯一标识符
*/
abstract protected function get_extension_slug(): string;
/**
* 检查核心插件兼容性
*/
private function check_core_compatibility(): bool {
if ( ! defined( 'wpcc_VERSION' ) ) {
add_action( 'admin_notices', [ $this, 'core_plugin_missing_notice' ] );
return false;
}
if ( version_compare( wpcc_VERSION, $this->required_core_version, '<' ) ) {
add_action( 'admin_notices', [ $this, 'core_plugin_outdated_notice' ] );
return false;
}
return true;
}
/**
* 初始化许可证系统
*/
private function init_license_system(): void {
if ( ! $this->is_premium ) {
return;
}
$this->license_key = get_option( $this->extension_slug . '_license_key', '' );
$this->license_status = get_option( $this->extension_slug . '_license_status', 'inactive' );
add_action( 'admin_init', [ $this, 'check_license_status' ] );
add_action( 'wp_ajax_' . $this->extension_slug . '_activate_license', [ $this, 'activate_license' ] );
add_action( 'wp_ajax_' . $this->extension_slug . '_deactivate_license', [ $this, 'deactivate_license' ] );
}
/**
* 检查许可证状态
*/
public function check_license_status(): void {
if ( empty( $this->license_key ) ) {
return;
}
// 每天检查一次许可证状态
$last_check = get_option( $this->extension_slug . '_license_last_check', 0 );
if ( time() - $last_check < DAY_IN_SECONDS ) {
return;
}
$response = wp_remote_post( $this->update_server_url . '/api/license/check', [
'body' => [
'license_key' => $this->license_key,
'product_slug' => $this->extension_slug,
'site_url' => home_url()
],
'timeout' => 15
] );
if ( is_wp_error( $response ) ) {
return;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( isset( $body['status'] ) ) {
$this->license_status = $body['status'];
update_option( $this->extension_slug . '_license_status', $this->license_status );
}
update_option( $this->extension_slug . '_license_last_check', time() );
}
/**
* 激活许可证
*/
public function activate_license(): void {
if ( ! check_ajax_referer( $this->extension_slug . '_license_nonce', 'nonce' ) ) {
wp_die( 'Security check failed' );
}
$license_key = sanitize_text_field( $_POST['license_key'] ?? '' );
if ( empty( $license_key ) ) {
wp_send_json_error( 'License key is required' );
}
$response = wp_remote_post( $this->update_server_url . '/api/license/activate', [
'body' => [
'license_key' => $license_key,
'product_slug' => $this->extension_slug,
'site_url' => home_url()
]
] );
if ( is_wp_error( $response ) ) {
wp_send_json_error( $response->get_error_message() );
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( isset( $body['success'] ) && $body['success'] ) {
update_option( $this->extension_slug . '_license_key', $license_key );
update_option( $this->extension_slug . '_license_status', 'active' );
$this->license_key = $license_key;
$this->license_status = 'active';
wp_send_json_success( 'License activated successfully' );
} else {
wp_send_json_error( $body['message'] ?? 'License activation failed' );
}
}
/**
* 停用许可证
*/
public function deactivate_license(): void {
if ( ! check_ajax_referer( $this->extension_slug . '_license_nonce', 'nonce' ) ) {
wp_die( 'Security check failed' );
}
wp_remote_post( $this->update_server_url . '/api/license/deactivate', [
'body' => [
'license_key' => $this->license_key,
'product_slug' => $this->extension_slug,
'site_url' => home_url()
]
] );
delete_option( $this->extension_slug . '_license_key' );
delete_option( $this->extension_slug . '_license_status' );
$this->license_key = '';
$this->license_status = 'inactive';
wp_send_json_success( 'License deactivated successfully' );
}
/**
* 是否已授权
*/
public function is_licensed(): bool {
return $this->license_status === 'active' || ! $this->is_premium;
}
/**
* 获取许可证信息
*/
public function get_license_info(): array {
return [
'key' => $this->license_key,
'status' => $this->license_status,
'is_premium' => $this->is_premium,
'pricing_tier' => $this->pricing_tier
];
}
/**
* 渲染许可证设置界面
*/
public function render_license_settings(): string {
if ( ! $this->is_premium ) {
return '';
}
ob_start();
?>
<div class="wpcc-license-settings">
<h3><?php echo esc_html( $this->get_name() ); ?> License</h3>
<?php if ( $this->license_status === 'active' ): ?>
<div class="notice notice-success inline">
<p>✅ License is active</p>
</div>
<button type="button" class="button wpcc-deactivate-license"
data-extension="<?php echo esc_attr( $this->extension_slug ); ?>">
Deactivate License
</button>
<?php else: ?>
<div class="notice notice-warning inline">
<p>⚠️ Please enter your license key to activate this extension</p>
</div>
<input type="text" class="regular-text wpcc-license-key"
placeholder="Enter your license key"
value="<?php echo esc_attr( $this->license_key ); ?>">
<button type="button" class="button button-primary wpcc-activate-license"
data-extension="<?php echo esc_attr( $this->extension_slug ); ?>">
Activate License
</button>
<?php endif; ?>
</div>
<script>
jQuery(document).ready(function($) {
$('.wpcc-activate-license').click(function() {
var button = $(this);
var extension = button.data('extension');
var licenseKey = button.siblings('.wpcc-license-key').val();
button.prop('disabled', true).text('Activating...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: extension + '_activate_license',
license_key: licenseKey,
nonce: '<?php echo wp_create_nonce( $this->extension_slug . '_license_nonce' ); ?>'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error: ' + response.data);
button.prop('disabled', false).text('Activate License');
}
}
});
});
$('.wpcc-deactivate-license').click(function() {
var button = $(this);
var extension = button.data('extension');
if (!confirm('Are you sure you want to deactivate this license?')) {
return;
}
button.prop('disabled', true).text('Deactivating...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: extension + '_deactivate_license',
nonce: '<?php echo wp_create_nonce( $this->extension_slug . '_license_nonce' ); ?>'
},
success: function(response) {
location.reload();
}
});
});
});
</script>
<?php
return ob_get_clean();
}
/**
* 核心插件缺失提醒
*/
public function core_plugin_missing_notice(): void {
?>
<div class="notice notice-error">
<p>
<strong><?php echo esc_html( $this->get_name() ); ?></strong> requires
<strong>WP Chinese Converter</strong> to be installed and activated.
</p>
</div>
<?php
}
/**
* 核心插件版本过低提醒
*/
public function core_plugin_outdated_notice(): void {
?>
<div class="notice notice-error">
<p>
<strong><?php echo esc_html( $this->get_name() ); ?></strong> requires
<strong>WP Chinese Converter</strong> version <?php echo esc_html( $this->required_core_version ); ?> or higher.
Current version: <?php echo esc_html( wpcc_VERSION ); ?>
</p>
</div>
<?php
}
/**
* 重写is_enabled方法加入许可证检查
*/
public function is_enabled(): bool {
return parent::is_enabled() && $this->is_licensed();
}
/**
* 获取扩展状态(包含许可证信息)
*/
public function get_status(): array {
$status = parent::get_status();
$status['license'] = $this->get_license_info();
return $status;
}
}

View file

@ -0,0 +1,165 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* WPCC语言配置中心
*
* 统一管理所有语言相关配置,避免重复定义
* 这是语言配置的唯一真实来源Single Source of Truth
*/
final class WPCC_Language_Config {
/**
* 语言配置数组
* 格式: [语言代码 => [转换函数名, 选项键名, 默认显示名, BCP47代码]]
*/
private static array $languages = [];
/**
* 默认语言显示名称
*/
private static array $default_names = [
'zh-cn' => '简体中文',
'zh-tw' => '台灣正體',
'zh-hk' => '港澳繁體',
'zh-hans' => '简体中文',
'zh-hant' => '繁体中文',
'zh-sg' => '马新简体',
'zh-jp' => '日式汉字',
];
/**
* 初始化语言配置
*/
public static function init(): void {
if ( ! empty( self::$languages ) ) {
return;
}
self::$languages = [
'zh-cn' => [ 'zhconversion_cn', 'cntip', self::get_translated_name( 'zh-cn' ), 'zh-CN' ],
'zh-tw' => [ 'zhconversion_tw', 'twtip', self::get_translated_name( 'zh-tw' ), 'zh-TW' ],
'zh-hk' => [ 'zhconversion_hk', 'hktip', self::get_translated_name( 'zh-hk' ), 'zh-HK' ],
'zh-hans' => [ 'zhconversion_hans', 'hanstip', self::get_translated_name( 'zh-hans' ), 'zh-Hans' ],
'zh-hant' => [ 'zhconversion_hant', 'hanttip', self::get_translated_name( 'zh-hant' ), 'zh-Hant' ],
'zh-sg' => [ 'zhconversion_sg', 'sgtip', self::get_translated_name( 'zh-sg' ), 'zh-SG' ],
'zh-jp' => [ 'zhconversion_jp', 'jptip', self::get_translated_name( 'zh-jp' ), 'zh-JP' ],
];
}
/**
* 获取翻译后的语言名称
*/
private static function get_translated_name( string $lang_code ): string {
// 只在WordPress完全初始化后才使用翻译函数
if ( function_exists( 'did_action' ) && did_action( 'init' ) && function_exists( '__' ) ) {
return __( self::$default_names[ $lang_code ] ?? $lang_code, 'wp-chinese-converter' );
}
return self::$default_names[ $lang_code ] ?? $lang_code;
}
/**
* 获取所有语言配置
*/
public static function get_all_languages(): array {
self::init();
return self::$languages;
}
/**
* 获取特定语言配置
*/
public static function get_language( string $lang_code ): ?array {
self::init();
return self::$languages[ $lang_code ] ?? null;
}
/**
* 获取语言显示名称
*/
public static function get_language_name( string $lang_code, ?array $custom_names = null ): string {
self::init();
// 优先使用自定义名称
if ( $custom_names && isset( $custom_names[ $lang_code ] ) ) {
return $custom_names[ $lang_code ];
}
// 使用配置中的名称
if ( isset( self::$languages[ $lang_code ][2] ) ) {
return self::$languages[ $lang_code ][2];
}
// 降级到默认名称
return self::$default_names[ $lang_code ] ?? $lang_code;
}
/**
* 获取语言的BCP47代码
*/
public static function get_bcp47_code( string $lang_code ): string {
self::init();
return self::$languages[ $lang_code ][3] ?? $lang_code;
}
/**
* 获取语言的转换函数名
*/
public static function get_conversion_function( string $lang_code ): ?string {
self::init();
return self::$languages[ $lang_code ][0] ?? null;
}
/**
* 获取语言的选项键名
*/
public static function get_option_key( string $lang_code ): ?string {
self::init();
return self::$languages[ $lang_code ][1] ?? null;
}
/**
* 检查语言代码是否有效
*/
public static function is_valid_language( string $lang_code ): bool {
self::init();
return isset( self::$languages[ $lang_code ] );
}
/**
* 获取所有有效的语言代码
*/
public static function get_valid_language_codes(): array {
self::init();
return array_keys( self::$languages );
}
/**
* 获取自定义语言名称配置
*
* @param array $options 插件选项数组
* @return array 自定义名称数组
*/
public static function get_custom_names( array $options ): array {
self::init();
$custom_names = [];
foreach ( self::$languages as $code => $config ) {
$option_key = $config[1];
$default_name = self::$default_names[ $code ];
$custom_names[ $code ] = $options[ $option_key ] ?? $default_name;
}
return $custom_names;
}
/**
* 获取默认语言名称
*/
public static function get_default_names(): array {
return self::$default_names;
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,6 @@ if ( ! defined( 'ABSPATH' ) ) {
require_once dirname( __FILE__ ) . '/interface-converter.php';

class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface {
private $conversion_tables_loaded = false;

public function convert( $text, $target_variant ) {
@ -15,24 +14,43 @@ class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface {
return $text;
}

$this->ensure_conversion_tables_loaded();
// 检查缓存
$cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant );
if ( $cached_result !== null ) {
return $cached_result;
}

// 回滚:始终加载所有转换表
$this->load_all_conversion_tables();

$result = $text;
switch ( $target_variant ) {
case 'zh-hans':
return $this->convert_to_hans( $text );
$result = $this->convert_to_hans( $text );
break;
case 'zh-hant':
return $this->convert_to_hant( $text );
$result = $this->convert_to_hant( $text );
break;
case 'zh-cn':
return $this->convert_to_cn( $text );
$result = $this->convert_to_cn( $text );
break;
case 'zh-tw':
return $this->convert_to_tw( $text );
$result = $this->convert_to_tw( $text );
break;
case 'zh-hk':
return $this->convert_to_hk( $text );
$result = $this->convert_to_hk( $text );
break;
case 'zh-sg':
return $this->convert_to_sg( $text );
default:
return $text;
$result = $this->convert_to_sg( $text );
break;
}

// 缓存转换结果
if ( $result !== $text ) {
WPCC_Conversion_Cache::set_cached_conversion( $text, $target_variant, $result );
}

return $result;
}

public function get_supported_variants() {
@ -47,14 +65,13 @@ class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface {
return array(
'name' => 'MediaWiki',
'version' => '1.23.5',
'description' => '基于 MediaWiki 的字符映射转换引擎',
'description' => '基于MediaWiki的字符映射转换引擎',
'features' => array(
'字符级精确映射',
'快速转换速度',
'良好的兼容性',
'支持多地区变体'
),
'memory_usage' => '约 1.5MB (转换表加载后)',
'conversion_type' => 'character_mapping'
);
}
@ -64,16 +81,14 @@ class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface {
return file_exists( $conversion_file );
}

private function ensure_conversion_tables_loaded() {
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;

View file

@ -0,0 +1,233 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

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<string, array<array<string,string>>> 每个策略对应若干个字典映射,保持原有顺序
*/
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,
'zh-cn' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-tw' => Strategy::SIMPLIFIED_TO_TAIWAN_WITH_PHRASE,
'zh-hk' => Strategy::SIMPLIFIED_TO_HONGKONG,
'zh-sg' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-jp' => Strategy::SIMPLIFIED_TO_JAPANESE,
);
public function convert( $text, $target_variant ) {
if ( empty( $text ) ) {
return $text;
}
// 快速路径:非中文内容不做转换
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 {
// 使用预处理字典避免每次调用时重复排序
$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;
}
}
public function get_supported_variants() {
return array_keys( $this->strategy_map );
}
public function get_engine_name() {
return 'opencc';
}
public function get_engine_info() {
return array(
'name' => 'OpenCC',
'version' => '1.2.1',
'description' => '基于 OpenCC 的智能词汇级转换引擎',
'features' => array(
'词汇级别转换',
'异体字转换',
'地区习惯用词转换',
'智能语境分析',
'支持批量转换'
),
'memory_usage' => '按需加载,内存占用较低',
'conversion_type' => 'vocabulary_based'
);
}
public function is_available() {
return class_exists( 'Overtrue\PHPOpenCC\OpenCC' );
}
public function batch_convert( $texts, $target_variant ) {
if ( ! $this->is_available() ) {
return $texts;
}
if ( ! isset( $this->strategy_map[ $target_variant ] ) ) {
return $texts;
}
if ( ! is_array( $texts ) ) {
return $texts;
}
// 检查缓存
$cached_results = array();
$uncached_texts = array();
$uncached_indices = array();
foreach ( $texts as $index => $text ) {
$cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant );
if ( $cached_result !== null ) {
$cached_results[ $index ] = $cached_result;
} else if ( ! $this->contains_chinese( $text ) ) {
// 非中文内容:无需调用 OpenCC直接作为“已处理”内容返回
$cached_results[ $index ] = $text;
} else {
$uncached_texts[] = $text;
$uncached_indices[] = $index;
}
}
// 如果所有内容都在缓存中,直接返回
if ( empty( $uncached_texts ) ) {
$results = array();
foreach ( $texts as $index => $text ) {
$results[] = $cached_results[ $index ];
}
return $results;
}
try {
// 使用预处理字典一次性转换,避免每次排序
$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;
foreach ( $texts as $index => $original_text ) {
if ( isset( $cached_results[ $index ] ) ) {
$results[] = $cached_results[ $index ];
} else {
$converted_text = is_array( $converted_texts ) ? $converted_texts[ $uncached_index ] : $converted_texts;
$results[] = $converted_text;
// 缓存转换结果
if ( $converted_text !== $original_text ) {
WPCC_Conversion_Cache::set_cached_conversion( $original_text, $target_variant, $converted_text );
}
$uncached_index++;
}
}
return $results;
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Batch Conversion Error: ' . $e->getMessage() );
return $texts;
}
}
public function get_available_strategies() {
return $this->strategy_map;
}
public function convert_with_strategy( $text, $strategy ) {
if ( empty( $text ) || ! $this->is_available() ) {
return $text;
}
try {
return OpenCC::convert( $text, $strategy );
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Strategy Conversion Error: ' . $e->getMessage() );
return $text;
}
}
}

View file

@ -0,0 +1,194 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* WPCC Presets
*
* 提供一组可快速应用的预设配置,便于用户初始化或重置插件设置
*/
final class WPCC_Presets {

/**
* 获取所有预设
*/
public static function get_presets(): array {
// 统一的通用键,避免遗漏
$base = [
'wpcc_used_langs' => ['zh-cn','zh-tw'],
'cntip' => '简体',
'twtip' => '繁体',
'hktip' => '港澳',
'hanstip' => '简体',
'hanttip' => '繁体',
'sgtip' => '马新',
'jptip' => '日式',
'wpcc_translate_type' => 0,
'wpcc_engine' => 'opencc',
'wpcc_search_conversion' => 1,
'wpcc_use_fullpage_conversion' => 0,
'wpcc_no_conversion_qtag' => 0,
'wpcc_enable_post_conversion' => 0,
'wpcc_post_conversion_target' => 'zh-cn',
'wpcc_no_conversion_tag' => 'pre,code,wp-block-code',
'wpcc_no_conversion_ja' => 1,
'nctip' => '',
'wpcc_browser_redirect' => 0,
'wpcc_auto_language_recong' => 0,
'wpcc_use_cookie_variant' => 2,
'wpcc_use_permalink' => 0,
'wpcco_use_sitemap' => 0,
'wpcco_sitemap_post_type' => 'post,page',
'wpcc_enable_cache_addon' => 1,
'wpcc_enable_network_module' => 0,
'wpcc_enable_hreflang_tags' => 1,
'wpcc_hreflang_x_default' => 'zh-cn',
'wpcc_enable_schema_conversion' => 1,
'wpcc_enable_meta_conversion' => 1,
];

return [
// 恢复到插件的“工厂默认值”(更广覆盖的默认语言集与基础选项)
'factory_default' => [
'label' => __( '恢复默认', 'wp-chinese-converter' ),
'options' => [
'wpcc_used_langs' => ['zh-cn','zh-tw'],
'cntip' => '简体',
'twtip' => '繁体',
'hktip' => '港澳',
'hanstip' => '简体',
'hanttip' => '繁体',
'sgtip' => '马新',
'jptip' => '日式',

'wpcc_engine' => 'mediawiki',
'wpcc_search_conversion' => 1,

'wpcc_browser_redirect' => 0,
'wpcc_auto_language_recong' => 0,
'wpcc_use_cookie_variant' => 1,

'wpcc_use_fullpage_conversion' => 0,

'wpcc_no_conversion_tag' => '',
'wpcc_no_conversion_ja' => 0,
'wpcc_no_conversion_qtag' => 0,

'wpcc_enable_post_conversion' => 0,
'wpcc_post_conversion_target' => 'zh-cn',

'wpcc_use_permalink' => 0,
'wpcco_use_sitemap' => 0,
'wpcco_sitemap_post_type' => 'post,page',

'wpcc_enable_hreflang_tags' => 1,
'wpcc_hreflang_x_default' => 'zh-cn',
'wpcc_enable_schema_conversion' => 1,
'wpcc_enable_meta_conversion' => 1,

'wpcc_enable_cache_addon' => 1,
'wpcc_enable_network_module' => 0,

'wpcc_translate_type' => 0,
'nctip' => '',
'wpcc_flag_option' => 1,
'wpcc_trackback_plugin_author' => 0,
'wpcc_add_author_link' => 0,
],
],

// 内容/资讯站
'content_site' => [
'label' => __( '内容站', 'wp-chinese-converter' ),
'options' => array_merge( $base, [
'wpcc_engine' => 'opencc',
'wpcc_search_conversion' => 2,
'wpcco_use_sitemap' => 1,
'wpcc_use_fullpage_conversion' => 0,
'wpcc_used_langs' => ['zh-cn','zh-tw'],
]),
],

// 企业站
'corporate' => [
'label' => __( '企业站', 'wp-chinese-converter' ),
'options' => array_merge( $base, [
'wpcc_engine' => 'mediawiki',
'wpcc_search_conversion' => 0,
'wpcco_use_sitemap' => 1,
'wpcc_used_langs' => ['zh-cn','zh-tw'],
]),
],

// 技术文档/知识库
'docs' => [
'label' => __( '技术文档', 'wp-chinese-converter' ),
'options' => array_merge( $base, [
'wpcc_engine' => 'opencc',
'wpcc_search_conversion' => 1,
'wpcc_use_fullpage_conversion' => 0,
'wpcc_no_conversion_tag' => 'pre,code,wp-block-code,kbd,samp',
'wpcc_no_conversion_ja' => 1,
'wpcc_used_langs' => ['zh-cn','zh-tw'],
]),
],

// 高性能/兼容优先
'performance' => [
'label' => __( '高性能', 'wp-chinese-converter' ),
'options' => array_merge( $base, [
'wpcc_engine' => 'mediawiki',
'wpcc_search_conversion' => 0,
'wpcc_use_fullpage_conversion' => 0,
'wpcc_used_langs' => ['zh-cn','zh-tw'],
]),
],
];
}

/**
* 应用预设
*/
public static function apply_preset( string $slug ): bool {
$presets = self::get_presets();
if ( ! isset( $presets[ $slug ] ) ) {
return false;
}

$new = $presets[ $slug ]['options'];
$current = get_wpcc_option( 'wpcc_options', [] );
if ( ! is_array( $current ) ) {
$current = [];
}

// 合并:仅覆盖预设中出现的键,其他用户自定义保持不变
$merged = array_merge( $current, $new );

$before_permalink = isset( $current['wpcc_use_permalink'] ) ? intval( $current['wpcc_use_permalink'] ) : 0;
$after_permalink = isset( $merged['wpcc_use_permalink'] ) ? intval( $merged['wpcc_use_permalink'] ) : 0;

$ok = update_wpcc_option( 'wpcc_options', $merged );

// 若固定链接设定发生变化,则刷新重写规则
if ( $before_permalink !== $after_permalink ) {
if ( function_exists( 'flush_rewrite_rules' ) ) {
flush_rewrite_rules();
}
}

return (bool) $ok;
}

/**
* 获取可用于UI的预设清单slug => label
*/
public static function get_preset_labels(): array {
$labels = [];
foreach ( self::get_presets() as $slug => $preset ) {
$labels[ $slug ] = $preset['label'] ?? $slug;
}
return $labels;
}
}

View file

@ -0,0 +1,213 @@
<?php

declare(strict_types=1);

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* WPCC 工具类
*
* 提供各种实用工具方法使用现代PHP语法和最佳实践
*/
final class WPCC_Utils {
/**
* 验证语言代码格式
*/
public static function is_valid_language_code( string $lang_code ): bool {
$valid_codes = [
'zh-cn', 'zh-tw', 'zh-hk', 'zh-sg',
'zh-hans', 'zh-hant', 'zh-jp'
];
return in_array( $lang_code, $valid_codes, true );
}
/**
* 安全地获取配置值
*/
public static function get_safe_option( string $key, $default = null ) {
global $wpcc_options;
return $wpcc_options[ $key ] ?? $default;
}
/**
* 检查字符串是否包含中文字符
*/
public static function contains_chinese_chars( string $text ): bool {
return preg_match( '/[\x{4e00}-\x{9fff}]+/u', $text ) === 1;
}
/**
* 获取文本的语言类型(简体/繁体)
*/
public static function detect_text_type( string $text ): string {
if ( ! self::contains_chinese_chars( $text ) ) {
return 'unknown';
}
// 简单的启发式检测
$simplified_chars = ['的', '了', '会', '这', '那', '个', '说'];
$traditional_chars = ['的', '了', '會', '這', '那', '個', '說'];
$simplified_count = 0;
$traditional_count = 0;
foreach ( $simplified_chars as $char ) {
if ( strpos( $text, $char ) !== false ) {
$simplified_count++;
}
}
foreach ( $traditional_chars as $char ) {
if ( strpos( $text, $char ) !== false ) {
$traditional_count++;
}
}
if ( $simplified_count > $traditional_count ) {
return 'simplified';
} elseif ( $traditional_count > $simplified_count ) {
return 'traditional';
}
return 'mixed';
}
/**
* 格式化内存使用量
*/
public static function format_memory_usage( int $bytes ): string {
$units = ['B', 'KB', 'MB', 'GB'];
$factor = floor( ( strlen( (string) $bytes ) - 1 ) / 3 );
return sprintf( "%.1f%s", $bytes / pow( 1024, $factor ), $units[ $factor ] ?? 'TB' );
}
/**
* 生成缓存键
*/
public static function generate_cache_key( string ...$parts ): string {
$key = implode( '|', array_filter( $parts ) );
return 'wpcc_' . md5( $key );
}
/**
* 安全的字符串截取(支持多字节字符)
*/
public static function safe_substr( string $str, int $start, ?int $length = null ): string {
return mb_substr( $str, $start, $length, 'UTF-8' );
}
/**
* 清理和验证用户输入
*/
public static function sanitize_language_input( $input ): array {
if ( ! is_array( $input ) ) {
return [];
}
return array_filter(
array_map( 'sanitize_text_field', $input ),
[ self::class, 'is_valid_language_code' ]
);
}
/**
* 获取当前请求的语言偏好
*/
public static function get_request_language_preference(): ?string {
// 从 URL 参数获取
$url_lang = $_GET['variant'] ?? null;
if ( $url_lang && self::is_valid_language_code( $url_lang ) ) {
return $url_lang;
}
// 从 Cookie 获取
$cookie_lang = $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ?? null;
if ( $cookie_lang && self::is_valid_language_code( $cookie_lang ) ) {
return $cookie_lang;
}
// 从浏览器 Accept-Language 头获取
$accept_lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
if ( $accept_lang ) {
return self::parse_accept_language( $accept_lang );
}
return null;
}
/**
* 解析 Accept-Language 头
*/
private static function parse_accept_language( string $accept_language ): ?string {
$supported_langs = [
'zh-cn' => ['zh-cn', 'zh-hans', 'zh'],
'zh-tw' => ['zh-tw', 'zh-hant'],
'zh-hk' => ['zh-hk'],
'zh-sg' => ['zh-sg']
];
// 解析语言偏好
preg_match_all( '/([a-z]{2,3}(?:-[a-z]{2,4})?)\s*(?:;\s*q\s*=\s*([\d.]+))?/i',
$accept_language, $matches, PREG_SET_ORDER );
$preferences = [];
foreach ( $matches as $match ) {
$lang = strtolower( $match[1] );
$quality = isset( $match[2] ) ? (float) $match[2] : 1.0;
$preferences[ $lang ] = $quality;
}
// 按质量值排序
arsort( $preferences );
// 匹配支持的语言
foreach ( $preferences as $lang => $quality ) {
foreach ( $supported_langs as $supported => $aliases ) {
if ( in_array( $lang, $aliases, true ) ) {
return $supported;
}
}
}
return null;
}
/**
* 记录性能指标
*/
public static function log_performance_metric( string $operation, float $duration, array $context = [] ): void {
if ( ! WP_DEBUG || ! defined( 'WPCC_LOG_PERFORMANCE' ) || ! WPCC_LOG_PERFORMANCE ) {
return;
}
$log_entry = sprintf(
'WPCC Performance: %s took %.4fs %s',
$operation,
$duration,
$context ? '(' . json_encode( $context ) . ')' : ''
);
error_log( $log_entry );
}
/**
* 执行带性能监控的操作
*/
public static function with_performance_monitoring( string $operation, callable $callback, array $context = [] ) {
$start_time = microtime( true );
try {
$result = $callback();
return $result;
} finally {
$duration = microtime( true ) - $start_time;
self::log_performance_metric( $operation, $duration, $context );
}
}
}

View file

@ -10,7 +10,7 @@ class WPCC_Cache_Addon extends WPCC_Abstract_Module {
public function init() {
$this->name = 'Cache Addon';
$this->version = '2.0.0';
$this->version = '1.5';
$this->description = '缓存插件兼容性模块,支持多种缓存插件';
$this->dependencies = array();

View file

@ -13,7 +13,7 @@ class WPCC_Modern_Cache extends WPCC_Abstract_Module {
public function init() {
$this->name = 'Modern Cache';
$this->version = '1.0.0';
$this->version = '1.5';
$this->description = '现代缓存模块支持对象缓存、Redis、Memcached';
$this->dependencies = array();

View file

@ -10,14 +10,15 @@ class WPCC_Network extends WPCC_Abstract_Module {
public function init() {
$this->name = 'Network Multisite';
$this->version = '1.0.0';
$this->version = '1.5';
$this->description = '多站点网络支持模块';
$this->dependencies = array(
array( 'type' => 'function', 'name' => 'is_multisite' )
);
if ( $this->is_enabled() && is_multisite() ) {
add_action( 'network_admin_menu', array( $this, 'add_network_admin_menu' ) );
// 移除重复的网络管理菜单注册使用主要的wp-chinese-converter菜单
// add_action( 'network_admin_menu', array( $this, 'add_network_admin_menu' ) );
add_action( 'wpmu_new_blog', array( $this, 'setup_new_site' ), 10, 6 );
add_action( 'wp_initialize_site', array( $this, 'initialize_site_settings' ) );
}
@ -138,7 +139,7 @@ class WPCC_Network extends WPCC_Abstract_Module {
$this->setup_new_site( $new_site->blog_id, 0, '', '', 0, array() );
}
private function display_sites_status() {
public function display_sites_status() {
$sites = get_sites( array( 'number' => 50 ) );
echo '<table class="wp-list-table widefat fixed striped">';
@ -166,19 +167,10 @@ class WPCC_Network extends WPCC_Abstract_Module {
echo '</tbody></table>';
}
public function sync_settings_to_all_sites( $settings ) {
$sites = get_sites();
foreach ( $sites as $site ) {
switch_to_blog( $site->blog_id );
$current_options = get_option( 'wpcc_options', array() );
$updated_options = array_merge( $current_options, $settings );
update_option( 'wpcc_options', $updated_options );
restore_current_blog();
}
}
// 同步功能已移至网络设置统一管理
// public function sync_settings_to_all_sites( $settings ) {
// // 此功能已由网络管理模块统一处理
// }
public function get_network_statistics() {
$sites = get_sites();

View file

@ -5,7 +5,7 @@ if ( ! defined( 'ABSPATH' ) ) {
}

require_once dirname( dirname( __FILE__ ) ) . '/core/abstract-module.php';
require_once dirname( dirname( __FILE__ ) ) . '/core/class-converter-factory.php';
require_once dirname( dirname( __FILE__ ) ) . '/core/class-wpcc-converter-factory.php';

class WPCC_Rest_Api extends WPCC_Abstract_Module {
@ -13,7 +13,7 @@ class WPCC_Rest_Api extends WPCC_Abstract_Module {
public function init() {
$this->name = 'REST API';
$this->version = '1.0.0';
$this->version = '1.5';
$this->description = 'REST API 接口模块,提供转换服务的 API 端点';
$this->dependencies = array(
array( 'type' => 'class', 'name' => 'WP_REST_Server' ),
@ -30,7 +30,13 @@ class WPCC_Rest_Api extends WPCC_Abstract_Module {
return class_exists( 'WP_REST_Server' ) && function_exists( 'rest_ensure_response' );
}


/**
* 确保API密钥存在
*/
public function ensure_api_key_exists() {
// 这个方法可以用来初始化API相关的设置
// 目前为空实现,保持兼容性
}
public function register_routes() {
register_rest_route( $this->namespace, '/convert', array(

View file

@ -13,19 +13,20 @@ class WPCC_SEO_Enhancement extends WPCC_Abstract_Module {
public function init() {
$this->name = 'SEO Enhancement';
$this->version = '1.0.0';
$this->version = '1.5';
$this->description = 'SEO插件增强模块支持Yoast SEO、RankMath、AIOSEO等主流SEO插件';
$this->dependencies = array();
$this->settings = array(
'enable_yoast_integration' => true,
'enable_rankmath_integration' => true,
'enable_aioseo_integration' => true,
'enable_hreflang_tags' => true,
'enable_schema_conversion' => true,
'enable_meta_conversion' => true,
'hreflang_x_default' => 'zh-cn'
);
$this->settings = array(
'enable_yoast_integration' => true,
'enable_rankmath_integration' => true,
'enable_aioseo_integration' => true,
'enable_hreflang_tags' => true,
'enable_hreflang_x_default' => true,
'enable_schema_conversion' => true,
'enable_meta_conversion' => true,
'hreflang_x_default' => 'zh-cn'
);
if ( $this->is_enabled() ) {
add_action( 'init', array( $this, 'detect_seo_plugins' ), 5 );
@ -97,10 +98,11 @@ class WPCC_SEO_Enhancement extends WPCC_Abstract_Module {
global $wpcc_options;
if ( ! empty( $wpcc_options ) ) {
$this->settings['enable_hreflang_tags'] = isset( $wpcc_options['wpcc_enable_hreflang_tags'] ) ? $wpcc_options['wpcc_enable_hreflang_tags'] : 1;
$this->settings['enable_schema_conversion'] = isset( $wpcc_options['wpcc_enable_schema_conversion'] ) ? $wpcc_options['wpcc_enable_schema_conversion'] : 1;
$this->settings['enable_meta_conversion'] = isset( $wpcc_options['wpcc_enable_meta_conversion'] ) ? $wpcc_options['wpcc_enable_meta_conversion'] : 1;
$this->settings['hreflang_x_default'] = isset( $wpcc_options['wpcc_hreflang_x_default'] ) ? $wpcc_options['wpcc_hreflang_x_default'] : 'zh-cn';
$this->settings['enable_hreflang_tags'] = isset( $wpcc_options['wpcc_enable_hreflang_tags'] ) ? $wpcc_options['wpcc_enable_hreflang_tags'] : 1;
$this->settings['enable_hreflang_x_default'] = isset( $wpcc_options['wpcc_enable_hreflang_x_default'] ) ? $wpcc_options['wpcc_enable_hreflang_x_default'] : 1;
$this->settings['enable_schema_conversion'] = isset( $wpcc_options['wpcc_enable_schema_conversion'] ) ? $wpcc_options['wpcc_enable_schema_conversion'] : 1;
$this->settings['enable_meta_conversion'] = isset( $wpcc_options['wpcc_enable_meta_conversion'] ) ? $wpcc_options['wpcc_enable_meta_conversion'] : 1;
$this->settings['hreflang_x_default'] = isset( $wpcc_options['wpcc_hreflang_x_default'] ) ? $wpcc_options['wpcc_hreflang_x_default'] : 'zh-cn';
}
}
@ -215,6 +217,16 @@ class WPCC_SEO_Enhancement extends WPCC_Abstract_Module {
}
public function output_hreflang_tags() {
// 防抖:确保仅输出一次,避免重复标签
static $wpcc_hreflang_printed = false;
if ( $wpcc_hreflang_printed ) {
return;
}
$wpcc_hreflang_printed = true;
// 确保在输出前同步一次全局设置(避免执行顺序导致读取到默认值)
$this->sync_global_settings();
if ( ! $this->settings['enable_hreflang_tags'] ) {
return;
}
@ -225,7 +237,9 @@ class WPCC_SEO_Enhancement extends WPCC_Abstract_Module {
return;
}
if ( count( $wpcc_options['wpcc_used_langs'] ) < 2 ) {
// 需要至少两个语言版本才有意义
$languages = array_values( array_unique( array_filter( $wpcc_options['wpcc_used_langs'] ) ) );
if ( count( $languages ) < 2 ) {
return;
}
@ -234,43 +248,48 @@ class WPCC_SEO_Enhancement extends WPCC_Abstract_Module {
return;
}
$languages = $wpcc_options['wpcc_used_langs'];
$printed = [];
foreach ( $languages as $lang ) {
if ( empty( $lang ) ) {
continue;
}
$lang_url = $this->get_language_url( $current_url, $lang );
$hreflang = $this->get_hreflang_code( $lang );
if ( ! empty( $lang_url ) && ! empty( $hreflang ) ) {
if ( $lang_url && $hreflang && empty( $printed[ $hreflang ] ) ) {
$printed[ $hreflang ] = $lang_url;
echo '<link rel="alternate" hreflang="' . esc_attr( $hreflang ) . '" href="' . esc_url( $lang_url ) . '" />' . "\n";
}
}
$default_lang = $this->settings['hreflang_x_default'];
if ( ! empty( $default_lang ) ) {
$default_url = $this->get_language_url( $current_url, $default_lang );
if ( ! empty( $default_url ) ) {
echo '<link rel="alternate" hreflang="x-default" href="' . esc_url( $default_url ) . '" />' . "\n";
}
}
$default_lang = $this->settings['hreflang_x_default'];
if ( $this->settings['enable_hreflang_x_default'] && ! empty( $default_lang ) ) {
$default_url = $this->get_language_url( $current_url, $default_lang );
if ( ! empty( $default_url ) ) {
echo '<link rel="alternate" hreflang="x-default" href="' . esc_url( $default_url ) . '" />' . "\n";
}
}
}
private function get_current_url() {
if ( is_admin() ) {
return home_url();
return home_url( '/' );
}
// 优先使用规范链接避免把内部查询变量year/monthnum/day/name 等)拼进 URL
if ( is_singular() ) {
$object_id = get_queried_object_id();
if ( $object_id ) {
$url = get_permalink( $object_id );
return trailingslashit( remove_query_arg( 'variant', $url ) );
}
}
// 搜索页保留搜索参数,其它归一化为当前路径,无附加查询串
if ( is_search() ) {
$url = get_search_link();
return trailingslashit( remove_query_arg( 'variant', $url ) );
}
global $wp;
$current_url = home_url( $wp->request );
if ( ! empty( $wp->query_string ) ) {
$current_url .= '?' . $wp->query_string;
}
return trailingslashit( $current_url );
$url = home_url( $wp->request );
return trailingslashit( remove_query_arg( 'variant', $url ) );
}
private function get_language_url( $url, $lang ) {
@ -278,22 +297,41 @@ class WPCC_SEO_Enhancement extends WPCC_Abstract_Module {
$clean_url = remove_query_arg( 'variant', $url );
// 允许的语言前缀集合(用于剥离已有语言段)
$valid_codes = method_exists( 'WPCC_Language_Config', 'get_valid_language_codes' )
? WPCC_Language_Config::get_valid_language_codes()
: array( 'zh-cn','zh-tw','zh-hk','zh-hans','zh-hant','zh-sg','zh-jp','zh','zh-reset' );
$valid_regex = implode( '|', array_map( 'preg_quote', $valid_codes ) );
// 仅 query 形式(模式 0
if ( empty( $wpcc_options['wpcc_use_permalink'] ) ) {
return add_query_arg( 'variant', $lang, $clean_url );
}
$permalink_type = intval( $wpcc_options['wpcc_use_permalink'] );
$parsed_url = parse_url( $clean_url );
$scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] : ( is_ssl() ? 'https' : 'http' );
$host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
$base = $scheme . '://' . $host;
$path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '/';
$query = isset( $parsed_url['query'] ) && $parsed_url['query'] !== '' ? '?' . $parsed_url['query'] : '';
// 统一剥离开头的语言前缀
$path = preg_replace( '#^/(?:' . $valid_regex . ')(/|$)#i', '/$1', $path );
if ( $path === '' ) { $path = '/'; }
switch ( $permalink_type ) {
case 1:
$clean_url = rtrim( $clean_url, '/' );
return $clean_url . '/' . $lang . '/';
// 语言段后缀:去掉末尾已存在的语言段再追加
$path = rtrim( $path, '/' );
$path = preg_replace( '#/(?:' . $valid_regex . ')/?$#i', '', $path );
return $base . rtrim( $path, '/' ) . '/' . $lang . '/' . $query;
case 2:
$parsed_url = parse_url( $clean_url );
$base_url = $parsed_url['scheme'] . '://' . $parsed_url['host'];
$path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '/';
$query = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
return $base_url . '/' . $lang . $path . $query;
// 语言段前缀:在剥离后的路径前面加上新语言前缀
$path = '/' . ltrim( $path, '/' );
// 确保不会出现双斜杠
$final = rtrim( $base, '/' ) . '/' . $lang . rtrim( $path, '/' ) . '/';
return $query ? ( $final . $query ) : $final;
default:
return add_query_arg( 'variant', $lang, $clean_url );
}
@ -307,7 +345,7 @@ class WPCC_SEO_Enhancement extends WPCC_Abstract_Module {
'zh-sg' => 'zh-SG',
'zh-hans' => 'zh-Hans',
'zh-hant' => 'zh-Hant',
'zh-jp' => 'zh'
'zh-jp' => 'zh-JP'
);
return isset( $hreflang_map[ $lang ] ) ? $hreflang_map[ $lang ] : $lang;

View file

@ -27,56 +27,197 @@ 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','zh-hk');
$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]';
// 分类网站地图
$new_rules['^sitemap-taxonomy-([a-zA-Z0-9_]+)-(' . $pattern . ')-(\d+)\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[2]&wpcc_sitemap_taxonomy=$matches[1]&wpcc_sitemap_page=$matches[3]';
}
return array_merge( $new_rules, $rules );
}

function custom_sitemap_query_vars( $vars ) {
$vars[] = 'wpcc_sitemap_lang';
$vars[] = 'wpcc_sitemap_page';
$vars[] = 'wpcc_sitemap_taxonomy';
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 ) ) {
// 获取原始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 = '/';
}
// 重建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;
}
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','zh-hk');
$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 );
if ( ! empty( $content ) ) {
echo $content;
} else {
echo '<?xml version="1.0" encoding="UTF-8"?><error>Sitemap not found</error>';
}
echo $content !== '' ? $content : '<?xml version="1.0" encoding="UTF-8"?><error>Sitemap not found</error>';
exit;
}

if ( preg_match( '/\/sitemap-(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)-(\d+)\.xml\/?$/', $uri, $matches ) ) {
$lang = $matches[1];
// 处理文章网站地图
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 ( $pat !== '' && preg_match( '#/sitemap-taxonomy-([a-zA-Z0-9_]+)-(' . $pat . ')-(\d+)\.xml/?$#i', $uri, $matches ) ) {
$taxonomy = sanitize_key($matches[1]);
$lang = strtolower($matches[2]);
$page = (int) $matches[3];
if ( ! in_array( $lang, $enabled, true ) ) {
status_header( 404 );
exit;
}
// 验证分类法是否存在且公开
if ( ! taxonomy_exists( $taxonomy ) || ! is_taxonomy_viewable( $taxonomy ) ) {
status_header( 404 );
exit;
}
header( 'Content-Type: application/xml; charset=utf-8' );
header( 'HTTP/1.1 200 OK' );
echo generate_taxonomy_sitemap_content( $taxonomy, $lang, $page );
exit;
}

if ( preg_match( '#/sitemap-style\.xsl/?$#i', $uri ) ) {
header( 'Content-Type: text/xsl; charset=utf-8' );
header( 'HTTP/1.1 200 OK' );

@ -88,6 +229,12 @@ 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'] ) ) {
@ -110,6 +257,7 @@ function generate_sitemap_index( string $lang ) {
$sitemap .= '<?xml-stylesheet type="text/xsl" href="' . site_url( '/sitemap-style.xsl' ) . '"?>';
$sitemap .= '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
// 添加文章网站地图
for ( $i = 1; $i <= $total_sitemaps; $i ++ ) {
$sitemap .= '<sitemap>';
$sitemap .= '<loc>' . site_url( "/sitemap-{$lang}-{$i}.xml" ) . '</loc>';
@ -117,6 +265,35 @@ function generate_sitemap_index( string $lang ) {
$sitemap .= '</sitemap>';
}
// 添加分类网站地图
$taxonomies = get_taxonomies( array( 'public' => true ), 'objects' );
foreach ( $taxonomies as $taxonomy ) {
// 跳过不需要的分类法
if ( in_array( $taxonomy->name, array( 'post_format', 'nav_menu' ), true ) ) {
continue;
}
// 获取该分类法的条目数量
$term_count = wp_count_terms( array(
'taxonomy' => $taxonomy->name,
'hide_empty' => true,
) );
if ( is_wp_error( $term_count ) || $term_count === 0 ) {
continue;
}
// 计算需要的分页数
$taxonomy_sitemaps = ceil( $term_count / $max_urls_per_sitemap );
for ( $i = 1; $i <= $taxonomy_sitemaps; $i ++ ) {
$sitemap .= '<sitemap>';
$sitemap .= '<loc>' . site_url( "/sitemap-taxonomy-{$taxonomy->name}-{$lang}-{$i}.xml" ) . '</loc>';
$sitemap .= '<lastmod>' . date( 'Y-m-d' ) . '</lastmod>';
$sitemap .= '</sitemap>';
}
}
$sitemap .= '</sitemapindex>';
return $sitemap;
@ -127,6 +304,12 @@ 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 '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />';
}

$max_urls_per_sitemap = 1000;

if ( empty( $wpcc_options['wpcco_sitemap_post_type'] ) ) {
@ -155,7 +338,7 @@ function generate_paged_sitemap_content( string $lang, int $page ) {
$postdate = explode( " ", $post->post_modified );
$sitemap .= '<url>';
$sitemap .= '<loc>' . wpcc_link_conversion( get_permalink( $post->ID ), $lang ) . '</loc>';
$sitemap .= '<loc>' . wpcc_sitemap_link_conversion( get_permalink( $post->ID ), $lang ) . '</loc>';
$sitemap .= '<lastmod>' . $postdate[0] . '</lastmod>';
$sitemap .= '<changefreq>weekly</changefreq>';
$sitemap .= '<priority>0.6</priority>';
@ -312,4 +495,93 @@ function generate_sitemap_styles() {
XSL;
}

/**
* 生成分类网站地图内容
*
* @param string $taxonomy 分类法名称
* @param string $lang 语言代码
* @param int $page 页码
* @return string XML内容
*/
function generate_taxonomy_sitemap_content( string $taxonomy, 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 '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />';
}

// 验证分类法
if ( ! taxonomy_exists( $taxonomy ) || ! is_taxonomy_viewable( $taxonomy ) ) {
return '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />';
}

$max_urls_per_sitemap = 1000;
$offset = ( $page - 1 ) * $max_urls_per_sitemap;
// 获取分类条目
$terms = get_terms( array(
'taxonomy' => $taxonomy,
'hide_empty' => true,
'number' => $max_urls_per_sitemap,
'offset' => $offset,
'orderby' => 'count',
'order' => 'DESC',
) );
if ( is_wp_error( $terms ) || empty( $terms ) ) {
return '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />';
}
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>';
$sitemap .= '<?xml-stylesheet type="text/xsl" href="' . site_url( '/sitemap-style.xsl' ) . '"?>';
$sitemap .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
foreach ( $terms as $term ) {
// 检查是否应该索引此条目
if ( ! wpcc_should_index_term( $term ) ) {
continue;
}
$term_link = get_term_link( $term, $taxonomy );
if ( is_wp_error( $term_link ) ) {
continue;
}
// 转换链接到指定语言
$converted_link = wpcc_sitemap_link_conversion( $term_link, $lang );
$sitemap .= '<url>';
$sitemap .= '<loc>' . esc_url( $converted_link ) . '</loc>';
$sitemap .= '<lastmod>' . date( 'Y-m-d' ) . '</lastmod>';
$sitemap .= '<changefreq>weekly</changefreq>';
$sitemap .= '<priority>0.5</priority>';
$sitemap .= '</url>';
}
$sitemap .= '</urlset>';
return $sitemap;
}

/**
* 检查是否应该索引分类条目
*
* @param WP_Term $term 分类条目
* @return bool
*/
function wpcc_should_index_term( $term ) {
// 可以在这里添加更多的检查逻辑
// 例如检查条目的元数据中是否有 noindex 标记
// 基本检查:确保条目有内容
if ( $term->count === 0 ) {
return false;
}
// 可以添加自定义过滤器让其他插件或主题控制
return apply_filters( 'wpcc_should_index_term', true, $term );
}

?>

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,101 @@
<?php

/**
* WP Chinese Converter - Admin Functions
* WP Chinese Converter Admin Panel Functions
*
* 包含所有后台管理相关功能
* 管理后台相关功能
*
* @package WPChineseConverter
* @version 1.2.0
* @version 1.4
*/

// 添加管理员功能:手动刷新重写规则
add_action( 'wp_ajax_wpcc_flush_rewrite_rules', 'wpcc_ajax_flush_rewrite_rules' );
function wpcc_ajax_flush_rewrite_rules() {
// 权限检查
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( '无权限操作' );
}
// 验证nonce
if ( ! wp_verify_nonce( $_POST['nonce'], 'wpcc_admin_nonce' ) ) {
wp_die( '安全验证失败' );
}
// 刷新重写规则
flush_rewrite_rules( false );
update_option( 'wpcc_rewrite_autoflush_ts', time() );
wp_send_json_success( '重写规则已刷新' );
}

// 添加管理面板提示
add_action( 'admin_notices', 'wpcc_rewrite_rules_notice' );
function wpcc_rewrite_rules_notice() {
// 仅在WPCC设置页面显示
$screen = get_current_screen();
if ( ! $screen || strpos( $screen->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 '<div class="notice notice-warning is-dismissible">';
echo '<p><strong>WP Chinese Converter:</strong> 检测到语言重写规则可能未正确配置这可能导致语言主页404错误。 ';
echo '<a href="#" id="wpcc-flush-rules" class="button button-secondary">刷新重写规则</a></p>';
echo '</div>';
// 添加JavaScript
echo '<script type="text/javascript">
jQuery(document).ready(function($) {
$("#wpcc-flush-rules").on("click", function(e) {
e.preventDefault();
var button = $(this);
button.prop("disabled", true).text("刷新中...");
$.post(ajaxurl, {
action: "wpcc_flush_rewrite_rules",
nonce: "' . wp_create_nonce( 'wpcc_admin_nonce' ) . '"
}, function(response) {
if (response.success) {
button.closest(".notice").fadeOut();
$(".wrap h1").after("<div class=\"notice notice-success is-dismissible\"><p>重写规则已成功刷新!请测试语言主页是否能正常访问。</p></div>");
} else {
alert("刷新失败:" + response.data);
button.prop("disabled", false).text("刷新重写规则");
}
}).fail(function() {
alert("请求失败,请稍后再试");
button.prop("disabled", false).text("刷新重写规则");
});
});
});
</script>';
}
}

// 防止直接访问
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 );
}
@ -80,17 +167,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'] ) ) {
?>
<script type="text/javascript">
//<![CDATA[
jQuery(document).ready(function($) {
if (typeof QTags !== 'undefined' && QTags.addButton) {
QTags.addButton('eg_wpcc_nc', 'wpcc_NC', '<!--wpcc_NC_START-->', '<!--wpcc_NC_END-->', null, 'WP Chinese Converter DO-NOT Convert Tag', 120);
QTags.addButton('eg_wpcc_nc', 'wpcc_NC', '[wpcc_nc]', '[/wpcc_nc]', null, 'WP Chinese Converter: Insert no-convert markers', 120);
} else {
setTimeout(function() {
if (typeof QTags !== 'undefined' && QTags.addButton) {
QTags.addButton('eg_wpcc_nc', 'wpcc_NC', '<!--wpcc_NC_START-->', '<!--wpcc_NC_END-->', null, 'WP Chinese Converter DO-NOT Convert Tag', 120);
QTags.addButton('eg_wpcc_nc', 'wpcc_NC', '[wpcc_nc]', '[/wpcc_nc]', null, 'WP Chinese Converter: Insert no-convert markers', 120);
}
}, 100);
}
@ -111,34 +198,90 @@ function wpcc_init_post_conversion() {
$wpcc_options = get_wpcc_option( 'wpcc_options' );
}

error_log( 'WPCC DEBUG: wpcc_init_post_conversion called' );
error_log( 'WPCC DEBUG: wpcc_options = ' . print_r( $wpcc_options, true ) );

if ( ! empty( $wpcc_options['wpcc_enable_post_conversion'] ) ) {
$target_lang = $wpcc_options['wpcc_post_conversion_target'] ?? 'zh-cn';

error_log( 'WPCC DEBUG: Adding filters for post conversion, target_lang = ' . $target_lang );

/**
* 在保存时安全地转换块内容:
* - 跳过 WPCC 自有区块wpcc/*),避免破坏区块占位和结构
* - 仅转换非 WPCC 区块的纯文本片段innerContent 字符串等)
*/
add_filter( 'content_save_pre', function ( $content ) use ( $target_lang ) {
error_log( 'WPCC DEBUG: content_save_pre filter triggered, content length = ' . strlen( $content ) );
$converted = zhconversion( $content, $target_lang );
error_log( 'WPCC DEBUG: content converted from ' . substr( $content, 0, 50 ) . ' to ' . substr( $converted, 0, 50 ) );
return $converted;
if ( empty( $content ) ) {
return $content;
}
try {
return wpcc_convert_post_content_safely( $content, $target_lang );
} catch ( Exception $e ) {
error_log( 'WPCC Content Conversion Error: ' . $e->getMessage() );
return $content;
}
} );

add_filter( 'title_save_pre', function ( $title ) use ( $target_lang ) {
error_log( 'WPCC DEBUG: title_save_pre filter triggered, title = ' . $title );
$converted = zhconversion( $title, $target_lang );
error_log( 'WPCC DEBUG: title converted from ' . $title . ' to ' . $converted );
return $converted;
if ( empty( $title ) ) {
return $title;
}
try {
return zhconversion( $title, $target_lang );
} catch ( Exception $e ) {
error_log( 'WPCC Title Conversion Error: ' . $e->getMessage() );
return $title;
}
} );

add_action( 'add_meta_boxes', 'wpcc_add_conversion_meta_box' );
} else {
error_log( 'WPCC DEBUG: Post conversion is disabled' );
}
}

/**
* 使用区块解析安全转换文章内容,仅转换非 WPCC 区块的纯文本
*/
function wpcc_convert_post_content_safely( $content, $target_lang ) {
if ( ! function_exists( 'parse_blocks' ) || ! function_exists( 'serialize_blocks' ) ) {
// 回退:无法解析区块时,使用整体转换(可能导致占位被转换)
return zhconversion( $content, $target_lang );
}

$blocks = parse_blocks( $content );
$converted = wpcc_convert_blocks_array_safely( $blocks, $target_lang );
return serialize_blocks( $converted );
}

function wpcc_convert_blocks_array_safely( $blocks, $target_lang ) {
foreach ( $blocks as &$block ) {
$name = isset( $block['blockName'] ) ? (string) $block['blockName'] : '';

// 跳过 WPCC 自有区块,避免转换其内部占位与结构
if ( substr( $name, 0, 5 ) === 'wpcc/' ) {
// 递归处理其子块(如不希望转换子块,也可以直接 continue
if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
$block['innerBlocks'] = wpcc_convert_blocks_array_safely( $block['innerBlocks'], $target_lang );
}
continue;
}

// 转换非 WPCC 区块的纯文本内容
if ( isset( $block['innerContent'] ) && is_array( $block['innerContent'] ) ) {
foreach ( $block['innerContent'] as $i => $piece ) {
if ( is_string( $piece ) && $piece !== '' ) {
$block['innerContent'][ $i ] = zhconversion( $piece, $target_lang );
}
}
}

if ( isset( $block['innerHTML'] ) && is_string( $block['innerHTML'] ) && $block['innerHTML'] !== '' ) {
$block['innerHTML'] = zhconversion( $block['innerHTML'], $target_lang );
}

// 递归处理子块
if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
$block['innerBlocks'] = wpcc_convert_blocks_array_safely( $block['innerBlocks'], $target_lang );
}
}
return $blocks;
}

/**
* 添加转换设置元框
*/
@ -155,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 );
}

/**
@ -305,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' );
}
@ -317,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' );


View file

@ -6,7 +6,7 @@
* 包含所有前台转换相关功能
*
* @package WPChineseConverter
* @version 1.2.0
* @version 1.4
*/

// 防止直接访问
@ -17,22 +17,25 @@ if ( ! defined( 'ABSPATH' ) ) {
// 全局变量初始化
$wpcc_langs = array();

// 短代码:提供 [wpcc_nc]...[/wpcc_nc] 与 [wpcs_nc]...[/wpcs_nc],在编辑器无法保留注释时作为稳健的占位
add_shortcode( 'wpcc_nc', function( $atts, $content = '' ) {
return '<span data-wpcc-no-conversion="true">' . $content . '</span>';
} );
add_shortcode( 'wpcs_nc', function( $atts, $content = '' ) {
return '<span data-wpcc-no-conversion="true">' . $content . '</span>';
} );


/**
* 初始化语言配置
* 使用中心化的语言配置管理
*/
function wpcc_init_languages() {
function wpcc_init_languages(): void {
global $wpcc_langs;

if ( empty( $wpcc_langs ) ) {
$wpcc_langs = array(
'zh-cn' => array( 'zhconversion_cn', 'cntip', __( '简体中文', 'wp-chinese-converter' ), 'zh-CN' ),
'zh-tw' => array( 'zhconversion_tw', 'twtip', __( '台灣正體', 'wp-chinese-converter' ), 'zh-TW' ),
'zh-hk' => array( 'zhconversion_hk', 'hktip', __( '港澳繁體', 'wp-chinese-converter' ), 'zh-HK' ),
'zh-hans' => array( 'zhconversion_hans', 'hanstip', __( '简体中文', 'wp-chinese-converter' ), 'zh-Hans' ),
'zh-hant' => array( 'zhconversion_hant', 'hanttip', __( '繁体中文', 'wp-chinese-converter' ), 'zh-Hant' ),
'zh-sg' => array( 'zhconversion_sg', 'sgtip', __( '马新简体', 'wp-chinese-converter' ), 'zh-SG' ),
'zh-jp' => array( 'zhconversion_jp', 'jptip', __( '日式汉字', 'wp-chinese-converter' ), 'zh-JP' ),
);
// 使用中心化的语言配置
$wpcc_langs = WPCC_Language_Config::get_all_languages();
}
}

@ -40,6 +43,10 @@ function wpcc_init_languages() {
* 插件核心初始化
*/
function wpcc_init() {
// 当新版核心 WPCC_Main 存在时,完全交由新内核处理
if ( class_exists( 'WPCC_Main' ) ) {
return;
}
global $wpcc_options, $wp_rewrite;

if ( isset( $wpcc_options['wpcc_use_permalink'] ) && $wpcc_options['wpcc_use_permalink'] != 0 && empty( $wp_rewrite->permalink_structure ) ) {
@ -52,17 +59,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时的问题
@ -126,6 +139,12 @@ function wpcc_parse_query( $query ) {

global $wpcc_target_lang, $wpcc_redirect_to, $wpcc_noconversion_url, $wpcc_options, $wpcc_direct_conversion_flag;

// 标记 AJAX/REST/wc-ajax 请求,跳过转换避免干扰 JSON 响应
$is_ajax = function_exists('wp_doing_ajax') ? wp_doing_ajax() : ( defined('DOING_AJAX') && DOING_AJAX );
$is_rest = defined('REST_REQUEST') && REST_REQUEST;
$is_wc_ajax = isset($_REQUEST['wc-ajax']) && is_string($_REQUEST['wc-ajax']) && $_REQUEST['wc-ajax'] !== '';
$wpcc_direct_conversion_flag = (bool) ( $is_ajax || $is_rest || $is_wc_ajax );

if ( ! is_404() ) {
$wpcc_noconversion_url = wpcc_get_noconversion_url();
} else {
@ -134,10 +153,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;
@ -147,9 +166,23 @@ function wpcc_parse_query( $query ) {
if ( ! $wpcc_target_lang ) {
if ( $request_lang == 'zh' && ! is_admin() ) {
if ( $wpcc_options['wpcc_use_cookie_variant'] != 0 ) {
setcookie( 'wpcc_variant_' . COOKIEHASH, 'zh', time() + 30000000, COOKIEPATH, COOKIE_DOMAIN );
setcookie( 'wpcc_variant_' . COOKIEHASH, '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',
] );
}
header( 'Location: ' . $wpcc_noconversion_url );
die();
@ -167,7 +200,14 @@ function wpcc_parse_query( $query ) {

// 设置Cookie
if ( $wpcc_target_lang && $wpcc_options['wpcc_use_cookie_variant'] != 0 && $cookie_lang != $wpcc_target_lang ) {
setcookie( 'wpcc_variant_' . COOKIEHASH, $wpcc_target_lang, time() + 30000000, COOKIEPATH, COOKIE_DOMAIN );
setcookie( 'wpcc_variant_' . COOKIEHASH, $wpcc_target_lang, [
'expires' => time() + 30000000,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
] );
}
}

@ -175,12 +215,24 @@ function wpcc_parse_query( $query ) {
* 模板重定向处理 - 核心转换逻辑
*/
function wpcc_template_redirect() {
global $wpcc_noconversion_url, $wpcc_langs_urls, $wpcc_options, $wpcc_target_lang, $wpcc_redirect_to;
global $wpcc_noconversion_url, $wpcc_langs_urls, $wpcc_options, $wpcc_target_lang, $wpcc_redirect_to, $wpcc_direct_conversion_flag;

// 若是 AJAX/REST/wc-ajax 等非 HTML 响应,直接跳过所有转换,避免破坏 JSON/片段
if ( $wpcc_direct_conversion_flag ) {
return;
}

set_wpcc_langs_urls();

if ( ! is_404() && $wpcc_redirect_to && ! is_admin() ) {
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( $wpcc_langs_urls[ $wpcc_redirect_to ], 302 );
}

@ -205,9 +257,13 @@ 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 ) {
@ -222,11 +278,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] ?? '', '/' ) );
@ -255,32 +313,55 @@ function wpcc_link_conversion( $link, $variant = null ) {
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;
}

/**
@ -329,7 +410,7 @@ function _wpcc_permalink_preg_callback( $matches ) {
/**
* 核心转换函数
*/
function zhconversion( $str, $variant = null ) {
function zhconversion( ?string $str, ?string $variant = null ): string {
global $wpcc_options, $wpcc_langs;
wpcc_init_languages();

@ -349,13 +430,27 @@ function zhconversion( $str, $variant = null ) {
return $str;
}

try {
$converter = WPCC_Converter_Factory::get_converter();
return $converter->convert( $str, $variant );
} catch ( Exception $e ) {
error_log( 'WPCC Conversion Error: ' . $e->getMessage() );
return $str;
// 检查缓存
$cached_result = WPCC_Conversion_Cache::get_cached_conversion( $str, $variant );
if ( $cached_result !== null ) {
return $cached_result;
}

return WPCC_Exception_Handler::safe_execute(
function() use ( $str, $variant ) {
$converter = WPCC_Converter_Factory::get_converter();
$result = $converter->convert( $str, $variant );
// 将结果存入缓存
if ( $result !== $str ) { // 只缓存真正有变化的转换
WPCC_Conversion_Cache::set_cached_conversion( $str, $variant, $result );
}
return $result;
},
$str, // 降级值:返回原文本
"zhconversion_{$variant}"
);
}

/**
@ -377,15 +472,18 @@ function zhconversion2( $str, $variant = null ) {
return $str;
}

// 兜底:如未加保护标记,则在转换前为“不转换内容”区块添加保护标记
$str = wpcc_protect_no_conversion_blocks( $str );

return limit_zhconversion( $str, $wpcc_langs[ $variant ][0] );
}

/**
* 各种特定语言的转换函数
*/
function zhconversion_hant( $str ) {
function zhconversion_hant( ?string $str ): string {
if ( $str === null || $str === '' ) {
return $str;
return $str ?? '';
}
try {
$converter = WPCC_Converter_Factory::get_converter();
@ -482,11 +580,45 @@ function zhconversion_deep( $value ) {
return $value;
}

/**
* 兜底保护 - 为“不转换内容”区块添加注释标记
*/
function wpcc_protect_no_conversion_blocks( $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;
}
$wrap_whole_callback = function( $matches ) {
$id = wpcc_id();
return '<!--wpcc_NC' . $id . '_START-->' . $matches[0] . '<!--wpcc_NC' . $id . '_END-->';
};
$wrap_inner_callback = function( $matches ) {
$id = wpcc_id();
$open = $matches[1];
$inner = $matches[2];
$close = $matches[3];
return $open . '<!--wpcc_NC' . $id . '_START-->' . $inner . '<!--wpcc_NC' . $id . '_END-->' . $close;
};
// 优先:包裹 .wpcc-no-conversion-content 内部
$str = preg_replace_callback( '/(<div[^>]*class="[^"]*wpcc-no-conversion-content[^"]*"[^>]*>)([\s\S]*?)(<\/div>)/i', $wrap_inner_callback, $str );
// 兜底:包裹外层 wrapper 或 data 属性
$str = preg_replace_callback( '/<div[^>]*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;
}

/**
* 有限转换函数 - 不转换指定标签内的内容
*/

function limit_zhconversion( $str, $function ) {
if ( $m = preg_split( '/(<!--wpcc_NC([a-zA-Z0-9]*)_START-->)(.*?)(<!--wpcc_NC\2_END-->)/s', $str, - 1, PREG_SPLIT_DELIM_CAPTURE ) ) {
if ( $m = preg_split( '/(<!--wpc(?:c|s)_NC([a-zA-Z0-9]*)_START-->)(.*?)(<!--wpc(?:c|s)_NC\2_END-->)/s', $str, - 1, PREG_SPLIT_DELIM_CAPTURE ) ) {
$r = '';
$count = 0;
foreach ( $m as $v ) {
@ -534,71 +666,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" . '<div id="wpcc_widget_inner"><!--wpcc_NC_START-->' . "\n";
if ( $wpcc_translate_type == 0 ) {
$output .= ' <span id="wpcc_original_link" class="' . ( $wpcc_target_lang == false ? 'wpcc_current_lang' : 'wpcc_lang' ) . '" ><a class="wpcc_link" href="' . esc_url( $default_url ) . '" title="' . esc_html( $noconverttip ) . '" langvar="">' . esc_html( $noconverttip ) . '</a></span>' . "\n";
$html = "\n" . '<div id="wpcc_widget_inner"><!--wpcc_NC_START-->' . "\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 .= '<span id="wpcc_original_link" class="' . ( $wpcc_target_lang == false ? 'wpcc_current_lang' : 'wpcc_lang' ) . '"><a class="wpcc_link"' . $__nofollow . ' href="' . esc_url( $default_url ) . '" title="' . esc_attr( $noconverttip ) . '" langvar="">' . esc_html( $noconverttip ) . '</a></span>' . "\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 );
}
$output .= ' <span id="wpcc_' . $key . '_link" class="' . ( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ) . '" ><a class="wpcc_link" rel="nofollow" href="' . esc_url( $value ) . '" title="' . esc_html( $tip ) . '" langvar="' . $key . '">' . esc_html( $tip ) . '</a></span>' . "\n";
$safe_key = esc_attr( $key );
$html .= '<span id="wpcc_' . $safe_key . '_link" class="' . ( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ) . '"><a class="wpcc_link" rel="nofollow" href="' . esc_url( $value ) . '" title="' . esc_attr( $tip ) . '" langvar="' . $safe_key . '">' . esc_html( $tip ) . '</a></span>' . "\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( '<select id="wpcc_translate_type" value="%s" onchange="wpccRedirectToPage(this)">', $wpcc_translate_type );
$output .= sprintf( '<option id="wpcc_original_link" value="" %s>%s</option>', $checkSelected( '' ), esc_html( $noconverttip ) );
$html .= '<select id="wpcc_translate_type" value="' . esc_attr( (string) $wpcc_translate_type ) . '" onchange="wpccRedirectToPage(this)">';
$html .= '<option id="wpcc_original_link" value="" ' . $checkSelected( '' ) . '>' . esc_html( $noconverttip ) . '</option>';
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 );
}
$output .= sprintf( '<option id="wpcc_%s_link" class="%s" value="%s" %s>%s</option>', $key, ( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ), $key, $checkSelected( $key ), esc_html( $tip ) );
$safe_key = esc_attr( $key );
$html .= '<option id="wpcc_' . $safe_key . '_link" class="' . esc_attr( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ) . '" value="' . $safe_key . '" ' . $checkSelected( $key ) . '>' . esc_html( $tip ) . '</option>';
}
$output .= sprintf( '</select>' );
$html .= '</select>';
}
$output .= '<!--wpcc_NC_END--></div>' . "\n";

$html .= '<!--wpcc_NC_END--></div>' . "\n";

if ( ! $echo || $isReturn ) {
return $output;
return $html;
}
echo $output;
echo $html;
}

/**
@ -607,29 +743,29 @@ 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" . '<div id="wpcc_widget_inner"><!--wpcc_NC_START-->' . "\n";
$output .= ' <span id="wpcc_original_link" class="' . ( $wpcc_target_lang == false ? 'wpcc_current_lang' : 'wpcc_lang' ) . '" ><a class="wpcc_link" href="' . esc_url( $default_url ) . '" title="' . esc_html( '不转换' ) . '">' . esc_html( '不转换' ) . '</a></span>' . "\n";
$output .= ' <span id="wpcc_cn_link" class="' . ( $wpcc_target_lang == 'zh-cn' ? 'wpcc_current_lang' : 'wpcc_lang' ) . '" ><a class="wpcc_link" rel="nofollow" href="' . esc_url( $wpcc_langs_urls['zh-cn'] ) . '" title="' . esc_html( '大陆简体' ) . '" >' . esc_html( '大陆简体' ) . '</a></span>' . "\n";
$output .= ' <span id="wpcc_tw_link" class="' . ( $wpcc_target_lang == 'zh-tw' ? 'wpcc_current_lang' : 'wpcc_lang' ) . '"><a class="wpcc_link" rel="nofollow" href="' . esc_url( $wpcc_langs_urls['zh-tw'] ) . '" title="' . esc_html( '台湾正体' ) . '" >' . esc_html( '台湾正体' ) . '</a></span>' . "\n";
$output .= '<!--wpcc_NC_END--></div>' . "\n";
echo $output;
$html = "\n" . '<div id="wpcc_widget_inner"><!--wpcc_NC_START-->' . "\n";
$__nofollow2 = ( preg_match( '/\/zh\//i', $default_url ) || preg_match( '/(?:[?&])variant=zh(?:&|$)/i', $default_url ) ) ? ' rel="nofollow"' : '';
$html .= '<span id="wpcc_original_link" class="' . ( $wpcc_target_lang == false ? 'wpcc_current_lang' : 'wpcc_lang' ) . '"><a class="wpcc_link"' . $__nofollow2 . ' href="' . esc_url( $default_url ) . '" title="' . esc_html( '不转换' ) . '">' . esc_html( '不转换' ) . '</a></span>' . "\n";
$html .= '<span id="wpcc_cn_link" class="' . ( $wpcc_target_lang == 'zh-cn' ? 'wpcc_current_lang' : 'wpcc_lang' ) . '"><a class="wpcc_link" rel="nofollow" href="' . esc_url( $wpcc_langs_urls['zh-cn'] ) . '" title="' . esc_html( '大陆简体' ) . '">' . esc_html( '大陆简体' ) . '</a></span>' . "\n";
$html .= '<span id="wpcc_tw_link" class="' . ( $wpcc_target_lang == 'zh-tw' ? 'wpcc_current_lang' : 'wpcc_lang' ) . '"><a class="wpcc_link" rel="nofollow" href="' . esc_url( $wpcc_langs_urls['zh-tw'] ) . '" title="' . esc_html( '台湾正体' ) . '">' . esc_html( '台湾正体' ) . '</a></span>' . "\n";
$html .= '<!--wpcc_NC_END--></div>' . "\n";
echo $html;
}

/**
* 短码处理函数
*/
function wp_chinese_converter_shortcode() {
function wp_chinese_converter_shortcode(): string {
set_wpcc_langs_urls();
return wpcc_output_navi( '', true );
}
@ -638,29 +774,30 @@ function wp_chinese_converter_shortcode() {
* 小部件类
*/
class wpcc_Widget extends WP_Widget {
function __construct() {
parent::__construct( 'widget_wpcc', 'WP Chinese Converter', array(
public function __construct() {
parent::__construct( 'widget_wpcc', 'WP Chinese Converter', [
'classname' => 'widget_wpcc',
'description' => 'WP Chinese Converter Widget'
) );
] );
}

function widget( $args, $instance ) {
public function widget( $args, $instance ): void {
extract( $args );
$title = apply_filters( 'widget_title', $instance['title'] ?? '' );
echo $before_widget;
if ( $title ) {
echo $before_title . $title . $after_title;
echo $before_title . esc_html( $title ) . $after_title;
}
wpcc_output_navi( isset( $instance['args'] ) ? $instance['args'] : '' );
$widget_args = isset( $instance['args'] ) ? sanitize_text_field( $instance['args'] ) : '';
wpcc_output_navi( $widget_args );
echo $after_widget;
}

function update( $new_instance, $old_instance ) {
public function update( $new_instance, $old_instance ): array {
return $new_instance;
}

function form( $instance ) {
public function form( $instance ): void {
$title = isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : '';
$args = isset( $instance['args'] ) ? esc_attr( $instance['args'] ) : '';
?>
@ -683,7 +820,7 @@ class wpcc_Widget extends WP_Widget {
/**
* 获取浏览器首选语言
*/
function wpcc_get_prefered_language( $accept_languages, $target_langs, $flag = 0 ) {
function wpcc_get_prefered_language( string $accept_languages, array $target_langs, int $flag = 0 ): string|false {
$langs = array();
preg_match_all( '/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $accept_languages, $lang_parse );

@ -733,7 +870,7 @@ function wpcc_get_prefered_language( $accept_languages, $target_langs, $flag = 0
/**
* 判断是否为搜索引擎访问
*/
function wpcc_is_robot() {
function wpcc_is_robot(): bool {
if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
return true;
}
@ -776,43 +913,71 @@ function wpcc_apply_filter_search_rule() {
*/
function wpcc_filter_search_rule( $where ) {
global $wp_query, $wpdb;
if ( empty( $wp_query->query_vars['s'] ) ) {
if ( empty( $wp_query->query_vars['s'] ) || empty( $wp_query->query_vars['search_terms'] ) ) {
return $where;
}
if ( ! preg_match( "/^([" . chr( 228 ) . "-" . chr( 233 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}){1}/", $wp_query->query_vars['s'] ) && ! preg_match( "/([" . chr( 228 ) . "-" . chr( 233 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}){1}$/", $wp_query->query_vars['s'] ) && ! preg_match( "/([" . chr( 228 ) . "-" . chr( 233 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}[" . chr( 128 ) . "-" . chr( 191 ) . "]{1}){2,}/", $wp_query->query_vars['s'] ) ) {
// 检查是否包含中文字符
if ( ! preg_match( '/[\x{4e00}-\x{9fff}]+/u', $wp_query->query_vars['s'] ) ) {
return $where;
}

wpcc_load_conversion_table();

$placeholder = '%';
if ( method_exists( $wpdb, 'placeholder_escape' ) ) {
$placeholder = $wpdb->placeholder_escape( "%" );
}
$sql = '';
$and1 = '';
$original = '';
foreach ( $wp_query->query_vars['search_terms'] as $value ) {
$value = addslashes_gpc( $value );
$original .= "{$and1}(($wpdb->posts.post_title LIKE '{$placeholder}{$value}{$placeholder}') OR ($wpdb->posts.post_excerpt LIKE '{$placeholder}{$value}{$placeholder}') OR ($wpdb->posts.post_content LIKE '{$placeholder}{$value}{$placeholder}'))";
$valuea = zhconversion_all( $value );
$valuea[] = $value;
$sql .= "{$and1}( ";
$or2 = '';
foreach ( $valuea as $v ) {
$sql .= "{$or2}( " . $wpdb->prefix . "posts.post_title LIKE '{$placeholder}" . $v . "{$placeholder}') ";
$sql .= " OR ( " . $wpdb->prefix . "posts.post_content LIKE '{$placeholder}" . $v . "{$placeholder}') ";
$sql .= " OR ( " . $wpdb->prefix . "posts.post_excerpt LIKE '{$placeholder}" . $v . "{$placeholder}') ";
$or2 = ' OR ';
$sql_parts = array();
$original_parts = array();
foreach ( $wp_query->query_vars['search_terms'] as $term ) {
// 安全处理搜索词
$safe_term = sanitize_text_field( $term );
if ( empty( $safe_term ) ) {
continue;
}
// 构建原始搜索条件(用于替换)
$original_condition = $wpdb->prepare(
"(({$wpdb->posts}.post_title LIKE %s) OR ({$wpdb->posts}.post_excerpt LIKE %s) OR ({$wpdb->posts}.post_content LIKE %s))",
'%' . $wpdb->esc_like( $safe_term ) . '%',
'%' . $wpdb->esc_like( $safe_term ) . '%',
'%' . $wpdb->esc_like( $safe_term ) . '%'
);
$original_parts[] = $original_condition;
// 获取转换后的变体
$variants = zhconversion_all( $safe_term );
$variants[] = $safe_term; // 包含原始词
$variants = array_unique( array_filter( $variants ) );
$variant_conditions = array();
foreach ( $variants as $variant ) {
$safe_variant = sanitize_text_field( $variant );
if ( empty( $safe_variant ) ) {
continue;
}
$variant_conditions[] = $wpdb->prepare(
"({$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_content LIKE %s OR {$wpdb->posts}.post_excerpt LIKE %s)",
'%' . $wpdb->esc_like( $safe_variant ) . '%',
'%' . $wpdb->esc_like( $safe_variant ) . '%',
'%' . $wpdb->esc_like( $safe_variant ) . '%'
);
}
if ( ! empty( $variant_conditions ) ) {
$sql_parts[] = '(' . implode( ' OR ', $variant_conditions ) . ')';
}
$sql .= ' ) ';
$and1 = ' AND ';
}

if ( empty( $sql ) ) {
if ( empty( $sql_parts ) || empty( $original_parts ) ) {
return $where;
}
$where = preg_replace( '/' . preg_quote( $original, '/' ) . '/', $sql, $where, 1 );
// 安全地替换原始查询
$original_pattern = implode( ' AND ', $original_parts );
$replacement_sql = implode( ' AND ', $sql_parts );
$where = str_replace( $original_pattern, $replacement_sql, $where );

return $where;
}
@ -842,11 +1007,22 @@ function wpcc_load_conversion_table() {
* 执行转换
*/
function wpcc_do_conversion() {
// 当新版核心 WPCC_Main 存在时,跳过旧版全页面转换与过滤器,避免重复执行
if ( class_exists( 'WPCC_Main' ) ) {
return;
}
global $wpcc_direct_conversion_flag, $wpcc_options;
// 若是 AJAX/REST/wc-ajax 等非 HTML 响应,直接跳过所有转换,避免破坏 JSON/片段
if ( $wpcc_direct_conversion_flag ) {
return;
}
wpcc_load_conversion_table();

add_action( 'wp_head', 'wpcc_header' );


if ( ! $wpcc_direct_conversion_flag ) {
remove_action( 'wp_head', 'rel_canonical' );
add_action( 'wp_head', 'wpcc_rel_canonical' );
@ -882,6 +1058,10 @@ function wpcc_do_conversion() {
add_filter( 'the_content_rss', 'wpcc_no_conversion_filter', 15 );
}

// 为了让“全页面转换”模式也能正确排除“不转换”片段,先把注释标记包裹的内容替换为持久性包装(不被压缩器移除)
add_filter( 'the_content', 'wpcc_wrap_nc_markers', 1 );
add_filter( 'the_excerpt', 'wpcc_wrap_nc_markers', 1 );
if ( $wpcc_options['wpcc_use_fullpage_conversion'] == 1 ) {
@ob_start( 'wpcc_ob_callback' );
return;
@ -913,20 +1093,34 @@ function wpcc_do_conversion() {
*/
function wpcc_header() {
global $wpcc_target_lang, $wpcc_langs_urls, $wpcc_noconversion_url, $wpcc_direct_conversion_flag;
echo "\n" . '<!-- WP WP Chinese Converter Plugin Version ' . wpcc_VERSION . ' -->';
echo "<script type=\"text/javascript\">
//<![CDATA[
var wpcc_target_lang=\"$wpcc_target_lang\";var wpcc_noconversion_url=\"$wpcc_noconversion_url\";var wpcc_langs_urls=new Array();";
foreach ( $wpcc_langs_urls as $key => $value ) {
echo 'wpcc_langs_urls["' . $key . '"]="' . $value . '";';
echo "\n" . '<!-- WP Chinese Converter Plugin Version ' . esc_html( wpcc_VERSION ) . ' -->';
$script_data = array(
'wpcc_target_lang' => $wpcc_target_lang ? esc_js( $wpcc_target_lang ) : '',
'wpcc_noconversion_url' => $wpcc_noconversion_url ? esc_url( $wpcc_noconversion_url ) : '',
'wpcc_langs_urls' => array()
);
if ( is_array( $wpcc_langs_urls ) ) {
foreach ( $wpcc_langs_urls as $key => $value ) {
$safe_key = preg_match( '/^[a-z-]+$/', $key ) ? $key : '';
if ( $safe_key && filter_var( $value, FILTER_VALIDATE_URL ) ) {
$script_data['wpcc_langs_urls'][ $safe_key ] = esc_url( $value );
}
}
}
echo '
//]]>
</script>';
echo '<script type="text/javascript">';
echo '/* <![CDATA[ */';
echo 'var wpcc_target_lang="' . $script_data['wpcc_target_lang'] . '";';
echo 'var wpcc_noconversion_url="' . $script_data['wpcc_noconversion_url'] . '";';
echo 'var wpcc_langs_urls=' . wp_json_encode( $script_data['wpcc_langs_urls'] ) . ';';
echo '/* ]]> */';
echo '</script>';

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 ||
@ -949,6 +1143,20 @@ function wpcc_ob_callback( $buffer ) {
return zhconversion2( $buffer ) . "\n" . '<!-- WP WP Chinese Converter Full Page Converted. Target Lang: ' . $wpcc_target_lang . ' -->';
}

/**
* 将注释标记包裹的区域转换为持久性 data 包裹避免被HTML压缩器移除注释
*/
function wpcc_wrap_nc_markers( $content ) {
if ( empty( $content ) || strpos( $content, 'wpc' ) === false ) {
return $content;
}
$pattern = '/<!--wpc(?:c|s)_NC([a-zA-Z0-9]*)_START-->([\\s\\S]*?)<!--wpc(?:c|s)_NC\1_END-->/i';
return preg_replace_callback( $pattern, function( $m ) {
$inner = $m[2];
return '<span data-wpcc-no-conversion="true">' . $inner . '</span>';
}, $content );
}

/**
* 过滤器 - 不转换指定标签内容
*/
@ -996,6 +1204,7 @@ function wpcc_id() {
return $_wpcc_id ++;
}


/**
* 修复链接转换
*/
@ -1005,7 +1214,10 @@ function wpcc_fix_link_conversion( $link ) {
if ( $flag = strstr( $link, '#' ) ) {
$link = substr( $link, 0, - strlen( $flag ) );
}
if ( preg_match( '/^(.*\/)(zh-tw|zh-cn|zh-sg|zh-hant|zh-hans|zh-my|zh-mo|zh-hk|zh|zh-reset)\/(.+)$/', $link, $tmp ) ) {
// 动态语言集
$langs = method_exists( 'WPCC_Language_Config', 'get_valid_language_codes' ) ? WPCC_Language_Config::get_valid_language_codes() : array( 'zh-cn','zh-tw','zh-hk','zh-hans','zh-hant','zh-sg','zh-jp' );
$reg = implode( '|', array_map( 'preg_quote', $langs ) );
if ( preg_match( '/^(.*\/)(' . $reg . '|zh|zh-reset)\/(.+)$/', $link, $tmp ) ) {
return user_trailingslashit( $tmp[1] . trailingslashit( $tmp[3] ) . $tmp[2] ) . $flag;
}
return $link . $flag;
@ -1025,7 +1237,9 @@ function wpcc_fix_link_conversion( $link ) {
function wpcc_cancel_link_conversion( $link ) {
global $wpcc_options;
if ( $wpcc_options['wpcc_use_permalink'] ) {
if ( preg_match( '/^(.*\/)(zh-tw|zh-cn|zh-sg|zh-hant|zh-hans|zh-my|zh-mo|zh-hk|zh|zh-reset)\/(.+)$/', $link, $tmp ) ) {
$langs = method_exists( 'WPCC_Language_Config', 'get_valid_language_codes' ) ? WPCC_Language_Config::get_valid_language_codes() : array( 'zh-cn','zh-tw','zh-hk','zh-hans','zh-hant','zh-sg','zh-jp' );
$reg = implode( '|', array_map( 'preg_quote', $langs ) );
if ( preg_match( '/^(.*\/)(' . $reg . '|zh|zh-reset)\/(.+)$/', $link, $tmp ) ) {
return $tmp[1] . $tmp[3];
}
return $link;
@ -1043,7 +1257,9 @@ function wpcc_cancel_link_conversion( $link ) {
function wpcc_pagenum_link_fix( $link ) {
global $wpcc_target_lang, $wpcc_options;
global $paged;
if ( $wpcc_options['wpcc_use_permalink'] != 1 ) {
// 检查配置是否存在
if ( ! is_array( $wpcc_options ) || ! isset( $wpcc_options['wpcc_use_permalink'] ) || $wpcc_options['wpcc_use_permalink'] != 1 ) {
return $link;
}

@ -1064,7 +1280,10 @@ function wpcc_pagenum_link_fix( $link ) {
*/
function wpcc_cancel_incorrect_redirect( $redirect_to, $redirect_from ) {
global $wp_rewrite;
if ( preg_match( '/^.*\/(zh-tw|zh-cn|zh-sg|zh-hant|zh-hans|zh-my|zh-mo|zh-hk|zh|zh-reset)\/?.+$/', $redirect_to ) ) {
// 动态语言集
$langs = method_exists( 'WPCC_Language_Config', 'get_valid_language_codes' ) ? WPCC_Language_Config::get_valid_language_codes() : array( 'zh-cn','zh-tw','zh-hk','zh-hans','zh-hant','zh-sg','zh-jp' );
$reg = implode( '|', array_map( 'preg_quote', $langs ) );
if ( preg_match( '/^.*\/(?:' . $reg . '|zh|zh-reset)\/? .+$/x', $redirect_to ) ) {
if ( ( $wp_rewrite->use_trailing_slashes && substr( $redirect_from, - 1 ) != '/' ) ||
( ! $wp_rewrite->use_trailing_slashes && substr( $redirect_from, - 1 ) == '/' )
) {
@ -1122,14 +1341,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] ) ) {

// 仅在“确实处于变体页面”时才覆盖 <html lang>
$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;
}
@ -1140,15 +1378,7 @@ function wpcc_locale( $output, $doctype = 'html' ) {
function wpcc_render_no_conversion_block( $block_content, $block ) {
if ( isset( $block['blockName'] ) && $block['blockName'] === 'wpcc/no-conversion' ) {
$unique_id = uniqid();

$pattern = '/<div[^>]*class="[^"]*wpcc-no-conversion-content[^"]*"[^>]*>(.*?)<\/div>/s';

$replacement = function ( $matches ) use ( $unique_id ) {
$content = $matches[1];
return '<div class="wpcc-no-conversion-content"><!--wpcc_NC' . $unique_id . '_START-->' . $content . '<!--wpcc_NC' . $unique_id . '_END--></div>';
};

$block_content = preg_replace_callback( $pattern, $replacement, $block_content );
return '<!--wpcc_NC' . $unique_id . '_START-->' . $block_content . '<!--wpcc_NC' . $unique_id . '_END-->';
}

return $block_content;
@ -1161,7 +1391,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' );
@ -1207,6 +1437,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 () {

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@
* Author URI: https://wpcc.net
* Text Domain: wp-chinese-converter
* Domain Path: /languages
* Version: 1.3.0
* 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.3.0");
define("wpcc_VERSION", "1.5");

// 插件URL常量
if (defined("WP_PLUGIN_URL")) {
@ -60,8 +60,17 @@ use Overtrue\PHPOpenCC\OpenCC;
use Overtrue\PHPOpenCC\Strategy;

// 载入核心依赖
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-wpcc-exception-handler.php";
require_once dirname(__FILE__) . "/includes/core/class-wpcc-utils.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";
require_once dirname(__FILE__) . "/includes/blocks/blocks-init.php";

// 加载JS和CSS资源
@ -73,52 +82,31 @@ 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,
]);
}

// 获取插件配置
$wpcc_options = get_wpcc_option("wpcc_options");
if (empty($wpcc_options)) {
$wpcc_options = [
"wpcc_search_conversion" => 1,
"wpcc_used_langs" => [
"zh-hans",
"zh-hant",
"zh-cn",
"zh-hk",
"zh-sg",
"zh-tw",
],
"wpcc_browser_redirect" => 0,
"wpcc_auto_language_recong" => 0,
"wpcc_flag_option" => 1,
"wpcc_use_cookie_variant" => 0,
"wpcc_use_fullpage_conversion" => 1,
"wpcco_use_sitemap" => 1,
"wpcc_trackback_plugin_author" => 0,
"wpcc_add_author_link" => 0,
"wpcc_use_permalink" => 0,
"wpcc_no_conversion_tag" => "",
"wpcc_no_conversion_ja" => 0,
"wpcc_no_conversion_qtag" => 0,
"wpcc_enable_post_conversion" => 0,
"wpcc_post_conversion_target" => "zh-cn",
"wpcc_engine" => "opencc", // alternative: mediawiki
"nctip" => "",
];
}
// 初始化现代化架构
$wpcc_main = WPCC_Main::get_instance();
$wpcc_config = $wpcc_main->get_config();

// 保持向后兼容性
$wpcc_options = $wpcc_config->get_all_options();

// 加载站点地图模块
$modules_dir = __DIR__ . "/includes/modules/";
@ -126,33 +114,37 @@ 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 &&
is_array($wpcc_options) &&
is_array($wpcc_options["wpcc_used_langs"])
) {
// 加载核心功能模块
// 加载遗留的核心功能模块(为了向后兼容)
require_once dirname(__FILE__) . "/includes/wpcc-core.php";

// 确保核心初始化与查询变量已注册(保证变体参数与重写规则生效)
if (!has_action("init", "wpcc_init")) {
add_action("init", "wpcc_init");
}
if ( ! class_exists( 'WPCC_Main' ) ) {
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");
add_filter("query_vars", "wpcc_insert_query_vars");
add_action("init", "wpcc_init");

// 调试模式下的重写规则刷新
if (WP_DEBUG || (defined("wpcc_DEBUG") && wpcc_DEBUG == true)) {
add_action("init", function () {
global $wp_rewrite;
$wp_rewrite->flush_rules();
});
add_action("wp_footer", "wpcc_debug");
}
}

/**
@ -169,11 +161,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);
}

/**
@ -181,9 +180,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);
}