diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b5aeb4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +assets/.DS_Store diff --git a/README.md b/README.md index e356fd3..4e9bfb8 100755 --- a/README.md +++ b/README.md @@ -1,33 +1,240 @@ -# 关于 WPCC.NET 中文简繁转换插件 +# WP Chinese Converter (WPCC) 文派简繁转换(WP Chinese Converter),简称 WPCC,是完全符合阅读习惯和 SEO 优化的 WordPress 网站中文繁简转换解决方案。 ![avatar](https://img.feibisi.com/2021/03/wpchinese-switcher-banner-1544x500-1.png) -## 关于简繁转换 +## 核心特性 -WPCC 是一个 WordPress 插件,用于将网站中的中文内容转换为简体中文或繁体中文。它基于 0 项目的转换引擎,提供了简单而强大的简繁转换功能。 +### 现代化架构 (2025重构) +- **面向对象设计**: 采用单例模式和依赖注入的现代PHP架构 +- **模块化系统**: 支持功能模块的动态加载和管理 +- **向后兼容**: 保持与旧版本配置和功能的兼容性 -### 技术特性 +### 多语言转换支持 +- **转换引擎**: + - 基于 PHP OpenCC 1.2.1 版本的高精度转换 + - MediaWiki 转换引擎支持 +- **支持地区**: + - `zh-cn` / `zh-hans`: 简体中文(中国大陆) + - `zh-tw` / `zh-hant`: 繁体中文(台湾) + - `zh-hk`: 港澳繁体中文 + - `zh-sg`: 马新简体中文 + - `zh-jp`: 日式汉字 +- **转换策略**: 词汇级别转换、异体字转换、地区习惯用词转换 -- **转换引擎**: 基于 PHP OpenCC 1.2.1 版本 -- **转换策略**: 支持词汇级别的转换、异体字转换和地区习惯用词转换 -- **支持地区**: 中国大陆、台湾、香港、日本新字体 -- **性能优化**: 支持字典预加载和缓存机制 +### Gutenberg 区块支持 +- **语言切换器区块**: 可视化语言切换按钮 +- **转换状态区块**: 显示当前语言状态 +- **不转换区块**: 指定不需要转换的内容区域 +- **现代编辑器体验**: 完全兼容WordPress 5.0+区块编辑器 -目前我们正在构建更多地域词义词源项目—— OpenCC.org ,并首要为 WPCC 提供支持,可以实现更好的原生中文简繁阅读体验。 +### 性能优化 +- **智能缓存系统**: 支持多种缓存策略(WordPress原生缓存、现代缓存系统) +- **内存优化**: 字典预加载和按需加载机制 +- **转换缓存**: 避免重复转换相同内容 +- **全页面转换**: 可选的服务端全页面转换模式 +### SEO 优化功能 +- **Canonical URL**: 自动生成规范链接,避免重复内容问题 +- **Hreflang 支持**: 自动添加语言版本链接标记 +- **URL 结构优化**: 支持伪静态和查询参数两种URL模式 +- **搜索增强**: 支持搜索关键词的简繁转换 +- **站点地图兼容**: 自动生成多语言版本的站点地图 ->此项目旧版本 WPChinese Switcher 分叉于原 WP Chinese Conversion 中文简繁转换器免费插件,感谢原作者 Ono Oogami 提供了此工具。由于原插件已经多年无更新,无维护及无支持回复,旧版本插件在新的 PHP 版本中也有多处错误函数警告。在简单修复了几个 bug 后越发认识到,这对于中文用户网站来说确实是非常好且实用的简繁双语解决方案,现借由文派开源重启项目,以便为 WordPress 中文用户提供长期更新和支持。 +### 高级功能 +- **浏览器语言检测**: 根据访客浏览器语言自动切换 +- **Cookie 记忆**: 记住用户的语言偏好设置 +- **多站点支持**: 完全兼容 WordPress 多站点模式 +- **REST API**: 提供完整的 REST API 接口 +- **AJAX 转换**: 支持动态内容的实时转换 ->为了避免与原始插件名称冲突,故新版本重命名为 WPChinese Switcher ——文派中文切换器或简繁切换器。 +### 兼容性 +- **WordPress 版本**: WordPress 5.0+ (推荐 6.0+) +- **PHP 版本**: PHP 7.4+ (推荐 8.0+) +- **数据库**: MySQL 5.7+ 或 MariaDB 10.2+ +- **缓存插件**: 兼容 WP Super Cache, W3 Total Cache 等 -## 品牌重塑 +## 系统要求 -2025 年为了满足更多自定义开发要求,文派开源开源团队对此项目进行重写和品牌重塑,正式命名为“文派简繁切换器(WP Chinese Converter),简称 WPCC。” +- WordPress 5.0 或更高版本 +- PHP 7.4 或更高版本(推荐 PHP 8.0+) +- MySQL 5.7+ 或 MariaDB 10.2+ +- 服务器内存至少 128MB(用于加载转换字典) -由于历史代码太过混乱,维护负担过重,且不再适用于 WordPress 最新区块编辑器项目,故后续版本不再与过完任何旧版本兼容。 +## 安装配置 + +### 自动安装 +1. 在 WordPress 后台导航到 `插件 > 安装插件` +2. 搜索 "WP Chinese Converter" 或 "WPCC" +3. 点击 `现在安装`,然后激活插件 +4. 在 `设置 > WP Chinese Converter` 中配置转换选项 + +### 手动安装 +1. 下载插件压缩包 +2. 解压到 `/wp-content/plugins/wp-chinese-converter/` 目录 +3. 在 WordPress 后台激活插件 +4. 配置插件设置 + +## 主要设置选项 + +### 基本设置 +- **启用语言**: 选择要启用的语言版本 +- **转换模式**: 选择使用的转换引擎(OpenCC 或 MediaWiki) +- **URL 模式**: 选择伪静态或查询参数模式 +- **默认语言**: 设置网站的默认语言版本 + +### 高级设置 +- **缓存设置**: 配置转换缓存策略 +- **SEO 优化**: 启用 canonical URL 和 hreflang 支持 +- **浏览器检测**: 启用自动语言检测 +- **Cookie 记忆**: 启用用户语言偏好记忆 + +## 使用方法 + +### Gutenberg 区块使用 +1. 在页面编辑器中点击 `+` 添加区块 +2. 搜索 "WP Chinese Converter" 相关区块 +3. 选择所需区块并配置选项: + - **语言切换器**: 设置按钮样式和显示语言 + - **转换状态**: 显示当前页面语言状态 + - **不转换**: 指定不需要转换的内容区域 + +### 短代码支持 +- 语言切换器(老短代码,平铺/下拉由“展示形式”设置控制) +``` +[wp-chinese-converter] +``` +- 不转换内容(编辑器无法保留注释时作为稳健占位): +``` +[wpcc_nc]不转换的内容[/wpcc_nc] +[wpcs_nc]不转换的内容[/wpcs_nc] +``` + +### PHP 函数调用 +```php +// 获取转换后的内容 + + +// 获取当前语言 + + +// 获取语言切换链接 + +``` + +## 开发者接口 + +### 过滤器钩子 +```php +// 自定义转换文本 +add_filter('wpcc_convert_text', 'custom_conversion', 10, 3); + +// 修改语言标签 +add_filter('wpcc_language_labels', 'custom_labels'); + +// 自定义缓存时间 +add_filter('wpcc_cache_expire', 'custom_cache_time'); +``` + +### 动作钩子 +```php +// 转换前执行 +add_action('wpcc_before_conversion', 'before_convert'); + +// 转换后执行 +add_action('wpcc_after_conversion', 'after_convert'); +``` + +## 链接与重写规则行为说明(重要) + +- 链接格式与固定链接的关系 + - 当 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. 文派 (广州) 科技有限公司** diff --git a/assets/.DS_Store b/assets/.DS_Store index 0b230cb..65796ab 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/assets/admin/admin.css b/assets/admin/admin.css index f50d039..843491d 100644 --- a/assets/admin/admin.css +++ b/assets/admin/admin.css @@ -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; +} \ No newline at end of file diff --git a/assets/css/blocks-editor.css b/assets/css/blocks-editor.css index e27e159..3466373 100644 --- a/assets/css/blocks-editor.css +++ b/assets/css/blocks-editor.css @@ -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; } @@ -160,4 +161,4 @@ .wpcc-language-switcher .wpcc-custom-label { font-weight: 500; -} \ No newline at end of file +} diff --git a/assets/css/blocks-frontend.css b/assets/css/blocks-frontend.css index 9d82ebc..316597a 100644 --- a/assets/css/blocks-frontend.css +++ b/assets/css/blocks-frontend.css @@ -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,25 +118,37 @@ 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; } - + .wpcc-lang-item { width: 100%; } - + .wpcc-lang-item .wpcc-link { padding: 8px 12px; font-size: 14px; @@ -127,7 +156,7 @@ width: 100%; box-sizing: border-box; } - + .wpcc-dropdown-switcher { width: 100%; padding: 8px 12px; @@ -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 { @@ -207,4 +237,4 @@ .wpcc-language-switcher.wpcc-current-first .wpcc-lang-item.wpcc-current { order: -1; -} \ No newline at end of file +} diff --git a/assets/js/blocks-frontend.js b/assets/js/blocks-frontend.js index 9ee5463..5bf39c0 100644 --- a/assets/js/blocks-frontend.js +++ b/assets/js/blocks-frontend.js @@ -75,7 +75,9 @@ function renderLanguageSwitcher( }; const target = openInNewWindow ? ' target="_blank"' : ""; - const noConversionText = customNoConversionLabel || "不转换"; + const rel = openInNewWindow ? ' rel="noopener noreferrer"' : ""; + const strings = (typeof wpccFrontendSettings !== 'undefined' && wpccFrontendSettings.strings) ? wpccFrontendSettings.strings : {}; + const noConversionText = customNoConversionLabel || strings.noConversionLabel || "不转换"; let html = ""; @@ -84,23 +86,27 @@ function renderLanguageSwitcher( if (showNoConversion) { const isActive = !currentLang || currentLang === ""; + const noConvUrl = getNoConversionUrl(); + const relForNoConv = buildRelAttribute(noConvUrl, openInNewWindow); html += ` - ${noConversionText} + ${noConversionText} `; } availableLanguages.forEach(function (lang) { const isActive = currentLang === lang.code; const label = getLanguageLabel(lang); + const ariaCurrent = isActive ? ' aria-current="page"' : ''; html += ` - ${label} + ${label} `; }); html += ""; } else { + const ariaLabel = (typeof strings !== 'undefined' && strings.languageSelectLabel) ? strings.languageSelectLabel : '选择语言'; html = - '`; if (showNoConversion) { const selected = !currentLang || currentLang === "" ? "selected" : ""; @@ -172,13 +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 += ``; + } html += `${statusText}`; 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) { diff --git a/assets/js/tinymce-wpcc-nc.js b/assets/js/tinymce-wpcc-nc.js new file mode 100644 index 0000000..12e12df --- /dev/null +++ b/assets/js/tinymce-wpcc-nc.js @@ -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' + }; + } + }; + }); +})(); diff --git a/assets/js/wpcc-block-compat.js b/assets/js/wpcc-block-compat.js new file mode 100644 index 0000000..4a94b47 --- /dev/null +++ b/assets/js/wpcc-block-compat.js @@ -0,0 +1,78 @@ +(function(){ + if (!window.wp || !wp.blocks || !wp.blockEditor || !wp.element || !wp.domReady) return; + const el = wp.element.createElement; + const useBlockProps = wp.blockEditor.useBlockProps; + + // Deprecated saver for Traditional Chinese placeholder (語言切換器) + function saveLanguageSwitcherDeprecated(props){ + const { + displayStyle, + showNoConversion, + alignment, + enabledLanguages, + buttonSize, + customNoConversionLabel, + sortOrder, + openInNewWindow, + showLanguageCode, + customLabels, + } = props.attributes; + + let finalEnabledLanguages = Array.isArray(enabledLanguages) ? enabledLanguages : []; + if (typeof wpccBlockSettings !== 'undefined' && Array.isArray(wpccBlockSettings.enabledLanguages)) { + const base = wpccBlockSettings.enabledLanguages; + const filtered = finalEnabledLanguages.filter(code => base.includes(code)); + finalEnabledLanguages = filtered.length ? filtered : base; + } + + const blockProps = useBlockProps.save({ + className: `wpcc-language-switcher wpcc-${displayStyle} wpcc-align-${alignment} wpcc-size-${buttonSize}` + }); + + return el('div', blockProps, + el('div', { + className: 'wpcc-switcher-placeholder', + 'data-display-style': displayStyle, + 'data-show-no-conversion': !!showNoConversion, + 'data-enabled-languages': JSON.stringify(finalEnabledLanguages), + 'data-button-size': buttonSize, + 'data-custom-no-conversion-label': customNoConversionLabel || '', + 'data-sort-order': sortOrder, + 'data-open-in-new-window': !!openInNewWindow, + 'data-show-language-code': !!showLanguageCode, + 'data-custom-labels': JSON.stringify(customLabels || {}) + }, '語言切換器') + ); + } + + function saveConversionStatusDeprecated(props){ + const { showIcon, displayFormat } = props.attributes; + const blockProps = useBlockProps.save({ + className: `wpcc-conversion-status wpcc-format-${displayFormat}` + }); + return el('div', blockProps, + el('div', { + className: 'wpcc-status-container', + 'data-show-icon': !!showIcon, + 'data-display-format': displayFormat + }, '轉換狀態指示器') + ); + } + + wp.domReady(function(){ + const { getBlockType, unregisterBlockType, registerBlockType } = wp.blocks; + + function extendDeprecated(name, saveFn){ + const type = getBlockType(name); + if (!type) return; + const deprecated = Array.isArray(type.deprecated) ? type.deprecated.slice() : []; + deprecated.push({ save: saveFn }); + const settings = Object.assign({}, type, { deprecated }); + unregisterBlockType(name); + registerBlockType(name, settings); + } + + extendDeprecated('wpcc/language-switcher', saveLanguageSwitcherDeprecated); + extendDeprecated('wpcc/conversion-status', saveConversionStatusDeprecated); + }); +})(); diff --git a/assets/js/wpcc-block-script-ok.js b/assets/js/wpcc-block-script-ok.js index 5b5926f..38a05b7 100755 --- a/assets/js/wpcc-block-script-ok.js +++ b/assets/js/wpcc-block-script-ok.js @@ -1,14 +1,14 @@ -let wpc_switcher_use_permalink_type +var wpc_switcher_use_permalink_type; if (typeof wpc_switcher_use_permalink !== 'undefined') { - wpc_switcher_use_permalink_type = wpc_switcher_use_permalink['type'] + wpc_switcher_use_permalink_type = wpc_switcher_use_permalink['type']; } function wpccRedirectToPage($event) { - if (typeof WPCSVariant === 'undefined') return ; - return WPCSVariant.wpccRedirectToPage($event) + if (typeof WPCSVariant === 'undefined') return; + return WPCSVariant.wpccRedirectToPage($event); } function wpccRedirectToVariant(variantValue) { - if (typeof WPCSVariant === 'undefined') return ; - return WPCSVariant.wpccRedirectToVariant(variantValue) -} \ No newline at end of file + if (typeof WPCSVariant === 'undefined') return; + return WPCSVariant.wpccRedirectToVariant(variantValue); +} diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..c0382fe --- /dev/null +++ b/changelog.txt @@ -0,0 +1,27 @@ +WP Chinese Converter (WPCC) — 更新日志 / Changelog + +Version 1.5.0 (2025-10-11) +- 修复 单站模式下“展示形式”反向的问题:统一语义为 1=平铺、0=下拉,短代码 [wp-chinese-converter] 与区块表现一致 +- 修复 单站设置保存问题:确保语言模块、全页面转换、展示形式等选项在单站模式可正确保存 +- 优化 zh 哨兵与 rel="nofollow" 规则:仅当“不转换”链接为覆盖策略而携带 zh 哨兵(/zh/ 或 ?variant=zh)时添加 nofollow;新窗口时追加 noopener noreferrer +- 优化 前端语言切换器:在变体页为“不转换”注入 zh 哨兵;缓存破除(基于文件 mtime 的版本号) +- 链接与重写 统一策略: + * WordPress 未启用固定链接时,所有语言链接自动回退为 ?variant=xx,避免 /zh-xx/ 404 + * 首页根级变体(/zh/、/zh-xx/)统一 302 到首页;/zh/ 同时设置 zh 偏好 +- 内核一致性:老函数(includes/wpcc-core.php)与新内核(WPCC_Main)策略对齐(固定链接检测、链接降级) +- 架构与清理:统一核心类文件命名为 class-wpcc-*.php;移除旧 url fix 入口;保留兼容层并与新内核对齐 +- 文档:更新 README,补充链接/重写行为说明与兼容策略 + +Version 1.4.x (2025) +- 统一 SEO 行为:在不需要 zh 哨兵时不添加 nofollow,避免权重传递受影响 +- 改善 区块与脚本的加载与配置注入,优化多语言切换体验 +- 多站点:网络设置与子站策略协调,增强稳定性 + +Version 1.3.0 (2025 重构) +- 完整重写插件架构为 OOP:WPCC_Main / WPCC_Config / 模块管理器等 +- 新增 Gutenberg 区块(语言切换器、转换状态、不转换区块) +- 优化 性能与内存占用;OpenCC 与 MediaWiki 双引擎;转换缓存 +- 增强 SEO(canonical、hreflang、schema/meta 转换)与 REST API 支持 + +历史说明 +- 本项目分叉自 WP Chinese Conversion,2025 年起由文派团队(WPCC.NET)进行长期维护与重构。 diff --git a/includes/admin/wp-chinese-converter-admin.php b/includes/admin/class-wpcc-admin.php similarity index 61% rename from includes/admin/wp-chinese-converter-admin.php rename to includes/admin/class-wpcc-admin.php index f9572e4..cf41f58 100755 --- a/includes/admin/wp-chinese-converter-admin.php +++ b/includes/admin/class-wpcc-admin.php @@ -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; + + private array $network_controlled_options = []; + private bool $is_network_enforced = false; + private array $network_options = []; - function wpcc_Admin() - { - return $this->__construct(); - } + // 移除旧式构造函数,只使用 __construct - function clean_invalid_langs() + 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 '

