Compare commits

...

15 commits
v1.4.0 ... main

Author SHA1 Message Date
feibisi
a7a6b1bd18 Add .gitignore to exclude .DS_Store files
Added a .gitignore file to prevent tracking of macOS .DS_Store files in the assets directory.
2025-11-07 21:13:30 +08:00
feibisi
2e42b5bb83 Refactor block styles registration and update frontend CSS
Moved block styles registration to a dedicated method hooked to 'init', allowing block.json to reference registered styles. Updated frontend CSS for language switcher and conversion status for improved appearance and consistency, and removed visual styles from non-conversion blocks on the frontend.
2025-11-07 20:21:18 +08:00
feibisi
85cd30a5b1 Skip conversion for AJAX, REST, and WooCommerce AJAX requests
Adds checks to bypass language/content conversion for AJAX, REST API, and WooCommerce AJAX requests to prevent interference with JSON or fragment responses. Also excludes WooCommerce critical pages (checkout, cart, account) from conversion to avoid breaking their functionality.
2025-11-07 17:23:06 +08:00
feibisi
cede6d1b02 Refactor and enhance language variant URL handling
Major refactor of WPCC_Main to improve and expand support for language variant URL handling, including custom rewrite rules, homepage and canonical fixes, and robust link conversion for different permalink styles. Adds new methods for rewrite rule management, homepage query correction, and canonical redirect cancellation. Also improves menu and logo link handling, and enhances content and link conversion filters. Minor fix in wpcc-core.php to check config existence before using options.
2025-11-07 16:35:13 +08:00
feibisi
f15abc6e30 Refactor and enhance language variant handling
Refactored core logic for language variant support, consolidating and simplifying rewrite rules, link conversion, and request handling. Improved AJAX/REST detection to skip conversion for non-HTML responses, unified link and menu conversion filters, and restructured homepage and comment handling for better reliability. The changes also streamline the addition of language variants to URLs and ensure correct behavior for canonical redirects and rewrite rule flushing.
2025-10-26 16:52:43 +08:00
feibisi
1a89f9ec27 Add taxonomy sitemap support for multiple languages
This update extends the sitemap functionality to include taxonomy (category, tag, etc.) sitemaps for each enabled language. It introduces new rewrite rules, query vars, and handlers for taxonomy sitemaps, generates taxonomy sitemap index entries, and provides a function to output taxonomy sitemap XML with pagination. The default language set now includes 'zh-hk' in addition to 'zh-cn' and 'zh-tw'.
2025-10-26 16:01:26 +08:00
feibisi
6c9407cbd5 Enhance language variant handling and sitemap support
This update improves dynamic language variant handling across URL, cookie, and redirect logic, including dynamic regex generation for enabled languages. It adds a 'first visit no conversion' option to both network and site settings, updates admin UI labels to Chinese, and refines the sitemap module to generate rules and output only for enabled languages. Cookie setting now uses secure attributes, and sitemap URLs in the UI reflect the actual enabled language set. Several regexes and logic paths are now dynamically generated based on the current language configuration, improving flexibility and correctness.
2025-10-26 15:20:25 +08:00
feibisi
f4720ecedb Update blocks-frontend.css 2025-10-25 05:11:55 +08:00
feibisi
54b872015a Update language switcher button styles
Adjusted padding and removed border-radius from .wpcc-link for a more compact appearance. Improved mobile layout for .wpcc-horizontal-switcher by adding flex display and align-items: stretch in media queries.
2025-10-23 16:38:52 +08:00
feibisi
039f3aa91a Improve zh variant URL handling and homepage mapping
Refines regex patterns in blocks-frontend.js to better match zh and its variants. Updates admin option handling for backward compatibility with no-conversion tag fields. In WPCC_Main, adds logic to map root-level zh variant paths to the static homepage to prevent 404s, avoids unnecessary redirects for root-level variants, and enhances redirect cancellation to prevent incorrect normalization between variant and non-variant paths.
2025-10-12 00:20:32 +08:00
feibisi
c50e16121b Release version 1.5.0 and update module versions
Bump core and all module versions to 1.5.0, update required core version in extension class, and add changelog for version 1.5.0. This release includes bug fixes for single-site mode, improvements to language switcher and SEO rules, unified permalink and rewrite strategies, and codebase cleanup for consistency.
2025-10-11 23:11:13 +08:00
feibisi
86d8dbeaf5 Update README.md 2025-10-11 22:59:08 +08:00
feibisi
a6157c75f4 Add network control and language config enhancements
Introduces network-controlled options for multisite environments, including enforcement and UI disabling for network-managed settings. Centralizes language configuration in a new WPCC_Language_Config class, improves admin and frontend JS/CSS for network and disabled state UI, updates block asset versioning, and refactors/renames core classes for consistency. Also adds a TinyMCE plugin for no-conversion shortcodes and enhances option handling for better compatibility and reliability.
2025-10-11 22:56:32 +08:00
feibisi
54ebb8cd91 Add [wpcc_nc] shortcode and improve no-conversion handling
Introduces [wpcc_nc] and [wpcs_nc] shortcodes for marking no-conversion regions, ensuring compatibility with editors that strip HTML comments. Refactors block and content protection logic to use persistent wrappers and data attributes, improving reliability against HTML compressors. Updates quicktag button to insert the new shortcode and revises related UI descriptions.
2025-10-05 23:24:08 +08:00
feibisi
f43da1c764 Revert lazy loading and update default no-conversion tags
This commit removes the lazy loading optimization from the MediaWiki converter, reverting to always loading all conversion tables for stability. It also changes the default value of 'wpcc_no_conversion_tag' to an empty string across config, presets, and admin, and updates global variable synchronization for backward compatibility. Additionally, OpenCC converter now pre-processes dictionaries for improved performance and skips conversion for non-Chinese text. The plugin version is reverted from 1.4.0 to 1.3.0.
2025-10-05 23:18:45 +08:00
35 changed files with 7529 additions and 1356 deletions

1
.gitignore vendored Normal file
View file

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

View file

@ -101,15 +101,14 @@
- **不转换**: 指定不需要转换的内容区域

### 短代码支持
```php
// 显示语言切换器
[wpcc_language_switcher]

// 显示转换状态
[wpcc_conversion_status]

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

### PHP 函数调用
@ -147,6 +146,25 @@ add_action('wpcc_before_conversion', 'before_convert');
add_action('wpcc_after_conversion', 'after_convert');
```

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

- 链接格式与固定链接的关系
- 当 WordPress 启用了固定链接且“URL 链接格式”选择了“前缀/后缀”时:生成 /zh-xx/ 样式链接(或 …/zh-xx/
- 当 WordPress 未启用固定链接(或环境未正确应用 rewrite 规则)时:自动回退为 ?variant=zh-xx避免 404
- 首页根级变体访问行为
- 访问 /zh/ 或 /zh-reset/作为“哨兵”回到不转换首页https://example.com/),并设置 zh 偏好以覆盖浏览器/Cookie 策略
- 访问 /zh-xx/(如 /zh-tw/、/zh-hk/):统一 302 跳转到首页,避免首页重复内容与 404
- zh 哨兵与 rel="nofollow"
- 仅当“不转换”链接为覆盖策略而携带 zh 哨兵URL 含 /zh/ 或 ?variant=zh时自动添加 rel="nofollow"
- 当不需要哨兵(直接是原始 URL不加 nofollow避免影响站内权重传递

## 兼容层与新版内核

- 新内核WPCC_Main / WPCC_Config 等)统一管理重写、变体解析、链接构造、注入脚本等;
- 为了兼容历史主题/插件/短代码调用,保留了 includes/wpcc-core.php 中的“老函数”(如 wpcc_link_conversion、set_wpcc_langs_urls、wpcc_output_navi、短代码 [wp-chinese-converter] 等);
- 老函数的行为已与新内核对齐(例如固定链接未启用时自动降级为 ?variant=xx确保前后端一致
- 建议新项目优先使用区块与新内核能力;对生态依赖的老接口,后续会以 @deprecated 标注与迁移指引逐步过渡。

## 故障排除

### 常见问题
@ -161,7 +179,7 @@ A: 请检查选择的转换引擎和目标语言是否正确。OpenCC 引擎适
A: 请检查 WordPress 缓存配置,或尝试清除转换缓存:`设置 > WP Chinese Converter > 高级设置 > 清除缓存`

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

## 性能优化建议

@ -195,6 +213,13 @@ A: 请确保服务器的 mod_rewrite 模块已启用,并重新保存固定链

## 版本历史

### v1.4.x (2025年稳定版补丁)
- 统一“展示形式”数值语义1=平铺0=下拉;修复短代码展示反向问题
- 单站模式:当 WordPress 未启用固定链接时,链接自动降级为 ?variant=xx避免 /zh-xx/ 404
- 首页根级变体:/zh/ 与 /zh-xx/ 统一 302 到首页;/zh/ 同时设置 zh 偏好覆盖浏览器/Cookie 策略
- 前端切换器:仅在携带 zh 哨兵时为“不转换”链接添加 rel="nofollow",并与新窗口 noopener noreferrer 兼容
- 统一文件命名风格:核心类重命名为 class-wpcc-*.php保留兼容层但与新内核对齐

### v1.3.0 (2025年重构版本)
- 完全重写插件架构,采用现代化 OOP 设计
- 新增 Gutenberg 区块支持

BIN
assets/.DS_Store vendored

Binary file not shown.

View file

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

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

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

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

View file

@ -24,11 +24,12 @@

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


View file

@ -26,46 +26,62 @@

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.wpcc-conversion-status.wpcc-clickable {

View file

@ -86,8 +86,10 @@ function renderLanguageSwitcher(

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

@ -261,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];
}
@ -284,27 +286,120 @@ function getLanguageUrl(langCode) {
// 优先使用服务端注入的 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;
}
} else if (wpcc_langs_urls[langCode]) {
return wpcc_langs_urls[langCode];
}
}
} catch (e) {
// 忽略映射读取错误,回退到查询参数模式
}

// 回退:使用查询参数模式
// 回退:使用查询参数模式(并避免重复:/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; // 已经是不转换
}

return currentUrl.toString();
// 检测站点使用的链接风格:查询参数 / 后缀 / 前缀
const detectStyle = () => {
try {
if (typeof wpcc_langs_urls === 'object' && wpcc_langs_urls) {
for (const k in wpcc_langs_urls) {
if (!Object.prototype.hasOwnProperty.call(wpcc_langs_urls, k)) continue;
const href = String(wpcc_langs_urls[k] || '');
if (/([?&])variant=zh-[a-z]+/i.test(href)) return 'query';
const u = new URL(href, window.location.origin);
const path = u.pathname;
// 前缀: /zh-xx/... 后缀: .../zh-xx/
if (/^\/(zh(?:-[a-z]+)?)(\/|$)/i.test(path)) return 'prefix';
if (/(\/)(zh(?:-[a-z]+)?)\/?$/i.test(path)) return 'suffix';
}
}
} catch (e) {}
return 'query';
};

const style = detectStyle();
try {
const u = new URL(baseUrl);
if (style === 'query') {
u.searchParams.set('variant', 'zh');
return u.toString();
}
if (style === 'suffix') {
// 确保末尾带 /zh/
u.pathname = u.pathname.replace(/\/$/, '') + '/zh/';
return u.toString();
}
// prefix
// 将路径改为 /zh/ + 原路径
u.pathname = u.pathname.replace(/^\/+/, '/');
// 避免在已是 /zh/ 的情况下重复添加 /zh
if (!/^\/(zh)(\/|$)/i.test(u.pathname)) {
u.pathname = '/zh' + (u.pathname.startsWith('/') ? '' : '/') + u.pathname;
}
// 合并多余斜杠
u.pathname = u.pathname.replace(/\/{2,}/g, '/');
return u.toString();
} catch (e) {
// 回退:总是可用的查询参数
try {
const u2 = new URL(baseUrl);
u2.searchParams.set('variant', 'zh');
return u2.toString();
} catch (_e) {
return baseUrl;
}
}
}

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

