PluginProbe
Linguise – AI Automatic Multilingual Translation / 2.2.59
Linguise – AI Automatic Multilingual Translation v2.2.59
2.2.63 2.2.62 2.2.61 2.2.60 2.2.59 2.2.58 2.2.57 2.2.56 2.2.55 2.2.54 2.2.53 2.2.52 2.2.51 2.2.50 2.2.49 2.2.47 2.2.48 2.2.46 2.2.45 2.2.44 2.2.43 2.2.42 1.9.7 1.9.8 1.9.9 All 254 releases
linguise / Configuration.php

Configuration.php in Linguise – AI Automatic Multilingual Translation 2.2.59, at Configuration.php

296 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Linguise\Script;
4
5 use Linguise\Vendor\Linguise\Script\Core\Response;
6 use Linguise\Vendor\Linguise\Script\Core\Request;
7 use Linguise\WordPress\AttributeHandler;
8 use Linguise\WordPress\FragmentHandler;
9
10 defined('LINGUISE_SCRIPT_TRANSLATION') || die('');
11
12 /**
13 * Configuration
14 */
15 class Configuration
16 {
17 /**
18 * WordPress CMS
19 *
20 * @var string
21 */
22 public static $cms = 'wordpress';
23
24 /**
25 * List of WordPress strings we found to translate
26 *
27 * @var array
28 */
29 protected static $WPOriginalStrings = [];
30
31 /**
32 * Extracted json data from WordPress vars
33 *
34 * @var array
35 */
36 protected static $WPJsonData = [];
37
38 /**
39 * List of nested variables we have json_decoded
40 * We'll need to json_encode it back before rendering
41 *
42 * @var array
43 */
44 protected static $WPJsonDecoded = [];
45
46 /**
47 * List of script and variables to look into
48 * We'll add the script to look for depending on the enabled plugins
49 *
50 * @var array
51 * dev can add their own WP vars by hooking into ConfigurationLocal.php and fill this array as their convenience
52 */
53 protected static $WPKnownVars = [];
54
55 /**
56 * Hook onBeforeTranslation
57 *
58 * @return void
59 */
60 public static function onBeforeTranslation()
61 {
62 $request = Request::getInstance();
63
64 if (strpos($request->getPathname(), 'wp-json/oembed/1.0/embed') !== false) {
65 return;
66 }
67
68 self::excludeAdminBar();
69
70 // Extract WordPress translation from Scripts
71 self::wpFragmentsScriptExtraction();
72 }
73
74 /**
75 * Hook onAfterTranslation
76 *
77 * @return void
78 */
79 public static function onAfterTranslation()
80 {
81 $request = Request::getInstance();
82
83 if (strpos($request->getPathname(), 'wp-json/oembed/1.0/embed') !== false) {
84 return;
85 }
86
87 // Insert back WordPress translation into Scripts
88 self::wpFragmentsScriptInsertion();
89
90 /**
91 * Hotfix for some of urls with non latin char become invalid (HTML entity–encoded Unicode characters)
92 * And causing some of SEO tool complaining, Semrush mark hreflang as broken.
93 * TODO: can be removed if core translate the URL properly
94 */
95 // Normalize canonical link href value
96 self::normalizeCanonical();
97 }
98
99 /**
100 * Do not translate the adminbar
101 *
102 * @return void
103 */
104 protected static function excludeAdminBar()
105 {
106 $response = Response::getInstance();
107 $content = $response->getContent();
108 // Use regex to handle cases where other attributes (e.g., data-rocket-location-hash) appear before id="wpadminbar"
109 $content = preg_replace('/<div\b[^>]*\bid="wpadminbar"/i', '$0 translate="no"', $content);
110 $response->setContent($content);
111 }
112
113 /**
114 * Decoed href value of hreflang
115 *
116 * @return void
117 */
118 protected static function normalizeCanonical()
119 {
120 $response = Response::getInstance();
121 $content = $response->getContent();
122
123 $content = self::normalizeCanonicalLink($content);
124
125 $response->setContent($content);
126 }
127
128 /**
129 * Normalize canonical link href values by decoding HTML entities.
130 * Converts things like:
131 * https://example.com/ar/&#x635;&#x641;&#x62D;&#x629;/
132 * into:
133 * https://example.com/ar/صفحة/
134 *
135 * @param string $html The HTML content
136 *
137 * @return string The modified HTML content
138 */
139 protected static function normalizeCanonicalLink(string $html): string
140 {
141 return preg_replace_callback(
142 '/(<link\s+rel="canonical"[^>]*?href=")([^"]+)(")/i',
143 function ($matches) {
144 // Decode HTML entities (&#xNNNN; or &amp;)
145 $decoded = html_entity_decode($matches[2], ENT_QUOTES | ENT_HTML5, 'UTF-8');
146
147 // Optional: percent-encode path segments for safety
148 $parts = parse_url($decoded);
149 if (isset($parts['path'])) {
150 $has_trailing_slash = (substr($parts['path'], -1) === '/');
151
152 $segments = array_map('rawurlencode', explode('/', trim($parts['path'], '/')));
153 $joined_path = implode('/', $segments);
154
155 if (empty($joined_path)) {
156 $parts['path'] = $has_trailing_slash ? '/' : '';
157 } else {
158 $parts['path'] = '/' . $joined_path . ($has_trailing_slash ? '/' : '');
159 }
160 }
161
162 // Rebuild URL
163 $rebuilt =
164 (isset($parts['scheme']) ? $parts['scheme'] . '://' : '') .
165 ($parts['host'] ?? '') .
166 ($parts['path'] ?? '') .
167 (isset($parts['query']) ? '?' . $parts['query'] : '') .
168 (isset($parts['fragment']) ? '#' . $parts['fragment'] : '');
169
170 return $matches[1] . $rebuilt . $matches[3];
171 },
172 $html
173 );
174 }
175
176 /**
177 * Parse the content to extract JavaScript JSON encoded translatable variables
178 *
179 * @return void
180 */
181 protected static function wpFragmentsScriptExtraction()
182 {
183 $response = Response::getInstance();
184 $content = $response->getContent();
185
186 if (self::isXml($content)) {
187 return;
188 }
189
190 $all_fragments = FragmentHandler::findWPFragments($content);
191 $attrs_fragments = AttributeHandler::findWPFragments($content);
192 $merged_fragments = array_merge($all_fragments, $attrs_fragments);
193
194 $newContent = $content;
195 foreach ($merged_fragments as $frag_key => $frag_json) {
196 foreach ($frag_json as $frag_param => $frag_values) {
197 $frag_as_html = FragmentHandler::intoHTMLFragments($frag_key, $frag_param, $frag_values);
198 $newContent = str_replace('</body>', $frag_as_html . '</body>', $newContent);
199 }
200 }
201
202 $response->setContent($newContent);
203 }
204
205 /**
206 * Insert back all translated strings
207 *
208 * @return void
209 */
210 protected static function wpFragmentsScriptInsertion()
211 {
212 $response = Response::getInstance();
213 $content = $response->getContent();
214
215 if (self::isXml($content)) {
216 return;
217 }
218
219 $all_fragments = FragmentHandler::intoJSONFragments($content);
220 try {
221 $translatedHtml = FragmentHandler::injectTranslatedWPFragments($content, $all_fragments);
222 } catch (\LogicException $e) {
223 $translatedHtml = $content;
224 }
225 $attrs_fragments = AttributeHandler::intoJSONFragments($translatedHtml);
226 try {
227 $translatedHtml = AttributeHandler::injectTranslatedWPFragments($translatedHtml, $attrs_fragments);
228 } catch (\LogicException $e) {
229 $translatedHtml = $content;
230 }
231
232 $response->setContent($translatedHtml);
233 }
234
235 /**
236 * Remove token after user set new password
237 *
238 * @return void
239 */
240 public static function onBeforeRedirect()
241 {
242 $response = Response::getInstance();
243 $cookies = $response->getCookies();
244 foreach ($cookies as $index => $cookie) {
245 if (strpos($cookie, 'wp-resetpass') !== false) {
246 /**
247 * If set cookie into translated page sometimes not working for Cyrillic alphabet, i.e: Russian
248 * Set path into / to work in translated and non translated URL.
249 */
250 $cookies[$index]->setPath('/');
251 }
252 }
253 }
254
255 /**
256 * We should translate JSON request to oembed
257 *
258 * @return boolean
259 */
260 public static function onJsonShouldTranslate()
261 {
262 $request = Request::getInstance();
263
264 if (strpos($request->getPathname(), 'wp-json/oembed/1.0/embed') !== false) {
265 return true;
266 }
267
268 return false;
269 }
270
271 /**
272 * Check if content is XML
273 *
274 * @param string $content The request content
275 *
276 * @return boolean
277 */
278 public static function isXml($content)
279 {
280 $patterns = [
281 '/^[\s]*<\?xml[\s\S]*?>[\s]*<(urlset|sitemapindex)[\s\S]*?>[\s\S]*?<\/(urlset|sitemapindex)>/mi',
282 '/^<(urlset|sitemapindex)/',
283 '/^[\s]*(<\?xml[^>]*>)?<rss[^>]*xmlns:g="http:\/\/base\.google\.com\/ns(?:\/[^"]*)?"[^>]*>/mi',
284 '/<\?xml[^>]*>\s*<rss[^>]*>([\s\S]*?<\/item>\s*<\/channel>\s*<\/rss>)/mi',
285 ];
286
287 foreach ($patterns as $pattern) {
288 if (preg_match($pattern, $content)) {
289 return true;
290 }
291 }
292
293 return false;
294 }
295 }
296