此选项由网络管理员统一控制,无法在子站点修改。

'; + } + return ''; + } + + /** + * 检查是否为网络强制模式 + */ + public function is_network_enforced() { + return $this->is_network_enforced; + } } diff --git a/includes/blocks/blocks-init.php b/includes/blocks/blocks-init.php index 0b3fc47..29db599 100644 --- a/includes/blocks/blocks-init.php +++ b/includes/blocks/blocks-init.php @@ -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') + ) ) ); } diff --git a/includes/blocks/build/language-switcher/index.js b/includes/blocks/build/language-switcher/index.js index dcb54e2..520f025 100644 --- a/includes/blocks/build/language-switcher/index.js +++ b/includes/blocks/build/language-switcher/index.js @@ -246,13 +246,8 @@ function sortLanguages(languages, sortOrder, showCurrentFirst) { customLabels }=attributes; + // Keep save output stable, do not depend on runtime settings let finalEnabledLanguages = enabledLanguages; - if (typeof wpccBlockSettings !== 'undefined' && wpccBlockSettings.enabledLanguages) { - finalEnabledLanguages = enabledLanguages.filter(lang => wpccBlockSettings.enabledLanguages.includes(lang)); - if (finalEnabledLanguages.length === 0) { - finalEnabledLanguages = wpccBlockSettings.enabledLanguages; - } - } const blockProps=a.useBlockProps.save({ className:`wpcc-language-switcher wpcc-${displayStyle} wpcc-align-${alignment} wpcc-size-${buttonSize}` @@ -270,7 +265,7 @@ function sortLanguages(languages, sortOrder, showCurrentFirst) { "data-open-in-new-window":openInNewWindow, "data-show-language-code":showLanguageCode, "data-custom-labels":JSON.stringify(customLabels) - },(0,n.__)("语言切换器","wp-chinese-converter")) + },"") ); } });})(); \ No newline at end of file diff --git a/includes/core/class-opencc-converter.php b/includes/core/class-opencc-converter.php deleted file mode 100644 index 573d953..0000000 --- a/includes/core/class-opencc-converter.php +++ /dev/null @@ -1,107 +0,0 @@ - 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; - } - } -} \ No newline at end of file diff --git a/includes/core/class-wpcc-config.php b/includes/core/class-wpcc-config.php new file mode 100644 index 0000000..e4a106e --- /dev/null +++ b/includes/core/class-wpcc-config.php @@ -0,0 +1,416 @@ +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; + } +} diff --git a/includes/core/class-wpcc-conversion-cache.php b/includes/core/class-wpcc-conversion-cache.php new file mode 100644 index 0000000..fe95e26 --- /dev/null +++ b/includes/core/class-wpcc-conversion-cache.php @@ -0,0 +1,270 @@ + 0, + 'misses' => 0, + 'sets' => 0 + ); + + /** + * 最大内存缓存条目数 + * + * @var int + */ + private static $max_memory_cache_size = 1000; + + /** + * 缓存过期时间(秒) + * + * @var int + */ + private static $cache_expire_time = 3600; // 1小时 + + /** + * 获取缓存的转换结果 + * + * @param string $text 原始文本 + * @param string $variant 目标语言变体 + * @return string|null 缓存的转换结果,如果不存在返回null + */ + public static function get_cached_conversion( $text, $variant ) { + if ( empty( $text ) || empty( $variant ) ) { + return null; + } + + $cache_key = self::generate_cache_key( $text, $variant ); + + // 先检查内存缓存 + if ( isset( self::$memory_cache[ $cache_key ] ) ) { + $cached_data = self::$memory_cache[ $cache_key ]; + if ( $cached_data['expires'] > time() ) { + self::$cache_stats['hits']++; + return $cached_data['result']; + } else { + // 清理过期的内存缓存 + unset( self::$memory_cache[ $cache_key ] ); + } + } + + // 检查WordPress对象缓存 + $cached_result = wp_cache_get( $cache_key, 'wpcc_conversion' ); + if ( $cached_result !== false ) { + // 同时存入内存缓存以加速后续访问 + self::set_memory_cache( $cache_key, $cached_result ); + self::$cache_stats['hits']++; + return $cached_result; + } + + self::$cache_stats['misses']++; + return null; + } + + /** + * 设置转换结果缓存 + * + * @param string $text 原始文本 + * @param string $variant 目标语言变体 + * @param string $result 转换结果 + * @return bool 是否设置成功 + */ + public static function set_cached_conversion( $text, $variant, $result ) { + if ( empty( $text ) || empty( $variant ) || empty( $result ) ) { + return false; + } + + $cache_key = self::generate_cache_key( $text, $variant ); + + // 设置内存缓存 + self::set_memory_cache( $cache_key, $result ); + + // 设置WordPress对象缓存 + wp_cache_set( $cache_key, $result, 'wpcc_conversion', self::$cache_expire_time ); + + self::$cache_stats['sets']++; + return true; + } + + /** + * 批量获取转换缓存 + * + * @param array $texts_variants 文本和变体的数组 [[text, variant], ...] + * @return array 缓存结果数组,键为cache_key,值为转换结果 + */ + public static function get_cached_conversions_batch( $texts_variants ) { + $results = array(); + + foreach ( $texts_variants as $item ) { + if ( ! is_array( $item ) || count( $item ) < 2 ) { + continue; + } + + list( $text, $variant ) = $item; + $cache_key = self::generate_cache_key( $text, $variant ); + $cached_result = self::get_cached_conversion( $text, $variant ); + + if ( $cached_result !== null ) { + $results[ $cache_key ] = $cached_result; + } + } + + return $results; + } + + /** + * 批量设置转换缓存 + * + * @param array $conversions 转换结果数组 [[text, variant, result], ...] + * @return int 成功设置的缓存数量 + */ + public static function set_cached_conversions_batch( $conversions ) { + $count = 0; + + foreach ( $conversions as $conversion ) { + if ( ! is_array( $conversion ) || count( $conversion ) < 3 ) { + continue; + } + + list( $text, $variant, $result ) = $conversion; + if ( self::set_cached_conversion( $text, $variant, $result ) ) { + $count++; + } + } + + return $count; + } + + /** + * 生成缓存键 + * + * @param string $text 原始文本 + * @param string $variant 目标语言变体 + * @return string 缓存键 + */ + private static function generate_cache_key( $text, $variant ) { + // 使用文本和变体的哈希值作为缓存键,确保唯一性 + return 'wpcc_' . md5( $text . '|' . $variant ); + } + + /** + * 设置内存缓存 + * + * @param string $cache_key 缓存键 + * @param string $result 转换结果 + */ + private static function set_memory_cache( $cache_key, $result ) { + // 如果内存缓存已满,移除最旧的条目 + if ( count( self::$memory_cache ) >= self::$max_memory_cache_size ) { + $oldest_key = array_key_first( self::$memory_cache ); + unset( self::$memory_cache[ $oldest_key ] ); + } + + self::$memory_cache[ $cache_key ] = array( + 'result' => $result, + 'expires' => time() + self::$cache_expire_time + ); + } + + /** + * 清理过期的内存缓存 + */ + public static function cleanup_expired_memory_cache() { + $current_time = time(); + $expired_keys = array(); + + foreach ( self::$memory_cache as $key => $data ) { + if ( $data['expires'] <= $current_time ) { + $expired_keys[] = $key; + } + } + + foreach ( $expired_keys as $key ) { + unset( self::$memory_cache[ $key ] ); + } + + return count( $expired_keys ); + } + + /** + * 清空所有缓存 + */ + public static function clear_all_cache() { + // 清空内存缓存 + self::$memory_cache = array(); + + // 清空WordPress对象缓存 + wp_cache_flush_group( 'wpcc_conversion' ); + + // 重置统计信息 + self::$cache_stats = array( + 'hits' => 0, + 'misses' => 0, + 'sets' => 0 + ); + } + + /** + * 获取缓存统计信息 + * + * @return array 缓存统计信息 + */ + public static function get_cache_stats() { + $stats = self::$cache_stats; + $stats['memory_cache_size'] = count( self::$memory_cache ); + $stats['hit_rate'] = $stats['hits'] + $stats['misses'] > 0 + ? round( $stats['hits'] / ( $stats['hits'] + $stats['misses'] ) * 100, 2 ) + : 0; + + return $stats; + } + + /** + * 设置缓存配置 + * + * @param array $config 配置数组 + */ + public static function set_cache_config( $config ) { + if ( isset( $config['max_memory_cache_size'] ) ) { + self::$max_memory_cache_size = (int) $config['max_memory_cache_size']; + } + + if ( isset( $config['cache_expire_time'] ) ) { + self::$cache_expire_time = (int) $config['cache_expire_time']; + } + } + + /** + * 获取缓存配置 + * + * @return array 当前缓存配置 + */ + public static function get_cache_config() { + return array( + 'max_memory_cache_size' => self::$max_memory_cache_size, + 'cache_expire_time' => self::$cache_expire_time + ); + } +} \ No newline at end of file diff --git a/includes/core/class-converter-factory.php b/includes/core/class-wpcc-converter-factory.php similarity index 79% rename from includes/core/class-converter-factory.php rename to includes/core/class-wpcc-converter-factory.php index 9f73b43..7e15e60 100644 --- a/includes/core/class-converter-factory.php +++ b/includes/core/class-wpcc-converter-factory.php @@ -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(); - - case 'mediawiki': - default: - require_once dirname( __FILE__ ) . '/class-mediawiki-converter.php'; - return new WPCC_MediaWiki_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-wpcc-mediawiki-converter.php'; + return new WPCC_MediaWiki_Converter(); + } } public static function get_available_engines() { diff --git a/includes/core/class-wpcc-exception-handler.php b/includes/core/class-wpcc-exception-handler.php new file mode 100644 index 0000000..07b7b10 --- /dev/null +++ b/includes/core/class-wpcc-exception-handler.php @@ -0,0 +1,301 @@ +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(); \ No newline at end of file diff --git a/includes/core/class-wpcc-extension.php b/includes/core/class-wpcc-extension.php new file mode 100644 index 0000000..b471551 --- /dev/null +++ b/includes/core/class-wpcc-extension.php @@ -0,0 +1,321 @@ +extension_slug = $this->get_extension_slug(); + $this->check_core_compatibility(); + $this->init_license_system(); + + parent::__construct(); + } + + /** + * 获取扩展唯一标识符 + */ + abstract protected function get_extension_slug(): string; + + /** + * 检查核心插件兼容性 + */ + private function check_core_compatibility(): bool { + if ( ! defined( 'wpcc_VERSION' ) ) { + add_action( 'admin_notices', [ $this, 'core_plugin_missing_notice' ] ); + return false; + } + + if ( version_compare( wpcc_VERSION, $this->required_core_version, '<' ) ) { + add_action( 'admin_notices', [ $this, 'core_plugin_outdated_notice' ] ); + return false; + } + + return true; + } + + /** + * 初始化许可证系统 + */ + private function init_license_system(): void { + if ( ! $this->is_premium ) { + return; + } + + $this->license_key = get_option( $this->extension_slug . '_license_key', '' ); + $this->license_status = get_option( $this->extension_slug . '_license_status', 'inactive' ); + + add_action( 'admin_init', [ $this, 'check_license_status' ] ); + add_action( 'wp_ajax_' . $this->extension_slug . '_activate_license', [ $this, 'activate_license' ] ); + add_action( 'wp_ajax_' . $this->extension_slug . '_deactivate_license', [ $this, 'deactivate_license' ] ); + } + + /** + * 检查许可证状态 + */ + public function check_license_status(): void { + if ( empty( $this->license_key ) ) { + return; + } + + // 每天检查一次许可证状态 + $last_check = get_option( $this->extension_slug . '_license_last_check', 0 ); + if ( time() - $last_check < DAY_IN_SECONDS ) { + return; + } + + $response = wp_remote_post( $this->update_server_url . '/api/license/check', [ + 'body' => [ + 'license_key' => $this->license_key, + 'product_slug' => $this->extension_slug, + 'site_url' => home_url() + ], + 'timeout' => 15 + ] ); + + if ( is_wp_error( $response ) ) { + return; + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( isset( $body['status'] ) ) { + $this->license_status = $body['status']; + update_option( $this->extension_slug . '_license_status', $this->license_status ); + } + + update_option( $this->extension_slug . '_license_last_check', time() ); + } + + /** + * 激活许可证 + */ + public function activate_license(): void { + if ( ! check_ajax_referer( $this->extension_slug . '_license_nonce', 'nonce' ) ) { + wp_die( 'Security check failed' ); + } + + $license_key = sanitize_text_field( $_POST['license_key'] ?? '' ); + if ( empty( $license_key ) ) { + wp_send_json_error( 'License key is required' ); + } + + $response = wp_remote_post( $this->update_server_url . '/api/license/activate', [ + 'body' => [ + 'license_key' => $license_key, + 'product_slug' => $this->extension_slug, + 'site_url' => home_url() + ] + ] ); + + if ( is_wp_error( $response ) ) { + wp_send_json_error( $response->get_error_message() ); + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( isset( $body['success'] ) && $body['success'] ) { + update_option( $this->extension_slug . '_license_key', $license_key ); + update_option( $this->extension_slug . '_license_status', 'active' ); + $this->license_key = $license_key; + $this->license_status = 'active'; + wp_send_json_success( 'License activated successfully' ); + } else { + wp_send_json_error( $body['message'] ?? 'License activation failed' ); + } + } + + /** + * 停用许可证 + */ + public function deactivate_license(): void { + if ( ! check_ajax_referer( $this->extension_slug . '_license_nonce', 'nonce' ) ) { + wp_die( 'Security check failed' ); + } + + wp_remote_post( $this->update_server_url . '/api/license/deactivate', [ + 'body' => [ + 'license_key' => $this->license_key, + 'product_slug' => $this->extension_slug, + 'site_url' => home_url() + ] + ] ); + + delete_option( $this->extension_slug . '_license_key' ); + delete_option( $this->extension_slug . '_license_status' ); + $this->license_key = ''; + $this->license_status = 'inactive'; + + wp_send_json_success( 'License deactivated successfully' ); + } + + /** + * 是否已授权 + */ + public function is_licensed(): bool { + return $this->license_status === 'active' || ! $this->is_premium; + } + + /** + * 获取许可证信息 + */ + public function get_license_info(): array { + return [ + 'key' => $this->license_key, + 'status' => $this->license_status, + 'is_premium' => $this->is_premium, + 'pricing_tier' => $this->pricing_tier + ]; + } + + /** + * 渲染许可证设置界面 + */ + public function render_license_settings(): string { + if ( ! $this->is_premium ) { + return ''; + } + + ob_start(); + ?> +
+