function handleLanguageChange(langCode, selectElement) {

View file

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

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

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

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

27
changelog.txt Normal file
View file

@ -0,0 +1,27 @@
WP Chinese Converter (WPCC) — 更新日志 / Changelog

Version 1.5.0 (2025-10-11)
- 修复 单站模式下“展示形式”反向的问题:统一语义为 1=平铺、0=下拉,短代码 [wp-chinese-converter] 与区块表现一致
- 修复 单站设置保存问题:确保语言模块、全页面转换、展示形式等选项在单站模式可正确保存
- 优化 zh 哨兵与 rel="nofollow" 规则:仅当“不转换”链接为覆盖策略而携带 zh 哨兵(/zh/ 或 ?variant=zh时添加 nofollow新窗口时追加 noopener noreferrer
- 优化 前端语言切换器:在变体页为“不转换”注入 zh 哨兵;缓存破除(基于文件 mtime 的版本号)
- 链接与重写 统一策略:
* WordPress 未启用固定链接时,所有语言链接自动回退为 ?variant=xx避免 /zh-xx/ 404
* 首页根级变体(/zh/、/zh-xx/)统一 302 到首页;/zh/ 同时设置 zh 偏好
- 内核一致性老函数includes/wpcc-core.php与新内核WPCC_Main策略对齐固定链接检测、链接降级
- 架构与清理:统一核心类文件命名为 class-wpcc-*.php移除旧 url fix 入口;保留兼容层并与新内核对齐
- 文档:更新 README补充链接/重写行为说明与兼容策略

Version 1.4.x (2025)
- 统一 SEO 行为:在不需要 zh 哨兵时不添加 nofollow避免权重传递受影响
- 改善 区块与脚本的加载与配置注入,优化多语言切换体验
- 多站点:网络设置与子站策略协调,增强稳定性

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

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

View file

@ -12,6 +12,10 @@ class wpcc_Admin
private string $url = "";
private $admin_lang = false;
private array $network_controlled_options = [];
private bool $is_network_enforced = false;
private array $network_options = [];

// 移除旧式构造函数,只使用 __construct

private function clean_invalid_langs(): void
@ -43,6 +47,10 @@ class wpcc_Admin
}
$this->langs = &$wpcc_langs;
$this->options = $wpcc_options;
// 初始化网络控制
$this->init_network_control();
if (empty($this->options)) {
$this->options = [
// 语言与标签
@ -66,7 +74,7 @@ class wpcc_Admin
"wpcc_use_cookie_variant" => 1,

// 不转换
"wpcc_no_conversion_tag" => "pre,code,pre.wp-block-code,pre.wp-block-preformatted,script,noscript,style,kbd,samp",
"wpcc_no_conversion_tag" => "",
"wpcc_no_conversion_ja" => 0,
"wpcc_no_conversion_qtag" => 0,

@ -85,6 +93,7 @@ class wpcc_Admin
"wpcc_enable_schema_conversion" => 1,
"wpcc_enable_meta_conversion" => 1,


// 其他
"wpcc_flag_option" => 1,
"wpcc_trackback_plugin_author" => 0,
@ -97,6 +106,7 @@ class wpcc_Admin
}

$this->clean_invalid_langs();

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

$this->base = plugin_basename(dirname(__FILE__)) . "/";
@ -108,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");
@ -237,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(
@ -246,7 +261,7 @@ class wpcc_Admin
dirname(dirname(__DIR__)) . "/wp-chinese-converter.php",
) . "assets/admin/admin.js",
["jquery"],
"2.0.0",
wpcc_VERSION,
true,
);

@ -401,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) {
@ -440,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",
@ -454,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,|",
@ -481,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 (
@ -494,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"]) &&
@ -505,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");
@ -533,6 +585,8 @@ class wpcc_Admin
$wp_rewrite->flush_rules();
}

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

update_wpcc_option("wpcc_options", $options);

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

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

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

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

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

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

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

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

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

View file

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

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

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

public function register_blocks() {
$blocks = array(
'language-switcher',
@ -36,22 +54,18 @@ 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' ),
'1.0.0',
wpcc_VERSION,
true
);

@ -59,7 +73,7 @@ class WPCC_Blocks {
$enabled_languages = $wpcc_options['wpcc_used_langs'] ?? array();

wp_localize_script(
'wp-blocks',
'wpcc-block-compat',
'wpccBlockSettings',
array(
'enabledLanguages' => $enabled_languages,
@ -85,24 +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 );
}

// 为前端状态指示器的 dashicons 图标提供样式支持
wp_enqueue_style( 'dashicons' );

wp_enqueue_style(
'wpcc-blocks-frontend',
plugins_url( 'assets/css/blocks-frontend.css', dirname( dirname( __FILE__ ) ) ),
array(),
'1.0.0'
);
// 样式已在 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
);

View file

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

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

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

class WPCC_MediaWiki_Converter implements WPCC_Converter_Interface {
private $conversion_tables_loaded = false;
private static $loaded_tables = array();
private $lazy_load_enabled = true;
public function convert( $text, $target_variant ) {
if ( empty( $text ) ) {
return $text;
}
// 检查缓存
$cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant );
if ( $cached_result !== null ) {
return $cached_result;
}
$this->ensure_conversion_tables_loaded( $target_variant );
$result = $text;
switch ( $target_variant ) {
case 'zh-hans':
$result = $this->convert_to_hans( $text );
break;
case 'zh-hant':
$result = $this->convert_to_hant( $text );
break;
case 'zh-cn':
$result = $this->convert_to_cn( $text );
break;
case 'zh-tw':
$result = $this->convert_to_tw( $text );
break;
case 'zh-hk':
$result = $this->convert_to_hk( $text );
break;
case 'zh-sg':
$result = $this->convert_to_sg( $text );
break;
}
// 缓存转换结果
if ( $result !== $text ) {
WPCC_Conversion_Cache::set_cached_conversion( $text, $target_variant, $result );
}
return $result;
}
public function get_supported_variants() {
return array( 'zh-hans', 'zh-hant', 'zh-cn', 'zh-tw', 'zh-hk', 'zh-sg' );
}
public function get_engine_name() {
return 'mediawiki';
}
public function get_engine_info() {
$memory_usage = $this->get_memory_usage_info();
return array(
'name' => 'MediaWiki',
'version' => '1.23.5',
'description' => '基于MediaWiki的字符映射转换引擎',
'features' => array(
'字符级精确映射',
'快速转换速度',
'良好的兼容性',
'支持多地区变体',
'按需加载优化'
),
'memory_usage' => $memory_usage,
'conversion_type' => 'character_mapping',
'lazy_load_enabled' => $this->lazy_load_enabled,
'loaded_tables' => array_keys( array_filter( self::$loaded_tables ) )
);
}
public function get_memory_usage_info() {
$loaded_count = count( array_filter( self::$loaded_tables ) );
$total_count = count( $this->get_supported_variants() );
if ( $this->lazy_load_enabled ) {
$estimated_usage = $loaded_count * 0.25; // MB per table approximately
return sprintf( '约 %.1fMB (%d/%d 表已加载, 按需加载开启)', $estimated_usage, $loaded_count, $total_count );
} else {
return '约 1.5MB (所有转换表已加载)';
}
}
public function enable_lazy_loading( $enable = true ) {
$this->lazy_load_enabled = $enable;
}
public function is_lazy_loading_enabled() {
return $this->lazy_load_enabled;
}
public static function get_loaded_tables_status() {
return self::$loaded_tables;
}
public static function unload_table( $variant ) {
if ( isset( self::$loaded_tables[ $variant ] ) ) {
unset( self::$loaded_tables[ $variant ] );
// 清理对应的全局变量这里只能标记PHP不能真正释放全局变量内存
return true;
}
return false;
}
public function is_available() {
$conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php';
return file_exists( $conversion_file );
}
private function ensure_conversion_tables_loaded( $target_variant = null ) {
if ( $this->lazy_load_enabled && $target_variant ) {
// 按需加载特定语言变体的转换表
$this->ensure_specific_table_loaded( $target_variant );
} else {
// 传统方式:加载所有转换表
$this->load_all_conversion_tables();
}
}
private function ensure_specific_table_loaded( $target_variant ) {
if ( isset( self::$loaded_tables[ $target_variant ] ) ) {
return; // 已经加载
}
$conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php';
if ( ! file_exists( $conversion_file ) ) {
return;
}
// 只加载需要的转换表
global $zh2Hans, $zh2Hant, $zh2CN, $zh2TW, $zh2HK, $zh2SG;
switch ( $target_variant ) {
case 'zh-hans':
if ( ! isset( $zh2Hans ) ) {
$this->load_conversion_table_partial( 'zh2Hans' );
}
self::$loaded_tables['zh-hans'] = true;
break;
case 'zh-hant':
if ( ! isset( $zh2Hant ) ) {
$this->load_conversion_table_partial( 'zh2Hant' );
}
self::$loaded_tables['zh-hant'] = true;
break;
case 'zh-cn':
if ( ! isset( $zh2CN ) || ! isset( $zh2Hans ) ) {
$this->load_conversion_table_partial( array( 'zh2CN', 'zh2Hans' ) );
}
self::$loaded_tables['zh-cn'] = true;
break;
case 'zh-tw':
if ( ! isset( $zh2TW ) || ! isset( $zh2Hant ) ) {
$this->load_conversion_table_partial( array( 'zh2TW', 'zh2Hant' ) );
}
self::$loaded_tables['zh-tw'] = true;
break;
case 'zh-hk':
if ( ! isset( $zh2HK ) || ! isset( $zh2Hant ) ) {
$this->load_conversion_table_partial( array( 'zh2HK', 'zh2Hant' ) );
}
self::$loaded_tables['zh-hk'] = true;
break;
case 'zh-sg':
if ( ! isset( $zh2SG ) || ! isset( $zh2Hans ) ) {
$this->load_conversion_table_partial( array( 'zh2SG', 'zh2Hans' ) );
}
self::$loaded_tables['zh-sg'] = true;
break;
default:
$this->load_all_conversion_tables();
break;
}
}
private function load_conversion_table_partial( $table_names ) {
$conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php';
if ( ! is_array( $table_names ) ) {
$table_names = array( $table_names );
}
// 读取文件内容并解析指定的转换表
$file_content = file_get_contents( $conversion_file );
foreach ( $table_names as $table_name ) {
$pattern = '/\$' . preg_quote( $table_name, '/' ) . '\s*=\s*array\s*\((.*?)\);/s';
if ( preg_match( $pattern, $file_content, $matches ) ) {
$table_code = '$' . $table_name . ' = array(' . $matches[1] . ');';
eval( $table_code );
// 将变量设置为全局
$GLOBALS[ $table_name ] = ${$table_name};
}
}
// 加载额外转换文件
$extra_conversion_file = WP_CONTENT_DIR . '/extra_zhconversion.php';
if ( file_exists( $extra_conversion_file ) ) {
require_once $extra_conversion_file;
}
}
private function load_all_conversion_tables() {
if ( $this->conversion_tables_loaded ) {
return;
}
global $zh2Hans, $zh2Hant, $zh2CN, $zh2TW, $zh2HK, $zh2SG;
if ( ! isset( $zh2Hans ) ) {
require_once dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php';
$extra_conversion_file = WP_CONTENT_DIR . '/extra_zhconversion.php';
if ( file_exists( $extra_conversion_file ) ) {
require_once $extra_conversion_file;
}
}
// 标记所有表为已加载
self::$loaded_tables = array(
'zh-hans' => true,
'zh-hant' => true,
'zh-cn' => true,
'zh-tw' => true,
'zh-hk' => true,
'zh-sg' => true
);
$this->conversion_tables_loaded = true;
}
private function convert_to_hans( $text ) {
global $zh2Hans;
return strtr( $text, $zh2Hans );
}
private function convert_to_hant( $text ) {
global $zh2Hant;
return strtr( $text, $zh2Hant );
}
private function convert_to_cn( $text ) {
global $zh2Hans, $zh2CN;
return strtr( strtr( $text, $zh2CN ), $zh2Hans );
}
private function convert_to_tw( $text ) {
global $zh2Hant, $zh2TW;
return strtr( strtr( $text, $zh2TW ), $zh2Hant );
}
private function convert_to_hk( $text ) {
global $zh2Hant, $zh2HK;
return strtr( strtr( $text, $zh2HK ), $zh2Hant );
}
private function convert_to_sg( $text ) {
global $zh2Hans, $zh2SG;
return strtr( strtr( $text, $zh2SG ), $zh2Hans );
}
}

View file

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

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

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

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

class WPCC_OpenCC_Converter implements WPCC_Converter_Interface {
private $strategy_map = array(
'zh-hans' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-hant' => Strategy::SIMPLIFIED_TO_TRADITIONAL,
'zh-cn' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-tw' => Strategy::SIMPLIFIED_TO_TAIWAN_WITH_PHRASE,
'zh-hk' => Strategy::SIMPLIFIED_TO_HONGKONG,
'zh-sg' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-jp' => Strategy::SIMPLIFIED_TO_JAPANESE,
);
public function convert( $text, $target_variant ) {
if ( empty( $text ) ) {
return $text;
}
if ( ! $this->is_available() ) {
throw new Exception( 'OpenCC library is not available' );
}
if ( ! isset( $this->strategy_map[ $target_variant ] ) ) {
return $text;
}
try {
return OpenCC::convert( $text, $this->strategy_map[ $target_variant ] );
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Conversion Error: ' . $e->getMessage() );
return $text;
}
}
public function get_supported_variants() {
return array_keys( $this->strategy_map );
}
public function get_engine_name() {
return 'opencc';
}
public function get_engine_info() {
return array(
'name' => 'OpenCC',
'version' => '1.2.1',
'description' => '基于 OpenCC 的智能词汇级转换引擎',
'features' => array(
'词汇级别转换',
'异体字转换',
'地区习惯用词转换',
'智能语境分析',
'支持批量转换'
),
'memory_usage' => '按需加载,内存占用较低',
'conversion_type' => 'vocabulary_based'
);
}
public function is_available() {
return class_exists( 'Overtrue\PHPOpenCC\OpenCC' );
}
public function batch_convert( $texts, $target_variant ) {
if ( ! $this->is_available() ) {
return $texts;
}
if ( ! isset( $this->strategy_map[ $target_variant ] ) ) {
return $texts;
}
if ( ! is_array( $texts ) ) {
return $texts;
}
// 检查缓存
$cached_results = array();
$uncached_texts = array();
$uncached_indices = array();
foreach ( $texts as $index => $text ) {
$cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant );
if ( $cached_result !== null ) {
$cached_results[ $index ] = $cached_result;
} else {
$uncached_texts[] = $text;
$uncached_indices[] = $index;
}
}
// 如果所有内容都在缓存中,直接返回
if ( empty( $uncached_texts ) ) {
$results = array();
foreach ( $texts as $index => $text ) {
$results[] = $cached_results[ $index ];
}
return $results;
}
try {
// 转换未缓存的内容
$converted_texts = OpenCC::convert( $uncached_texts, $this->strategy_map[ $target_variant ] );
// 合并结果并缓存新转换的内容
$results = array();
$uncached_index = 0;
foreach ( $texts as $index => $original_text ) {
if ( isset( $cached_results[ $index ] ) ) {
$results[] = $cached_results[ $index ];
} else {
$converted_text = is_array( $converted_texts ) ? $converted_texts[ $uncached_index ] : $converted_texts;
$results[] = $converted_text;
// 缓存转换结果
if ( $converted_text !== $original_text ) {
WPCC_Conversion_Cache::set_cached_conversion( $original_text, $target_variant, $converted_text );
}
$uncached_index++;
}
}
return $results;
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Batch Conversion Error: ' . $e->getMessage() );
return $texts;
}
}
public function get_available_strategies() {
return $this->strategy_map;
}
public function convert_with_strategy( $text, $strategy ) {
if ( empty( $text ) || ! $this->is_available() ) {
return $text;
}
try {
return OpenCC::convert( $text, $strategy );
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Strategy Conversion Error: ' . $e->getMessage() );
return $text;
}
}
}

