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
wplingua / inc / sitemap.php

sitemap.php in Translate and Go multilingual – Automatic AI translation – wpLingua 2.16.7, at inc/sitemap.php

424 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
424