get_name() ); ?> License

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

✅ License is active

+
+ + +
+

⚠️ Please enter your license key to activate this extension

+
+ + + +
+ + + +
+

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

+
+ +
+

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

+
+ is_licensed(); + } + + /** + * 获取扩展状态(包含许可证信息) + */ + public function get_status(): array { + $status = parent::get_status(); + $status['license'] = $this->get_license_info(); + return $status; + } +} \ No newline at end of file diff --git a/includes/core/class-wpcc-language-config.php b/includes/core/class-wpcc-language-config.php new file mode 100644 index 0000000..979b3a6 --- /dev/null +++ b/includes/core/class-wpcc-language-config.php @@ -0,0 +1,165 @@ + [转换函数名, 选项键名, 默认显示名, 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; + } +} diff --git a/includes/core/class-wpcc-main.php b/includes/core/class-wpcc-main.php new file mode 100644 index 0000000..2dfa84d --- /dev/null +++ b/includes/core/class-wpcc-main.php @@ -0,0 +1,1151 @@ +config = WPCC_Config::get_instance(); + $this->converter_factory = new WPCC_Converter_Factory(); + $this->module_manager = WPCC_Module_Manager::get_instance(); + $this->cache = new WPCC_Conversion_Cache(); + + $this->init_hooks(); + } + + public static function get_instance(): self { + if ( null === self::$instance ) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * 初始化WordPress钩子 + */ + private function init_hooks(): void { + add_action( 'init', [ $this, 'init' ], 1 ); + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); + add_filter( 'query_vars', [ $this, 'add_query_vars' ] ); + // 在 WordPress 基于重写规则生成 query_vars 之后,优先级0先行修正 pagename=zh-xx 的场景 + add_filter( 'request', [ $this, 'filter_request_vars' ], 0 ); + add_action( 'parse_request', [ $this, 'parse_request' ] ); + add_action( 'template_redirect', [ $this, 'template_redirect' ], -100 ); + + // 进一步兜底:在主查询生成前修正“纯语言前缀”为首页查询 + add_action( 'pre_get_posts', [ $this, 'pre_get_posts_fix' ], 0 ); + + // 调试模式钩子 + if ( WP_DEBUG || ( defined( 'wpcc_DEBUG' ) && wpcc_DEBUG ) ) { + add_action( 'init', [ $this, 'flush_rewrite_rules' ] ); + add_action( 'wp_footer', [ $this, 'debug_output' ] ); + } + } + + /** + * 插件初始化 + */ + public function init(): void { + // 验证配置 + $config_errors = $this->config->validate_config(); + if ( ! empty( $config_errors ) ) { + $this->handle_config_errors( $config_errors ); + return; + } + + // 标记 AJAX/REST/wc-ajax 请求为"直接输出",跳过页面级转换 + $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'] !== ''; + $this->config->set_direct_conversion_flag( (bool) ( $is_ajax || $is_rest || $is_wc_ajax ) ); + + // 初始化模块 + $this->init_modules(); + + // 设置重写规则 + if ( $this->config->is_feature_enabled( 'wpcc_use_permalink' ) ) { + $this->setup_rewrite_rules(); + // 自愈:在启用固定链接模式但尚未生成 WPCC 语言规则时,尝试一次性刷新重写规则,避免 /zh-xx/ 访问 404 + $this->maybe_autoflush_rewrite_rules(); + } + + // 处理评论提交 + $this->handle_comment_submission(); + + // 修复首页显示问题 + if ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) ) { + add_action( 'parse_query', [ $this, 'fix_homepage_query' ] ); + } + + // 注册区块渲染过滤器 + add_filter( 'render_block', [ $this, 'render_no_conversion_block' ], 5, 2 ); + } + + /** + * 处理配置错误 + */ + private function handle_config_errors( array $errors ): void { + if ( is_admin() ) { + foreach ( $errors as $error ) { + add_action( 'admin_notices', function() use ( $error ) { + printf( + '

WP Chinese Converter: %s

', + esc_html( $error ) + ); + }); + } + } + + // 记录错误日志 + error_log( 'WPCC Config Errors: ' . implode( ', ', $errors ) ); + } + + /** + * 初始化模块 + */ + private function init_modules(): void { + $modules_dir = dirname( dirname( __FILE__ ) ) . '/modules/'; + + // 注册核心模块 + $this->module_manager->register_module( 'WPCC_Cache_Addon', $modules_dir . 'wpcc-cache-addon.php' ); + // $this->module_manager->register_module( 'WPCC_Network', $modules_dir . 'wpcc-network.php' ); // 已被新的网络设置模块替代 + $this->module_manager->register_module( 'WPCC_Rest_Api', $modules_dir . 'wpcc-rest-api.php' ); + $this->module_manager->register_module( 'WPCC_Modern_Cache', $modules_dir . 'wpcc-modern-cache.php' ); + $this->module_manager->register_module( 'WPCC_SEO_Enhancement', $modules_dir . 'wpcc-seo-enhancement.php' ); + + // 自动发现并加载模块 + $this->module_manager->auto_discover_modules(); + } + + /** + * 加载脚本和样式 + */ + public function enqueue_scripts(): void { + wp_register_script( + 'wpcc-variant', + plugins_url( '/assets/dist/wpcc-variant.umd.js', dirname( dirname( __FILE__ ) ) ), + [], + wpcc_VERSION + ); + + wp_register_script( + 'wpcc-block-script', + plugins_url( '/assets/js/wpcc-block-script-ok.js', dirname( dirname( __FILE__ ) ) ), + [ 'wp-blocks', 'wp-element', 'wpcc-variant' ], + wpcc_VERSION + ); + + wp_enqueue_script( [ 'wpcc-variant', 'wpcc-block-script' ] ); + + wp_localize_script( 'wpcc-block-script', 'wpcc_config', [ + 'use_permalink' => $this->config->get_option( 'wpcc_use_permalink', 0 ), + 'target_lang' => $this->config->get_target_lang(), + 'enabled_languages' => $this->config->get_enabled_languages(), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wpcc_nonce' ) + ]); + } + + /** + * 添加查询变量 + */ + public function add_query_vars( array $vars ): array { + $vars[] = 'variant'; + return $vars; + } + + /** + * 解析请求 + */ + public function parse_request( $wp ): void { + if ( is_robots() ) { + return; + } + + // 兜底:若 rewrite 未捕获到 variant,但路径以语言前缀开头,则从路径中注入 variant + $path = isset( $wp->request ) ? trim( (string) $wp->request, "\r\n\t " ) : ''; + if ( empty( $wp->query_vars['variant'] ) && is_string( $path ) && $path !== '' ) { + $enabled = $this->config->get_enabled_languages(); + if ( ! empty( $enabled ) && is_array( $enabled ) ) { + $reg = implode( '|', array_map( 'preg_quote', $enabled ) ); + if ( preg_match( '/^(' . $reg . '|zh|zh-reset)(?:\/)?$/i', $path, $m ) ) { + // 仅当是“纯前缀”(如 zh-tw/ 或 zh/)时兜底注入; + $wp->query_vars['variant'] = strtolower( $m[1] ); + } + } + } + + if ( is_404() ) { + $this->config->set_noconversion_url( home_url( '/' ) ); + $this->config->set_target_lang( '' ); + return; + } + + // 设置无转换URL + $noconversion_url = $this->get_noconversion_url(); + $this->config->set_noconversion_url( $noconversion_url ); + // 向后兼容:同步全局变量 + $GLOBALS['wpcc_noconversion_url'] = $noconversion_url; + + // 获取目标语言 + $target_lang = $this->determine_target_language( $wp ); + $this->config->set_target_lang( $target_lang ); + // 向后兼容:同步全局变量 + $GLOBALS['wpcc_target_lang'] = $target_lang; + + // 若首页为静态页面且请求为根级变体路径,则直接映射到首页,避免 404 + if ( get_option( 'show_on_front' ) === 'page' && get_option( 'page_on_front' ) ) { + $req_path = isset( $wp->request ) ? trim( (string) $wp->request, '/' ) : ''; + if ( $req_path !== '' ) { + $enabled = $this->config->get_enabled_languages(); + if ( ! empty( $enabled ) ) { + $pattern = '/^(?:' . implode( '|', array_map( 'preg_quote', $enabled ) ) . '|zh|zh-reset)$/i'; + if ( preg_match( $pattern, $req_path ) ) { + $wp->query_vars['page_id'] = (int) get_option( 'page_on_front' ); + unset( $wp->query_vars['pagename'], $wp->query_vars['name'] ); + } + } + } + } + + // 处理搜索转换 + $this->handle_search_conversion(); + + // 设置Cookie + $this->handle_language_cookie( $target_lang ); + } + + /** + * 确定目标语言 + */ + private function determine_target_language( $wp ): string { + $request_lang = isset( $wp->query_vars['variant'] ) ? sanitize_text_field( $wp->query_vars['variant'] ) : ''; + $cookie_lang = isset( $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ) ) : ''; + + // 检查URL参数中的语言 + if ( $request_lang && $this->config->is_language_enabled( $request_lang ) ) { + return $request_lang; + } + + // 处理特殊的'zh'重定向(用户明确选择“原始/不转换”) + if ( $request_lang === 'zh' && ! is_admin() ) { + $this->handle_zh_redirect(); + return ''; + } + + // 如果Cookie中记录了'zh'哨兵,则优先恢复为“不转换” + if ( $cookie_lang === 'zh' ) { + return ''; + } + + // 浏览器语言检测 + if ( $this->config->is_feature_enabled( 'wpcc_browser_redirect' ) ) { + $browser_lang = $this->detect_browser_language(); + if ( $browser_lang ) { + return $browser_lang; + } + } + + // Cookie语言偏好 + if ( $this->config->is_feature_enabled( 'wpcc_use_cookie_variant' ) && $cookie_lang ) { + if ( $this->config->is_language_enabled( $cookie_lang ) ) { + return $cookie_lang; + } + } + + return ''; + } + + /** + * 处理'zh'重定向 + */ + private function handle_zh_redirect(): void { + $cookie_key = 'wpcc_variant_' . COOKIEHASH; + + if ( $this->config->is_feature_enabled( 'wpcc_use_cookie_variant' ) ) { + setcookie( $cookie_key, 'zh', [ + 'expires' => time() + 30000000, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ] ); + } else { + setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', [ + 'expires' => 0, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ] ); + } + + wp_redirect( $this->config->get_noconversion_url() ); + exit; + } + + /** + * 检测浏览器语言 + */ + private function detect_browser_language(): string { + $accept_language = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) : ''; + if ( empty( $accept_language ) ) { + return ''; + } + + return wpcc_get_prefered_language( + $accept_language, + $this->config->get_enabled_languages(), + $this->config->get_option( 'wpcc_auto_language_recong', 0 ) + ) ?: ''; + } + + /** + * 处理语言Cookie + */ + private function handle_language_cookie( string $target_lang ): void { + if ( ! $target_lang || ! $this->config->is_feature_enabled( 'wpcc_use_cookie_variant' ) ) { + return; + } + + $cookie_key = 'wpcc_variant_' . COOKIEHASH; + $current_cookie = $_COOKIE[$cookie_key] ?? ''; + + if ( $current_cookie !== $target_lang ) { + setcookie( $cookie_key, $target_lang, [ + 'expires' => time() + 30000000, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ] ); + } + } + + /** + * 处理搜索转换 + */ + private function handle_search_conversion(): void { + $search_option = $this->config->get_option( 'wpcc_search_conversion', 0 ); + $target_lang = $this->config->get_target_lang(); + + if ( $search_option === 2 || ( $target_lang && $search_option === 1 ) ) { + add_filter( 'posts_where', [ $this, 'filter_search_query' ], 100 ); + add_filter( 'posts_distinct', function() { return 'DISTINCT'; } ); + } + } + + /** + * 模板重定向处理 + */ + public function template_redirect(): void { + // 排除 WooCommerce 关键页面(结算、购物车、我的账户等),避免转换影响功能 + if ( function_exists( 'is_checkout' ) && is_checkout() ) { + return; + } + if ( function_exists( 'is_cart' ) && is_cart() ) { + return; + } + if ( function_exists( 'is_account_page' ) && is_account_page() ) { + return; + } + + $this->set_language_urls(); + + // 处理“根级语言前缀”访问:/zh-xx/ 或 /zh/ + if ( ! is_admin() ) { + global $wp; + $req = isset( $wp->request ) ? trim( (string) $wp->request, "/" ) : ''; + if ( $req !== '' ) { + $enabled = $this->config->get_enabled_languages(); + $pattern = '/^(?:' . implode( '|', array_map( 'preg_quote', $enabled ) ) . '|zh|zh-reset)$/i'; + if ( preg_match( $pattern, $req ) ) { + // 禁用本次自动重定向(避免将根级变体再次重定向到其它 URL) + if ( method_exists( $this->config, 'set_redirect_to' ) ) { + $this->config->set_redirect_to( false ); + } + $v = strtolower( $req ); + + // 仅 zh/zh-reset 使用哨兵重定向到不转换首页;其它根级变体在当前 URL 下渲染(避免缓存都命中 /) + if ( $v === 'zh' || $v === 'zh-reset' ) { + $this->handle_zh_redirect(); + return; + } + + // 在根级变体 URL 下渲染首页:由下游 pre_get_posts/filter_request_vars 修正查询为首页 + // 注入头部脚本供前端使用 + add_action( 'wp_head', [ $this, 'output_header' ] ); + } + } + } + + // 处理重定向 + if ( ! is_404() && $this->config->get_redirect_to() && ! is_admin() ) { + $redirect_url = $this->config->get_langs_urls()[ $this->config->get_redirect_to() ]; + setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', [ + 'expires' => 0, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ] ); + wp_redirect( $redirect_url, 302 ); + exit; + } + + $target_lang = $this->config->get_target_lang(); + if ( ! $target_lang ) { + // 即使是“未选择语言(不转换)”也输出头部数据,供前端开关生成漂亮链接 + add_action( 'wp_head', [ $this, 'output_header' ] ); + return; + } + + // 添加评论表单语言参数 + add_action( 'comment_form', [ $this, 'add_comment_form_variant' ] ); + + // 执行转换 + $this->do_conversion(); + } + + /** + * 设置各语言版本的URL + */ + private function set_language_urls(): void { + $langs_urls = []; + $noconversion_url = $this->config->get_noconversion_url(); + $style = (int) $this->config->get_option( 'wpcc_use_permalink', 0 ); + $style_effective = $this->is_permalinks_enabled() ? $style : 0; + + // 语言映射 + foreach ( $this->config->get_enabled_languages() as $lang ) { + if ( $style_effective !== 0 && $noconversion_url === home_url( '/' ) ) { + $langs_urls[$lang] = $noconversion_url . $lang . '/'; + } else { + $langs_urls[$lang] = $this->convert_link( $noconversion_url, $lang ); + } + } + + // 增加 zh 哨兵映射,便于前端/菜单快速回到“不转换”状态 + // 在前缀/后缀模式下输出漂亮链接;在查询参数模式下回退为 ?variant=zh + $langs_urls['zh'] = $this->convert_link( $noconversion_url, 'zh' ); + + $this->config->set_langs_urls( $langs_urls ); + // 向后兼容:同步全局变量 + $GLOBALS['wpcc_langs_urls'] = $langs_urls; + } + + /** + * 执行转换 + */ + private function do_conversion(): void { + // 若是 AJAX/REST/wc-ajax 等非 HTML 响应,直接跳过所有转换,避免破坏 JSON/片段 + if ( $this->config->get_direct_conversion_flag() ) { + return; + } + + // 加载转换表 + $this->load_conversion_table(); + + // 输出头部信息 + add_action( 'wp_head', [ $this, 'output_header' ] ); + + if ( ! $this->config->get_direct_conversion_flag() ) { + // 移除默认canonical并添加自定义的 + remove_action( 'wp_head', 'rel_canonical' ); + add_action( 'wp_head', [ $this, 'output_canonical' ] ); + + // 添加链接转换过滤器 + $this->add_link_conversion_filters(); + + // 导航与 Logo 链接也加入变体(兼容经典菜单与站点 Logo) + // 注意:不在 home_url 上挂钩,避免 convert_link 内部调用 home_url 时产生递归 + add_filter( 'get_custom_logo', [ $this, 'filter_custom_logo' ], 10, 1 ); + add_filter( 'wp_nav_menu', [ $this, 'filter_wp_nav_menu' ], 20, 2 ); + + // 取消错误的 canonical 跳转,避免语言前缀被 WP 错误重定向 + add_filter( 'redirect_canonical', [ $this, 'cancel_incorrect_redirect' ], 10, 2 ); + } + + // 内容转换过滤器 + $this->add_content_conversion_filters(); + + // 全页面转换模式 + if ( $this->config->is_feature_enabled( 'wpcc_use_fullpage_conversion' ) ) { + ob_start( [ $this, 'full_page_conversion_callback' ] ); + } + } + + /** + * 获取配置实例 + */ + public function get_config(): WPCC_Config { + return $this->config; + } + + /** + * 获取转换器工厂 + */ + public function get_converter_factory(): WPCC_Converter_Factory { + return $this->converter_factory; + } + + /** + * 获取模块管理器 + */ + public function get_module_manager(): WPCC_Module_Manager { + return $this->module_manager; + } + + /** + * 获取缓存实例 + */ + public function get_cache(): WPCC_Conversion_Cache { + return $this->cache; + } + + /** + * 调试输出 + */ + public function debug_output(): void { + if ( ! ( WP_DEBUG || ( defined( 'wpcc_DEBUG' ) && wpcc_DEBUG ) ) ) { + return; + } + + $debug_data = $this->config->get_debug_data(); + if ( empty( $debug_data ) ) { + return; + } + + echo ''; + echo ''; + } + + /** + * 刷新重写规则(调试模式) + */ + public function flush_rewrite_rules(): void { + global $wp_rewrite; + $wp_rewrite->flush_rules(); + } + + // 这里添加其他必要的私有方法... + + /** + * 检查 WordPress 是否启用了固定链接 + */ + private function is_permalinks_enabled(): bool { + $structure = get_option( 'permalink_structure' ); + return is_string( $structure ) && $structure !== ''; + } + + /** + * 获取无转换URL + */ + private function get_noconversion_url(): string { + $enabled_langs = $this->config->get_enabled_languages(); + $reg = implode( '|', array_map( 'preg_quote', $enabled_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] ?? '', '/' ) ); + if ( $tmp === home_url() ) { + $tmp .= '/'; + } + } + + return $tmp; + } + + /** + * 设置重写规则 + */ + private function setup_rewrite_rules(): void { + // 仅在 WP 启用了固定链接 且 插件启用了固定链接格式时设置重写规则 + if ( ! $this->is_permalinks_enabled() || ! $this->config->is_feature_enabled( 'wpcc_use_permalink' ) ) { + return; + } + add_filter( 'rewrite_rules_array', [ $this, 'modify_rewrite_rules' ] ); + + // 显式在规则顶端加入"根级变体"规则,确保 /zh-xx/ 能优先匹配到首页 + $enabled = $this->config->get_enabled_languages(); + if ( ! empty( $enabled ) && function_exists( 'add_rewrite_rule' ) ) { + $reg = implode( '|', array_map( 'preg_quote', $enabled ) ); + add_rewrite_rule( '^(' . $reg . '|zh|zh-reset)/?$', 'index.php?variant=$matches[1]', 'top' ); + + // 强制在init钩子中添加根级规则,确保优先级 + add_action( 'init', function() use ( $reg ) { + add_rewrite_rule( '^(' . $reg . '|zh|zh-reset)/?$', 'index.php?variant=$matches[1]', 'top' ); + }, 5 ); // 优先级5,早于大部分其他规则 + } + } + + /** + * 如有必要,自动刷新一次重写规则,避免未刷新导致的 404 + */ + private function maybe_autoflush_rewrite_rules(): void { + // 仅在启用了固定链接模式且 WP 启用了固定链接时尝试 + if ( ! $this->config->is_feature_enabled( 'wpcc_use_permalink' ) || ! $this->is_permalinks_enabled() ) { + return; + } + + // 延迟执行,确保所有规则都已加载 + add_action( 'wp_loaded', [ $this, 'check_and_flush_rules' ], 10 ); + } + + /** + * 检查并刷新规则 + */ + public function check_and_flush_rules(): void { + // 6 小时内最多尝试一次 + $last = (int) get_option( 'wpcc_rewrite_autoflush_ts', 0 ); + if ( $last && ( time() - $last ) < 6 * 3600 ) { + return; + } + + // 检查当前规则中是否已经包含根级的语言捕获规则 + $rules = get_option( 'rewrite_rules' ); + if ( ! is_array( $rules ) ) { + $rules = []; + } + + $enabled_langs = $this->config->get_enabled_languages(); + if ( empty( $enabled_langs ) ) { + return; + } + + $reg = implode( '|', $enabled_langs ); + $expected = '^(' . $reg . '|zh|zh-reset)/?$'; + $has_expected = false; + + foreach ( $rules as $regex => $query ) { + if ( $regex === $expected && strpos( $query, 'variant=' ) !== false ) { + $has_expected = true; + break; + } + } + + if ( ! $has_expected && function_exists( 'flush_rewrite_rules' ) ) { + // 刷新一次规则 + flush_rewrite_rules( false ); + update_option( 'wpcc_rewrite_autoflush_ts', time() ); + + // 记录日志 + if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { + error_log( 'WPCC: Auto-flushed rewrite rules for language variants' ); + } + } + } + + /** + * 修改重写规则 + */ + public function modify_rewrite_rules( array $rules ): array { + $enabled_langs = $this->config->get_enabled_languages(); + $reg = implode( '|', $enabled_langs ); + $rules2 = []; + + $use_permalink = $this->config->get_option( 'wpcc_use_permalink', 0 ); + + // 首先添加根级语言规则,确保最高优先级 + $root_rule_key = '^(' . $reg . '|zh|zh-reset)/?$'; + $rules2[ $root_rule_key ] = 'index.php?variant=$matches[1]'; + + if ( $use_permalink == 1 ) { + foreach ( $rules as $key => $value ) { + if ( strpos( $key, 'trackback' ) !== false || strpos( $key, 'print' ) !== false || strpos( $value, 'lang=' ) !== false ) { + continue; + } + if ( substr( $key, -3 ) == '/?$' ) { + if ( ! preg_match_all( '/\\$matches\\[(\\d+)\\]/', $value, $matches, PREG_PATTERN_ORDER ) ) { + continue; + } + $number = count( $matches[0] ) + 1; + $rules2[ substr( $key, 0, -3 ) . '/(' . $reg . '|zh|zh-reset)/?$' ] = $value . '&variant=$matches[' . $number . ']'; + } + } + } else { + foreach ( $rules as $key => $value ) { + if ( strpos( $key, 'trackback' ) !== false || strpos( $key, 'print' ) !== false || strpos( $value, 'lang=' ) !== false ) { + continue; + } + if ( substr( $key, -3 ) == '/?$' ) { + $rules2[ '(' . $reg . '|zh|zh-reset)/' . $key ] = preg_replace_callback( '/\\$matches\\[(\\d+)\\]/', [ $this, 'permalink_preg_callback' ], $value ) . '&variant=$matches[1]'; + } + } + } + + return array_merge( $rules2, $rules ); + } + + /** + * URL重写回调函数 + */ + private function permalink_preg_callback( array $matches ): string { + return '$matches[' . ( intval( $matches[1] ) + 1 ) . ']'; + } + + /** + * 处理评论提交 + */ + private function handle_comment_submission(): void { + $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 ) + ) && + $request_method === 'POST' && + isset( $_POST['variant'] ) && ! empty( $_POST['variant'] ) + ) { + $variant = sanitize_text_field( wp_unslash( $_POST['variant'] ) ); + if ( $this->config->is_language_enabled( $variant ) ) { + $this->config->set_target_lang( $variant ); + $this->do_conversion(); + } + } + } + + /** + * 修复首页查询 + */ + public function fix_homepage_query( $wp_query ) { + $qv = &$wp_query->query_vars; + + if ( $wp_query->is_home && 'page' == get_option( 'show_on_front' ) && get_option( 'page_on_front' ) ) { + $_query = wp_parse_args( $wp_query->query ); + if ( isset( $_query['pagename'] ) && '' == $_query['pagename'] ) { + unset( $_query['pagename'] ); + } + if ( empty( $_query ) || ! array_diff( array_keys( $_query ), [ + 'preview', 'page', 'paged', 'cpage', 'variant' + ] ) ) { + $wp_query->is_page = true; + $wp_query->is_home = false; + $qv['page_id'] = get_option( 'page_on_front' ); + if ( ! empty( $qv['paged'] ) ) { + $qv['page'] = $qv['paged']; + unset( $qv['paged'] ); + } + } + } + + return $wp_query; + } + + /** + * 渲染不转换区块 + */ + public function render_no_conversion_block( string $block_content, array $block ): string { + if ( isset( $block['blockName'] ) && $block['blockName'] === 'wpcc/no-conversion' ) { + $unique_id = uniqid(); + return '' . $block_content . ''; + } + return $block_content; + } + + /** + * 添加评论表单语言参数 + */ + public function add_comment_form_variant(): void { + $target_lang = $this->config->get_target_lang(); + if ( $target_lang ) { + echo ''; + } + } + + /** + * 转换链接 + */ + private function convert_link( string $link, string $variant ): string { + static $wp_home; + if ( empty( $wp_home ) ) { + $wp_home = home_url(); + } + + if ( empty( $variant ) ) { + return $link; + } + + // 仅当路径段或查询参数中已包含有效变体时,认为“已包含” + $enabled = $this->config->get_enabled_languages(); + $enabled = is_array( $enabled ) ? $enabled : []; + $variant_regex = '#/(?:' . implode( '|', array_map( 'preg_quote', $enabled ) ) . '|zh|zh-reset)(/|$)#i'; + + $qpos = strpos( $link, '?' ); + $path = $qpos !== false ? substr( $link, 0, $qpos ) : $link; + $qs = $qpos !== false ? substr( $link, $qpos ) : ''; + $path_only = parse_url( $path, PHP_URL_PATH ); + if ( $path_only === null ) { $path_only = $path; } + + // 如路径中已有变体,仅清理冗余的 variant 查询参数然后返回 + if ( preg_match( $variant_regex, (string) $path_only ) ) { + if ( $qpos !== false ) { + $qs = preg_replace( '/([?&])variant=[^&]*(&|$)/', '$1', $qs ); + $qs = rtrim( $qs, '?&' ); + if ( $qs && $qs[0] !== '?' ) { $qs = '?' . ltrim( $qs, '?' ); } + } + return $path . $qs; + } + + $style = (int) $this->config->get_option( 'wpcc_use_permalink', 0 ); + + // 查询字符串模式或未启用固定链接(WP未启用固定链接时也回退到查询参数) + if ( $style === 0 || ! $this->config->is_feature_enabled( 'wpcc_use_permalink' ) || ! $this->is_permalinks_enabled() ) { + return add_query_arg( 'variant', $variant, $link ); + } + + if ( $style === 1 ) { + // 后缀模式: .../permalink/.../zh-xx/ + return user_trailingslashit( trailingslashit( $path ) . $variant ) . $qs; + } + + // 前缀模式 (style 2): .../zh-xx/permalink/... + return str_replace( $wp_home, "$wp_home/$variant", $path ) . $qs; + } + + /** + * 加载转换表 + */ + private function load_conversion_table(): void { + // 这个方法会调用原有的全局函数来保持兼容性 + if ( function_exists( 'wpcc_load_conversion_table' ) ) { + wpcc_load_conversion_table(); + } + } + + /** + * 添加链接转换过滤器 + */ + private function add_link_conversion_filters(): void { + add_filter( 'post_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'month_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'day_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'year_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'page_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'tag_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'author_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'category_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'feed_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'attachment_link', [ $this, 'filter_link_conversion' ] ); + add_filter( 'search_feed_link', [ $this, 'filter_link_conversion' ] ); + + // 添加链接修复过滤器(重要!) + if ( function_exists( 'wpcc_fix_link_conversion' ) ) { + add_filter( 'category_feed_link', 'wpcc_fix_link_conversion' ); + add_filter( 'tag_feed_link', 'wpcc_fix_link_conversion' ); + add_filter( 'author_feed_link', 'wpcc_fix_link_conversion' ); + add_filter( 'post_comments_feed_link', 'wpcc_fix_link_conversion' ); + add_filter( 'get_comments_pagenum_link', 'wpcc_fix_link_conversion' ); + add_filter( 'get_comment_link', 'wpcc_fix_link_conversion' ); + } + + // 添加取消转换过滤器 + if ( function_exists( 'wpcc_cancel_link_conversion' ) ) { + add_filter( 'attachment_link', 'wpcc_cancel_link_conversion' ); + add_filter( 'trackback_url', 'wpcc_cancel_link_conversion' ); + } + + // 添加分页链接修复 + if ( function_exists( 'wpcc_pagenum_link_fix' ) ) { + add_filter( 'get_pagenum_link', 'wpcc_pagenum_link_fix' ); + } + } + + /** + * 过滤链接转换 + */ + public function filter_link_conversion( string $link ): string { + // 使用全局函数以保持一致性 + if ( function_exists( 'wpcc_link_conversion' ) ) { + return wpcc_link_conversion( $link, $this->config->get_target_lang() ); + } + return $this->convert_link( $link, $this->config->get_target_lang() ); + } + + /** + * 过滤 request 阶段的 query_vars: + * - 当 pagename 等于语言前缀(zh-xx|zh|zh-reset)时,注入 variant 并指向首页,避免 404 + */ + public function filter_request_vars( array $qv ): array { + if ( is_admin() ) { return $qv; } + $enabled = $this->config->get_enabled_languages(); + if ( empty( $enabled ) ) { return $qv; } + $candidate = ''; + if ( isset( $qv['pagename'] ) && is_string( $qv['pagename'] ) ) { + $candidate = trim( $qv['pagename'], '/' ); + } elseif ( isset( $qv['name'] ) && is_string( $qv['name'] ) ) { + $candidate = trim( $qv['name'], '/' ); + } + if ( $candidate === '' ) { return $qv; } + $langs = array_map( 'strtolower', $enabled ); + $candidate_l = strtolower( $candidate ); + if ( in_array( $candidate_l, $langs, true ) || $candidate_l === 'zh' || $candidate_l === 'zh-reset' ) { + // 注入 variant + $qv['variant'] = $candidate_l; + // 指向首页 + if ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) ) { + $qv['page_id'] = (int) get_option( 'page_on_front' ); + if ( ! empty( $qv['paged'] ) ) { $qv['page'] = $qv['paged']; unset( $qv['paged'] ); } + } else { + // 文章作为首页:移除 pagename/name,交由 is_home 处理 + unset( $qv['pagename'], $qv['name'] ); + } + } + return $qv; + } + + /** + * 过滤 home_url,使首页链接带上变体(仅前台且存在目标语言时) + */ + public function filter_home_url( string $url, string $path, ?string $orig_scheme, ?int $blog_id ): string { + if ( is_admin() ) { return $url; } + $target = $this->config->get_target_lang(); + if ( ! $target ) { return $url; } + return $this->convert_link( $url, $target ); + } + + /** + * 调整自定义 Logo 的链接 href(仅前台且存在目标语言时) + */ + public function filter_custom_logo( string $html ): string { + if ( is_admin() ) { return $html; } + $target = $this->config->get_target_lang(); + if ( ! $target ) { return $html; } + $home = home_url(); + $that = $this; + $out = preg_replace_callback('/href=(\"|\')(.*?)(\1)/i', function($m) use ($home, $target, $that) { + $href = $m[2]; + if ( strpos( $href, $home ) === 0 ) { + $new = $that->convert_link( $href, $target ); + return 'href=' . $m[1] . esc_url( $new ) . $m[1]; + } + return $m[0]; + }, $html ); + return $out ?: $html; + } + + /** + * 调整经典菜单(wp_nav_menu)输出中的 href(仅前台且存在目标语言时) + */ + public function filter_wp_nav_menu( string $nav_menu, $args ): string { + if ( is_admin() ) { return $nav_menu; } + $target = $this->config->get_target_lang(); + if ( ! $target ) { return $nav_menu; } + $home = home_url(); + $that = $this; + $out = preg_replace_callback('/href=(\"|\')(.*?)(\1)/i', function($m) use ($home, $target, $that) { + $href = html_entity_decode( $m[2] ); + // 仅转换本站链接 + if ( strpos( $href, $home ) === 0 ) { + $new = $that->convert_link( $href, $target ); + return 'href=' . $m[1] . esc_url( $new ) . $m[1]; + } + return $m[0]; + }, $nav_menu ); + return $out ?: $nav_menu; + } + + /** + * pre_get_posts 兜底:当请求仅为语言前缀时,强制首页查询,避免 404。 + */ + public function pre_get_posts_fix( $q ): void { + if ( is_admin() || ! $q || ! method_exists( $q, 'is_main_query' ) || ! $q->is_main_query() ) { + return; + } + $enabled = $this->config->get_enabled_languages(); + if ( empty( $enabled ) ) { return; } + $path = isset( $_SERVER['REQUEST_URI'] ) ? (string) $_SERVER['REQUEST_URI'] : ''; + if ( $path === '' ) { return; } + $path = trim( $path, '/' ); + $candidate = $path; + // 仅当整个路径就是语言前缀(忽略末尾斜杠和查询串) + if ( strpos( $candidate, '/' ) !== false ) { + // 含有更多段,忽略 + return; + } + $langs = array_map( 'strtolower', $enabled ); + $candidate_l = strtolower( $candidate ); + if ( in_array( $candidate_l, $langs, true ) || $candidate_l === 'zh' || $candidate_l === 'zh-reset' ) { + // 注入 variant + $q->set( 'variant', $candidate_l ); + // 修正首页查询 + if ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) ) { + $q->set( 'page_id', (int) get_option( 'page_on_front' ) ); + if ( $q->get( 'paged' ) ) { $q->set( 'page', $q->get( 'paged' ) ); $q->set( 'paged', 0 ); } + } else { + $q->set( 'pagename', '' ); + $q->set( 'name', '' ); + } + // 避免被错误判为 404 + if ( method_exists( $q, 'is_404' ) ) { + $q->is_404 = false; + } + } + } + + /** + * 取消错误 canonical 跳转,参考旧版逻辑 + */ + public function cancel_incorrect_redirect( $redirect_to, $redirect_from ) { + if ( ! is_string( $redirect_to ) || ! is_string( $redirect_from ) ) { return $redirect_to; } + + // 动态构建允许的语言前缀集合 + $langs = method_exists( 'WPCC_Language_Config', 'get_valid_language_codes' ) ? WPCC_Language_Config::get_valid_language_codes() : [ 'zh-cn','zh-tw','zh-hk','zh-hans','zh-hant','zh-sg','zh-jp' ]; + $reg = implode( '|', array_map( 'preg_quote', $langs ) ); + + // 如果来源是变体路径(根级或包含变体段),阻止将其规范化到非变体路径(例如首页 /) + if ( preg_match( '/\/(' . $reg . '|zh|zh-reset)(\/|$)/i', $redirect_from ) ) { + // 允许仅修正末尾斜杠 + global $wp_rewrite; + if ( ( $wp_rewrite && $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) != '/' ) || + ( $wp_rewrite && ! $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) == '/' ) ) { + return user_trailingslashit( $redirect_from ); + } + return false; // 阻止从变体路径跳到非变体路径 + } + + // 如果目标是变体路径,确保不因斜杠规范导致错误跳转 + if ( preg_match( '/\/(' . $reg . '|zh|zh-reset)(\/|$)/i', $redirect_to ) ) { + global $wp_rewrite; + if ( ( $wp_rewrite && $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) != '/' ) || + ( $wp_rewrite && ! $wp_rewrite->use_trailing_slashes && substr( $redirect_from, -1 ) == '/' ) ) { + return user_trailingslashit( $redirect_from ); + } + return false; // 阻止多余跳转 + } + + return $redirect_to; + } + + /** + * 添加内容转换过滤器 + */ + private function add_content_conversion_filters(): void { + add_filter( 'the_content', 'zhconversion2', 20 ); + add_filter( 'the_content_rss', 'zhconversion2', 20 ); + add_filter( 'the_excerpt', 'zhconversion2', 20 ); + add_filter( 'the_excerpt_rss', 'zhconversion2', 20 ); + add_filter( 'the_title', 'zhconversion' ); + add_filter( 'comment_text', 'zhconversion' ); + add_filter( 'bloginfo', 'zhconversion' ); + add_filter( 'the_tags', 'zhconversion_deep' ); + add_filter( 'term_links-post_tag', 'zhconversion_deep' ); + add_filter( 'wp_tag_cloud', 'zhconversion' ); + add_filter( 'the_category', 'zhconversion' ); + add_filter( 'list_cats', 'zhconversion' ); + add_filter( 'category_description', 'zhconversion' ); + add_filter( 'single_cat_title', 'zhconversion' ); + add_filter( 'single_post_title', 'zhconversion' ); + add_filter( 'bloginfo_rss', 'zhconversion' ); + add_filter( 'the_title_rss', 'zhconversion' ); + add_filter( 'comment_text_rss', 'zhconversion' ); + } + + /** + * 输出头部信息 + */ + public function output_header(): void { + $target_lang = $this->config->get_target_lang(); + $noconversion_url = $this->config->get_noconversion_url(); + $langs_urls = $this->config->get_langs_urls(); + + // 当启用了“浏览器语言”或“Cookie偏好”的显示模式时,为“不转换”链接注入 zh 哨兵,确保能覆盖浏览器/Cookie策略 + $browser_pref = (int) $this->config->get_option( 'wpcc_browser_redirect', 0 ); + $cookie_pref = (int) $this->config->get_option( 'wpcc_use_cookie_variant', 0 ); + $noconv_forced = $noconversion_url; + if ( ($browser_pref === 2 || $cookie_pref === 2) && $target_lang ) { + $noconv_forced = $this->convert_link( $noconversion_url, 'zh' ); + } + + echo "\n" . ''; + + $script_data = [ + 'wpcc_target_lang' => $target_lang ? esc_js( $target_lang ) : '', + 'wpcc_noconversion_url' => $noconv_forced ? esc_url( $noconv_forced ) : '', + 'wpcc_langs_urls' => [] + ]; + + if ( is_array( $langs_urls ) ) { + foreach ( $langs_urls as $key => $value ) { + $safe_key = preg_match( '/^[a-z-]+$/', $key ) ? $key : ''; + if ( $safe_key && filter_var( $value, FILTER_VALIDATE_URL ) ) { + $script_data['wpcc_langs_urls'][ $safe_key ] = esc_url( $value ); + } + } + } + + echo ''; + } + + /** + * 输出canonical链接 + */ + public function output_canonical(): void { + if ( ! is_singular() ) { + return; + } + global $wp_the_query; + if ( ! $id = $wp_the_query->get_queried_object_id() ) { + return; + } + $link = get_permalink( $id ); + // 移除语言参数 + $link = remove_query_arg( 'variant', $link ); + echo "\n"; + } + + /** + * 全页面转换回调 + */ + public function full_page_conversion_callback( string $buffer ): string { + $target_lang = $this->config->get_target_lang(); + if ( $target_lang && ! $this->config->get_direct_conversion_flag() ) { + $home_url = $this->convert_link( home_url( '/' ), $target_lang ); + $home_pattern = preg_quote( esc_url( home_url( '' ) ), '|' ); + $buffer = preg_replace( '|(]*?href=([\'\"]))' . $home_pattern . '/?(\2[^<>]*?>)|', '${1}' . esc_url( $home_url ) . '${3}', $buffer ); + } + return zhconversion2( $buffer ) . "\n" . ''; + } + + /** + * 过滤搜索查询 + */ + public function filter_search_query( string $where ): string { + // 调用原有的全局函数来保持兼容性 + if ( function_exists( 'wpcc_filter_search_rule' ) ) { + return wpcc_filter_search_rule( $where ); + } + return $where; + } +} diff --git a/includes/core/class-mediawiki-converter.php b/includes/core/class-wpcc-mediawiki-converter.php similarity index 70% rename from includes/core/class-mediawiki-converter.php rename to includes/core/class-wpcc-mediawiki-converter.php index 564716b..7819cc2 100644 --- a/includes/core/class-mediawiki-converter.php +++ b/includes/core/class-wpcc-mediawiki-converter.php @@ -7,109 +7,124 @@ 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 ) { if ( empty( $text ) ) { return $text; } - - $this->ensure_conversion_tables_loaded(); - + + // 检查缓存 + $cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant ); + if ( $cached_result !== null ) { + return $cached_result; + } + + // 回滚:始终加载所有转换表 + $this->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() { return array( 'zh-hans', 'zh-hant', 'zh-cn', 'zh-tw', 'zh-hk', 'zh-sg' ); } - + public function get_engine_name() { return 'mediawiki'; } - + public function get_engine_info() { return array( 'name' => 'MediaWiki', 'version' => '1.23.5', - 'description' => '基于 MediaWiki 的字符映射转换引擎', + 'description' => '基于MediaWiki的字符映射转换引擎', 'features' => array( '字符级精确映射', '快速转换速度', '良好的兼容性', '支持多地区变体' ), - 'memory_usage' => '约 1.5MB (转换表加载后)', 'conversion_type' => 'character_mapping' ); } - + public function is_available() { $conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php'; return file_exists( $conversion_file ); } - - private function ensure_conversion_tables_loaded() { + + private function 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; } } - + $this->conversion_tables_loaded = true; } - + private function convert_to_hans( $text ) { global $zh2Hans; return strtr( $text, $zh2Hans ); } - + private function convert_to_hant( $text ) { global $zh2Hant; return strtr( $text, $zh2Hant ); } - + private function convert_to_cn( $text ) { global $zh2Hans, $zh2CN; return strtr( strtr( $text, $zh2CN ), $zh2Hans ); } - + private function convert_to_tw( $text ) { global $zh2Hant, $zh2TW; return strtr( strtr( $text, $zh2TW ), $zh2Hant ); } - + private function convert_to_hk( $text ) { global $zh2Hant, $zh2HK; return strtr( strtr( $text, $zh2HK ), $zh2Hant ); } - + private function convert_to_sg( $text ) { global $zh2Hans, $zh2SG; return strtr( strtr( $text, $zh2SG ), $zh2Hans ); } -} \ No newline at end of file +} diff --git a/includes/core/class-module-manager.php b/includes/core/class-wpcc-module-manager.php similarity index 100% rename from includes/core/class-module-manager.php rename to includes/core/class-wpcc-module-manager.php diff --git a/includes/core/class-wpcc-opencc-converter.php b/includes/core/class-wpcc-opencc-converter.php new file mode 100644 index 0000000..54fd00e --- /dev/null +++ b/includes/core/class-wpcc-opencc-converter.php @@ -0,0 +1,233 @@ +>> 每个策略对应若干个字典映射,保持原有顺序 + */ + 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; + } + } +} \ No newline at end of file diff --git a/includes/core/class-wpcc-presets.php b/includes/core/class-wpcc-presets.php new file mode 100644 index 0000000..285f7be --- /dev/null +++ b/includes/core/class-wpcc-presets.php @@ -0,0 +1,194 @@ + ['zh-cn','zh-tw'], + 'cntip' => '简体', + 'twtip' => '繁体', + 'hktip' => '港澳', + 'hanstip' => '简体', + 'hanttip' => '繁体', + 'sgtip' => '马新', + 'jptip' => '日式', + 'wpcc_translate_type' => 0, + 'wpcc_engine' => 'opencc', + 'wpcc_search_conversion' => 1, + 'wpcc_use_fullpage_conversion' => 0, + 'wpcc_no_conversion_qtag' => 0, + 'wpcc_enable_post_conversion' => 0, + 'wpcc_post_conversion_target' => 'zh-cn', + 'wpcc_no_conversion_tag' => 'pre,code,wp-block-code', + 'wpcc_no_conversion_ja' => 1, + 'nctip' => '', + 'wpcc_browser_redirect' => 0, + 'wpcc_auto_language_recong' => 0, + 'wpcc_use_cookie_variant' => 2, + 'wpcc_use_permalink' => 0, + 'wpcco_use_sitemap' => 0, + 'wpcco_sitemap_post_type' => 'post,page', + 'wpcc_enable_cache_addon' => 1, + 'wpcc_enable_network_module' => 0, + 'wpcc_enable_hreflang_tags' => 1, + 'wpcc_hreflang_x_default' => 'zh-cn', + 'wpcc_enable_schema_conversion' => 1, + 'wpcc_enable_meta_conversion' => 1, + ]; + + return [ + // 恢复到插件的“工厂默认值”(更广覆盖的默认语言集与基础选项) +'factory_default' => [ + 'label' => __( '恢复默认', 'wp-chinese-converter' ), + 'options' => [ + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + 'cntip' => '简体', + 'twtip' => '繁体', + 'hktip' => '港澳', + 'hanstip' => '简体', + 'hanttip' => '繁体', + 'sgtip' => '马新', + 'jptip' => '日式', + + 'wpcc_engine' => 'mediawiki', + 'wpcc_search_conversion' => 1, + + 'wpcc_browser_redirect' => 0, + 'wpcc_auto_language_recong' => 0, + 'wpcc_use_cookie_variant' => 1, + + 'wpcc_use_fullpage_conversion' => 0, + +'wpcc_no_conversion_tag' => '', + 'wpcc_no_conversion_ja' => 0, + 'wpcc_no_conversion_qtag' => 0, + + 'wpcc_enable_post_conversion' => 0, + 'wpcc_post_conversion_target' => 'zh-cn', + + 'wpcc_use_permalink' => 0, + 'wpcco_use_sitemap' => 0, + 'wpcco_sitemap_post_type' => 'post,page', + + 'wpcc_enable_hreflang_tags' => 1, + 'wpcc_hreflang_x_default' => 'zh-cn', + 'wpcc_enable_schema_conversion' => 1, + 'wpcc_enable_meta_conversion' => 1, + + 'wpcc_enable_cache_addon' => 1, + 'wpcc_enable_network_module' => 0, + + 'wpcc_translate_type' => 0, + 'nctip' => '', + 'wpcc_flag_option' => 1, + 'wpcc_trackback_plugin_author' => 0, + 'wpcc_add_author_link' => 0, + ], + ], + + // 内容/资讯站 + 'content_site' => [ + 'label' => __( '内容站', 'wp-chinese-converter' ), + 'options' => array_merge( $base, [ + 'wpcc_engine' => 'opencc', + 'wpcc_search_conversion' => 2, + 'wpcco_use_sitemap' => 1, + 'wpcc_use_fullpage_conversion' => 0, + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + ]), + ], + + // 企业站 + 'corporate' => [ + 'label' => __( '企业站', 'wp-chinese-converter' ), + 'options' => array_merge( $base, [ + 'wpcc_engine' => 'mediawiki', + 'wpcc_search_conversion' => 0, + 'wpcco_use_sitemap' => 1, + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + ]), + ], + + // 技术文档/知识库 + 'docs' => [ + 'label' => __( '技术文档', 'wp-chinese-converter' ), + 'options' => array_merge( $base, [ + 'wpcc_engine' => 'opencc', + 'wpcc_search_conversion' => 1, + 'wpcc_use_fullpage_conversion' => 0, + 'wpcc_no_conversion_tag' => 'pre,code,wp-block-code,kbd,samp', + 'wpcc_no_conversion_ja' => 1, + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + ]), + ], + + // 高性能/兼容优先 + 'performance' => [ + 'label' => __( '高性能', 'wp-chinese-converter' ), + 'options' => array_merge( $base, [ + 'wpcc_engine' => 'mediawiki', + 'wpcc_search_conversion' => 0, + 'wpcc_use_fullpage_conversion' => 0, + 'wpcc_used_langs' => ['zh-cn','zh-tw'], + ]), + ], + ]; + } + + /** + * 应用预设 + */ + public static function apply_preset( string $slug ): bool { + $presets = self::get_presets(); + if ( ! isset( $presets[ $slug ] ) ) { + return false; + } + + $new = $presets[ $slug ]['options']; + $current = get_wpcc_option( 'wpcc_options', [] ); + if ( ! is_array( $current ) ) { + $current = []; + } + + // 合并:仅覆盖预设中出现的键,其他用户自定义保持不变 + $merged = array_merge( $current, $new ); + + $before_permalink = isset( $current['wpcc_use_permalink'] ) ? intval( $current['wpcc_use_permalink'] ) : 0; + $after_permalink = isset( $merged['wpcc_use_permalink'] ) ? intval( $merged['wpcc_use_permalink'] ) : 0; + + $ok = update_wpcc_option( 'wpcc_options', $merged ); + + // 若固定链接设定发生变化,则刷新重写规则 + if ( $before_permalink !== $after_permalink ) { + if ( function_exists( 'flush_rewrite_rules' ) ) { + flush_rewrite_rules(); + } + } + + return (bool) $ok; + } + + /** + * 获取可用于UI的预设清单(slug => label) + */ + public static function get_preset_labels(): array { + $labels = []; + foreach ( self::get_presets() as $slug => $preset ) { + $labels[ $slug ] = $preset['label'] ?? $slug; + } + return $labels; + } +} diff --git a/includes/core/class-wpcc-utils.php b/includes/core/class-wpcc-utils.php new file mode 100644 index 0000000..eb6b929 --- /dev/null +++ b/includes/core/class-wpcc-utils.php @@ -0,0 +1,213 @@ + $traditional_count ) { + return 'simplified'; + } elseif ( $traditional_count > $simplified_count ) { + return 'traditional'; + } + + return 'mixed'; + } + + /** + * 格式化内存使用量 + */ + public static function format_memory_usage( int $bytes ): string { + $units = ['B', 'KB', 'MB', 'GB']; + $factor = floor( ( strlen( (string) $bytes ) - 1 ) / 3 ); + + return sprintf( "%.1f%s", $bytes / pow( 1024, $factor ), $units[ $factor ] ?? 'TB' ); + } + + /** + * 生成缓存键 + */ + public static function generate_cache_key( string ...$parts ): string { + $key = implode( '|', array_filter( $parts ) ); + return 'wpcc_' . md5( $key ); + } + + /** + * 安全的字符串截取(支持多字节字符) + */ + public static function safe_substr( string $str, int $start, ?int $length = null ): string { + return mb_substr( $str, $start, $length, 'UTF-8' ); + } + + /** + * 清理和验证用户输入 + */ + public static function sanitize_language_input( $input ): array { + if ( ! is_array( $input ) ) { + return []; + } + + return array_filter( + array_map( 'sanitize_text_field', $input ), + [ self::class, 'is_valid_language_code' ] + ); + } + + /** + * 获取当前请求的语言偏好 + */ + public static function get_request_language_preference(): ?string { + // 从 URL 参数获取 + $url_lang = $_GET['variant'] ?? null; + if ( $url_lang && self::is_valid_language_code( $url_lang ) ) { + return $url_lang; + } + + // 从 Cookie 获取 + $cookie_lang = $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ?? null; + if ( $cookie_lang && self::is_valid_language_code( $cookie_lang ) ) { + return $cookie_lang; + } + + // 从浏览器 Accept-Language 头获取 + $accept_lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''; + if ( $accept_lang ) { + return self::parse_accept_language( $accept_lang ); + } + + return null; + } + + /** + * 解析 Accept-Language 头 + */ + private static function parse_accept_language( string $accept_language ): ?string { + $supported_langs = [ + 'zh-cn' => ['zh-cn', 'zh-hans', 'zh'], + 'zh-tw' => ['zh-tw', 'zh-hant'], + 'zh-hk' => ['zh-hk'], + 'zh-sg' => ['zh-sg'] + ]; + + // 解析语言偏好 + preg_match_all( '/([a-z]{2,3}(?:-[a-z]{2,4})?)\s*(?:;\s*q\s*=\s*([\d.]+))?/i', + $accept_language, $matches, PREG_SET_ORDER ); + + $preferences = []; + foreach ( $matches as $match ) { + $lang = strtolower( $match[1] ); + $quality = isset( $match[2] ) ? (float) $match[2] : 1.0; + $preferences[ $lang ] = $quality; + } + + // 按质量值排序 + arsort( $preferences ); + + // 匹配支持的语言 + foreach ( $preferences as $lang => $quality ) { + foreach ( $supported_langs as $supported => $aliases ) { + if ( in_array( $lang, $aliases, true ) ) { + return $supported; + } + } + } + + return null; + } + + /** + * 记录性能指标 + */ + public static function log_performance_metric( string $operation, float $duration, array $context = [] ): void { + if ( ! WP_DEBUG || ! defined( 'WPCC_LOG_PERFORMANCE' ) || ! WPCC_LOG_PERFORMANCE ) { + return; + } + + $log_entry = sprintf( + 'WPCC Performance: %s took %.4fs %s', + $operation, + $duration, + $context ? '(' . json_encode( $context ) . ')' : '' + ); + + error_log( $log_entry ); + } + + /** + * 执行带性能监控的操作 + */ + public static function with_performance_monitoring( string $operation, callable $callback, array $context = [] ) { + $start_time = microtime( true ); + + try { + $result = $callback(); + return $result; + } finally { + $duration = microtime( true ) - $start_time; + self::log_performance_metric( $operation, $duration, $context ); + } + } +} \ No newline at end of file diff --git a/includes/modules/wpcc-cache-addon.php b/includes/modules/wpcc-cache-addon.php index 2d9810a..8f9e84e 100755 --- a/includes/modules/wpcc-cache-addon.php +++ b/includes/modules/wpcc-cache-addon.php @@ -10,7 +10,7 @@ class WPCC_Cache_Addon extends WPCC_Abstract_Module { public function init() { $this->name = 'Cache Addon'; - $this->version = '2.0.0'; +$this->version = '1.5'; $this->description = '缓存插件兼容性模块,支持多种缓存插件'; $this->dependencies = array(); diff --git a/includes/modules/wpcc-modern-cache.php b/includes/modules/wpcc-modern-cache.php index e05a0c9..bb89b02 100644 --- a/includes/modules/wpcc-modern-cache.php +++ b/includes/modules/wpcc-modern-cache.php @@ -13,7 +13,7 @@ class WPCC_Modern_Cache extends WPCC_Abstract_Module { public function init() { $this->name = 'Modern Cache'; - $this->version = '1.0.0'; +$this->version = '1.5'; $this->description = '现代缓存模块,支持对象缓存、Redis、Memcached'; $this->dependencies = array(); diff --git a/includes/modules/wpcc-network.php b/includes/modules/wpcc-network.php index 8cba93c..d18d2c6 100755 --- a/includes/modules/wpcc-network.php +++ b/includes/modules/wpcc-network.php @@ -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 ''; @@ -166,19 +167,10 @@ class WPCC_Network extends WPCC_Abstract_Module { echo '
'; } - 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(); diff --git a/includes/modules/wpcc-rest-api.php b/includes/modules/wpcc-rest-api.php index bf068cf..e5bce0b 100644 --- a/includes/modules/wpcc-rest-api.php +++ b/includes/modules/wpcc-rest-api.php @@ -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' ), @@ -29,8 +29,14 @@ class WPCC_Rest_Api extends WPCC_Abstract_Module { private function check_rest_api_availability() { return class_exists( 'WP_REST_Server' ) && function_exists( 'rest_ensure_response' ); } - + /** + * 确保API密钥存在 + */ + public function ensure_api_key_exists() { + // 这个方法可以用来初始化API相关的设置 + // 目前为空实现,保持兼容性 + } public function register_routes() { register_rest_route( $this->namespace, '/convert', array( diff --git a/includes/modules/wpcc-seo-enhancement.php b/includes/modules/wpcc-seo-enhancement.php index 1cb83ce..ef6244f 100644 --- a/includes/modules/wpcc-seo-enhancement.php +++ b/includes/modules/wpcc-seo-enhancement.php @@ -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 '' . "\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 '' . "\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 '' . "\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; diff --git a/includes/modules/wpcc-sitemap.php b/includes/modules/wpcc-sitemap.php index f2103a3..451f9ec 100755 --- a/includes/modules/wpcc-sitemap.php +++ b/includes/modules/wpcc-sitemap.php @@ -27,59 +27,200 @@ 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 ) ) { - header( 'Content-Type: application/xml; charset=utf-8' ); - header( 'HTTP/1.1 200 OK' ); - - $content = generate_sitemap_index( $lang ); - - if ( ! empty( $content ) ) { - echo $content; - } else { - echo 'Sitemap not found'; + // 获取原始URL(去除所有语言变体) + $original_link = wpcc_remove_language_from_url( $link ); + + $style = (int) ( $wpcc_options['wpcc_use_permalink'] ?? 0 ); + $permalinks_enabled = (string) get_option( 'permalink_structure' ) !== ''; + + // 当 WP 未启用固定链接时,使用查询参数 + if ( ! $permalinks_enabled || $style === 0 ) { + return add_query_arg( 'variant', $variant, $original_link ); + } + + // Split path and query + $qpos = strpos( $original_link, '?' ); + $path = $qpos !== false ? substr( $original_link, 0, $qpos ) : $original_link; + $qs = $qpos !== false ? substr( $original_link, $qpos ) : ''; + + if ( $style === 1 ) { + // suffix style: /postname/zh-xx/ + return user_trailingslashit( trailingslashit( $path ) . $variant ) . $qs; + } + + // prefix style (2): /zh-xx/postname/ + if ( is_multisite() && wpcc_mobile_exist( 'network' ) ) { + $sites = get_sites(); + foreach ( $sites as $site ) { + if ( '/' == $site->path ) { + continue; + } + $path_seg = str_replace( '/', '', $site->path ); + $sub_url = "$site->domain/$path_seg"; + if ( str_contains( $path, $sub_url ) ) { + return str_replace( $sub_url, "$sub_url/$variant", $path ) . $qs; + } + } + } + + // 默认前缀样式 + $home_url = home_url(); + return str_replace( $home_url, $home_url . '/' . $variant, $original_link ); +} + +/** + * 从URL中移除语言变体 + */ +function wpcc_remove_language_from_url( $url ) { + global $wpcc_options; + + if ( empty( $url ) ) { + return $url; + } + + // 获取启用的语言列表 + $enabled = isset( $wpcc_options['wpcc_used_langs'] ) && is_array( $wpcc_options['wpcc_used_langs'] ) ? $wpcc_options['wpcc_used_langs'] : []; + if ( empty( $enabled ) ) { + return $url; + } + + // 移除查询参数中的variant + $url = remove_query_arg( 'variant', $url ); + + // 创建语言变体的正则表达式 + $reg = implode( '|', array_map( 'preg_quote', $enabled ) ); + $variant_regex = '/\/(' . $reg . '|zh|zh-reset)(\/|$)/i'; + + // 移除路径中的语言变体 + $parsed = parse_url( $url ); + if ( isset( $parsed['path'] ) ) { + $path = $parsed['path']; + $path = preg_replace( $variant_regex, '/', $path ); + $path = rtrim( $path, '/' ); + if ( empty( $path ) ) { + $path = '/'; } - exit; + // 重建URL + $scheme = isset( $parsed['scheme'] ) ? $parsed['scheme'] . '://' : ''; + $host = $parsed['host'] ?? ''; + $port = isset( $parsed['port'] ) ? ':' . $parsed['port'] : ''; + $query = isset( $parsed['query'] ) ? '?' . $parsed['query'] : ''; + $fragment = isset( $parsed['fragment'] ) ? '#' . $parsed['fragment'] : ''; + + $url = $scheme . $host . $port . $path . $query . $fragment; } - if ( preg_match( '/\/sitemap-(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)-(\d+)\.xml\/?$/', $uri, $matches ) ) { - $lang = $matches[1]; - $page = (int) $matches[2]; - + return $url; +} + +function custom_sitemap_template_redirect() { + $uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : ''; + + // 动态语言模式:仅允许已启用语言 + $enabled = isset($GLOBALS['wpcc_options']['wpcc_used_langs']) && is_array($GLOBALS['wpcc_options']['wpcc_used_langs']) ? $GLOBALS['wpcc_options']['wpcc_used_langs'] : array('zh-cn','zh-tw','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 ); + echo $content !== '' ? $content : 'Sitemap not found'; + exit; + } + + // 处理文章网站地图 + if ( $pat !== '' && preg_match( '#/sitemap-(' . $pat . ')-(\d+)\.xml/?$#i', $uri, $matches ) ) { + $lang = strtolower($matches[1]); + $page = (int) $matches[2]; + if ( ! in_array( $lang, $enabled, true ) ) { + status_header( 404 ); + exit; + } header( 'Content-Type: application/xml; charset=utf-8' ); header( 'HTTP/1.1 200 OK' ); - echo generate_paged_sitemap_content( $lang, $page ); exit; } - - if ( preg_match( '/\/sitemap-style\.xsl\/?$/', $uri ) ) { + + // 处理分类网站地图 + if ( $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' ); - + echo generate_sitemap_styles(); exit; } @@ -87,9 +228,15 @@ function custom_sitemap_template_redirect() { function generate_sitemap_index( string $lang ) { global $wpdb, $wpcc_options; - + + // 仅允许启用语言 + $enabled = isset($wpcc_options['wpcc_used_langs']) && is_array($wpcc_options['wpcc_used_langs']) ? $wpcc_options['wpcc_used_langs'] : array('zh-cn','zh-tw'); + if ( ! in_array( $lang, $enabled, true ) ) { + return ''; + } + $max_urls_per_sitemap = 1000; - + if ( empty( $wpcc_options['wpcco_sitemap_post_type'] ) ) { $post_type = 'post,page'; } else { @@ -110,6 +257,7 @@ function generate_sitemap_index( string $lang ) { $sitemap .= ''; $sitemap .= ''; + // 添加文章网站地图 for ( $i = 1; $i <= $total_sitemaps; $i ++ ) { $sitemap .= ''; $sitemap .= '' . site_url( "/sitemap-{$lang}-{$i}.xml" ) . ''; @@ -117,6 +265,35 @@ function generate_sitemap_index( string $lang ) { $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 .= '' . site_url( "/sitemap-taxonomy-{$taxonomy->name}-{$lang}-{$i}.xml" ) . ''; + $sitemap .= '' . date( 'Y-m-d' ) . ''; + $sitemap .= ''; + } + } + $sitemap .= ''; return $sitemap; @@ -126,9 +303,15 @@ function generate_sitemap_index( string $lang ) { function generate_paged_sitemap_content( string $lang, int $page ) { global $wpcc_options; - + + // 仅允许启用语言 + $enabled = isset($wpcc_options['wpcc_used_langs']) && is_array($wpcc_options['wpcc_used_langs']) ? $wpcc_options['wpcc_used_langs'] : array('zh-cn','zh-tw'); + if ( ! in_array( $lang, $enabled, true ) ) { + return ''; + } + $max_urls_per_sitemap = 1000; - + if ( empty( $wpcc_options['wpcco_sitemap_post_type'] ) ) { $post_type = 'post,page'; } else { @@ -155,7 +338,7 @@ function generate_paged_sitemap_content( string $lang, int $page ) { $postdate = explode( " ", $post->post_modified ); $sitemap .= ''; - $sitemap .= '' . wpcc_link_conversion( get_permalink( $post->ID ), $lang ) . ''; + $sitemap .= '' . wpcc_sitemap_link_conversion( get_permalink( $post->ID ), $lang ) . ''; $sitemap .= '' . $postdate[0] . ''; $sitemap .= 'weekly'; $sitemap .= '0.6'; @@ -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 ''; + } + + // 验证分类法 + if ( ! taxonomy_exists( $taxonomy ) || ! is_taxonomy_viewable( $taxonomy ) ) { + return ''; + } + + $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 ''; + } + + $sitemap = ''; + $sitemap .= ''; + $sitemap .= ''; + + 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 .= ''; + $sitemap .= '' . esc_url( $converted_link ) . ''; + $sitemap .= '' . date( 'Y-m-d' ) . ''; + $sitemap .= 'weekly'; + $sitemap .= '0.5'; + $sitemap .= ''; + } + + $sitemap .= ''; + + 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 ); +} + ?> diff --git a/includes/network/wpcc-network-settings.php b/includes/network/wpcc-network-settings.php new file mode 100644 index 0000000..dd64f82 --- /dev/null +++ b/includes/network/wpcc-network-settings.php @@ -0,0 +1,3021 @@ +id !== "settings_page_wp-chinese-converter") { + return; + } + + $network_controlled_options = get_site_option( + "wpcc_network_controlled_options", + self::$default_controlled_options, + ); + if (!is_array($network_controlled_options)) { + $network_controlled_options = explode( + ",", + $network_controlled_options, + ); + } + + $controlled_count = count($network_controlled_options); + + if (get_site_option("wpcc_network_enforce", 0)) { + echo '

' . + "WP Chinese Converter 插件正由网络管理员强制管理。所有设置将使用网络级别配置,任何更改将被忽略。如有疑问请联系网络管理员。" . + "

"; + } elseif ($controlled_count > 0) { + echo '

' . + sprintf( + "WP Chinese Converter 插件的 %d 项设置由网络管理员控制。这些设置的更改将不会生效。", + $controlled_count, + ) . + "

"; + } + } + + public static function network_admin_notices() + { + if (isset($_GET["updated"]) && $_GET["updated"] === "true") { + echo '

网络设置已更新。

'; + } + if (isset($_GET["imported"]) && $_GET["imported"] === "true") { + echo '

设置导入成功。

'; + } + if (isset($_GET["reset"]) && $_GET["reset"] === "true") { + echo '

网络设置已重置为默认值。

'; + } + if (isset($_GET["bulk_reset"]) && $_GET["bulk_reset"] === "true") { + $reset_count = isset($_GET["reset_count"]) + ? intval($_GET["reset_count"]) + : 0; + echo '

已成功重置 ' . + $reset_count . + " 个站点的设置。

"; + } + } + + public static function is_network_enforced() + { + return get_site_option("wpcc_network_enforce", 0); + } + + public static function is_option_controlled($option_name) + { + if (!is_multisite()) { + return false; + } + + $controlled_options = get_site_option( + "wpcc_network_controlled_options", + self::$default_controlled_options, + ); + return in_array($option_name, $controlled_options); + } + + public static function get_network_option($option_name, $default = null) + { + if (self::is_option_controlled($option_name)) { + return get_site_option($option_name, $default); + } + return $default; + } + + public static function render_network_page() + { + $active_tab = isset($_GET["tab"]) + ? sanitize_text_field($_GET["tab"]) + : "network"; + + wp_enqueue_style( + "wpcc-admin", + plugin_dir_url( + dirname(dirname(__DIR__)) . "/wp-chinese-converter.php", + ) . "assets/admin/admin.css", + [], + wpcc_VERSION, + ); + + wp_enqueue_script( + "wpcc-admin", + plugin_dir_url( + dirname(dirname(__DIR__)) . "/wp-chinese-converter.php", + ) . "assets/admin/admin.js", + ["jquery"], + wpcc_VERSION, + true, + ); + ?> +
+

+ + + + + + + + + + +

+ + + +
+
+
+ + + + +
+
+ + +
"> +

+

+ +
+ + + + + + + + + + + + + + + + +
启用网络级管理 + +

启用后,所有子站点将使用选定的网络设置,子站点的对应设置将被覆盖

+
强制使用网络设置 + +

启用后,子站点将无法访问WP Chinese Converter设置页面,所有设置完全由网络管理员控制

+
网络控制选项 + "语言模块", + "cntip" => "简体中文显示名", + "twtip" => "台湾正体显示名", + "hktip" => "港澳繁体显示名", + "hanstip" => "简体中文(国际)显示名", + "hanttip" => "繁体中文(国际)显示名", + "sgtip" => "马新简体显示名", + "jptip" => "日式汉字显示名", + "nctip" => '"不转换"标签显示名', + "wpcc_flag_option" => "展示形式", + // Harmonize key with single-site UI + "wpcc_show_more_langs" => + "扩展语言模块", + "wpcc_engine" => "转换引擎", + "wpcc_search_conversion" => "搜索转换", + "wpcc_use_fullpage_conversion" => + "全页面转换", + // Harmonize quicktags key with single-site + "wpcc_no_conversion_qtag" => + "编辑器快速标签", + "wpcc_enable_post_conversion" => + "发表时转换", + "wpcc_post_conversion_target" => + "发表时转换目标语言", + "wpcc_use_permalink" => "URL链接格式", + "wpcco_sitemap_post_type" => + "站点地图内容类型", + "wpcco_use_sitemap" => "多语言站点地图", + "wpcc_browser_redirect" => + "浏览器语言检测", + "wpcc_auto_language_recong" => + "语系内通用", + "wpcc_use_cookie_variant" => + "Cookie偏好记忆", + "wpcc_first_visit_default" => + "首次访问不转换", + "wpcc_no_conversion_tag" => "标签排除", + "wpcc_no_conversion_ja" => + "日语内容排除", + "wpcc_hreflang_x_default" => + "SEO默认语言", + "wpcc_enable_hreflang_tags" => + "hreflang标签", + "wpcc_enable_hreflang_x_default" => + "x-default 标签", + "wpcc_enable_schema_conversion" => + "Schema数据转换", + "wpcc_enable_meta_conversion" => + "SEO元数据转换", + "wpcc_enable_cache_addon" => + "缓存插件兼容", + "wpcc_allow_uninstall" => + "允许插件卸载", + ]; + + $option_groups = [ + "language" => [ + "title" => "语言设置", + "options" => [ + "wpcc_used_langs", + "cntip", + "twtip", + "hktip", + "hanstip", + "hanttip", + "sgtip", + "jptip", + "nctip", + "wpcc_flag_option", + "wpcc_show_more_langs", + ], + ], + "engine" => [ + "title" => "核心功能", + "options" => [ + "wpcc_engine", + "wpcc_search_conversion", + "wpcc_use_fullpage_conversion", + ], + ], + "editor" => [ + "title" => "编辑器增强", + "options" => [ + "wpcc_no_conversion_qtag", + "wpcc_enable_post_conversion", + "wpcc_post_conversion_target", + ], + ], + "url_sitemap" => [ + "title" => "站点地图", + "options" => [ + "wpcc_use_permalink", + "wpcco_sitemap_post_type", + "wpcco_use_sitemap", + ], + ], + "detection" => [ + "title" => "智能检测", + "options" => [ + "wpcc_browser_redirect", + "wpcc_auto_language_recong", + "wpcc_use_cookie_variant", + "wpcc_first_visit_default", + ], + ], + "filter" => [ + "title" => "内容过滤", + "options" => [ + "wpcc_no_conversion_tag", + "wpcc_no_conversion_ja", + ], + ], + "seo" => [ + "title" => "SEO优化", + "options" => [ + "wpcc_hreflang_x_default", + "wpcc_enable_hreflang_tags", + "wpcc_enable_hreflang_x_default", + "wpcc_enable_schema_conversion", + "wpcc_enable_meta_conversion", + ], + ], + "compatibility" => [ + "title" => "缓存兼容", + "options" => [ + "wpcc_enable_cache_addon", + "wpcc_allow_uninstall", + ], + ], + ]; + + echo '
'; + foreach ( + $option_groups + as $group => $group_data + ) { + echo '
'; + echo "

" . + esc_html($group_data["title"]) . + "

"; + if (isset($group_data["description"])) { + echo '

' . + esc_html( + $group_data["description"], + ) . + "

"; + } + + foreach ( + $group_data["options"] + as $option + ) { + if (isset($all_options[$option])) { + echo '"; + } + } + + echo "
"; + } + echo "
"; + ?> +