View file

@ -1,6 +1,6 @@
<?php

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

@ -9,7 +9,8 @@ if ( ! defined( 'ABSPATH' ) ) {
*
* 统一管理插件配置,减少全局变量的使用
*/
class WPCC_Config {
class WPCC_Config
{

private static ?self $instance = null;
private array $options = [];
@ -21,13 +22,15 @@ class WPCC_Config {
private array $langs_urls = [];
private array $debug_data = [];

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

public static function get_instance(): self {
if ( null === self::$instance ) {
public static function get_instance(): self
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
@ -36,20 +39,124 @@ class WPCC_Config {
/**
* 加载插件选项
*/
private function load_options(): void {
$this->options = get_wpcc_option( 'wpcc_options' );
if ( empty( $this->options ) ) {
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 {
private function get_default_options(): array
{
return [
// 语言与标签
'wpcc_used_langs' => ['zh-cn','zh-tw'],
'wpcc_used_langs' => ['zh-cn', 'zh-tw'],
'cntip' => '简体',
'twtip' => '繁体',
'hktip' => '港澳',
@ -69,7 +176,7 @@ class WPCC_Config {
'wpcc_use_cookie_variant' => 1,

// 不转换
'wpcc_no_conversion_tag' => 'pre,code,pre.wp-block-code,pre.wp-block-preformatted,script,noscript,style,kbd,samp',
'wpcc_no_conversion_tag' => '',
'wpcc_no_conversion_ja' => 0,
'wpcc_no_conversion_qtag' => 0,

@ -84,10 +191,12 @@ class WPCC_Config {

// 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,
@ -102,135 +211,146 @@ class WPCC_Config {
/**
* 初始化语言配置
*/
private function init_languages(): void {
$this->languages = [
'zh-cn' => [ 'zhconversion_cn', 'cntip', __( '简体中文', 'wp-chinese-converter' ), 'zh-CN' ],
'zh-tw' => [ 'zhconversion_tw', 'twtip', __( '台灣正體', 'wp-chinese-converter' ), 'zh-TW' ],
'zh-hk' => [ 'zhconversion_hk', 'hktip', __( '港澳繁體', 'wp-chinese-converter' ), 'zh-HK' ],
'zh-hans' => [ 'zhconversion_hans', 'hanstip', __( '简体中文', 'wp-chinese-converter' ), 'zh-Hans' ],
'zh-hant' => [ 'zhconversion_hant', 'hanttip', __( '繁体中文', 'wp-chinese-converter' ), 'zh-Hant' ],
'zh-sg' => [ 'zhconversion_sg', 'sgtip', __( '马新简体', 'wp-chinese-converter' ), 'zh-SG' ],
'zh-jp' => [ 'zhconversion_jp', 'jptip', __( '日式汉字', 'wp-chinese-converter' ), 'zh-JP' ],
];
private function init_languages(): void
{
// 使用中心化的语言配置
$this->languages = WPCC_Language_Config::get_all_languages();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/**
* 获取直接转换标志
*/
public function get_direct_conversion_flag(): bool {
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 ) {
public function add_debug_data(string $key, $data): void
{
if (defined('wpcc_DEBUG') && wpcc_DEBUG) {
$this->debug_data[$key] = $data;
}
}
@ -238,50 +358,56 @@ class WPCC_Config {
/**
* 获取调试数据
*/
public function get_debug_data(): array {
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 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 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 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 get_conversion_engine(): string
{
return $this->get_option('wpcc_engine', 'opencc');
}

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

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

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


View file

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

View file

@ -194,8 +194,14 @@ final class WPCC_Exception_Handler {
return;
}
$error_key = get_class( $exception );
self::$error_counts[ $error_key ] = ( self::$error_counts[ $error_key ] ?? 0 ) + 1;
// 统一异常计数键,保证与 should_suppress_error 使用的键一致
if ( $exception instanceof WPCC_Exception ) {
$ekey = 'wpcc_error_' . $exception->get_error_code();
self::$error_counts[ $ekey ] = ( self::$error_counts[ $ekey ] ?? 0 ) + 1;
} else {
$ekey = get_class( $exception );
self::$error_counts[ $ekey ] = ( self::$error_counts[ $ekey ] ?? 0 ) + 1;
}
$log_message = sprintf(
'WPCC Error in %s: %s [%s:%d] Context: %s',

View file

@ -18,7 +18,7 @@ abstract class WPCC_Extension extends WPCC_Abstract_Module {
protected $update_server_url;
protected $extension_slug;
protected $is_premium = true;
protected $required_core_version = '1.3.0';
protected $required_core_version = '1.5';
protected $pricing_tier = 'pro'; // free, pro, enterprise, ultimate
public function __construct() {

View file

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

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

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

View file

@ -40,9 +40,14 @@ class WPCC_Main {
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' ] );
@ -61,12 +66,20 @@ class WPCC_Main {
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();
}
// 处理评论提交
@ -108,7 +121,7 @@ class WPCC_Main {
// 注册核心模块
$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_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' );
@ -162,6 +175,19 @@ class WPCC_Main {
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( '' );
@ -171,10 +197,29 @@ class WPCC_Main {
// 设置无转换URL
$noconversion_url = $this->get_noconversion_url();
$this->config->set_noconversion_url( $noconversion_url );
// 向后兼容:同步全局变量
$GLOBALS['wpcc_noconversion_url'] = $noconversion_url;
// 获取目标语言
$target_lang = $this->determine_target_language( $wp );
$this->config->set_target_lang( $target_lang );
// 向后兼容:同步全局变量
$GLOBALS['wpcc_target_lang'] = $target_lang;
// 若首页为静态页面且请求为根级变体路径,则直接映射到首页,避免 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();
@ -187,20 +232,25 @@ class WPCC_Main {
* 确定目标语言
*/
private function determine_target_language( $wp ): string {
$request_lang = $wp->query_vars['variant'] ?? '';
$cookie_lang = $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ?? '';
$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'重定向
// 处理特殊的'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();
@ -226,9 +276,23 @@ class WPCC_Main {
$cookie_key = 'wpcc_variant_' . COOKIEHASH;
if ( $this->config->is_feature_enabled( 'wpcc_use_cookie_variant' ) ) {
setcookie( $cookie_key, 'zh', time() + 30000000, COOKIEPATH, COOKIE_DOMAIN );
setcookie( $cookie_key, 'zh', [
'expires' => time() + 30000000,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
] );
} else {
setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', 0, COOKIEPATH, COOKIE_DOMAIN );
setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', [
'expires' => 0,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
] );
}
wp_redirect( $this->config->get_noconversion_url() );
@ -239,7 +303,7 @@ class WPCC_Main {
* 检测浏览器语言
*/
private function detect_browser_language(): string {
$accept_language = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
$accept_language = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) : '';
if ( empty( $accept_language ) ) {
return '';
}
@ -263,7 +327,14 @@ class WPCC_Main {
$current_cookie = $_COOKIE[$cookie_key] ?? '';
if ( $current_cookie !== $target_lang ) {
setcookie( $cookie_key, $target_lang, time() + 30000000, COOKIEPATH, COOKIE_DOMAIN );
setcookie( $cookie_key, $target_lang, [
'expires' => time() + 30000000,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
] );
}
}
@ -284,18 +355,65 @@ class WPCC_Main {
* 模板重定向处理
*/
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', 0, COOKIEPATH, COOKIE_DOMAIN );
setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', [
'expires' => 0,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
] );
wp_redirect( $redirect_url, 302 );
exit;
}
$target_lang = $this->config->get_target_lang();
if ( ! $target_lang ) {
// 即使是“未选择语言(不转换)”也输出头部数据,供前端开关生成漂亮链接
add_action( 'wp_head', [ $this, 'output_header' ] );
return;
}
@ -312,23 +430,36 @@ class WPCC_Main {
private function set_language_urls(): void {
$langs_urls = [];
$noconversion_url = $this->config->get_noconversion_url();
$use_permalink = $this->config->is_feature_enabled( 'wpcc_use_permalink' );
$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 ( $noconversion_url === home_url( '/' ) && $use_permalink ) {
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();
@ -342,6 +473,14 @@ class WPCC_Main {
// 添加链接转换过滤器
$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 );
}
// 内容转换过滤器
@ -410,16 +549,24 @@ class WPCC_Main {
// 这里添加其他必要的私有方法...
/**
* 检查 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( '|', $enabled_langs );
$reg = implode( '|', array_map( 'preg_quote', $enabled_langs ) );
$protocol = is_ssl() ? 'https://' : 'http://';
$host = $_SERVER['HTTP_HOST'] ?? '';
$uri = $_SERVER['REQUEST_URI'] ?? '';
$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 ) ) );
@ -437,7 +584,80 @@ class WPCC_Main {
* 设置重写规则
*/
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' );
}
}
}
/**
@ -450,6 +670,10 @@ class WPCC_Main {
$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 ) {
@ -474,7 +698,6 @@ class WPCC_Main {
}
}
$rules2[ '^(' . $reg . '|zh|zh-reset)/?$' ] = 'index.php?variant=$matches[1]';
return array_merge( $rules2, $rules );
}
@ -489,15 +712,21 @@ class WPCC_Main {
* 处理评论提交
*/
private function handle_comment_submission(): void {
if ( ( isset( $_SERVER['PHP_SELF'] ) && ( strpos( $_SERVER['PHP_SELF'], 'wp-comments-post.php' ) !== false
|| strpos( $_SERVER['PHP_SELF'], 'ajax-comments.php' ) !== false
|| strpos( $_SERVER['PHP_SELF'], 'comments-ajax.php' ) !== false )
$php_self = isset( $_SERVER['PHP_SELF'] ) ? sanitize_text_field( wp_unslash( $_SERVER['PHP_SELF'] ) ) : '';
$request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
if ( ( $php_self && ( strpos( $php_self, 'wp-comments-post.php' ) !== false
|| strpos( $php_self, 'ajax-comments.php' ) !== false
|| strpos( $php_self, 'comments-ajax.php' ) !== false )
) &&
isset( $_SERVER["REQUEST_METHOD"] ) && $_SERVER["REQUEST_METHOD"] == "POST" &&
isset( $_POST['variant'] ) && ! empty( $_POST['variant'] ) && $this->config->is_language_enabled( $_POST['variant'] )
$request_method === 'POST' &&
isset( $_POST['variant'] ) && ! empty( $_POST['variant'] )
) {
$this->config->set_target_lang( sanitize_text_field( $_POST['variant'] ) );
$this->do_conversion();
$variant = sanitize_text_field( wp_unslash( $_POST['variant'] ) );
if ( $this->config->is_language_enabled( $variant ) ) {
$this->config->set_target_lang( $variant );
$this->do_conversion();
}
}
}
@ -534,17 +763,8 @@ class WPCC_Main {
public function render_no_conversion_block( string $block_content, array $block ): string {
if ( isset( $block['blockName'] ) && $block['blockName'] === 'wpcc/no-conversion' ) {
$unique_id = uniqid();
$pattern = '/<div[^>]*class="[^"]*wpcc-no-conversion-content[^"]*"[^>]*>(.*?)<\\/div>/s';
$replacement = function ( $matches ) use ( $unique_id ) {
$content = $matches[1];
return '<div class="wpcc-no-conversion-content"><!--wpcc_NC' . $unique_id . '_START-->' . $content . '<!--wpcc_NC' . $unique_id . '_END--></div>';
};
$block_content = preg_replace_callback( $pattern, $replacement, $block_content );
return '<!--wpcc_NC' . $unique_id . '_START-->' . $block_content . '<!--wpcc_NC' . $unique_id . '_END-->';
}
return $block_content;
}
@ -567,19 +787,45 @@ class WPCC_Main {
$wp_home = home_url();
}

if ( str_contains( $link, $variant ) ) {
if ( empty( $variant ) ) {
return $link;
}

if ( str_contains( $link, '?' ) || ! $this->config->is_feature_enabled( 'wpcc_use_permalink' ) ) {
// 仅当路径段或查询参数中已包含有效变体时,认为“已包含”
$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 ( $this->config->get_option( 'wpcc_use_permalink' ) == 1 ) {
return user_trailingslashit( trailingslashit( $link ) . $variant );
if ( $style === 1 ) {
// 后缀模式: .../permalink/.../zh-xx/
return user_trailingslashit( trailingslashit( $path ) . $variant ) . $qs;
}

return str_replace( $wp_home, "$wp_home/$variant", $link );
// 前缀模式 (style 2): .../zh-xx/permalink/...
return str_replace( $wp_home, "$wp_home/$variant", $path ) . $qs;
}
/**
@ -607,15 +853,195 @@ class WPCC_Main {
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;
}
/**
* 添加内容转换过滤器
*/
@ -648,11 +1074,19 @@ class WPCC_Main {
$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" . '<!-- WP Chinese Converter Plugin Version ' . esc_html( wpcc_VERSION ) . ' -->';
$script_data = [
'wpcc_target_lang' => $target_lang ? esc_js( $target_lang ) : '',
'wpcc_noconversion_url' => $noconversion_url ? esc_url( $noconversion_url ) : '',
'wpcc_noconversion_url' => $noconv_forced ? esc_url( $noconv_forced ) : '',
'wpcc_langs_urls' => []
];
@ -699,7 +1133,7 @@ class WPCC_Main {
if ( $target_lang && ! $this->config->get_direct_conversion_flag() ) {
$home_url = $this->convert_link( home_url( '/' ), $target_lang );
$home_pattern = preg_quote( esc_url( home_url( '' ) ), '|' );
$buffer = preg_replace( '|(<a\s(?!class="wpcc_link")[^<>]*?href=([\'"]]))' . $home_pattern . '/?(\2[^<>]*?>)|', '${1}' . esc_url( $home_url ) . '${3}', $buffer );
$buffer = preg_replace( '|(<a\s(?!class=\"wpcc_link\")[^<>]*?href=([\'\"]))' . $home_pattern . '/?(\2[^<>]*?>)|', '${1}' . esc_url( $home_url ) . '${3}', $buffer );
}
return zhconversion2( $buffer ) . "\n" . '<!-- WP Chinese Converter Full Page Converted. Target Lang: ' . $target_lang . ' -->';
}

View file

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

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

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;
}

// 检查缓存
$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':
$result = $this->convert_to_hans( $text );
break;
case 'zh-hant':
$result = $this->convert_to_hant( $text );
break;
case 'zh-cn':
$result = $this->convert_to_cn( $text );
break;
case 'zh-tw':
$result = $this->convert_to_tw( $text );
break;
case 'zh-hk':
$result = $this->convert_to_hk( $text );
break;
case 'zh-sg':
$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的字符映射转换引擎',
'features' => array(
'字符级精确映射',
'快速转换速度',
'良好的兼容性',
'支持多地区变体'
),
'conversion_type' => 'character_mapping'
);
}

public function is_available() {
$conversion_file = dirname( dirname( __FILE__ ) ) . '/core/ZhConversion.php';
return file_exists( $conversion_file );
}

private function load_all_conversion_tables() {
if ( $this->conversion_tables_loaded ) {
return;
}

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 );
}
}

View file

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

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

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

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

class WPCC_OpenCC_Converter implements WPCC_Converter_Interface {
/**
* 预处理并排序后的字典缓存(按策略)
* @var array<string, array<array<string,string>>> 每个策略对应若干个字典映射,保持原有顺序
*/
private $prepared = [];

/**
* 是否包含中文字符的快速检测(避免不必要的 OpenCC 调用)
*/
private function contains_chinese($text) {
return is_string($text) && preg_match('/[\x{4e00}-\x{9fff}]/u', $text) === 1;
}

/**
* 获取预处理后的字典(已展开并按键长度降序排序),仅在本请求内执行一次
*/
private function get_prepared_dictionaries(string $strategy): array {
if (isset($this->prepared[$strategy])) {
return $this->prepared[$strategy];
}

$sets = Dictionary::get($strategy); // 与 upstream 一致的字典结构
$prepared = [];

foreach ($sets as $dictionary) {
// 可能是分组数组(数组的数组),需要先展平
if (is_array(reset($dictionary))) {
$flat = [];
foreach ($dictionary as $dict) {
$flat = array_merge($flat, $dict);
}
// 按键长度降序排序,优先匹配长词
uksort($flat, function ($a, $b) {
return mb_strlen($b) <=> mb_strlen($a);
});
$prepared[] = $flat;
continue;
}

// 普通单字典
$flat = $dictionary;
uksort($flat, function ($a, $b) {
return mb_strlen($b) <=> mb_strlen($a);
});
$prepared[] = $flat;
}

$this->prepared[$strategy] = $prepared;
return $prepared;
}

private $strategy_map = array(
'zh-hans' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-hant' => Strategy::SIMPLIFIED_TO_TRADITIONAL,
'zh-cn' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-tw' => Strategy::SIMPLIFIED_TO_TAIWAN_WITH_PHRASE,
'zh-hk' => Strategy::SIMPLIFIED_TO_HONGKONG,
'zh-sg' => Strategy::TRADITIONAL_TO_SIMPLIFIED,
'zh-jp' => Strategy::SIMPLIFIED_TO_JAPANESE,
);
public function convert( $text, $target_variant ) {
if ( empty( $text ) ) {
return $text;
}
// 快速路径:非中文内容不做转换
if ( ! $this->contains_chinese( $text ) ) {
return $text;
}

if ( ! $this->is_available() ) {
throw new Exception( 'OpenCC library is not available' );
}
if ( ! isset( $this->strategy_map[ $target_variant ] ) ) {
return $text;
}
try {
// 使用预处理字典避免每次调用时重复排序
$strategy = $this->strategy_map[ $target_variant ];
$dictionaries = $this->get_prepared_dictionaries($strategy);
$output = $text;
foreach ($dictionaries as $dict) {
$output = strtr($output, $dict);
}
return $output;
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Conversion Error: ' . $e->getMessage() );
return $text;
}
}
public function get_supported_variants() {
return array_keys( $this->strategy_map );
}
public function get_engine_name() {
return 'opencc';
}
public function get_engine_info() {
return array(
'name' => 'OpenCC',
'version' => '1.2.1',
'description' => '基于 OpenCC 的智能词汇级转换引擎',
'features' => array(
'词汇级别转换',
'异体字转换',
'地区习惯用词转换',
'智能语境分析',
'支持批量转换'
),
'memory_usage' => '按需加载,内存占用较低',
'conversion_type' => 'vocabulary_based'
);
}
public function is_available() {
return class_exists( 'Overtrue\PHPOpenCC\OpenCC' );
}
public function batch_convert( $texts, $target_variant ) {
if ( ! $this->is_available() ) {
return $texts;
}
if ( ! isset( $this->strategy_map[ $target_variant ] ) ) {
return $texts;
}
if ( ! is_array( $texts ) ) {
return $texts;
}
// 检查缓存
$cached_results = array();
$uncached_texts = array();
$uncached_indices = array();
foreach ( $texts as $index => $text ) {
$cached_result = WPCC_Conversion_Cache::get_cached_conversion( $text, $target_variant );
if ( $cached_result !== null ) {
$cached_results[ $index ] = $cached_result;
} else if ( ! $this->contains_chinese( $text ) ) {
// 非中文内容:无需调用 OpenCC直接作为“已处理”内容返回
$cached_results[ $index ] = $text;
} else {
$uncached_texts[] = $text;
$uncached_indices[] = $index;
}
}
// 如果所有内容都在缓存中,直接返回
if ( empty( $uncached_texts ) ) {
$results = array();
foreach ( $texts as $index => $text ) {
$results[] = $cached_results[ $index ];
}
return $results;
}
try {
// 使用预处理字典一次性转换,避免每次排序
$strategy = $this->strategy_map[ $target_variant ];
$dictionaries = $this->get_prepared_dictionaries($strategy);

$converted_texts = $uncached_texts;
foreach ($dictionaries as $dict) {
$converted_texts = array_map(function ($str) use ($dict) {
return strtr($str, $dict);
}, $converted_texts);
}
// 合并结果并缓存新转换的内容
$results = array();
$uncached_index = 0;
foreach ( $texts as $index => $original_text ) {
if ( isset( $cached_results[ $index ] ) ) {
$results[] = $cached_results[ $index ];
} else {
$converted_text = is_array( $converted_texts ) ? $converted_texts[ $uncached_index ] : $converted_texts;
$results[] = $converted_text;
// 缓存转换结果
if ( $converted_text !== $original_text ) {
WPCC_Conversion_Cache::set_cached_conversion( $original_text, $target_variant, $converted_text );
}
$uncached_index++;
}
}
return $results;
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Batch Conversion Error: ' . $e->getMessage() );
return $texts;
}
}
public function get_available_strategies() {
return $this->strategy_map;
}
public function convert_with_strategy( $text, $strategy ) {
if ( empty( $text ) || ! $this->is_available() ) {
return $text;
}
try {
return OpenCC::convert( $text, $strategy );
} catch ( Exception $e ) {
error_log( 'WPCC OpenCC Strategy Conversion Error: ' . $e->getMessage() );
return $text;
}
}
}

View file

@ -72,7 +72,7 @@ final class WPCC_Presets {

'wpcc_use_fullpage_conversion' => 0,

'wpcc_no_conversion_tag' => 'pre,code,pre.wp-block-code,pre.wp-block-preformatted,script,noscript,style,kbd,samp',
'wpcc_no_conversion_tag' => '',
'wpcc_no_conversion_ja' => 0,
'wpcc_no_conversion_qtag' => 0,


View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

@ -27,56 +27,197 @@ function wpcc_sitemap_setup() {


function wpcc_sitemap_rewrite_rules( $rules ) {
global $wpcc_options;
$langs = isset($wpcc_options['wpcc_used_langs']) && is_array($wpcc_options['wpcc_used_langs']) ? $wpcc_options['wpcc_used_langs'] : array('zh-cn','zh-tw','zh-hk');
$langs = array_values(array_unique(array_filter($langs)));
$pattern = implode('|', array_map('preg_quote', $langs));

$new_rules = array();
$new_rules['^(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)/wp-sitemap\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]';
$new_rules['^(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)/sitemap\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]';
$new_rules['^sitemap-(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)-(\d+)\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]&wpcc_sitemap_page=$matches[2]';
if ( $pattern !== '' ) {
// 主网站地图索引
$new_rules['^(' . $pattern . ')/wp-sitemap\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]';
$new_rules['^(' . $pattern . ')/sitemap\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]';
// 文章网站地图
$new_rules['^sitemap-(' . $pattern . ')-(\d+)\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[1]&wpcc_sitemap_page=$matches[2]';
// 分类网站地图
$new_rules['^sitemap-taxonomy-([a-zA-Z0-9_]+)-(' . $pattern . ')-(\d+)\.xml/?$'] = 'index.php?wpcc_sitemap_lang=$matches[2]&wpcc_sitemap_taxonomy=$matches[1]&wpcc_sitemap_page=$matches[3]';
}
return array_merge( $new_rules, $rules );
}

function custom_sitemap_query_vars( $vars ) {
$vars[] = 'wpcc_sitemap_lang';
$vars[] = 'wpcc_sitemap_page';
$vars[] = 'wpcc_sitemap_taxonomy';
return $vars;
}

function custom_sitemap_template_redirect() {
$uri = $_SERVER['REQUEST_URI'];
/**
* 专门用于站点地图的URL转换函数
* 与wpcc_link_conversion不同这个函数会强制转换URL到指定语言即使原URL已包含语言变体
*/
function wpcc_sitemap_link_conversion( $link, $variant ) {
global $wpcc_options;
$lang = '';
if ( preg_match( '/\/(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)\/sitemap\.xml\/?$/', $uri, $matches ) ) {
$lang = $matches[1];
} elseif ( preg_match( '/\/(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)\/wp-sitemap\.xml\/?$/', $uri, $matches ) ) {
$lang = $matches[1];
if ( empty( $variant ) || empty( $link ) ) {
return $link;
}
if ( ! empty( $lang ) ) {
// 获取原始URL去除所有语言变体
$original_link = wpcc_remove_language_from_url( $link );
$style = (int) ( $wpcc_options['wpcc_use_permalink'] ?? 0 );
$permalinks_enabled = (string) get_option( 'permalink_structure' ) !== '';
// 当 WP 未启用固定链接时,使用查询参数
if ( ! $permalinks_enabled || $style === 0 ) {
return add_query_arg( 'variant', $variant, $original_link );
}
// Split path and query
$qpos = strpos( $original_link, '?' );
$path = $qpos !== false ? substr( $original_link, 0, $qpos ) : $original_link;
$qs = $qpos !== false ? substr( $original_link, $qpos ) : '';
if ( $style === 1 ) {
// suffix style: /postname/zh-xx/
return user_trailingslashit( trailingslashit( $path ) . $variant ) . $qs;
}
// prefix style (2): /zh-xx/postname/
if ( is_multisite() && wpcc_mobile_exist( 'network' ) ) {
$sites = get_sites();
foreach ( $sites as $site ) {
if ( '/' == $site->path ) {
continue;
}
$path_seg = str_replace( '/', '', $site->path );
$sub_url = "$site->domain/$path_seg";
if ( str_contains( $path, $sub_url ) ) {
return str_replace( $sub_url, "$sub_url/$variant", $path ) . $qs;
}
}
}
// 默认前缀样式
$home_url = home_url();
return str_replace( $home_url, $home_url . '/' . $variant, $original_link );
}

/**
* 从URL中移除语言变体
*/
function wpcc_remove_language_from_url( $url ) {
global $wpcc_options;
if ( empty( $url ) ) {
return $url;
}
// 获取启用的语言列表
$enabled = isset( $wpcc_options['wpcc_used_langs'] ) && is_array( $wpcc_options['wpcc_used_langs'] ) ? $wpcc_options['wpcc_used_langs'] : [];
if ( empty( $enabled ) ) {
return $url;
}
// 移除查询参数中的variant
$url = remove_query_arg( 'variant', $url );
// 创建语言变体的正则表达式
$reg = implode( '|', array_map( 'preg_quote', $enabled ) );
$variant_regex = '/\/(' . $reg . '|zh|zh-reset)(\/|$)/i';
// 移除路径中的语言变体
$parsed = parse_url( $url );
if ( isset( $parsed['path'] ) ) {
$path = $parsed['path'];
$path = preg_replace( $variant_regex, '/', $path );
$path = rtrim( $path, '/' );
if ( empty( $path ) ) {
$path = '/';
}
// 重建URL
$scheme = isset( $parsed['scheme'] ) ? $parsed['scheme'] . '://' : '';
$host = $parsed['host'] ?? '';
$port = isset( $parsed['port'] ) ? ':' . $parsed['port'] : '';
$query = isset( $parsed['query'] ) ? '?' . $parsed['query'] : '';
$fragment = isset( $parsed['fragment'] ) ? '#' . $parsed['fragment'] : '';
$url = $scheme . $host . $port . $path . $query . $fragment;
}
return $url;
}

function custom_sitemap_template_redirect() {
$uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '';

// 动态语言模式:仅允许已启用语言
$enabled = isset($GLOBALS['wpcc_options']['wpcc_used_langs']) && is_array($GLOBALS['wpcc_options']['wpcc_used_langs']) ? $GLOBALS['wpcc_options']['wpcc_used_langs'] : array('zh-cn','zh-tw','zh-hk');
$enabled = array_values(array_unique(array_filter($enabled)));
$pat = implode('|', array_map('preg_quote', $enabled));

$lang = '';
if ( $pat !== '' && preg_match( '#/(' . $pat . ')/sitemap\.xml/?$#i', $uri, $matches ) ) {
$lang = strtolower($matches[1]);
} elseif ( $pat !== '' && preg_match( '#/(' . $pat . ')/wp-sitemap\.xml/?$#i', $uri, $matches ) ) {
$lang = strtolower($matches[1]);
}

if ( $lang !== '' ) {
if ( ! in_array( $lang, $enabled, true ) ) {
status_header( 404 );
exit;
}
header( 'Content-Type: application/xml; charset=utf-8' );
header( 'HTTP/1.1 200 OK' );

$content = generate_sitemap_index( $lang );
if ( ! empty( $content ) ) {
echo $content;
} else {
echo '<?xml version="1.0" encoding="UTF-8"?><error>Sitemap not found</error>';
}
echo $content !== '' ? $content : '<?xml version="1.0" encoding="UTF-8"?><error>Sitemap not found</error>';
exit;
}

if ( preg_match( '/\/sitemap-(zh-tw|zh-cn|zh-hk|zh-sg|zh-hans|zh-hant)-(\d+)\.xml\/?$/', $uri, $matches ) ) {
$lang = $matches[1];
// 处理文章网站地图
if ( $pat !== '' && preg_match( '#/sitemap-(' . $pat . ')-(\d+)\.xml/?$#i', $uri, $matches ) ) {
$lang = strtolower($matches[1]);
$page = (int) $matches[2];
if ( ! in_array( $lang, $enabled, true ) ) {
status_header( 404 );
exit;
}
header( 'Content-Type: application/xml; charset=utf-8' );
header( 'HTTP/1.1 200 OK' );
echo generate_paged_sitemap_content( $lang, $page );
exit;
}

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

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

@ -88,6 +229,12 @@ function custom_sitemap_template_redirect() {
function generate_sitemap_index( string $lang ) {
global $wpdb, $wpcc_options;

// 仅允许启用语言
$enabled = isset($wpcc_options['wpcc_used_langs']) && is_array($wpcc_options['wpcc_used_langs']) ? $wpcc_options['wpcc_used_langs'] : array('zh-cn','zh-tw');
if ( ! in_array( $lang, $enabled, true ) ) {
return '';
}

$max_urls_per_sitemap = 1000;

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

// 仅允许启用语言
$enabled = isset($wpcc_options['wpcc_used_langs']) && is_array($wpcc_options['wpcc_used_langs']) ? $wpcc_options['wpcc_used_langs'] : array('zh-cn','zh-tw');
if ( ! in_array( $lang, $enabled, true ) ) {
return '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />';
}

$max_urls_per_sitemap = 1000;

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

/**
* 生成分类网站地图内容
*
* @param string $taxonomy 分类法名称
* @param string $lang 语言代码
* @param int $page 页码
* @return string XML内容
*/
function generate_taxonomy_sitemap_content( string $taxonomy, string $lang, int $page ) {
global $wpcc_options;

// 仅允许启用语言
$enabled = isset($wpcc_options['wpcc_used_langs']) && is_array($wpcc_options['wpcc_used_langs']) ? $wpcc_options['wpcc_used_langs'] : array('zh-cn','zh-tw');
if ( ! in_array( $lang, $enabled, true ) ) {
return '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />';
}

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

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

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

?>

File diff suppressed because it is too large Load diff

View file

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

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

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

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

// 防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
exit;
@ -19,7 +106,7 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
function wpcc_admin_init() {
global $wpcc_admin;
require_once __DIR__ . '/admin/wp-chinese-converter-admin.php';
require_once __DIR__ . '/admin/class-wpcc-admin.php';
$wpcc_admin = new wpcc_Admin();
add_filter( 'plugin_action_links', array( $wpcc_admin, 'action_links' ), 10, 2 );
}
@ -80,17 +167,17 @@ function wpcc_activate() {
*/
function wpcc_appthemes_add_quicktags() {
global $wpcc_options;
if ( ! empty( $wpcc_options ) && ! empty( $wpcc_options['wpcc_no_conversion_qtag'] ) && wp_script_is( 'quicktags', 'enqueued' ) ) {
if ( ! empty( $wpcc_options ) && ! empty( $wpcc_options['wpcc_no_conversion_qtag'] ) ) {
?>
<script type="text/javascript">
//<![CDATA[
jQuery(document).ready(function($) {
if (typeof QTags !== 'undefined' && QTags.addButton) {
QTags.addButton('eg_wpcc_nc', 'wpcc_NC', '<!--wpcc_NC_START-->', '<!--wpcc_NC_END-->', null, 'WP Chinese Converter DO-NOT Convert Tag', 120);
QTags.addButton('eg_wpcc_nc', 'wpcc_NC', '[wpcc_nc]', '[/wpcc_nc]', null, 'WP Chinese Converter: Insert no-convert markers', 120);
} else {
setTimeout(function() {
if (typeof QTags !== 'undefined' && QTags.addButton) {
QTags.addButton('eg_wpcc_nc', 'wpcc_NC', '<!--wpcc_NC_START-->', '<!--wpcc_NC_END-->', null, 'WP Chinese Converter DO-NOT Convert Tag', 120);
QTags.addButton('eg_wpcc_nc', 'wpcc_NC', '[wpcc_nc]', '[/wpcc_nc]', null, 'WP Chinese Converter: Insert no-convert markers', 120);
}
}, 100);
}
@ -211,31 +298,13 @@ function wpcc_add_conversion_meta_box() {

/**
* 获取语言模块配置
* 使用中心化的语言配置管理
*/
function wpcc_get_language_config() {
global $wpcc_options;
$default_names = array(
'zh-cn' => '中国大陆',
'zh-tw' => '台湾正体',
'zh-hk' => '港澳繁体',
'zh-hans' => '简体中文',
'zh-hant' => '繁体中文',
'zh-sg' => '马新简体',
'zh-jp' => '日式汉字'
);
$custom_names = array(
'zh-cn' => $wpcc_options['cntip'] ?? $default_names['zh-cn'],
'zh-tw' => $wpcc_options['twtip'] ?? $default_names['zh-tw'],
'zh-hk' => $wpcc_options['hktip'] ?? $default_names['zh-hk'],
'zh-hans' => $wpcc_options['hanstip'] ?? $default_names['zh-hans'],
'zh-hant' => $wpcc_options['hanttip'] ?? $default_names['zh-hant'],
'zh-sg' => $wpcc_options['sgtip'] ?? $default_names['zh-sg'],
'zh-jp' => $wpcc_options['jptip'] ?? $default_names['zh-jp']
);
return $custom_names;
// 使用中心化的语言配置
return WPCC_Language_Config::get_custom_names( $wpcc_options );
}

/**
@ -361,8 +430,10 @@ function my_ajax_clear_cache_handler() {


// 注册管理钩子
// 注意:网络管理菜单现在由 wpcc-network-settings.php 处理
if ( is_multisite() && wpcc_mobile_exist( 'network' ) ) {
add_action( 'network_admin_menu', 'wpcc_admin_init' );
// add_action( 'network_admin_menu', 'wpcc_admin_init' ); // 已被新的网络设置模块替代
add_action( 'admin_menu', 'wpcc_admin_init' );
} else {
add_action( 'admin_menu', 'wpcc_admin_init' );
}
@ -373,6 +444,31 @@ register_activation_hook( dirname( __DIR__ ) . '/wp-chinese-converter.php', 'wpc
// 注册编辑器增强钩子
add_action( 'admin_print_footer_scripts', 'wpcc_appthemes_add_quicktags' );

// TinyMCE 可视化编辑器按钮(仅在开启“快速标签”选项时添加按钮;短代码始终可用)
function wpcc_register_tinymce_plugin( $plugins ) {
global $wpcc_options;
if ( empty( $wpcc_options ) ) {
$wpcc_options = get_wpcc_option( 'wpcc_options' );
}
// 仅当设置开启时添加按钮脚本
if ( ! empty( $wpcc_options['wpcc_no_conversion_qtag'] ) ) {
$plugins['wpcc_nc'] = wpcc_DIR_URL . 'assets/js/tinymce-wpcc-nc.js';
}
return $plugins;
}
function wpcc_add_tinymce_button( $buttons ) {
global $wpcc_options;
if ( empty( $wpcc_options ) ) {
$wpcc_options = get_wpcc_option( 'wpcc_options' );
}
if ( ! empty( $wpcc_options['wpcc_no_conversion_qtag'] ) ) {
$buttons[] = 'wpcc_nc';
}
return $buttons;
}
add_filter( 'mce_external_plugins', 'wpcc_register_tinymce_plugin' );
add_filter( 'mce_buttons', 'wpcc_add_tinymce_button' );

// 注册文章转换钩子
add_action( 'init', 'wpcc_init_post_conversion' );


View file

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

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

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


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

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

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

if ( isset( $wpcc_options['wpcc_use_permalink'] ) && $wpcc_options['wpcc_use_permalink'] != 0 && empty( $wp_rewrite->permalink_structure ) ) {
@ -52,17 +59,23 @@ function wpcc_init() {
}

// 处理评论提交的转换
if ( ( isset( $_SERVER['PHP_SELF'] ) && ( strpos( $_SERVER['PHP_SELF'], 'wp-comments-post.php' ) !== false
|| strpos( $_SERVER['PHP_SELF'], 'ajax-comments.php' ) !== false
|| strpos( $_SERVER['PHP_SELF'], 'comments-ajax.php' ) !== false )
$php_self = isset( $_SERVER['PHP_SELF'] ) ? sanitize_text_field( wp_unslash( $_SERVER['PHP_SELF'] ) ) : '';
$request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
if ( ( $php_self && ( strpos( $php_self, 'wp-comments-post.php' ) !== false
|| strpos( $php_self, 'ajax-comments.php' ) !== false
|| strpos( $php_self, 'comments-ajax.php' ) !== false )
) &&
isset( $_SERVER["REQUEST_METHOD"] ) && $_SERVER["REQUEST_METHOD"] == "POST" &&
isset( $_POST['variant'] ) && ! empty( $_POST['variant'] ) && in_array( $_POST['variant'], $wpcc_options['wpcc_used_langs'] )
$request_method === 'POST' &&
isset( $_POST['variant'] ) && ! empty( $_POST['variant'] )
) {
global $wpcc_target_lang;
$wpcc_target_lang = $_POST['variant'];
wpcc_do_conversion();
return;
$variant = sanitize_text_field( wp_unslash( $_POST['variant'] ) );
if ( in_array( $variant, $wpcc_options['wpcc_used_langs'], true ) ) {
global $wpcc_target_lang;
$wpcc_target_lang = $variant;
wpcc_do_conversion();
return;
}
}

// 修复首页显示Page时的问题
@ -126,6 +139,12 @@ function wpcc_parse_query( $query ) {

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

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

if ( ! is_404() ) {
$wpcc_noconversion_url = wpcc_get_noconversion_url();
} else {
@ -134,10 +153,10 @@ function wpcc_parse_query( $query ) {
return;
}

$request_lang = isset( $query->query_vars['variant'] ) ? $query->query_vars['variant'] : '';
$cookie_lang = isset( $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ) ? $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] : '';
$request_lang = isset( $query->query_vars['variant'] ) ? sanitize_text_field( $query->query_vars['variant'] ) : '';
$cookie_lang = isset( $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ 'wpcc_variant_' . COOKIEHASH ] ) ) : '';

if ( $request_lang && in_array( $request_lang, $wpcc_options['wpcc_used_langs'] ) ) {
if ( $request_lang && in_array( $request_lang, $wpcc_options['wpcc_used_langs'], true ) ) {
$wpcc_target_lang = $request_lang;
} else {
$wpcc_target_lang = false;
@ -147,9 +166,23 @@ function wpcc_parse_query( $query ) {
if ( ! $wpcc_target_lang ) {
if ( $request_lang == 'zh' && ! is_admin() ) {
if ( $wpcc_options['wpcc_use_cookie_variant'] != 0 ) {
setcookie( 'wpcc_variant_' . COOKIEHASH, 'zh', time() + 30000000, COOKIEPATH, COOKIE_DOMAIN );
setcookie( 'wpcc_variant_' . COOKIEHASH, 'zh', [
'expires' => time() + 30000000,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
] );
} else {
setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', 0, COOKIEPATH, COOKIE_DOMAIN );
setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', [
'expires' => 0,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
] );
}
header( 'Location: ' . $wpcc_noconversion_url );
die();
@ -167,7 +200,14 @@ function wpcc_parse_query( $query ) {

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

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

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

set_wpcc_langs_urls();

if ( ! is_404() && $wpcc_redirect_to && ! is_admin() ) {
setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', 0, COOKIEPATH, COOKIE_DOMAIN );
setcookie( 'wpcc_is_redirect_' . COOKIEHASH, '1', [
'expires' => 0,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
] );
wp_redirect( $wpcc_langs_urls[ $wpcc_redirect_to ], 302 );
}

@ -205,9 +257,13 @@ function set_wpcc_langs_urls() {
global $wpcc_langs_urls, $wpcc_options, $wpcc_noconversion_url;
if ( ! $wpcc_langs_urls ) {
if ( $wpcc_noconversion_url == get_option( 'home' ) . '/' && $wpcc_options['wpcc_use_permalink'] ) {
$permalinks_enabled = (string) get_option( 'permalink_structure' ) !== '';
$style = (int) ( $wpcc_options['wpcc_use_permalink'] ?? 0 );
$style_effective = $permalinks_enabled ? $style : 0;
if ( $wpcc_noconversion_url == get_option( 'home' ) . '/' && $style_effective ) {
foreach ( $wpcc_options['wpcc_used_langs'] as $value ) {
$wpcc_langs_urls[ $value ] = $wpcc_noconversion_url . $value . '/';
$wpcc_langs_urls[ $value ] = trailingslashit( $wpcc_noconversion_url . $value );
}
} else {
foreach ( $wpcc_options['wpcc_used_langs'] as $value ) {
@ -222,11 +278,13 @@ function set_wpcc_langs_urls() {
*/
function wpcc_get_noconversion_url() {
global $wpcc_options;
$reg = implode( '|', $wpcc_options['wpcc_used_langs'] );
$tmp = ( is_ssl() ? 'https://' : 'http://' ) .
( isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' ) .
( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' );
$tmp = trim( strtolower( remove_query_arg( 'variant', $tmp ) ) );
$reg = implode( '|', array_map( 'preg_quote', $wpcc_options['wpcc_used_langs'] ) );
$protocol = is_ssl() ? 'https://' : 'http://';
$host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
$uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
$tmp = trim( strtolower( remove_query_arg( 'variant', $protocol . $host . $uri ) ) );

if ( preg_match( '/^(.*)\/(' . $reg . '|zh|zh-reset)(\/.*)?$/', $tmp, $matches ) ) {
$tmp = user_trailingslashit( trailingslashit( $matches[1] ) . ltrim( $matches[3] ?? '', '/' ) );
@ -255,32 +313,55 @@ function wpcc_link_conversion( $link, $variant = null ) {
if ( $variant == false ) {
return $link;
}
if ( str_contains( $link, $variant ) ) {
return $link;
$style = (int) ( $wpcc_options['wpcc_use_permalink'] ?? 0 );
$permalinks_enabled = (string) get_option( 'permalink_structure' ) !== '';
// Split path and query
$qpos = strpos( $link, '?' );
$path = $qpos !== false ? substr( $link, 0, $qpos ) : $link;
$qs = $qpos !== false ? substr( $link, $qpos ) : '';
// Detect existing variant in path; if present, strip duplicate query param if any
$enabled = isset( $wpcc_options['wpcc_used_langs'] ) && is_array( $wpcc_options['wpcc_used_langs'] ) ? $wpcc_options['wpcc_used_langs'] : [];
$variant_regex = '#/(?:' . implode( '|', array_map( 'preg_quote', $enabled ) ) . '|zh|zh-reset)(/|$)#i';
$path_only = parse_url( $path, PHP_URL_PATH );
if ( $path_only === null ) { $path_only = $path; }
if ( preg_match( $variant_regex, $path_only ) ) {
if ( $qpos !== false ) {
$qs = preg_replace( '/([?&])variant=[^&]*(&|$)/', '$1', $qs );
$qs = rtrim( $qs, '?&' );
if ( $qs && $qs[0] !== '?' ) { $qs = '?' . ltrim( $qs, '?' ); }
}
return $path . $qs;
}
if ( str_contains( $link, '?' ) || ! ($wpcc_options['wpcc_use_permalink'] ?? 0) ) {
// 当 WP 未启用固定链接时,强制使用查询参数,避免 /zh-xx/ 404
if ( ! $permalinks_enabled || $style === 0 ) {
return add_query_arg( 'variant', $variant, $link );
}
if ( $wpcc_options['wpcc_use_permalink'] == 1 ) {
return user_trailingslashit( trailingslashit( $link ) . $variant );
if ( $style === 1 ) {
// suffix style
return user_trailingslashit( trailingslashit( $path ) . $variant ) . $qs;
}
// prefix style (2)
if ( is_multisite() && wpcc_mobile_exist( 'network' ) ) {
$sites = get_sites();
foreach ( $sites as $site ) {
if ( '/' == $site->path ) {
continue;
}
$path = str_replace( '/', '', $site->path );
$sub_url = "$site->domain/$path";
if ( str_contains( $link, $sub_url ) ) {
return str_replace( $sub_url, "$sub_url/$variant", $link );
$path_seg = str_replace( '/', '', $site->path );
$sub_url = "$site->domain/$path_seg";
if ( str_contains( $path, $sub_url ) ) {
return str_replace( $sub_url, "$sub_url/$variant", $path ) . $qs;
}
}
}
return str_replace( $wpcc_wp_home, "$wpcc_wp_home/$variant", $link );
return str_replace( $wpcc_wp_home, "$wpcc_wp_home/$variant", $path ) . $qs;
}

/**
@ -391,6 +472,9 @@ function zhconversion2( $str, $variant = null ) {
return $str;
}

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

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

@ -496,11 +580,45 @@ function zhconversion_deep( $value ) {
return $value;
}

/**
* 兜底保护 - 为“不转换内容”区块添加注释标记
*/
function wpcc_protect_no_conversion_blocks( $str ) {
// 若内容中不存在可识别的不转换占位data 属性或指定类),则无需处理
$has_data_attr = preg_match( '/data-wpcc-no-conversion=("|\')true\1/i', $str );
$has_nc_class = preg_match( '/wpcc-no-conversion-(content|wrapper)/i', $str );
if ( ! $has_data_attr && ! $has_nc_class ) {
return $str;
}
$wrap_whole_callback = function( $matches ) {
$id = wpcc_id();
return '<!--wpcc_NC' . $id . '_START-->' . $matches[0] . '<!--wpcc_NC' . $id . '_END-->';
};
$wrap_inner_callback = function( $matches ) {
$id = wpcc_id();
$open = $matches[1];
$inner = $matches[2];
$close = $matches[3];
return $open . '<!--wpcc_NC' . $id . '_START-->' . $inner . '<!--wpcc_NC' . $id . '_END-->' . $close;
};
// 优先:包裹 .wpcc-no-conversion-content 内部
$str = preg_replace_callback( '/(<div[^>]*class="[^"]*wpcc-no-conversion-content[^"]*"[^>]*>)([\s\S]*?)(<\/div>)/i', $wrap_inner_callback, $str );
// 兜底:包裹外层 wrapper 或 data 属性
$str = preg_replace_callback( '/<div[^>]*class="[^"]*wpcc-no-conversion-wrapper[^"]*"[^>]*>[\s\S]*?<\/div>/i', $wrap_whole_callback, $str );
$str = preg_replace_callback( '/<[^>]*data-wpcc-no-conversion="true"[^>]*>[\s\S]*?<\/[a-zA-Z0-9]+>/i', $wrap_whole_callback, $str );
return $str;
}

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

function limit_zhconversion( $str, $function ) {
if ( $m = preg_split( '/(<!--wpcc_NC([a-zA-Z0-9]*)_START-->)(.*?)(<!--wpcc_NC\2_END-->)/s', $str, - 1, PREG_SPLIT_DELIM_CAPTURE ) ) {
if ( $m = preg_split( '/(<!--wpc(?:c|s)_NC([a-zA-Z0-9]*)_START-->)(.*?)(<!--wpc(?:c|s)_NC\2_END-->)/s', $str, - 1, PREG_SPLIT_DELIM_CAPTURE ) ) {
$r = '';
$count = 0;
foreach ( $m as $v ) {
@ -548,73 +666,75 @@ function wpcc_output_navi( $args = '', $isReturn = false ) {
return;
}

// 计算“不转换”标签
if ( ! empty( $wpcc_options['nctip'] ) ) {
$noconverttip = $wpcc_options['nctip'];
} else {
$locale = str_replace( '_', '-', strtolower( get_locale() ) );
if ( in_array( $locale, array( 'zh-hant', 'zh-tw', 'zh-hk', 'zh-mo' ) ) ) {
$noconverttip = '不转换';
} else {
$noconverttip = '不转换';
}
$noconverttip = in_array( $locale, array( 'zh-hant', 'zh-tw', 'zh-hk', 'zh-mo' ) ) ? '不转换' : '不转换';
}
if ( $wpcc_target_lang ) {
$noconverttip = zhconversion( $noconverttip );
}

if ( ( $wpcc_options['wpcc_browser_redirect'] == 2 || $wpcc_options['wpcc_use_cookie_variant'] == 2 ) &&
$wpcc_target_lang
) {
$default_url = wpcc_link_conversion( $wpcc_noconversion_url, 'zh' );
if ( $wpcc_options['wpcc_use_permalink'] != 0 && is_home() && ! is_paged() ) {
// 计算“不转换”链接(必要时注入 zh 哨兵以覆盖浏览器/Cookie 策略)
if ( ( ! empty($wpcc_options['wpcc_browser_redirect']) && $wpcc_options['wpcc_browser_redirect'] == 2 ) ||
( ! empty($wpcc_options['wpcc_use_cookie_variant']) && $wpcc_options['wpcc_use_cookie_variant'] == 2 ) ) {
$default_url = $wpcc_target_lang ? wpcc_link_conversion( $wpcc_noconversion_url, 'zh' ) : $wpcc_noconversion_url;
if ( ! empty($wpcc_options['wpcc_use_permalink']) && is_home() && ! is_paged() ) {
$default_url = trailingslashit( $default_url );
}
} else {
$default_url = $wpcc_noconversion_url;
}

$wpcc_translate_type = $wpcc_options['wpcc_translate_type'] ?? 0;
// 展示形式:优先使用新字段 wpcc_translate_type兼容旧字段 wpcc_flag_option
$wpcc_translate_type = $wpcc_options['wpcc_translate_type'] ?? ($wpcc_options['wpcc_flag_option'] ?? 0);

$output = "\n" . '<div id="wpcc_widget_inner"><!--wpcc_NC_START-->' . "\n";
if ( $wpcc_translate_type == 0 ) {
$output .= ' <span id="wpcc_original_link" class="' . ( $wpcc_target_lang == false ? 'wpcc_current_lang' : 'wpcc_lang' ) . '" ><a class="wpcc_link" href="' . esc_url( $default_url ) . '" title="' . esc_html( $noconverttip ) . '" langvar="">' . esc_html( $noconverttip ) . '</a></span>' . "\n";
$html = "\n" . '<div id="wpcc_widget_inner"><!--wpcc_NC_START-->' . "\n";

// 统一语义1 = 平铺0 = 下拉
if ( $wpcc_translate_type == 1 ) {
$__nofollow = ( preg_match( '/\/zh\//i', $default_url ) || preg_match( '/(?:[?&])variant=zh(?:&|$)/i', $default_url ) ) ? ' rel="nofollow"' : '';
$html .= '<span id="wpcc_original_link" class="' . ( $wpcc_target_lang == false ? 'wpcc_current_lang' : 'wpcc_lang' ) . '"><a class="wpcc_link"' . $__nofollow . ' href="' . esc_url( $default_url ) . '" title="' . esc_attr( $noconverttip ) . '" langvar="">' . esc_html( $noconverttip ) . '</a></span>' . "\n";

foreach ( $wpcc_langs_urls as $key => $value ) {
if ( !isset( $wpcc_langs[ $key ] ) || !isset( $wpcc_langs[ $key ][1] ) || !isset( $wpcc_langs[ $key ][2] ) ) {
continue;
}
$tip = ! empty( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) ? esc_html( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) : $wpcc_langs[ $key ][2];
$tip = ! empty( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) ? $wpcc_options[ $wpcc_langs[ $key ][1] ] : $wpcc_langs[ $key ][2];
if ( $wpcc_target_lang ) {
$tip = zhconversion( $tip );
}
$safe_key = esc_attr( $key );
$output .= ' <span id="wpcc_' . $safe_key . '_link" class="' . ( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ) . '" ><a class="wpcc_link" rel="nofollow" href="' . esc_url( $value ) . '" title="' . esc_attr( $tip ) . '" langvar="' . $safe_key . '">' . esc_html( $tip ) . '</a></span>' . "\n";
$html .= '<span id="wpcc_' . $safe_key . '_link" class="' . ( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ) . '"><a class="wpcc_link" rel="nofollow" href="' . esc_url( $value ) . '" title="' . esc_attr( $tip ) . '" langvar="' . $safe_key . '">' . esc_html( $tip ) . '</a></span>' . "\n";
}
} else if ( $wpcc_translate_type == 1 ) {
} else if ( $wpcc_translate_type == 0 ) {
$checkSelected = function ( $selected_lang ) use ( $wpcc_target_lang ) {
return $selected_lang == $wpcc_target_lang ? "selected" : "";
return $selected_lang == $wpcc_target_lang ? 'selected' : '';
};
$output .= sprintf( '<select id="wpcc_translate_type" value="%s" onchange="wpccRedirectToPage(this)">', $wpcc_translate_type );
$output .= sprintf( '<option id="wpcc_original_link" value="" %s>%s</option>', $checkSelected( '' ), esc_html( $noconverttip ) );
$html .= '<select id="wpcc_translate_type" value="' . esc_attr( (string) $wpcc_translate_type ) . '" onchange="wpccRedirectToPage(this)">';
$html .= '<option id="wpcc_original_link" value="" ' . $checkSelected( '' ) . '>' . esc_html( $noconverttip ) . '</option>';
foreach ( $wpcc_langs_urls as $key => $value ) {
if ( !isset( $wpcc_langs[ $key ] ) || !isset( $wpcc_langs[ $key ][1] ) || !isset( $wpcc_langs[ $key ][2] ) ) {
continue;
}
$tip = ! empty( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) ? esc_html( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) : $wpcc_langs[ $key ][2];
$tip = ! empty( $wpcc_options[ $wpcc_langs[ $key ][1] ] ) ? $wpcc_options[ $wpcc_langs[ $key ][1] ] : $wpcc_langs[ $key ][2];
if ( $wpcc_target_lang ) {
$tip = zhconversion( $tip );
}
$safe_key = esc_attr( $key );
$output .= sprintf( '<option id="wpcc_%s_link" class="%s" value="%s" %s>%s</option>', $safe_key, esc_attr( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ), $safe_key, $checkSelected( $key ), esc_html( $tip ) );
$html .= '<option id="wpcc_' . $safe_key . '_link" class="' . esc_attr( $wpcc_target_lang == $key ? 'wpcc_current_lang' : 'wpcc_lang' ) . '" value="' . $safe_key . '" ' . $checkSelected( $key ) . '>' . esc_html( $tip ) . '</option>';
}
$output .= sprintf( '</select>' );
$html .= '</select>';
}
$output .= '<!--wpcc_NC_END--></div>' . "\n";

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

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

/**
@ -623,23 +743,23 @@ function wpcc_output_navi( $args = '', $isReturn = false ) {
function wpcc_output_navi2() {
global $wpcc_target_lang, $wpcc_noconversion_url, $wpcc_langs_urls, $wpcc_options;

if ( ( $wpcc_options['wpcc_browser_redirect'] == 2 || $wpcc_options['wpcc_use_cookie_variant'] == 2 ) &&
$wpcc_target_lang
) {
if ( ( ! empty($wpcc_options['wpcc_browser_redirect']) && $wpcc_options['wpcc_browser_redirect'] == 2 ) && $wpcc_target_lang ||
( ! empty($wpcc_options['wpcc_use_cookie_variant']) && $wpcc_options['wpcc_use_cookie_variant'] == 2 ) && $wpcc_target_lang ) {
$default_url = wpcc_link_conversion( $wpcc_noconversion_url, 'zh' );
if ( $wpcc_options['wpcc_use_permalink'] != 0 && is_home() && ! is_paged() ) {
if ( ! empty($wpcc_options['wpcc_use_permalink']) && is_home() && ! is_paged() ) {
$default_url = trailingslashit( $default_url );
}
} else {
$default_url = $wpcc_noconversion_url;
}

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

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

add_action( 'wp_head', 'wpcc_header' );


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

// 为了让“全页面转换”模式也能正确排除“不转换”片段,先把注释标记包裹的内容替换为持久性包装(不被压缩器移除)
add_filter( 'the_content', 'wpcc_wrap_nc_markers', 1 );
add_filter( 'the_excerpt', 'wpcc_wrap_nc_markers', 1 );
if ( $wpcc_options['wpcc_use_fullpage_conversion'] == 1 ) {
@ob_start( 'wpcc_ob_callback' );
return;
@ -985,7 +1120,7 @@ function wpcc_header() {
echo '</script>';

if ( ! $wpcc_direct_conversion_flag ) {
wp_enqueue_script( 'wpcc-search-js', wpcc_DIR_URL . 'assets/js/search-variant.min.js', array(), '1.1', false );
wp_enqueue_script( 'wpcc-search-js', wpcc_DIR_URL . 'assets/js/search-variant.min.js', array(), wpcc_VERSION, false );
}

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

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

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


/**
* 修复链接转换
*/
@ -1064,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;
@ -1084,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;
@ -1102,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;
}

@ -1123,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 ) == '/' )
) {
@ -1181,14 +1341,33 @@ function wpcc_body_class( $classes ) {
* 语言属性过滤器
*/
function wpcc_locale( $output, $doctype = 'html' ) {
global $wpcc_target_lang, $wpcc_langs;
global $wpcc_target_lang, $wpcc_langs, $wp;

wpcc_init_languages();

$lang = get_bloginfo( 'language' );
if ( $wpcc_target_lang && strpos( $lang, 'zh-' ) === 0 && isset( $wpcc_langs[ $wpcc_target_lang ] ) && isset( $wpcc_langs[ $wpcc_target_lang ][3] ) ) {

// 仅在“确实处于变体页面”时才覆盖 <html lang>
$variant_from_query = '';
if ( isset( $wp->query_vars['variant'] ) ) {
$variant_from_query = sanitize_text_field( $wp->query_vars['variant'] );
}
$variant_from_path = '';
$path = isset( $wp->request ) ? $wp->request : '';
if ( $path && preg_match( '/^(zh-cn|zh-tw|zh-hk|zh-hans|zh-hant|zh-sg|zh|zh-reset)\b/i', $path, $m ) ) {
$variant_from_path = strtolower( $m[1] );
}
$is_variant_request = ( $variant_from_query !== '' ) || ( $variant_from_path !== '' );

if (
$wpcc_target_lang &&
$is_variant_request &&
strpos( $lang, 'zh-' ) === 0 &&
isset( $wpcc_langs[ $wpcc_target_lang ] ) &&
isset( $wpcc_langs[ $wpcc_target_lang ][3] )
) {
$lang = $wpcc_langs[ $wpcc_target_lang ][3];
$output = preg_replace( '/lang="[^"]+"/', "lang=\"{$lang}\"", $output );
$output = preg_replace( '/lang=\"[^\"]+\"/', "lang=\"{$lang}\"", $output );
}
return $output;
}
@ -1199,15 +1378,7 @@ function wpcc_locale( $output, $doctype = 'html' ) {
function wpcc_render_no_conversion_block( $block_content, $block ) {
if ( isset( $block['blockName'] ) && $block['blockName'] === 'wpcc/no-conversion' ) {
$unique_id = uniqid();

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

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

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

return $block_content;
@ -1220,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' );
@ -1266,6 +1437,33 @@ add_shortcode( 'wp-chinese-converter', 'wp_chinese_converter_shortcode' );
add_filter( "body_class", "wpcc_body_class" );
add_filter( 'language_attributes', 'wpcc_locale' );

// 兜底层兜底:在未启用新内核时,显式将根级变体规则置于顶端,避免 /zh-xx/ 404
if ( ! class_exists( 'WPCC_Main' ) ) {
add_action( 'init', function() {
global $wpcc_options;
if ( empty( $wpcc_options ) || empty( $wpcc_options['wpcc_use_permalink'] ) ) {
return;
}
$enabled = isset( $wpcc_options['wpcc_used_langs'] ) && is_array( $wpcc_options['wpcc_used_langs'] ) ? $wpcc_options['wpcc_used_langs'] : [];
if ( empty( $enabled ) ) {
return;
}
$reg = implode( '|', array_map( 'preg_quote', $enabled ) );
if ( function_exists( 'add_rewrite_rule' ) ) {
// 多次尝试添加规则,确保优先级
add_rewrite_rule( '^(' . $reg . '|zh|zh-reset)/?$', 'index.php?variant=$matches[1]', 'top' );
// 添加钩子确保规则被正确处理
add_filter( 'rewrite_rules_array', function( $rules ) use ( $reg ) {
$new_rules = [];
$root_rule = '^(' . $reg . '|zh|zh-reset)/?$';
$new_rules[ $root_rule ] = 'index.php?variant=$matches[1]';
return array_merge( $new_rules, $rules );
}, 1 ); // 最高优先级
}
}, 1 );
}

// 初始化小部件
if ( ! empty( $wpcc_options ) && is_array( $wpcc_options ) && is_array( $wpcc_options['wpcc_used_langs'] ) ) {
add_action( 'widgets_init', function () {

File diff suppressed because it is too large Load diff

View file

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

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

// 载入核心依赖
require_once dirname(__FILE__) . "/includes/core/class-wpcc-exception-handler.php";
require_once dirname(__FILE__) .
"/includes/core/class-wpcc-exception-handler.php";
require_once dirname(__FILE__) . "/includes/core/class-wpcc-utils.php";
require_once dirname(__FILE__) . "/includes/core/class-converter-factory.php";
require_once dirname(__FILE__) . "/includes/core/class-module-manager.php";
require_once dirname(__FILE__) . "/includes/core/class-conversion-cache.php";
require_once dirname(__FILE__) .
"/includes/core/class-wpcc-language-config.php";
require_once dirname(__FILE__) . "/includes/core/class-wpcc-converter-factory.php";
require_once dirname(__FILE__) . "/includes/core/class-wpcc-module-manager.php";
require_once dirname(__FILE__) . "/includes/core/class-wpcc-conversion-cache.php";
require_once dirname(__FILE__) . "/includes/core/class-wpcc-config.php";
require_once dirname(__FILE__) . "/includes/core/class-wpcc-presets.php";
require_once dirname(__FILE__) . "/includes/core/class-wpcc-main.php";
@ -79,18 +82,22 @@ function wpcc_add_global_js()
"wpcc-variant",
plugins_url("/assets/dist/wpcc-variant.umd.js", __FILE__),
[],
"1.1.0",
wpcc_VERSION,
);
wp_register_script(
"wpcc-block-script-ok",
plugins_url("/assets/js/wpcc-block-script-ok.js", __FILE__),
["wp-blocks", "wp-element", "wpcc-variant"],
"1.3.0",
wpcc_VERSION,
);

wp_enqueue_script(["wpcc-variant", "wpcc-block-script-ok"]);
$use_permalink_type = 0;
if (is_array($wpcc_options) && isset($wpcc_options["wpcc_use_permalink"])) {
$use_permalink_type = (int) $wpcc_options["wpcc_use_permalink"];
}
wp_localize_script("wpcc-block-script-ok", "wpc_switcher_use_permalink", [
"type" => $wpcc_options["wpcc_use_permalink"],
"type" => $use_permalink_type,
]);
}

@ -107,6 +114,16 @@ if (file_exists($modules_dir . "wpcc-sitemap.php")) {
require_once $modules_dir . "wpcc-sitemap.php";
}

// 加载网络设置模块
if (
is_multisite() &&
file_exists(__DIR__ . "/includes/network/wpcc-network-settings.php")
) {
require_once __DIR__ . "/includes/network/wpcc-network-settings.php";
add_action("init", ["WPCC_Network_Settings", "init"]);
}


// 容错处理 - 只有配置正确时才加载功能
if (
$wpcc_options != false &&
@ -116,13 +133,18 @@ if (
// 加载遗留的核心功能模块(为了向后兼容)
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");
}

/**
@ -139,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);
}

/**
@ -151,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);
}