PluginProbe ʕ •ᴥ•ʔ
GPTranslate – Multilingual AI Translation for WordPress: Automatically Translate Websites / 2.12
GPTranslate – Multilingual AI Translation for WordPress: Automatically Translate Websites v2.12
2.33.7 2.33.6 2.33.5 2.33.2 2.32.10 2.33 2.33.1 2.32.6 2.32.7 2.32.8 trunk 2.10.3 2.10.4 2.10.5 2.10.6 2.11 2.12 2.13 2.14 2.14.1 2.15 2.15.1 2.16.1 2.16.2 2.17 2.18 2.18.1 2.18.2 2.19 2.20 2.21 2.22 2.23 2.24 2.25 2.25.1 2.25.2 2.26 2.27 2.27.10 2.27.5 2.28 2.28.1 2.29 2.30 2.31 2.32 2.32.5
gptranslate / serverside-translations.php
gptranslate Last commit date
assets 1 year ago flags 1 year ago language 1 year ago gptranslate.php 1 year ago multilang-routing.php 1 year ago readme.txt 1 year ago serverside-translations.php 1 year ago settings.php 1 year ago simplehtmldom.php 1 year ago uninstall.php 1 year ago
serverside-translations.php
620 lines
1 <?php
2 if (!defined('ABSPATH')) exit;
3
4 // Hook to apply server-side translations before output is sent to the browser
5 add_action ( 'template_redirect', function () {
6 if (is_admin ()) {
7 return;
8 }
9
10 $raw_request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
11 $uri = esc_url_raw( $raw_request_uri );
12
13 $settings = get_option ( 'gptranslate_options', [ ] );
14
15 if (empty ( $settings ['serverside_translations'] ) || $settings ['serverside_translations'] != 1)
16 return;
17
18 // Skip static assets and admin/API routes
19 if (preg_match ( '#\.(ico|png|jpe?g|gif|svg|css|js|woff2?|ttf|eot|mp4|webm)$#i', $uri ) || strpos ( $uri, '/wp-' ) === 0 || strpos ( $uri, '/wp-json/' ) === 0) {
20 return;
21 }
22
23 function normalizeText($text) {
24 $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
25 $text = str_replace(["\xC2\xA0", "\n", "\r", "\t"], ' ', $text); // NBSP e spazi strani
26 $text = preg_replace('/\s+/u', ' ', $text); // spazi multipli → singolo spazio
27 $text = trim($text);
28 return $text;
29 }
30
31 ob_start ( function ($html) use ($uri, $settings) {
32 global $wpdb;
33
34 $originalLang = $settings ['language'] ?? '';
35
36 if (empty ( $originalLang ) || $originalLang === $translatedLang)
37 return $html;
38
39 if (! defined ( 'GPTRANSLATE_CURRENT_LANG' )) {
40 return $html;
41 }
42
43 $translatedLang = GPTRANSLATE_CURRENT_LANG;
44
45 // Exceptions and management for subfolder installations
46 if($settings['subfolder_installation']) {
47 // Explode URI into parts
48 $uri_parts = explode('/', ltrim($uri, '/'));
49
50 // Remove the first part (subfolder)
51 array_shift($uri_parts);
52
53 // Rebuild the URI
54 $uri = '/' . implode('/', $uri_parts);
55 }
56
57 $pageLink = rtrim ( get_site_url (), '/' ) . '/' . ltrim ( GPTRANSLATE_CURRENT_LANG, '/' ) . $uri;
58
59 if ( $settings ['serverside_translations_urldecode'] ) {
60 $pageLink = urldecode ( $pageLink );
61 }
62
63 if ($settings ['serverside_translations_ignore_querystring'] == 1) {
64 // Remove query string
65 $pageLink = strtok ( $pageLink, '?' );
66 } elseif (! empty ( $_SERVER ['QUERY_STRING'] )) {
67 // Sanitize and rebuild query string
68 $raw_qs = isset($_SERVER['QUERY_STRING']) ? wp_unslash($_SERVER['QUERY_STRING']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
69
70 parse_str ( $raw_qs, $qs_args );
71
72 $clean_args = [ ];
73 foreach ( $qs_args as $key => $value ) {
74 $clean_key = sanitize_key ( $key );
75 $clean_value = is_array ( $value ) ? array_map ( 'sanitize_text_field', $value ) : sanitize_text_field ( $value );
76 $clean_args [$clean_key] = $clean_value;
77 }
78
79 $clean_qs = http_build_query ( $clean_args, '', '&', PHP_QUERY_RFC3986 );
80
81 if ($clean_qs !== '') {
82 $pageLink = strtok ( $pageLink, '?' ) . '?' . $clean_qs;
83 }
84 }
85
86 if ( $settings ['serverside_translations_urlencode_space'] ) {
87 $pageLink = str_ireplace ( ' ', '%20', $pageLink );
88 }
89
90 $table = $wpdb->prefix . 'gptranslate';
91
92 if( $settings ['rewrite_language_alias'] ) {
93 $row = $wpdb->get_row ( $wpdb->prepare ( "SELECT pagelink, translations, alt_translations FROM {$wpdb->prefix}gptranslate" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
94 "\n WHERE ( pagelink = %s OR pagelink = %s OR translated_alias = %s OR translated_alias = %s) AND languageoriginal = %s AND languagetranslated = %s AND published = 1",
95 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $originalLang, $translatedLang ), ARRAY_A );
96 } else {
97 $row = $wpdb->get_row ( $wpdb->prepare ( "SELECT pagelink, translations, alt_translations FROM {$wpdb->prefix}gptranslate" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
98 "\n WHERE ( pagelink = %s OR pagelink = %s ) AND languageoriginal = %s AND languagetranslated = %s AND published = 1",
99 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $originalLang, $translatedLang ), ARRAY_A );
100 }
101
102 if (! $row) {
103 return $html;
104 }
105
106 // Process replacement method
107 if ($settings ['serverside_translations_method'] == 'regex') {
108 $translations = json_decode ( $row ['translations'], true ) ?? [ ];
109 $altTranslations = json_decode ( $row ['alt_translations'], true ) ?? [ ];
110 uksort ( $translations, fn ($a, $b) => strlen ( $b ) - strlen ( $a ) );
111 uksort ( $altTranslations, fn ($a, $b) => strlen ( $b ) - strlen ( $a ) );
112
113 $caseInsensitive = ! empty ( $settings ['serverside_translations_caseinsensitive'] );
114 $matchQuotes = ! empty ( $settings ['serverside_translations_matchquotes'] );
115 $excludedPatterns = [ ];
116
117 $excludedCss = preg_replace ( '/,+/', ',', str_ireplace ( [
118 "\r",
119 "\n",
120 '"'
121 ], [
122 ',',
123 ',',
124 ''
125 ], $settings ['css_selector_serverside_leafnodes_excluded'] ?? '') );
126 $excludedCss = array_filter ( array_map ( 'trim', explode ( ',', $excludedCss ) ) );
127
128 foreach ( $excludedCss as $selector ) {
129 if (preg_match ( '/^([a-z0-9]+)\.(.+)$/i', $selector, $m )) {
130 $excludedPatterns [] = '/<' . preg_quote ( $m [1], '/' ) . '(?=[^>]*\sclass\s*=\s*["\'][^"\']*\b' . preg_quote ( $m [2], '/' ) . '\b)[^>]*>/i';
131 } elseif (preg_match ( '/^\.(.+)$/', $selector, $m )) {
132 $excludedPatterns [] = '/<([a-z0-9]+)(?=[^>]*\sclass\s*=\s*["\'][^"\']*\b' . preg_quote ( $m [1], '/' ) . '\b)[^>]*>/i';
133 }
134 }
135
136 $segments = preg_split ( '/(<[^>]+>)/i', $html, - 1, PREG_SPLIT_DELIM_CAPTURE );
137
138 $skipStack = [ ];
139 foreach ( $segments as $index => $segment ) {
140 if (preg_match ( '/^<\s*(script|style)(\s|>)/i', $segment, $matches )) {
141 $skipStack [] = strtolower ( $matches [1] );
142 } elseif (preg_match ( '/<\/\s*(script|style)[^>]*>/i', $segment, $matches )) { // <--- FIX QUI
143 $tag = strtolower ( $matches [1] );
144 if (! empty ( $skipStack ) && end ( $skipStack ) === $tag) {
145 array_pop ( $skipStack );
146 }
147 } elseif (preg_match ( '/^<\s*([a-zA-Z0-9]+)/', $segment, $tagMatch )) {
148 $tagName = strtolower ( $tagMatch [1] );
149 foreach ( $excludedPatterns as $pattern ) {
150 if (preg_match ( $pattern, $segment )) {
151 $skipStack [] = $tagName;
152 break;
153 }
154 }
155 } elseif (preg_match ( '/^<\/\s*([a-zA-Z0-9]+)/', $segment, $tagMatch )) {
156 $tagName = strtolower ( $tagMatch [1] );
157 if (! empty ( $skipStack ) && end ( $skipStack ) === $tagName) {
158 array_pop ( $skipStack );
159 }
160 } elseif (! preg_match ( '/^<[^>]+>$/', $segment )) {
161 if (empty ( $skipStack )) {
162 if ($matchQuotes) {
163 $segment = str_ireplace ( '"', "'", $segment );
164 }
165 foreach ( $translations as $originalText => $translatedText ) {
166 $originalText = normalizeText($originalText);
167 $segment = normalizeText($segment);
168
169 $prevSegment = $segment;
170 if ($caseInsensitive) {
171 $segment = str_ireplace ( trim ( $originalText ), $translatedText, $segment );
172 } else {
173 $segment = str_replace ( trim ( $originalText ), $translatedText, $segment );
174 }
175 if ($segment !== $prevSegment) {
176 break;
177 }
178 }
179 }
180 }
181
182 $segments [$index] = $segment;
183 }
184
185 $html = implode ( '', $segments );
186
187 // Replace alt and title in images
188 if (! empty ( $settings ['translate_altimages'] )) {
189 $html = preg_replace_callback ( '/<img[^>]*\b(alt|title)\s*=\s*([\"\'])(.*?)\2[^>]*>/i', function ($matches) use ($altTranslations, $caseInsensitive, $matchQuotes) {
190 $attr = $matches [1];
191 $quote = $matches [2];
192 $value = $matches [3];
193 if ($matchQuotes)
194 $value = str_ireplace ( '"', "'", $value );
195 foreach ( $altTranslations as $original => $translated ) {
196 $original = normalizeText($original);
197 $value = normalizeText($value);
198
199 $value = $caseInsensitive ? str_ireplace ( trim ( $original ), $translated, $value ) : str_replace ( trim ( $original ), $translated, $value );
200 }
201 return preg_replace ( '/\b' . $attr . '\s*=\s*["\'].*?["\']/', "$attr=$quote$value$quote", $matches [0] );
202 }, $html );
203 }
204
205 // Add skip marker
206 $html = preg_replace ( '/<body/i', '<body data-gptranslateskip="1" data-gptranslateoriginalalias="' . $row['pagelink'] . '"', $html, 1 );
207 } elseif ($settings ['serverside_translations_method'] == 'domdocument') {
208 // Solution 2: classic DOMDocument approach, effective but could cause closing tags and encoding issues
209 try {
210 $translationsArray = json_decode ( $row['translations'], true ) ?? [ ];
211 $altTranslationsArray = json_decode ( $row['alt_translations'] ?? '', true ) ?: [ ];
212
213 // Sort the translation keys in descending order by length.
214 uksort ( $translationsArray, function ( $a, $b ) {
215 return strlen ( $b ) - strlen ( $a );
216 } );
217
218 // Sort the alt translation keys in descending order by length.
219 uksort ( $altTranslationsArray, function ( $a, $b ) {
220 return strlen ( $b ) - strlen ( $a );
221 } );
222
223 // Initialize DOMDocument without converting the body content via mb_convert_encoding.
224 $doc = new DOMDocument ();
225 libxml_use_internal_errors ( true );
226 // Use an XML encoding hack to inform DOMDocument about UTF-8 while preserving original characters.
227 $doc->loadHTML ( '<?xml encoding="UTF-8">' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
228 libxml_clear_errors ();
229
230 // Use XPath to locate text nodes, skipping those in <script> or <style> tags.
231 $xpath = new DOMXPath ( $doc );
232 $cssSelectorLeafnodesExcluded = str_ireplace ( '"', '', trim ( trim ( preg_replace ( '/,+/', ',', str_ireplace ( [ "\r", "\n" ], ",", $settings['css_selector_serverside_leafnodes_excluded'] ?? '' ) ) ), ',' ) );
233 $excludedNodes = [ ];
234
235 if (! empty ( $cssSelectorLeafnodesExcluded )) {
236 $selectors = explode ( ',', $cssSelectorLeafnodesExcluded );
237 $xpathQueries = [ ];
238
239 foreach ( $selectors as $selector ) {
240 $selector = trim ( $selector );
241 if (empty ( $selector ))
242 continue;
243
244 // Convert CSS selector to XPath
245 $selector = preg_replace ( '/\s+/', ' ', $selector ); // Normalize spaces
246 $selectorParts = explode ( ' ', $selector );
247
248 $xpathQuery = '';
249 foreach ( $selectorParts as $part ) {
250 if (preg_match ( '/^([a-zA-Z0-9_-]+)?(\.[a-zA-Z0-9_-]+)?(#[a-zA-Z0-9_-]+)?$/', $part, $matches )) {
251 $tag = ! empty ( $matches[1] ) ? $matches[1] : '*'; // If no tag, use '*'
252 $class = ! empty ( $matches[2] ) ? substr ( $matches[2], 1 ) : ''; // Remove leading '.'
253 $id = ! empty ( $matches[3] ) ? substr ( $matches[3], 1 ) : ''; // Remove leading '#'
254
255 $conditions = [ ];
256 if ($class) {
257 $conditions[] = "contains(concat(' ', normalize-space(@class), ' '), ' $class ')";
258 }
259 if ($id) {
260 $conditions[] = "@id='$id'";
261 }
262
263 $xpathQuery .= "//{$tag}" . (! empty ( $conditions ) ? "[" . implode ( " and ", $conditions ) . "]" : "");
264 }
265 }
266
267 if (! empty ( $xpathQuery )) {
268 $xpathQueries[] = $xpathQuery;
269 }
270 }
271
272 // Execute all XPath queries and collect excluded nodes
273 foreach ( $xpathQueries as $query ) {
274 foreach ( $xpath->query ( $query ) as $excludedNode ) {
275 $excludedNodes[] = $excludedNode;
276 }
277 }
278 }
279
280 $textNodes = $xpath->query ( '//text()[not(ancestor::script) and not(ancestor::style) and normalize-space()]' );
281 $caseInsensitive = ! empty ( $settings['serverside_translations_caseinsensitive'] );
282 $matchQuotes = ! empty ( $settings['serverside_translations_matchquotes'] );
283
284 // Process each text node with translation replacements.
285 foreach ( $textNodes as $textNode ) {
286 if ($cssSelectorLeafnodesExcluded) {
287 $skipNode = false;
288 foreach ( $excludedNodes as $excludedNode ) {
289 if ($excludedNode->isSameNode ( $textNode->parentNode )) {
290 $skipNode = true;
291 break;
292 }
293 }
294 if ($skipNode)
295 continue;
296 }
297
298 $textContent = $textNode->nodeValue;
299 if ($matchQuotes) {
300 $textContent = str_ireplace ( '"', "'", $textContent );
301 }
302 foreach ( $translationsArray as $originalText => $translatedText ) {
303 $originalText = normalizeText($originalText);
304 $textContent = normalizeText($textContent);
305
306 $prevTextContent = $textContent; // Save the current state
307 if ($caseInsensitive) {
308 $textContent = str_ireplace ( trim ( $originalText ), $translatedText, $textContent );
309 } else {
310 $textContent = str_replace ( trim ( $originalText ), $translatedText, $textContent );
311 }
312 // Break out of the loop if a replacement occurred.
313 if ($textContent !== $prevTextContent) {
314 break;
315 }
316 }
317 $textNode->nodeValue = $textContent;
318 }
319
320 // Check if also images 'alt' are enabled to be translated
321 if (! empty ( $settings['translate_altimages'] )) {
322 $imgNodesAlt = $xpath->query ( '//img[@alt]' );
323 foreach ( $imgNodesAlt as $imgNode ) {
324 $altText = $imgNode->getAttribute ( 'alt' );
325 if ($matchQuotes) {
326 $altText = str_ireplace ( '"', "'", $altText );
327 }
328 foreach ( $altTranslationsArray as $originalText => $translatedText ) {
329 $originalText = normalizeText($originalText);
330 $altText = normalizeText($altText);
331
332 $prevAltText = $altText;
333 if ($caseInsensitive) {
334 $altText = str_ireplace ( trim ( $originalText ), $translatedText, $altText );
335 } else {
336 $altText = str_replace ( trim ( $originalText ), $translatedText, $altText );
337 }
338 if ($altText !== $prevAltText) {
339 break;
340 }
341 }
342 $imgNode->setAttribute ( 'alt', $altText );
343 }
344
345 $imgNodesTitle = $xpath->query ( '//img[@title]' );
346 foreach ( $imgNodesTitle as $imgNode ) {
347 $titleText = $imgNode->getAttribute ( 'title' );
348 if ($matchQuotes) {
349 $titleText = str_ireplace ( '"', "'", $titleText );
350 }
351 foreach ( $altTranslationsArray as $originalText => $translatedText ) {
352 $originalText = normalizeText($originalText);
353 $titleText = normalizeText($titleText);
354
355 $prevTitleText = $titleText;
356 if ($caseInsensitive) {
357 $titleText = str_ireplace ( trim ( $originalText ), $translatedText, $titleText );
358 } else {
359 $titleText = str_replace ( trim ( $originalText ), $translatedText, $titleText );
360 }
361 if ($titleText !== $prevTitleText) {
362 break;
363 }
364 }
365 $imgNode->setAttribute ( 'title', $titleText );
366 }
367 }
368
369 // Optionally, add an attribute to the <body> tag to signal that translations were applied.
370 $bodyTag = $doc->getElementsByTagName ( 'body' )->item ( 0 );
371 if ($bodyTag) {
372 $bodyTag->setAttribute ( 'data-gptranslateskip', '1' );
373 $bodyTag->setAttribute ( 'data-gptranslateoriginalalias', htmlspecialchars($row['pagelink'], ENT_QUOTES, 'UTF-8') );
374 }
375
376 // Save the modified HTML.
377 $html = $doc->saveHTML ();
378
379 // Decode HTML entities back into UTF-8 characters.
380 $html = html_entity_decode ( $html, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
381
382 } catch ( Exception $e ) {
383 // Handle exception if needed
384 }
385 } elseif ($settings['serverside_translations_method'] == 'simplehtmldom') {
386 require_once plugin_dir_path(__FILE__) . 'simplehtmldom.php';
387
388 // Helper: checks if $child is inside (or is) $parent.
389 function nodeIsInsideExcluded($child, $excludedNodes) {
390 while ($child !== null) {
391 foreach ($excludedNodes as $ex) {
392 if ($child === $ex) {
393 return true;
394 }
395 }
396 $child = $child->parent;
397 }
398 return false;
399 }
400
401 // Recursive function to process all text nodes.
402 function processTextNodes($node, $excludedNodes, $translationsArray, $caseInsensitive, $matchQuotes) {
403 if ($node->tag === 'text') {
404 if (! nodeIsInsideExcluded($node, $excludedNodes) && trim($node->innertext) && $node->innertext != "\t") {
405 $text = $node->innertext;
406 if ($matchQuotes) {
407 $text = str_ireplace('"', "'", $text);
408 }
409
410 // Apply translations without overriding longer translations
411 $processedParts = [];
412 foreach ($translationsArray as $originalText => $translatedText) {
413 $originalText = normalizeText($originalText);
414 $text = normalizeText($text);
415
416 if ($caseInsensitive) {
417 $text = preg_replace_callback(
418 '/(?<!\w)' . preg_quote(trim($originalText), '/') . '(?!\w)/ui',
419 function ($matches) use ($translatedText, &$processedParts) {
420 if (in_array($matches[0], $processedParts, true)) {
421 return $matches[0];
422 }
423 $processedParts[] = $translatedText;
424 return $translatedText;
425 },
426 $text
427 );
428 } else {
429 $text = preg_replace_callback(
430 '/(?<!\w)' . preg_quote(trim($originalText), '/') . '(?!\w)/',
431 function ($matches) use ($translatedText, &$processedParts) {
432 if (in_array($matches[0], $processedParts, true)) {
433 return $matches[0];
434 }
435 $processedParts[] = $translatedText;
436 return $translatedText;
437 },
438 $text
439 );
440 }
441 }
442 $node->innertext = $text;
443 }
444 } else {
445 $tagLower = strtolower($node->tag ?? '');
446 if (in_array($tagLower, ['script', 'style'])) {
447 return;
448 }
449 if (isset($node->nodes) && is_array($node->nodes)) {
450 foreach ($node->nodes as $child) {
451 processTextNodes($child, $excludedNodes, $translationsArray, $caseInsensitive, $matchQuotes);
452 }
453 }
454 }
455 }
456
457 // Main processing code.
458 try {
459 $translationsArray = json_decode($row['translations'], true) ?? [];
460 $altTranslationsArray = json_decode($row['alt_translations'] ?? '', true) ?: [];
461
462 // Sort translations by descending key length
463 uksort($translationsArray, function ($a, $b) {
464 return strlen($b) - strlen($a);
465 });
466
467 // Sort alt translations by descending key length
468 uksort($altTranslationsArray, function ($a, $b) {
469 return strlen($b) - strlen($a);
470 });
471
472 $htmlObj = gptranslate_simplehtmldom_str_get_html($html);
473
474 $cssSelectorLeafnodesExcluded = str_ireplace(
475 '"',
476 '',
477 trim(trim(preg_replace('/,+/', ',', str_ireplace(["\r", "\n"], ",", $settings['css_selector_serverside_leafnodes_excluded'] ?? ''))), ',')
478 );
479
480 $excludedNodes = [];
481 if (! empty($cssSelectorLeafnodesExcluded)) {
482 $selectors = explode(',', $cssSelectorLeafnodesExcluded);
483 foreach ($selectors as $selector) {
484 $selector = trim($selector);
485 if (! empty($selector)) {
486 $foundNodes = $htmlObj->find($selector);
487 foreach ($foundNodes as $node) {
488 $excludedNodes[] = $node;
489 }
490 }
491 }
492 }
493
494 $caseInsensitive = ! empty($settings['serverside_translations_caseinsensitive']);
495 $matchQuotes = ! empty($settings['serverside_translations_matchquotes']);
496
497 processTextNodes($htmlObj, $excludedNodes, $translationsArray, $caseInsensitive, $matchQuotes);
498
499 // Check if also images 'alt' are enabled to be translated
500 if (! empty($settings['translate_altimages'])) {
501 foreach ($htmlObj->find('img[alt]') as $imgNode) {
502 $altText = $imgNode->alt;
503 if ($matchQuotes) {
504 $altText = str_ireplace('"', "'", $altText);
505 }
506 foreach ($altTranslationsArray as $originalText => $translatedText) {
507 $originalText = normalizeText($originalText);
508 $altText = normalizeText($altText);
509
510 $prevAltText = $altText;
511 if ($caseInsensitive) {
512 $altText = str_ireplace(trim($originalText), $translatedText, $altText);
513 } else {
514 $altText = str_replace(trim($originalText), $translatedText, $altText);
515 }
516 if ($altText !== $prevAltText) {
517 break;
518 }
519 }
520 $imgNode->alt = $altText;
521 }
522
523 foreach ($htmlObj->find('img[title]') as $imgNode) {
524 $titleText = $imgNode->title;
525 if ($matchQuotes) {
526 $titleText = str_ireplace('"', "'", $titleText);
527 }
528 foreach ($altTranslationsArray as $originalText => $translatedText) {
529 $originalText = normalizeText($originalText);
530 $titleText = normalizeText($titleText);
531
532 $prevTitleText = $titleText;
533 if ($caseInsensitive) {
534 $titleText = str_ireplace(trim($originalText), $translatedText, $titleText);
535 } else {
536 $titleText = str_replace(trim($originalText), $translatedText, $titleText);
537 }
538 if ($titleText !== $prevTitleText) {
539 break;
540 }
541 }
542 $imgNode->title = $titleText;
543 }
544 }
545
546 if ($bodyElement = $htmlObj->find('body', 0)) {
547 $bodyElement->setAttribute('data-gptranslateskip', '1');
548 $bodyElement->setAttribute ( 'data-gptranslateoriginalalias', htmlspecialchars($row['pagelink'], ENT_QUOTES, 'UTF-8') );
549 }
550
551 $modifiedHtml = $htmlObj->save();
552 $modifiedHtml = html_entity_decode($modifiedHtml, ENT_QUOTES | ENT_HTML5, 'UTF-8');
553
554 $html = $modifiedHtml;
555 } catch (Exception $e) {
556 // Handle exceptions as needed
557 }
558 } elseif ($settings['serverside_translations_method'] == 'strireplace') {
559 // Solution 3: simplest approach, unconditional str_ireplace that could cause unintentional replacements
560 try {
561 $translationsArray = json_decode($row['translations'], true) ?? [];
562 $altTranslationsArray = json_decode($row['alt_translations'] ?? '', true) ?: [];
563
564 // Do body page translations replacements
565 uksort($translationsArray, function ($a, $b) {
566 return strlen($b) - strlen($a);
567 });
568
569 $caseInsensitive = !empty($settings['serverside_translations_caseinsensitive']);
570 $matchQuotes = !empty($settings['serverside_translations_matchquotes']);
571
572 foreach ($translationsArray as $originalText => $translatedText) {
573 $originalText = normalizeText($originalText);
574 $html = normalizeText($html);
575
576 if ($caseInsensitive) {
577 $html = str_ireplace(trim($originalText), $translatedText, $html);
578
579 // Check also if both single quotes or double quotes should be checked to replace
580 if ($matchQuotes && strpos($originalText, "'") !== false) {
581 $originalTextAlt = str_ireplace("'", '"', $originalText);
582 $html = str_ireplace(trim($originalTextAlt), $translatedText, $html);
583 }
584 } else {
585 $html = str_replace(trim($originalText), $translatedText, $html);
586
587 if ($matchQuotes && strpos($originalText, "'") !== false) {
588 $originalTextAlt = str_replace("'", '"', $originalText);
589 $html = str_replace(trim($originalTextAlt), $translatedText, $html);
590 }
591 }
592 }
593
594 // Check if also images 'alt' are enabled to be translated
595 if (!empty($settings['translate_altimages'])) {
596 foreach ($altTranslationsArray as $originalAlt => $translatedAlt) {
597 if ($matchQuotes) {
598 $originalAlt = str_ireplace('"', "'", $originalAlt);
599 }
600 if ($caseInsensitive) {
601 $html = str_ireplace('alt="' . trim($originalAlt) . '"', 'alt="' . $translatedAlt . '"', $html);
602 $html = str_ireplace("alt='" . trim($originalAlt) . "'", "alt='" . $translatedAlt . "'", $html);
603 } else {
604 $html = str_replace('alt="' . trim($originalAlt) . '"', 'alt="' . $translatedAlt . '"', $html);
605 $html = str_replace("alt='" . trim($originalAlt) . "'", "alt='" . $translatedAlt . "'", $html);
606 }
607 }
608 }
609
610 $html = str_ireplace('<body', '<body data-gptranslateskip="1" data-gptranslateoriginalalias="' . $row['pagelink'] . '"', $html);
611
612 } catch (Exception $e) {
613 // Handle exceptions as needed
614 }
615 }
616
617 return $html;
618 } );
619 } );
620