选中的选项将在所有站点使用网络设置,未选中的选项可由站点管理员自定义。

+
+ + + +
+
+ +
+ +
+
+
+ + +
"> +

+

+ +
+ + + +

语言和界面设置

+ + + + + + + + + + + + + + + + + +
语言模块 +
+
+ + " placeholder="中国大陆" class="regular-text wpcc-input" style="margin-left: 20px;" /> +
+
+ + " placeholder="台灣正體" class="regular-text wpcc-input" style="margin-left: 20px;" /> +
+
+ + " placeholder="港澳繁體" class="regular-text wpcc-input" style="margin-left: 20px;" /> +
+
+ +
"> +

扩展语言模块(国际)

+
+ + " placeholder="简体中文" class="regular-text wpcc-input" style="margin-left: 20px;" /> +
+
+ + " placeholder="繁体中文" class="regular-text wpcc-input" style="margin-left: 20px;" /> +
+
+ + " placeholder="马新简体" class="regular-text wpcc-input" style="margin-left: 20px;" /> +
+
+ + " placeholder="日式汉字" class="regular-text wpcc-input" style="margin-left: 20px;" /> +
+
+

请至少勾选一种中文语言,否则插件无法正常运行。支持自定义名称,留空为默认值。

+
"不转换"标签 + " placeholder="请输入显示名(默认值如左)" class="regular-text" /> +

