mirror of
https://github.com/WenPai-org/wp-chinese-converter.git
synced 2025-11-22 15:33:16 +08:00
Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7a6b1bd18 | ||
|
|
2e42b5bb83 | ||
|
|
85cd30a5b1 | ||
|
|
cede6d1b02 | ||
|
|
f15abc6e30 | ||
|
|
1a89f9ec27 | ||
|
|
6c9407cbd5 | ||
|
|
f4720ecedb | ||
|
|
54b872015a | ||
|
|
039f3aa91a | ||
|
|
c50e16121b | ||
|
|
86d8dbeaf5 | ||
|
|
a6157c75f4 | ||
|
|
54ebb8cd91 | ||
|
|
f43da1c764 | ||
|
|
5a7813a7a0 | ||
|
|
837b659fa9 | ||
|
|
a395045607 | ||
|
|
b5e9fc686e |
38 changed files with 10240 additions and 1089 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
assets/.DS_Store
|
||||
237
README.md
237
README.md
|
|
@ -1,33 +1,240 @@
|
|||
# 关于 WPCC.NET 中文简繁转换插件
|
||||
# WP Chinese Converter (WPCC)
|
||||
|
||||
文派简繁转换(WP Chinese Converter),简称 WPCC,是完全符合阅读习惯和 SEO 优化的 WordPress 网站中文繁简转换解决方案。
|
||||
|
||||

|
||||
|
||||
## 关于简繁转换
|
||||
## 核心特性
|
||||
|
||||
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
BIN
assets/.DS_Store
vendored
Binary file not shown.
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
44
assets/js/tinymce-wpcc-nc.js
Normal file
44
assets/js/tinymce-wpcc-nc.js
Normal 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'
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
})();
|
||||
78
assets/js/wpcc-block-compat.js
Normal file
78
assets/js/wpcc-block-compat.js
Normal 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);
|
||||
});
|
||||
})();
|
||||
|
|
@ -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
27
changelog.txt
Normal 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 重构)
|
||||
- 完整重写插件架构为 OOP:WPCC_Main / WPCC_Config / 模块管理器等
|
||||
- 新增 Gutenberg 区块(语言切换器、转换状态、不转换区块)
|
||||
- 优化 性能与内存占用;OpenCC 与 MediaWiki 双引擎;转换缓存
|
||||
- 增强 SEO(canonical、hreflang、schema/meta 转换)与 REST API 支持
|
||||
|
||||
历史说明
|
||||
- 本项目分叉自 WP Chinese Conversion,2025 年起由文派团队(WPCC.NET)进行长期维护与重构。
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
},"")
|
||||
);
|
||||
}
|
||||
});})();
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
416
includes/core/class-wpcc-config.php
Normal file
416
includes/core/class-wpcc-config.php
Normal 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;
|
||||
}
|
||||
}
|
||||
270
includes/core/class-wpcc-conversion-cache.php
Normal file
270
includes/core/class-wpcc-conversion-cache.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
301
includes/core/class-wpcc-exception-handler.php
Normal file
301
includes/core/class-wpcc-exception-handler.php
Normal 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();
|
||||
321
includes/core/class-wpcc-extension.php
Normal file
321
includes/core/class-wpcc-extension.php
Normal 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;
|
||||
}
|
||||
}
|
||||
165
includes/core/class-wpcc-language-config.php
Normal file
165
includes/core/class-wpcc-language-config.php
Normal 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;
|
||||
}
|
||||
}
|
||||
1151
includes/core/class-wpcc-main.php
Normal file
1151
includes/core/class-wpcc-main.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
233
includes/core/class-wpcc-opencc-converter.php
Normal file
233
includes/core/class-wpcc-opencc-converter.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
194
includes/core/class-wpcc-presets.php
Normal file
194
includes/core/class-wpcc-presets.php
Normal 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;
|
||||
}
|
||||
}
|
||||
213
includes/core/class-wpcc-utils.php
Normal file
213
includes/core/class-wpcc-utils.php
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
|||
3021
includes/network/wpcc-network-settings.php
Normal file
3021
includes/network/wpcc-network-settings.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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' );
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue