PluginProbe
Translate and Go multilingual – Automatic AI translation – wpLingua / 2.16.7
Translate and Go multilingual – Automatic AI translation – wpLingua v2.16.7
2.16.7 2.16.6 2.16.5 2.16.4 2.16.3 2.16.2 2.16.1 2.16.0 2.15.2 2.15.1 2.15.0 2.14.3 2.14.2 2.14.1 2.14.0 2.13.1 2.13.0 2.12.3 2.12.2 2.12.1 trunk 1.0.3 1.0.4 1.0.5 1.1.0 All 112 releases
← All changes | inc/sitemap.php +423 -423 2.12.22.16.7 View file →
@@ -1,423 +1,423 @@
1 -<?php
2 -
3 -// If this file is called directly, abort.
4 -if ( ! defined( 'WPINC' ) ) {
5 - die;
6 -}
7 -
8 -
9 -/**
10 - * Add hreflang alternate links to XML sitemap for multilingual support.
11 - *
12 - * @param string $content The original XML sitemap content.
13 - * @return string The modified XML content with added xhtml:link alternate tags.
14 - */
15 -function wplng_sitemap_add_hreflang_links( $content ) {
16 -
17 - // Return the content as is if it's empty or not valid XML.
18 - if ( empty( $content ) || ! wplng_str_is_xml( $content ) ) {
19 - return $content;
20 - }
21 -
22 - // Get language data.
23 - $language_website_id = wplng_get_language_website_id();
24 - $languages_target_ids = wplng_get_languages_target_ids();
25 -
26 - if ( empty( $language_website_id ) || empty( $languages_target_ids ) ) {
27 - return $content;
28 - }
29 -
30 - // Load the XML content into a DOMDocument.
31 - $dom = new DOMDocument( '1.0', 'UTF-8' );
32 - $dom->preserveWhiteSpace = false;
33 - $dom->formatOutput = true;
34 -
35 - // Enable internal error handling
36 - $previous_libxml_setting = libxml_use_internal_errors( true );
37 -
38 - // Check XML errors
39 - if ( ! $dom->loadXML( $content ) ) {
40 - if ( defined( 'WPLNG_DEBUG_XML' ) && true === WPLNG_DEBUG_XML ) {
41 - $errors = array();
42 -
43 - foreach ( libxml_get_errors() as $error ) {
44 - $errors[] = $error->message;
45 - }
46 -
47 - libxml_clear_errors();
48 -
49 - $debug = array(
50 - 'title' => 'wpLingua XML debug - Invalid XML content',
51 - 'errors' => $errors,
52 - );
53 -
54 - error_log( var_export( $debug, true ) );
55 - }
56 -
57 - return $content;
58 - }
59 -
60 - // Restore previous setting
61 - libxml_use_internal_errors( $previous_libxml_setting );
62 -
63 - // Replace XSL stylesheet URL if option enabled
64 - if ( get_option( 'wplng_sitemap_xsl_override', false ) ) {
65 - $wplng_xsl_url = plugins_url( 'assets/sitemap.xsl', __DIR__ );
66 -
67 - // Find existing xml-stylesheet processing instruction
68 - $xsl_found = false;
69 - foreach ( $dom->childNodes as $node ) {
70 - if ( $node->nodeType === XML_PI_NODE && $node->target === 'xml-stylesheet' ) {
71 - $node->data = 'type="text/xsl" href="' . esc_url( $wplng_xsl_url ) . '"';
72 - $xsl_found = true;
73 - break;
74 - }
75 - }
76 -
77 - // If no xml-stylesheet found, add one before the root element
78 - if ( ! $xsl_found ) {
79 - $xsl_pi = $dom->createProcessingInstruction(
80 - 'xml-stylesheet',
81 - 'type="text/xsl" href="' . esc_url( $wplng_xsl_url ) . '"'
82 - );
83 - $dom->insertBefore( $xsl_pi, $dom->documentElement );
84 - }
85 - }
86 -
87 - $signature = '<!-- XML Sitemap is made multilingual by wpLingua -->' . PHP_EOL;
88 -
89 - // Register namespaces and prepare XPath.
90 - $xpath = new DOMXPath( $dom );
91 - $xpath->registerNamespace( 'sm', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
92 - $xpath->registerNamespace( 'xhtml', 'http://www.w3.org/1999/xhtml' );
93 -
94 - // Ensure the root element declares the required namespaces.
95 - $urlset = $dom->documentElement;
96 - if ( ! $urlset->hasAttribute( 'xmlns' ) ) {
97 - $urlset->setAttribute( 'xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
98 - }
99 - if ( ! $urlset->hasAttribute( 'xmlns:xhtml' ) ) {
100 - $urlset->setAttribute( 'xmlns:xhtml', 'http://www.w3.org/1999/xhtml' );
101 - }
102 -
103 - // Get all <url> nodes.
104 - $url_nodes = $xpath->query( '//sm:url' );
105 -
106 - if ( empty( $url_nodes ) ) {
107 - return $dom->saveXML() . $signature;
108 - }
109 -
110 - foreach ( $url_nodes as $url_node ) {
111 -
112 - // Get original URL.
113 - $loc_node = $xpath->query( 'sm:loc', $url_node )->item( 0 );
114 -
115 - if ( empty( $loc_node ) ) {
116 - continue;
117 - }
118 -
119 - $url_original = trim( $loc_node->nodeValue );
120 -
121 - if ( '' === $url_original || ! wplng_url_is_translatable( $url_original ) ) {
122 - continue;
123 - }
124 -
125 - // Add link for original language.
126 - $link_node = $dom->createElement( 'xhtml:link' );
127 - $link_node->setAttribute( 'rel', 'alternate' );
128 - $link_node->setAttribute( 'hreflang', esc_attr( $language_website_id ) );
129 - $link_node->setAttribute( 'href', esc_url( $url_original ) );
130 - $url_node->appendChild( $link_node );
131 -
132 - // Add links for target languages.
133 - foreach ( $languages_target_ids as $language_id ) {
134 -
135 - $translated_url = wplng_url_translate(
136 - $url_original,
137 - $language_id
138 - );
139 -
140 - // Validate the translated URL.
141 - if ( empty( $translated_url )
142 - || ! filter_var( $translated_url, FILTER_VALIDATE_URL )
143 - ) {
144 - continue;
145 - }
146 -
147 - $link_node = $dom->createElement( 'xhtml:link' );
148 - $link_node->setAttribute( 'rel', 'alternate' );
149 - $link_node->setAttribute( 'hreflang', esc_attr( $language_id ) );
150 - $link_node->setAttribute( 'href', esc_url( $translated_url ) );
151 - $url_node->appendChild( $link_node );
152 - }
153 - }
154 -
155 - $sitemap = $dom->saveXML();
156 -
157 - if ( empty( $sitemap ) ) {
158 - return $content;
159 - }
160 -
161 - return $sitemap . $signature;
162 -}
163 -
164 -
165 -/**
166 - * Modify XSL stylesheet to display translated links in sitemap.
167 - *
168 - * @param string $content The original XSL content.
169 - * @return string The modified XSL content with translations displayed under each URL.
170 - */
171 -function wplng_sitemap_modify_xsl( $content ) {
172 -
173 - if ( empty( $content ) ) {
174 - return $content;
175 - }
176 -
177 - // Check if this XSL already has translations support
178 - if ( strpos( $content, 'wplng-translations' ) !== false ) {
179 - return $content;
180 - }
181 -
182 - // Load XSL into DOMDocument
183 - $dom = new DOMDocument( '1.0', 'UTF-8' );
184 - $dom->preserveWhiteSpace = false;
185 - $dom->formatOutput = true;
186 -
187 - // Enable internal error handling
188 - $previous_libxml_setting = libxml_use_internal_errors( true );
189 -
190 - if ( ! $dom->loadXML( $content ) ) {
191 - libxml_clear_errors();
192 - libxml_use_internal_errors( $previous_libxml_setting );
193 - return $content;
194 - }
195 -
196 - libxml_use_internal_errors( $previous_libxml_setting );
197 -
198 - $xpath = new DOMXPath( $dom );
199 - $xpath->registerNamespace( 'xsl', 'http://www.w3.org/1999/XSL/Transform' );
200 -
201 - // Add xhtml namespace to xsl:stylesheet if not present
202 - $stylesheet = $dom->documentElement;
203 - if ( ! $stylesheet->hasAttribute( 'xmlns:xhtml' ) ) {
204 - $stylesheet->setAttribute( 'xmlns:xhtml', 'http://www.w3.org/1999/xhtml' );
205 - }
206 -
207 - // Add CSS to existing <style> element or create one in <head>
208 - $css = '
209 - .wplng-translations { margin-top: 8px; padding-top: 8px; border-top: 1px dashed #ddd; }
210 - .wplng-translation-row { display: block; margin: 4px 0; font-size: 13px; }
211 - .wplng-lang-badge { display: inline-block; min-width: 24px; padding: 2px 6px; margin-right: 8px; background: #0073aa; color: white; border-radius: 3px; font-size: 11px; text-align: center; font-weight: bold; }
212 - .wplng-translation-row a { color: #0073aa; text-decoration: none; }
213 - .wplng-translation-row a:hover { text-decoration: underline; }
214 - .wplng-credit { margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px; font-size: 12px; color: #666; text-align: center; }
215 - .wplng-credit a { color: #0073aa; }
216 - ';
217 -
218 - $style_nodes = $xpath->query( '//style' );
219 - if ( $style_nodes->length > 0 ) {
220 - $style_node = $style_nodes->item( 0 );
221 - $style_node->nodeValue .= $css;
222 - } else {
223 - $head_nodes = $xpath->query( '//head' );
224 - if ( $head_nodes->length > 0 ) {
225 - $style_element = $dom->createElement( 'style', $css );
226 - $head_nodes->item( 0 )->appendChild( $style_element );
227 - }
228 - }
229 -
230 - // Create the XSL fragment for translations
231 - $xsl_ns = 'http://www.w3.org/1999/XSL/Transform';
232 -
233 - // Find the <td> containing the URL (class="loc" or containing sitemap:loc)
234 - $loc_cells = $xpath->query( '//td[contains(@class, "loc")]' );
235 -
236 - if ( $loc_cells->length > 0 ) {
237 - $loc_cell = $loc_cells->item( 0 );
238 -
239 - // Create xsl:if element
240 - $xsl_if = $dom->createElementNS( $xsl_ns, 'xsl:if' );
241 - $xsl_if->setAttribute( 'test', "xhtml:link[@rel='alternate']" );
242 -
243 - // Create wrapper div
244 - $div = $dom->createElement( 'div' );
245 - $div->setAttribute( 'class', 'wplng-translations' );
246 -
247 - // Create xsl:for-each element
248 - $xsl_foreach = $dom->createElementNS( $xsl_ns, 'xsl:for-each' );
249 - $xsl_foreach->setAttribute( 'select', "xhtml:link[@rel='alternate']" );
250 -
251 - // Create span.wplng-translation-row
252 - $span_row = $dom->createElement( 'span' );
253 - $span_row->setAttribute( 'class', 'wplng-translation-row' );
254 -
255 - // Create span.wplng-lang-badge with xsl:value-of
256 - $span_badge = $dom->createElement( 'span' );
257 - $span_badge->setAttribute( 'class', 'wplng-lang-badge' );
258 - $xsl_value_lang = $dom->createElementNS( $xsl_ns, 'xsl:value-of' );
259 - $xsl_value_lang->setAttribute( 'select', '@hreflang' );
260 - $span_badge->appendChild( $xsl_value_lang );
261 -
262 - // Create anchor element
263 - $anchor = $dom->createElement( 'a' );
264 - $anchor->setAttribute( 'href', '{@href}' );
265 - $xsl_value_href = $dom->createElementNS( $xsl_ns, 'xsl:value-of' );
266 - $xsl_value_href->setAttribute( 'select', '@href' );
267 - $anchor->appendChild( $xsl_value_href );
268 -
269 - // Assemble the structure
270 - $span_row->appendChild( $span_badge );
271 - $span_row->appendChild( $anchor );
272 - $xsl_foreach->appendChild( $span_row );
273 - $div->appendChild( $xsl_foreach );
274 - $xsl_if->appendChild( $div );
275 -
276 - // Append to loc cell
277 - $loc_cell->appendChild( $xsl_if );
278 - }
279 -
280 - // Add credit message to the page footer
281 - $body_nodes = $xpath->query( '//body' );
282 - if ( $body_nodes->length > 0 ) {
283 - $body = $body_nodes->item( 0 );
284 -
285 - // Find the main container div or append to body
286 - $container_nodes = $xpath->query( '//div[@id="sitemap"] | //div[@id="content"] | //div[contains(@class, "sitemap")]' );
287 - $target = ( $container_nodes->length > 0 ) ? $container_nodes->item( 0 ) : $body;
288 -
289 - $credit_div = $dom->createElement( 'p' );
290 - $credit_div->setAttribute( 'class', 'wplng-credit' );
291 -
292 - $credit_text = $dom->createTextNode( 'Multilingual sitemap powered by ' );
293 - $credit_div->appendChild( $credit_text );
294 -
295 - $credit_link = $dom->createElement( 'a', 'wpLingua' );
296 - $credit_link->setAttribute( 'href', 'https://wplingua.com' );
297 - $credit_link->setAttribute( 'target', '_blank' );
298 - $credit_link->setAttribute( 'rel', 'noopener' );
299 - $credit_div->appendChild( $credit_link );
300 -
301 - $target->appendChild( $credit_div );
302 - }
303 -
304 - $result = $dom->saveXML();
305 -
306 - if ( empty( $result ) ) {
307 - return $content;
308 - }
309 -
310 - // Add XML comment at the end
311 - $result .= PHP_EOL . '<!-- XSL Sitemap stylesheet is made multilingual by wpLingua -->';
312 -
313 - return $result;
314 -}
315 -
316 -
317 -/**
318 - * Filter All In One SEO sitemap post entries to add multilingual alternate URLs.
319 - *
320 - * @param array $entry The sitemap entry data.
321 - * @param int $post_id The post ID.
322 - * @return array The modified sitemap entry with language alternatives.
323 - */
324 -function wplng_aioseo_filter_sitemap_post( $entry, $post_id ) {
325 -
326 - // Get the post URL
327 - $post_url = get_permalink( $post_id );
328 -
329 - if ( empty( $post_url ) || ! wplng_url_is_translatable( $post_url ) ) {
330 - return $entry;
331 - }
332 -
333 - // Get language data
334 - $language_website_id = wplng_get_language_website_id();
335 - $languages_target_ids = wplng_get_languages_target_ids();
336 -
337 - if ( empty( $language_website_id ) || empty( $languages_target_ids ) ) {
338 - return $entry;
339 - }
340 -
341 - // Set the main language
342 - $entry['language'] = $language_website_id;
343 -
344 - // Add translated versions (including original language for self-reference)
345 - $entry['languages'] = array();
346 -
347 - // Add original language (self-referencing hreflang)
348 - $entry['languages'][] = array(
349 - 'language' => $language_website_id,
350 - 'location' => $post_url,
351 - );
352 -
353 - foreach ( $languages_target_ids as $language_id ) {
354 - $translated_url = wplng_url_translate( $post_url, $language_id );
355 -
356 - if ( ! empty( $translated_url ) && filter_var( $translated_url, FILTER_VALIDATE_URL ) ) {
357 - $entry['languages'][] = array(
358 - 'language' => $language_id,
359 - 'location' => $translated_url,
360 - );
361 - }
362 - }
363 -
364 - return $entry;
365 -}
366 -
367 -
368 -/**
369 - * Filter All In One SEO sitemap term entries to add multilingual alternate URLs.
370 - *
371 - * @param array $entry The sitemap entry data.
372 - * @param int $term_id The term ID.
373 - * @return array The modified sitemap entry with language alternatives.
374 - */
375 -function wplng_aioseo_filter_sitemap_term( $entry, $term_id ) {
376 -
377 - // Get the term object
378 - $term = get_term( $term_id );
379 -
380 - if ( is_wp_error( $term ) || empty( $term ) ) {
381 - return $entry;
382 - }
383 -
384 - // Get the term URL
385 - $term_url = get_term_link( $term );
386 -
387 - if ( is_wp_error( $term_url ) || empty( $term_url ) || ! wplng_url_is_translatable( $term_url ) ) {
388 - return $entry;
389 - }
390 -
391 - // Get language data
392 - $language_website_id = wplng_get_language_website_id();
393 - $languages_target_ids = wplng_get_languages_target_ids();
394 -
395 - if ( empty( $language_website_id ) || empty( $languages_target_ids ) ) {
396 - return $entry;
397 - }
398 -
399 - // Set the main language
400 - $entry['language'] = $language_website_id;
401 -
402 - // Add translated versions (including original language for self-reference)
403 - $entry['languages'] = array();
404 -
405 - // Add original language (self-referencing hreflang)
406 - $entry['languages'][] = array(
407 - 'language' => $language_website_id,
408 - 'location' => $term_url,
409 - );
410 -
411 - foreach ( $languages_target_ids as $language_id ) {
412 - $translated_url = wplng_url_translate( $term_url, $language_id );
413 -
414 - if ( ! empty( $translated_url ) && filter_var( $translated_url, FILTER_VALIDATE_URL ) ) {
415 - $entry['languages'][] = array(
416 - 'language' => $language_id,
417 - 'location' => $translated_url,
418 - );
419 - }
420 - }
421 -
422 - return $entry;
423 -}
1 +<?php
2 +
3 +// If this file is called directly, abort.
4 +if ( ! defined( 'WPINC' ) ) {
5 + die;
6 +}
7 +
8 +
9 +/**
10 + * Add hreflang alternate links to XML sitemap for multilingual support.
11 + *
12 + * @param string $content The original XML sitemap content.
13 + * @return string The modified XML content with added xhtml:link alternate tags.
14 + */
15 +function wplng_sitemap_add_hreflang_links( $content ) {
16 +
17 + // Return the content as is if it's empty or not valid XML.
18 + if ( empty( $content ) || ! wplng_str_is_xml( $content ) ) {
19 + return $content;
20 + }
21 +
22 + // Get language data.
23 + $language_website_id = wplng_get_language_website_id();
24 + $languages_target_ids = wplng_get_languages_target_ids();
25 +
26 + if ( empty( $language_website_id ) || empty( $languages_target_ids ) ) {
27 + return $content;
28 + }
29 +
30 + // Load the XML content into a DOMDocument.
31 + $dom = new DOMDocument( '1.0', 'UTF-8' );
32 + $dom->preserveWhiteSpace = false;
33 + $dom->formatOutput = true;
34 +
35 + // Enable internal error handling
36 + $previous_libxml_setting = libxml_use_internal_errors( true );
37 +
38 + // Check XML errors
39 + if ( ! $dom->loadXML( $content ) ) {
40 + if ( defined( 'WPLNG_DEBUG_XML' ) && true === WPLNG_DEBUG_XML ) {
41 + $errors = array();
42 +
43 + foreach ( libxml_get_errors() as $error ) {
44 + $errors[] = $error->message;
45 + }
46 +
47 + libxml_clear_errors();
48 +
49 + $debug = array(
50 + 'title' => 'wpLingua XML debug - Invalid XML content',
51 + 'errors' => $errors,
52 + );
53 +
54 + error_log( var_export( $debug, true ) );
55 + }
56 +
57 + return $content;
58 + }
59 +
60 + // Restore previous setting
61 + libxml_use_internal_errors( $previous_libxml_setting );
62 +
63 + // Replace XSL stylesheet URL if option enabled
64 + if ( get_option( 'wplng_sitemap_xsl_override', false ) ) {
65 + $wplng_xsl_url = plugins_url( 'assets/sitemap.xsl', __DIR__ );
66 +
67 + // Find existing xml-stylesheet processing instruction
68 + $xsl_found = false;
69 + foreach ( $dom->childNodes as $node ) {
70 + if ( $node->nodeType === XML_PI_NODE && $node->target === 'xml-stylesheet' ) {
71 + $node->data = 'type="text/xsl" href="' . esc_url( $wplng_xsl_url ) . '"';
72 + $xsl_found = true;
73 + break;
74 + }
75 + }
76 +
77 + // If no xml-stylesheet found, add one before the root element
78 + if ( ! $xsl_found ) {
79 + $xsl_pi = $dom->createProcessingInstruction(
80 + 'xml-stylesheet',
81 + 'type="text/xsl" href="' . esc_url( $wplng_xsl_url ) . '"'
82 + );
83 + $dom->insertBefore( $xsl_pi, $dom->documentElement );
84 + }
85 + }
86 +
87 + $signature = '<!-- XML Sitemap is made multilingual by wpLingua -->' . PHP_EOL;
88 +
89 + // Register namespaces and prepare XPath.
90 + $xpath = new DOMXPath( $dom );
91 + $xpath->registerNamespace( 'sm', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
92 + $xpath->registerNamespace( 'xhtml', 'http://www.w3.org/1999/xhtml' );
93 +
94 + // Ensure the root element declares the required namespaces.
95 + $urlset = $dom->documentElement;
96 + if ( ! $urlset->hasAttribute( 'xmlns' ) ) {
97 + $urlset->setAttribute( 'xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
98 + }
99 + if ( ! $urlset->hasAttribute( 'xmlns:xhtml' ) ) {
100 + $urlset->setAttribute( 'xmlns:xhtml', 'http://www.w3.org/1999/xhtml' );
101 + }
102 +
103 + // Get all <url> nodes.
104 + $url_nodes = $xpath->query( '//sm:url' );
105 +
106 + if ( empty( $url_nodes ) ) {
107 + return $dom->saveXML() . $signature;
108 + }
109 +
110 + foreach ( $url_nodes as $url_node ) {
111 +
112 + // Get original URL.
113 + $loc_node = $xpath->query( 'sm:loc', $url_node )->item( 0 );
114 +
115 + if ( empty( $loc_node ) ) {
116 + continue;
117 + }
118 +
119 + $url_original = trim( $loc_node->nodeValue );
120 +
121 + if ( '' === $url_original || ! wplng_url_is_translatable( $url_original ) ) {
122 + continue;
123 + }
124 +
125 + // Add link for original language.
126 + $link_node = $dom->createElement( 'xhtml:link' );
127 + $link_node->setAttribute( 'rel', 'alternate' );
128 + $link_node->setAttribute( 'hreflang', esc_attr( $language_website_id ) );
129 + $link_node->setAttribute( 'href', esc_url( $url_original ) );
130 + $url_node->appendChild( $link_node );
131 +
132 + // Add links for target languages.
133 + foreach ( $languages_target_ids as $language_id ) {
134 +
135 + $translated_url = wplng_url_translate(
136 + $url_original,
137 + $language_id
138 + );
139 +
140 + // Validate the translated URL.
141 + if ( empty( $translated_url )
142 + || ! filter_var( $translated_url, FILTER_VALIDATE_URL )
143 + ) {
144 + continue;
145 + }
146 +
147 + $link_node = $dom->createElement( 'xhtml:link' );
148 + $link_node->setAttribute( 'rel', 'alternate' );
149 + $link_node->setAttribute( 'hreflang', esc_attr( $language_id ) );
150 + $link_node->setAttribute( 'href', esc_url( $translated_url ) );
151 + $url_node->appendChild( $link_node );
152 + }
153 + }
154 +
155 + $sitemap = $dom->saveXML();
156 +
157 + if ( empty( $sitemap ) ) {
158 + return $content;
159 + }
160 +
161 + return $sitemap . $signature;
162 +}
163 +
164 +
165 +/**
166 + * Modify XSL stylesheet to display translated links in sitemap.
167 + *
168 + * @param string $content The original XSL content.
169 + * @return string The modified XSL content with translations displayed under each URL.
170 + */
171 +function wplng_sitemap_modify_xsl( $content ) {
172 +
173 + if ( empty( $content ) ) {
174 + return $content;
175 + }
176 +
177 + // Check if this XSL already has translations support
178 + if ( wplng_str_contains( $content, 'wplng-translations' ) ) {
179 + return $content;
180 + }
181 +
182 + // Load XSL into DOMDocument
183 + $dom = new DOMDocument( '1.0', 'UTF-8' );
184 + $dom->preserveWhiteSpace = false;
185 + $dom->formatOutput = true;
186 +
187 + // Enable internal error handling
188 + $previous_libxml_setting = libxml_use_internal_errors( true );
189 +
190 + if ( ! $dom->loadXML( $content ) ) {
191 + libxml_clear_errors();
192 + libxml_use_internal_errors( $previous_libxml_setting );
193 + return $content;
194 + }
195 +
196 + libxml_use_internal_errors( $previous_libxml_setting );
197 +
198 + $xpath = new DOMXPath( $dom );
199 + $xpath->registerNamespace( 'xsl', 'http://www.w3.org/1999/XSL/Transform' );
200 +
201 + // Add xhtml namespace to xsl:stylesheet if not present
202 + $stylesheet = $dom->documentElement;
203 + if ( ! $stylesheet->hasAttribute( 'xmlns:xhtml' ) ) {
204 + $stylesheet->setAttribute( 'xmlns:xhtml', 'http://www.w3.org/1999/xhtml' );
205 + }
206 +
207 + // Add CSS to existing <style> element or create one in <head>
208 + $css = '
209 + .wplng-translations { margin-top: 8px; padding-top: 8px; border-top: 1px dashed #ddd; }
210 + .wplng-translation-row { display: block; margin: 4px 0; font-size: 13px; }
211 + .wplng-lang-badge { display: inline-block; min-width: 24px; padding: 2px 6px; margin-right: 8px; background: #0073aa; color: white; border-radius: 3px; font-size: 11px; text-align: center; font-weight: bold; }
212 + .wplng-translation-row a { color: #0073aa; text-decoration: none; }
213 + .wplng-translation-row a:hover { text-decoration: underline; }
214 + .wplng-credit { margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px; font-size: 12px; color: #666; text-align: center; }
215 + .wplng-credit a { color: #0073aa; }
216 + ';
217 +
218 + $style_nodes = $xpath->query( '//style' );
219 + if ( $style_nodes->length > 0 ) {
220 + $style_node = $style_nodes->item( 0 );
221 + $style_node->nodeValue .= $css;
222 + } else {
223 + $head_nodes = $xpath->query( '//head' );
224 + if ( $head_nodes->length > 0 ) {
225 + $style_element = $dom->createElement( 'style', $css );
226 + $head_nodes->item( 0 )->appendChild( $style_element );
227 + }
228 + }
229 +
230 + // Create the XSL fragment for translations
231 + $xsl_ns = 'http://www.w3.org/1999/XSL/Transform';
232 +
233 + // Find the <td> containing the URL (class="loc" or containing sitemap:loc)
234 + $loc_cells = $xpath->query( '//td[contains(@class, "loc")]' );
235 +
236 + if ( $loc_cells->length > 0 ) {
237 + $loc_cell = $loc_cells->item( 0 );
238 +
239 + // Create xsl:if element
240 + $xsl_if = $dom->createElementNS( $xsl_ns, 'xsl:if' );
241 + $xsl_if->setAttribute( 'test', "xhtml:link[@rel='alternate']" );
242 +
243 + // Create wrapper div
244 + $div = $dom->createElement( 'div' );
245 + $div->setAttribute( 'class', 'wplng-translations' );
246 +
247 + // Create xsl:for-each element
248 + $xsl_foreach = $dom->createElementNS( $xsl_ns, 'xsl:for-each' );
249 + $xsl_foreach->setAttribute( 'select', "xhtml:link[@rel='alternate']" );
250 +
251 + // Create span.wplng-translation-row
252 + $span_row = $dom->createElement( 'span' );
253 + $span_row->setAttribute( 'class', 'wplng-translation-row' );
254 +
255 + // Create span.wplng-lang-badge with xsl:value-of
256 + $span_badge = $dom->createElement( 'span' );
257 + $span_badge->setAttribute( 'class', 'wplng-lang-badge' );
258 + $xsl_value_lang = $dom->createElementNS( $xsl_ns, 'xsl:value-of' );
259 + $xsl_value_lang->setAttribute( 'select', '@hreflang' );
260 + $span_badge->appendChild( $xsl_value_lang );
261 +
262 + // Create anchor element
263 + $anchor = $dom->createElement( 'a' );
264 + $anchor->setAttribute( 'href', '{@href}' );
265 + $xsl_value_href = $dom->createElementNS( $xsl_ns, 'xsl:value-of' );
266 + $xsl_value_href->setAttribute( 'select', '@href' );
267 + $anchor->appendChild( $xsl_value_href );
268 +
269 + // Assemble the structure
270 + $span_row->appendChild( $span_badge );
271 + $span_row->appendChild( $anchor );
272 + $xsl_foreach->appendChild( $span_row );
273 + $div->appendChild( $xsl_foreach );
274 + $xsl_if->appendChild( $div );
275 +
276 + // Append to loc cell
277 + $loc_cell->appendChild( $xsl_if );
278 + }
279 +
280 + // Add credit message to the page footer
281 + $body_nodes = $xpath->query( '//body' );
282 + if ( $body_nodes->length > 0 ) {
283 + $body = $body_nodes->item( 0 );
284 +
285 + // Find the main container div or append to body
286 + $container_nodes = $xpath->query( '//div[@id="sitemap"] | //div[@id="content"] | //div[contains(@class, "sitemap")]' );
287 + $target = ( $container_nodes->length > 0 ) ? $container_nodes->item( 0 ) : $body;
288 +
289 + $credit_div = $dom->createElement( 'p' );
290 + $credit_div->setAttribute( 'class', 'wplng-credit' );
291 +
292 + $credit_text = $dom->createTextNode( 'Multilingual sitemap powered by ' );
293 + $credit_div->appendChild( $credit_text );
294 +
295 + $credit_link = $dom->createElement( 'a', 'wpLingua' );
296 + $credit_link->setAttribute( 'href', 'https://wplingua.com' );
297 + $credit_link->setAttribute( 'target', '_blank' );
298 + $credit_link->setAttribute( 'rel', 'noopener' );
299 + $credit_div->appendChild( $credit_link );
300 +
301 + $target->appendChild( $credit_div );
302 + }
303 +
304 + $result = $dom->saveXML();
305 +
306 + if ( empty( $result ) ) {
307 + return $content;
308 + }
309 +
310 + // Add XML comment at the end
311 + $result .= PHP_EOL . '<!-- XSL Sitemap stylesheet is made multilingual by wpLingua -->';
312 +
313 + return $result;
314 +}
315 +
316 +
317 +/**
318 + * Filter All In One SEO sitemap post entries to add multilingual alternate URLs.
319 + *
320 + * @param array $entry The sitemap entry data.
321 + * @param int $post_id The post ID.
322 + * @return array The modified sitemap entry with language alternatives.
323 + */
324 +function wplng_aioseo_filter_sitemap_post( $entry, $post_id ) {
325 +
326 + // Get the post URL
327 + $post_url = get_permalink( $post_id );
328 +
329 + if ( empty( $post_url ) || ! wplng_url_is_translatable( $post_url ) ) {
330 + return $entry;
331 + }
332 +
333 + // Get language data
334 + $language_website_id = wplng_get_language_website_id();
335 + $languages_target_ids = wplng_get_languages_target_ids();
336 +
337 + if ( empty( $language_website_id ) || empty( $languages_target_ids ) ) {
338 + return $entry;
339 + }
340 +
341 + // Set the main language
342 + $entry['language'] = $language_website_id;
343 +
344 + // Add translated versions (including original language for self-reference)
345 + $entry['languages'] = array();
346 +
347 + // Add original language (self-referencing hreflang)
348 + $entry['languages'][] = array(
349 + 'language' => $language_website_id,
350 + 'location' => $post_url,
351 + );
352 +
353 + foreach ( $languages_target_ids as $language_id ) {
354 + $translated_url = wplng_url_translate( $post_url, $language_id );
355 +
356 + if ( ! empty( $translated_url ) && filter_var( $translated_url, FILTER_VALIDATE_URL ) ) {
357 + $entry['languages'][] = array(
358 + 'language' => $language_id,
359 + 'location' => $translated_url,
360 + );
361 + }
362 + }
363 +
364 + return $entry;
365 +}
366 +
367 +
368 +/**
369 + * Filter All In One SEO sitemap term entries to add multilingual alternate URLs.
370 + *
371 + * @param array $entry The sitemap entry data.
372 + * @param int $term_id The term ID.
373 + * @return array The modified sitemap entry with language alternatives.
374 + */
375 +function wplng_aioseo_filter_sitemap_term( $entry, $term_id ) {
376 +
377 + // Get the term object
378 + $term = get_term( $term_id );
379 +
380 + if ( is_wp_error( $term ) || empty( $term ) ) {
381 + return $entry;
382 + }
383 +
384 + // Get the term URL
385 + $term_url = get_term_link( $term );
386 +
387 + if ( is_wp_error( $term_url ) || empty( $term_url ) || ! wplng_url_is_translatable( $term_url ) ) {
388 + return $entry;
389 + }
390 +
391 + // Get language data
392 + $language_website_id = wplng_get_language_website_id();
393 + $languages_target_ids = wplng_get_languages_target_ids();
394 +
395 + if ( empty( $language_website_id ) || empty( $languages_target_ids ) ) {
396 + return $entry;
397 + }
398 +
399 + // Set the main language
400 + $entry['language'] = $language_website_id;
401 +
402 + // Add translated versions (including original language for self-reference)
403 + $entry['languages'] = array();
404 +
405 + // Add original language (self-referencing hreflang)
406 + $entry['languages'][] = array(
407 + 'language' => $language_website_id,
408 + 'location' => $term_url,
409 + );
410 +
411 + foreach ( $languages_target_ids as $language_id ) {
412 + $translated_url = wplng_url_translate( $term_url, $language_id );
413 +
414 + if ( ! empty( $translated_url ) && filter_var( $translated_url, FILTER_VALIDATE_URL ) ) {
415 + $entry['languages'][] = array(
416 + 'language' => $language_id,
417 + 'location' => $translated_url,
418 + );
419 + }
420 + }
421 +
422 + return $entry;
423 +}