自定义小工具中"不转换"链接的显示名称。

+
展示形式 +
+ +

选择语言切换按钮的展现方式。

+
显示更多语言 + +

启用后将显示更多中文语言变体选项,包括 zh-hans、zh-hant、zh-sg、zh-jp 等。

+
+ +

转换引擎和核心功能

+ + + + + + + + + + + + + +
转换引擎 + +

词汇级转换, 地区习惯用词, 异体字转换

+
搜索转换 + +

此功能将增加搜索时数据库负担,低配服务器建议关闭。

+
全页面转换 + +

如果遇到异常(包括中文转换错误,HTML页面错误或PHP错误等),请关闭此选项。

+
+ +

编辑器增强

+ + + + + + + + + +
快速标签 + +

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

+
发表时转换 + +
"> + + +
+

启用后,在发布或更新文章时自动转换内容。

+
+ +

URL 和站点地图设置

+ + + + + + + + + + + + + +
链接格式 + +
+
+ +

注意:此选项影响插件生成的转换页面链接。提示:若未开启固定链接,则只能选第一种默认URL形式。

+
地图内容类型 + " placeholder="默认为:post,page" class="regular-text" /> +

默认为 post 和 page 生成地图,如需添加自定义 post_type 请用逗号分隔。

+
多语言地图 + + + +

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

