add sitemap rewrite

This commit is contained in:
hanyixuanten
2026-07-27 17:34:13 +08:00
parent 116e696efb
commit 4ddeada0c9
5 changed files with 133 additions and 2 deletions
+3 -1
View File
@@ -49,7 +49,9 @@ Post, page, term, and home links are generated for the active language. For exam
For a bound subdomain, enter a value such as `en.example.com=en`. The server and DNS must route that hostname to the same WordPress installation before WordPress can resolve the language.
After changing routing settings, rewrite rules are refreshed. The initial implementation does not yet generate translated permalinks, canonical URLs, `hreflang` alternates, redirects, or sitemap entries.
Sitemap requests made through a configured target-language subdirectory or bound domain are proxied to the same sitemap path on the source site. The plugin rewrites standard `<loc>` entries in both sitemap indexes and URL sets to the active language URL, so sitemap index links continue through the same language route. Source sitemap failures remain non-fatal and fall back to normal WordPress request handling. The source sitemap must be available from the WordPress `home` origin; upstream redirects are not followed.
After changing routing settings, rewrite rules are refreshed. The initial implementation does not yet generate translated permalinks, canonical URLs, `hreflang` alternates, or redirects.
## Security And Data Retention
+3 -1
View File
@@ -48,7 +48,9 @@ Btranslate 是一个 WordPress 插件骨架,使用百度翻译 API 持久化
对于已绑定的子域名,请填写类似 `en.example.com=en` 的值。服务器和 DNS 必须先将该主机名指向同一 WordPress 安装,WordPress 才能解析该语言。
路由设置变更后会刷新重写规则。初始实现尚不生成翻译固定链接、规范 URL、`hreflang` 替代链接、重定向或站点地图条目
通过已配置目标语言子目录或绑定域名发起的 sitemap 请求,会代理到源站相同路径的 sitemap。插件会改写 sitemap 索引和 URL 集中的标准 `<loc>` 条目,使索引内的子 sitemap 链接继续使用同一语言路由。源站 sitemap 获取失败时不会造成致命错误,而是交回 WordPress 按普通请求处理。源 sitemap 必须可从 WordPress `home` 配置的源站访问,且插件不会跟随上游重定向
路由设置变更后会刷新重写规则。初始实现尚不生成翻译固定链接、规范 URL、`hreflang` 替代链接或重定向。
## 安全和数据保留
+3
View File
@@ -5,11 +5,13 @@ defined( 'ABSPATH' ) || exit;
class WPT_Plugin {
private static $instance;
private $router;
private $sitemap_controller;
private $content_controller;
private $admin;
private function __construct() {
$this->router = new WPT_Language_Router();
$this->sitemap_controller = new WPT_Sitemap_Controller( $this->router );
$this->content_controller = new WPT_Content_Controller( new WPT_Translation_Store(), $this->router );
$this->admin = new WPT_Admin();
@@ -52,6 +54,7 @@ class WPT_Plugin {
public function register() {
$this->router->register();
$this->sitemap_controller->register();
$this->content_controller->register();
}
+123
View File
@@ -0,0 +1,123 @@
<?php
defined( 'ABSPATH' ) || exit;
class WPT_Sitemap_Controller {
private $router;
public function __construct( WPT_Language_Router $router ) {
$this->router = $router;
}
public function register() {
add_action( 'template_redirect', array( $this, 'maybe_serve_localized_sitemap' ), 0 );
}
public function maybe_serve_localized_sitemap() {
$settings = WPT_Settings::get();
$language = $this->router->current_language();
if ( $language === sanitize_key( $settings['source_language'] ) || ! in_array( $language, WPT_Settings::target_languages(), true ) ) {
return;
}
$request_path = $this->request_path();
if ( ! $this->is_sitemap_path( $request_path ) ) {
return;
}
$source_url = $this->source_sitemap_url( $request_path, $language, $settings['routing_mode'] );
if ( '' === $source_url ) {
return;
}
$response = wp_remote_get(
$source_url,
array(
'timeout' => 10,
'redirection' => 0,
'headers' => array(
'Accept' => 'application/xml, text/xml;q=0.9',
),
)
);
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return;
}
$xml = $this->rewrite_sitemap( wp_remote_retrieve_body( $response ) );
if ( '' === $xml ) {
return;
}
status_header( 200 );
header( 'Content-Type: application/xml; charset=UTF-8' );
header( 'X-Robots-Tag: noindex, follow', true );
echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Serialized XML from DOMDocument.
exit;
}
private function request_path() {
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
$path = wp_parse_url( $request_uri, PHP_URL_PATH );
return is_string( $path ) ? '/' . ltrim( $path, '/' ) : '';
}
private function is_sitemap_path( $path ) {
$filename = basename( $path );
return 1 === preg_match( '/^[a-z0-9_-]*sitemap[a-z0-9_.-]*\.xml$/i', $filename );
}
private function source_sitemap_url( $request_path, $language, $routing_mode ) {
$home_url = (string) get_option( 'home', '' );
$parts = wp_parse_url( $home_url );
if ( ! is_array( $parts ) || empty( $parts['scheme'] ) || empty( $parts['host'] ) ) {
return '';
}
if ( 'subdirectory' === $routing_mode ) {
$request_path = preg_replace( '#^/' . preg_quote( $language, '#' ) . '(?=/|$)#i', '', $request_path, 1 );
$request_path = '/' . ltrim( (string) $request_path, '/' );
}
$port = isset( $parts['port'] ) ? ':' . $parts['port'] : '';
return $parts['scheme'] . '://' . $parts['host'] . $port . $request_path;
}
private function rewrite_sitemap( $xml ) {
if ( '' === trim( $xml ) || ! class_exists( 'DOMDocument' ) ) {
return '';
}
$document = new DOMDocument();
$document->preserveWhiteSpace = true;
$previous_errors = libxml_use_internal_errors( true );
$loaded = $document->loadXML( $xml, LIBXML_NONET );
libxml_clear_errors();
libxml_use_internal_errors( $previous_errors );
if ( ! $loaded ) {
return '';
}
$xpath = new DOMXPath( $document );
$nodes = $xpath->query( '/*[local-name()="urlset" or local-name()="sitemapindex"]/*[local-name()="url" or local-name()="sitemap"]/*[local-name()="loc"]' );
if ( false === $nodes ) {
return '';
}
foreach ( $nodes as $node ) {
$node->nodeValue = $this->router->localized_url( trim( $node->textContent ) );
}
$result = $document->saveXML();
return false === $result ? '' : $result;
}
}
+1
View File
@@ -24,6 +24,7 @@ require_once WPT_PATH . 'includes/class-wpt-translation-service.php';
require_once WPT_PATH . 'includes/class-wpt-content-translator.php';
require_once WPT_PATH . 'includes/class-wpt-settings.php';
require_once WPT_PATH . 'includes/class-wpt-language-router.php';
require_once WPT_PATH . 'includes/class-wpt-sitemap-controller.php';
require_once WPT_PATH . 'includes/class-wpt-content-controller.php';
require_once WPT_PATH . 'includes/class-wpt-admin.php';
require_once WPT_PATH . 'includes/class-wpt-plugin.php';