+
+ +

智能检测功能

+ + + + + + + + + + + + + + + + + +
浏览器语言 + +

注意:此项设置不会应用于搜索引擎。

+
首次访问 + +

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

+
Cookie偏好 + +

记住用户的语言选择偏好,下次访问时自动应用。

+
语系内通用 + +
+ +

内容过滤设置

+ + + + + + + + + +
标签排除 + +

这里输入的HTML标签或选择器内内容将不进行中文繁简转换,推荐设置 pre,code,pre.wp-block-code,pre.wp-block-preformatted 来排除区块编辑器的代码块与预格式化内容。

+
日语内容排除 + +

启用后,标记为日语(lang="ja")的内容将不进行中文繁简转换,避免日语汉字被错误转换。

+
+ +

SEO 优化功能

+ + + + + + + + + + + + + + + + + + + + + +
hreflang 标签 + +

自动为每个语言版本生成 hreflang 标签,提升多语言 SEO 效果。

+
x-default 标签 + +
默认语言 + +

设置 x-default hreflang 标签指向的默认语言版本。

+
Schema 数据 + +

自动转换 JSON-LD 结构化数据中的文本内容,支持 Article、Product、Organization 等类型。

+
元数据转换 + +

转换页面标题、描述、Open Graph 和 Twitter Card 等元数据。

+
+ +

缓存和兼容性

+ + + + + + +
缓存兼容 + +

支持 WP Super Cache、WP Rocket、LiteSpeed Cache、W3 Total Cache 等流行的缓存插件。

+
+ + + +
+ + +
+
+
+ + + + +
"> +

+

+ +
+
+

+

+
+ +
+
+
+ +
+

+ +

+
+
+ +
+

+

+
+ + + + + + + + + + +
选择文件 + +

请选择之前导出的JSON设置文件

+
导入选项 +
+
+
+ +

勾选后将把导入的设置应用到所有现有站点

+
+

+ +

+
+
+ + +
+
+ + +
"> +

+

+ +
+ + + +

+ + + + + + + + + + +
启用工具维护功能 + +

启用后,网络管理员可以使用批量管理、统计监控等高级功能

+
启用批量操作 + +

启用后,可以执行批量重置站点设置等操作。建议谨慎启用

+
+ +
+ +
+
+ + +

+ + + + + +
+ + ">↗ + + 0, + "opencc" => 0, + "other" => 0, + ]; + $language_stats = []; + + foreach ($sites as $site) { + switch_to_blog($site->blog_id); + $options = get_option("wpcc_options", []); + + if (!empty($options)) { + $active_sites++; + + // 统计引擎使用情况 + $engine = + $options["wpcc_engine"] ?? + "mediawiki"; + if (isset($engine_stats[$engine])) { + $engine_stats[$engine]++; + } else { + $engine_stats["other"]++; + } + + // 统计语言使用情况 + $used_langs = + $options["wpcc_used_langs"] ?? []; + foreach ($used_langs as $lang) { + if ( + !isset($language_stats[$lang]) + ) { + $language_stats[$lang] = 0; + } + $language_stats[$lang]++; + } + } + + restore_current_blog(); + } + ?> + +
+
+
+
总站点数
+
+
+
+
已激活站点
+
+
+
+
未激活站点
+
+
+ + 0): ?> +
+

转换引擎分布

+
+ $count + ): ?> + 0): ?> + + : + + + +
+ + +

语言模块使用情况

+
+ "简体中文", + "zh-tw" => "台湾正体", + "zh-hk" => "港澳繁体", + "zh-hans" => "简体中文(国际)", + "zh-hant" => "繁体中文(国际)", + "zh-sg" => "马新简体", + "zh-jp" => "日式汉字", + ]; + foreach ( + $language_stats + as $lang => $count + ): + $lang_name = isset( + $lang_names[$lang], + ) + ? $lang_names[$lang] + : $lang; ?> + + : + + +
+ +
+ +
+ + + +

+ + + + + +
+ + ">↗ + +
+ + + + + + + + + + + "简体", + "zh-tw" => "繁体", + "zh-hk" => "港澳", + "zh-hans" => "简体(国际)", + "zh-hant" => "繁体(国际)", + "zh-sg" => "马新", + "zh-jp" => "日式", + ]; + + foreach ($sites as $site) { + switch_to_blog($site->blog_id); + $options = get_option( + "wpcc_options", + [], + ); + $site_details = get_blog_details( + $site->blog_id, + ); + + echo ""; + + // 站点信息 + echo '"; + + // 状态 + echo '"; + + // 配置信息 + echo '"; + + // 操作 + echo '"; + + echo ""; + + restore_current_blog(); + } + ?> + +
站点信息状态配置操作
'; + echo '
' . + esc_html( + $site_details->blogname, + ) . + "
"; + echo '
' . + esc_html( + $site_details->domain . + $site_details->path, + ) . + "
"; + echo "
'; + if (!empty($options)) { + echo '✓ 已激活'; + } else { + echo '✗ 未激活'; + } + echo "'; + if (!empty($options)) { + $engine = + $options[ + "wpcc_engine" + ] ?? "mediawiki"; + echo '
'; + echo '引擎: '; + echo '' . + esc_html( + ucfirst($engine), + ) . + ""; + echo "
"; + + if ( + isset( + $options[ + "wpcc_used_langs" + ], + ) && + !empty( + $options[ + "wpcc_used_langs" + ] + ) + ) { + echo "
"; + echo '语言: '; + $lang_tags = []; + foreach ( + $options[ + "wpcc_used_langs" + ] + as $lang + ) { + $lang_name = isset( + $lang_names[ + $lang + ], + ) + ? $lang_names[ + $lang + ] + : $lang; + $lang_tags[] = + '' . + esc_html( + $lang_name, + ) . + ""; + } + echo implode( + " ", + $lang_tags, + ); + echo "
"; + } + } else { + echo '未配置'; + } + echo "
'; + $site_admin_url = get_admin_url( + $site->blog_id, + "options-general.php?page=wp-chinese-converter", + ); + echo '管理设置'; + echo "
+
+

+ +

+
+ + +

+ + + + + +
+ + ">↗ + + [ + "name" => "WP Super Cache", + "check" => + function_exists( + "wp_cache_is_enabled", + ) || + function_exists( + "wp_super_cache_init", + ), + ], + "wp_rocket" => [ + "name" => "WP Rocket", + "check" => function_exists( + "rocket_clean_domain", + ), + ], + "litespeed_cache" => [ + "name" => "LiteSpeed Cache", + "check" => class_exists( + "LiteSpeed\Core", + ), + ], + "w3_total_cache" => [ + "name" => "W3 Total Cache", + "check" => function_exists( + "w3tc_flush_all", + ), + ], + "wp_fastest_cache" => [ + "name" => "WP Fastest Cache", + "check" => class_exists( + "WpFastestCache", + ), + ], + "autoptimize" => [ + "name" => "Autoptimize", + "check" => + class_exists("autoptimizeMain") || + function_exists( + "autoptimize_autoload", + ), + ], + "jetpack_boost" => [ + "name" => "Jetpack Boost", + "check" => + defined("JETPACK_BOOST_VERSION") || + class_exists( + "Automattic\Jetpack_Boost\Jetpack_Boost", + ), + ], + ]; + + $active_cache_plugins = []; + foreach ($cache_plugins as $key => $plugin) { + if ($plugin["check"]) { + $active_cache_plugins[$key] = + $plugin["name"]; + } + } + + if (!empty($active_cache_plugins)) { + echo '
'; + echo '
检测到的缓存插件:
'; + echo '
'; + foreach ( + $active_cache_plugins + as $plugin_name + ) { + echo '' . + esc_html($plugin_name) . + ""; + } + echo "
"; + echo "
"; + + // 检查缓存兼容设置 + $cache_controlled = in_array( + "wpcc_enable_cache_addon", + get_site_option( + "wpcc_network_controlled_options", + [], + ), + ); + if ($cache_controlled) { + $cache_enabled = get_site_option( + "wpcc_default_enable_cache_addon", + 1, + ); + if ($cache_enabled) { + echo '
'; + echo '网络级缓存兼容已启用
'; + echo '所有子站点将自动使用缓存兼容功能'; + echo "
"; + } else { + echo '
'; + echo '网络级缓存兼容已禁用
'; + echo '子站点的缓存兼容功能将不可用'; + echo "
"; + } + } else { + echo '
'; + echo '缓存兼容选项未被网络控制
'; + echo '各子站点可自行配置缓存兼容功能'; + echo "
"; + } + } else { + echo '
'; + echo "未检测到活跃的缓存插件"; + echo "
"; + } + ?> +
+ + +

+ + + + + +
+ + ">↗ + +
+
危险操作警告
+
+ 此操作将重置网络中所有站点的 WP Chinese Converter 设置为默认值。
+ 此操作不可逆,请谨慎使用! +
+
+ +
+ + +
+ +
+ +
+ +
+
+
+ +
+
批量操作已禁用
+
+ 为了安全考虑,批量重置等危险操作已被禁用。如需启用,请在上方的"网络控制选项"中开启"启用批量操作"。 +
+
+ +
+ + + + +
+
+
+ + + + + + 网络默认键 的映射 + $map = [ + "wpcc_used_langs" => "wpcc_default_used_langs", + "cntip" => "wpcc_default_cntip", + "twtip" => "wpcc_default_twtip", + "hktip" => "wpcc_default_hktip", + "hanstip" => "wpcc_default_hanstip", + "hanttip" => "wpcc_default_hanttip", + "sgtip" => "wpcc_default_sgtip", + "jptip" => "wpcc_default_jptip", + "nctip" => "wpcc_default_nctip", + "wpcc_flag_option" => "wpcc_default_flag_option", + // 扩展语言模块(单站点键为 wpcc_show_more_langs) + "wpcc_show_more_langs" => + "wpcc_default_enable_extended_langs", + "wpcc_engine" => "wpcc_default_engine", + "wpcc_search_conversion" => + "wpcc_default_search_conversion", + "wpcc_use_fullpage_conversion" => + "wpcc_default_use_fullpage_conversion", + // 快速标签(单站点键为 wpcc_no_conversion_qtag) + "wpcc_no_conversion_qtag" => + "wpcc_default_no_conversion_qtag", + "wpcc_enable_post_conversion" => + "wpcc_default_enable_post_conversion", + "wpcc_post_conversion_target" => + "wpcc_default_post_conversion_target", + "wpcc_use_permalink" => "wpcc_default_use_permalink", + "wpcco_sitemap_post_type" => + "wpcc_default_sitemap_post_type", + "wpcco_use_sitemap" => "wpcc_default_use_sitemap", + "wpcc_browser_redirect" => "wpcc_default_browser_redirect", + "wpcc_auto_language_recong" => + "wpcc_default_auto_language_recong", + "wpcc_use_cookie_variant" => + "wpcc_default_use_cookie_variant", + "wpcc_first_visit_default" => + "wpcc_default_first_visit_default", + "wpcc_no_conversion_tag" => + "wpcc_default_no_conversion_tag", + "wpcc_no_conversion_ja" => "wpcc_default_no_conversion_ja", + "wpcc_hreflang_x_default" => + "wpcc_default_hreflang_x_default", + "wpcc_enable_hreflang_tags" => + "wpcc_default_enable_hreflang_tags", + "wpcc_enable_hreflang_x_default" => + "wpcc_default_enable_hreflang_x_default", + "wpcc_enable_schema_conversion" => + "wpcc_default_enable_schema_conversion", + "wpcc_enable_meta_conversion" => + "wpcc_default_enable_meta_conversion", + "wpcc_enable_cache_addon" => + "wpcc_default_enable_cache_addon", + "wpcc_enable_network_module" => + "wpcc_default_enable_network_module", + ]; + + foreach ($controlled_options as $local_key) { + if (isset($map[$local_key])) { + $network_key = $map[$local_key]; + $sync_settings[$network_key] = get_site_option( + $network_key, + ); + } + } + + // 仅当有需要同步的键时才应用 + if (!empty($sync_settings)) { + self::apply_settings_to_all_sites($sync_settings); + } + } + } + + $redirect_url = add_query_arg( + [ + "page" => "wpcc-network", + "updated" => "true", + ], + network_admin_url("settings.php"), + ); + + if (!empty($current_tab)) { + $redirect_url = add_query_arg("tab", $current_tab, $redirect_url); + } + + wp_redirect($redirect_url); + exit(); + } + + public static function display_sites_status() + { + $sites = get_sites(["number" => 50]); + + if (empty($sites)) { + echo "

没有找到站点。

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

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

'; + echo '
'; + + // 添加JavaScript + echo ''; + } +} + // 防止直接访问 if ( ! defined( 'ABSPATH' ) ) { exit; @@ -19,7 +106,7 @@ if ( ! defined( 'ABSPATH' ) ) { */ function wpcc_admin_init() { global $wpcc_admin; - require_once __DIR__ . '/admin/wp-chinese-converter-admin.php'; + require_once __DIR__ . '/admin/class-wpcc-admin.php'; $wpcc_admin = new wpcc_Admin(); add_filter( 'plugin_action_links', array( $wpcc_admin, 'action_links' ), 10, 2 ); } @@ -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'] ) ) { ?> '; + + echo ''; if ( ! $wpcc_direct_conversion_flag ) { - wp_enqueue_script( 'wpcc-search-js', wpcc_DIR_URL . 'assets/js/search-variant.min.js', array(), '1.1', false ); +wp_enqueue_script( 'wpcc-search-js', wpcc_DIR_URL . 'assets/js/search-variant.min.js', array(), wpcc_VERSION, false ); } if ( $wpcc_direct_conversion_flag || @@ -949,6 +1143,20 @@ function wpcc_ob_callback( $buffer ) { return zhconversion2( $buffer ) . "\n" . ''; } +/** + * 将注释标记包裹的区域转换为持久性 data 包裹,避免被HTML压缩器移除注释 + */ +function wpcc_wrap_nc_markers( $content ) { + if ( empty( $content ) || strpos( $content, 'wpc' ) === false ) { + return $content; + } + $pattern = '/([\\s\\S]*?)/i'; + return preg_replace_callback( $pattern, function( $m ) { + $inner = $m[2]; + return '' . $inner . ''; + }, $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] ) ) { + + // 仅在“确实处于变体页面”时才覆盖 + $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 = '/]*class="[^"]*wpcc-no-conversion-content[^"]*"[^>]*>(.*?)<\/div>/s'; - - $replacement = function ( $matches ) use ( $unique_id ) { - $content = $matches[1]; - return '
' . $content . '
'; - }; - - $block_content = preg_replace_callback( $pattern, $replacement, $block_content ); + return '' . $block_content . ''; } return $block_content; @@ -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 () { diff --git a/includes/wpcc-settings-page.php b/includes/wpcc-settings-page.php index e8b97d5..1dd1f28 100644 --- a/includes/wpcc-settings-page.php +++ b/includes/wpcc-settings-page.php @@ -1,18 +1,123 @@ "语言模块", + "cntip" => "简体中文显示名", + "twtip" => "台湾正体显示名", + "hktip" => "港澳繁体显示名", + "hanstip" => "简体中文(国际)显示名", + "hanttip" => "繁体中文(国际)显示名", + "sgtip" => "马新简体显示名", + "jptip" => "日式汉字显示名", + "nctip" => '"不转换"标签显示名', + "wpcc_flag_option" => "展示形式", + "wpcc_show_more_langs" => "扩展语言模块", + "wpcc_engine" => "转换引擎", + "wpcc_search_conversion" => "搜索转换", + "wpcc_use_fullpage_conversion" => "全页面转换", + "wpcc_no_conversion_qtag" => "编辑器快速标签", + "wpcc_enable_post_conversion" => "发表时转换", + "wpcc_post_conversion_target" => "发表时转换目标语言", + "wpcc_use_permalink" => "URL链接格式", + "wpcco_sitemap_post_type" => "站点地图内容类型", + "wpcco_use_sitemap" => "多语言站点地图", + "wpcc_browser_redirect" => "浏览器语言检测", + "wpcc_auto_language_recong" => "语系内通用", + "wpcc_use_cookie_variant" => "Cookie偏好记忆", + "wpcc_first_visit_default" => "首次访问不转换", + "wpcc_no_conversion_tag" => "标签排除", + "wpcc_no_conversion_ja" => "日语内容排除", + "wpcc_hreflang_x_default" => "SEO默认语言", + "wpcc_enable_hreflang_tags" => "hreflang标签", + "wpcc_enable_hreflang_x_default" => "x-default 标签", + "wpcc_enable_schema_conversion" => "Schema数据转换", + "wpcc_enable_meta_conversion" => "SEO元数据转换", + "wpcc_enable_cache_addon" => "缓存插件兼容", + "wpcc_allow_uninstall" => "允许插件卸载", + ]; + + $notice = + '
'; + + // 简洁的单行显示 + if (count($controlled_options) == 1) { + $label = + $option_labels[$controlled_options[0]] ?? + $controlled_options[0]; + $notice .= + '

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

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

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

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

' . + esc_html($label) . + "

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

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

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

- + - + - + - +

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

-

+
"> +

+

-

+ + +

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

+ placeholder="" + /> +

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

+

- - + + ">↗ -
-

+

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

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

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

+ +

- - + + ">↗ -
-

+

+
- - + + ">↗ -

- + + +

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

- - + + ">↗ -
-

+

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

- +

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

- +

- - + + ">↗ -
-

+

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

- +

-

+

+
- - + + ">↗ - +

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

- - + + ">↗ -
-

+

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

+
+ + ">↗ + +

- +

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

+

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

- - - - - - -

-
+
- + + +
-
-

-

+
"> +

+

-

+ + +

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

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

'; + echo '

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

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

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

'; + echo '

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

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

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

'; + echo '

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

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

+

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

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

'; } else { echo '

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

'; } } else { - echo '

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

'; + echo '

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

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

- -

- -
- - -
- + +
+

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

+

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

- + +

+ +

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

+
+
@@ -802,7 +2091,7 @@ jQuery(document).ready(function($) { $('#post-conversion-options').hide(); } }); - + // 控制浏览器重定向相关选项的显示/隐藏 $('select[name="wpcco_browser_redirect"]').change(function() { if ($(this).val() == '0') { @@ -811,19 +2100,19 @@ jQuery(document).ready(function($) { $('.browser-redirect-dependent').show(); } }); - + // 控制扩展语言模块的显示/隐藏 function toggleExtendedLanguages() { var showMoreLangs = $('#wpcc_show_more_langs').is(':checked'); var extendedSection = $('.wpcc-extended-languages'); - + if (showMoreLangs) { extendedSection.show(); } else { extendedSection.hide(); } } - + // 检测是否使用Gutenberg编辑器 function isGutenbergActive() { // 检查WordPress是否启用了Gutenberg编辑器 @@ -831,49 +2120,61 @@ jQuery(document).ready(function($) { if (typeof wpccAdminData !== 'undefined' && wpccAdminData.isGutenbergActive !== undefined) { return wpccAdminData.isGutenbergActive; } - + // 备用检测方法:检查是否存在Gutenberg相关的全局变量 - return (typeof wp !== 'undefined' && wp.blocks) || + return (typeof wp !== 'undefined' && wp.blocks) || document.body.classList.contains('block-editor-page') || document.querySelector('.block-editor') !== null || (typeof window.wp !== 'undefined' && window.wp.blockEditor); } - + // 控制编辑器增强选项的启用/禁用 function toggleEditorEnhancementOptions() { var isGutenberg = isGutenbergActive(); var quicktagsOption = $('input[name="wpcc_no_conversion_qtag"]').closest('tr'); var postConversionOption = $('input[name="wpcc_enable_post_conversion"]').closest('tr'); - + if (isGutenberg) { // Gutenberg环境下只禁用快速标签功能,保留发表时转换功能 quicktagsOption.find('.wpcc-switch').addClass('wpcc-disabled'); quicktagsOption.find('input[type="checkbox"]').prop('disabled', true); - quicktagsOption.find('.description').html('
'); - +quicktagsOption.find('.description').html(''); + // 发表时转换功能在区块编辑器下保持可用 postConversionOption.find('.wpcc-switch').removeClass('wpcc-disabled'); postConversionOption.find('input[type="checkbox"]').prop('disabled', false); - postConversionOption.find('.description').html(''); + postConversionOption.find('.description').html(''); } else { // 经典编辑器环境下启用所有编辑器增强功能 quicktagsOption.find('.wpcc-switch').removeClass('wpcc-disabled'); quicktagsOption.find('input[type="checkbox"]').prop('disabled', false); - quicktagsOption.find('.description').html(''); - +quicktagsOption.find('.description').html(''); + postConversionOption.find('.wpcc-switch').removeClass('wpcc-disabled'); postConversionOption.find('input[type="checkbox"]').prop('disabled', false); - postConversionOption.find('.description').html(''); + postConversionOption.find('.description').html(''); } } - + // 控制引擎相关选项的启用/禁用 function toggleEngineRelatedOptions() { var engine = $('select[name="wpcc_engine"]').val(); var jpOption = $('#zh-jp-option'); var jpCheckbox = jpOption.find('input[type="checkbox"]'); var jpInput = jpOption.find('input[type="text"]'); - + if (engine === 'opencc') { // OpenCC 引擎支持日式汉字 jpCheckbox.prop('disabled', false); @@ -886,20 +2187,29 @@ jQuery(document).ready(function($) { jpOption.find('.wpcc-engine-note').text('(不支持此引擎)').css('color', '#d63638'); } } - + + + // 联动:x-default 标签 -> 默认语言行显示/隐藏 + function toggleSiteXDefault() { + var enabled = $('input[name="wpcc_enable_hreflang_x_default"]').is(':checked'); + $('#wpcc-site-x-default-options').toggle(enabled); + } + $('input[name="wpcc_enable_hreflang_x_default"]').on('change', toggleSiteXDefault); + // 页面加载时检查 toggleExtendedLanguages(); toggleEngineRelatedOptions(); toggleEditorEnhancementOptions(); - + toggleSiteXDefault(); + // 显示更多语言选项改变时 $('#wpcc_show_more_langs').change(function() { toggleExtendedLanguages(); }); - + // 转换引擎改变时检查 $('select[name="wpcc_engine"]').change(function() { toggleEngineRelatedOptions(); }); }); - \ No newline at end of file + diff --git a/wp-chinese-converter.php b/wp-chinese-converter.php index b5d39dc..ec4346d 100755 --- a/wp-chinese-converter.php +++ b/wp-chinese-converter.php @@ -6,7 +6,7 @@ * Author URI: https://wpcc.net * Text Domain: wp-chinese-converter * Domain Path: /languages - * Version: 1.3.0 +* Version: 1.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); }