PluginProbe ʕ •ᴥ•ʔ
GPTranslate – Multilingual AI Translation for WordPress: Automatically Translate Websites / 2.30
GPTranslate – Multilingual AI Translation for WordPress: Automatically Translate Websites v2.30
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 / ajax-handler.php
gptranslate Last commit date
assets 1 month ago flags 1 month ago includes 1 month ago language 1 month ago ajax-handler.php 1 month ago gptranslate.php 1 month ago multilang-routing.php 1 month ago readme.txt 1 month ago serverside-translations.php 1 month ago settings.php 1 month ago simplehtmldom.php 1 month ago uninstall.php 1 month ago
ajax-handler.php
293 lines
1 <?php
2 /**
3 * GPTranslate Lightweight AJAX Handler
4 *
5 * This endpoint bypasses the full WordPress stack for read-only translation queries,
6 * drastically reducing response times from ~1-2s to ~50-100ms.
7 *
8 * Handles: gettranslations, getaliastranslation
9 * All write operations remain on the REST API endpoint.
10 */
11
12 // Security: prevent direct browser access without proper headers
13 if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
14 http_response_code(405);
15 header('Content-Type: application/json; charset=utf-8');
16 echo json_encode(['result' => false, 'error' => 'Method not allowed']);
17 exit;
18 }
19
20 // CORS headers
21 header('Content-Type: application/json; charset=utf-8');
22
23 // Load minimal WordPress (database + options only, no plugins/themes/hooks)
24 define('SHORTINIT', true);
25
26 // Resolve wp-load.php path dynamically
27 $wp_load = dirname(__FILE__);
28 for ($i = 0; $i < 10; $i++) {
29 $wp_load = dirname($wp_load);
30 if (file_exists($wp_load . '/wp-load.php')) {
31 require_once $wp_load . '/wp-load.php';
32 break;
33 }
34 }
35
36 if (!defined('ABSPATH')) {
37 http_response_code(500);
38 echo json_encode(['result' => false, 'error' => 'WordPress not found']);
39 exit;
40 }
41
42 // SHORTINIT does not load options API, so we query directly
43 global $wpdb;
44
45 // Validate API key
46 $headers = function_exists('getallheaders') ? getallheaders() : [];
47 $headerApiKey = '';
48 foreach ($headers as $name => $value) {
49 if (strtolower($name) === 'x-gptranslate-key') {
50 $headerApiKey = $value;
51 break;
52 }
53 }
54
55 // Validate API key - simple shared secret hash
56 $expected_key = hash('sha256', 'gptranslate');
57
58 if ($headerApiKey !== $expected_key) {
59 http_response_code(403);
60 echo json_encode(['result' => false, 'error' => 'Forbidden']);
61 exit;
62 }
63
64 // Parse input
65 $raw_input = file_get_contents('php://input');
66 $params = json_decode($raw_input, true);
67
68 if (!$params) {
69 parse_str($raw_input, $params);
70 }
71
72 if (empty($params['task'])) {
73 http_response_code(400);
74 echo json_encode(['result' => false, 'error' => 'Missing task parameter']);
75 exit;
76 }
77
78 $task = preg_replace('/[^a-zA-Z]/', '', $params['task'] ?? '');
79 $pageLink = filter_var($params['pagelink'] ?? '', FILTER_SANITIZE_URL);
80 $languageOriginal = preg_replace('/[^a-zA-Z\-]/', '', $params['language_original'] ?? '');
81 $languageTranslated = preg_replace('/[^a-zA-Z\-]/', '', $params['language_translated'] ?? '');
82 $retriggerTranslation = (int)($params['retrigger'] ?? 0);
83
84 $table = $wpdb->prefix . 'gptranslate';
85 $response = ['result' => false];
86
87 // Lightweight replacement for WP trailingslashit + untrailingslashit + wp_parse_url
88 function gpt_light_trailingslashit($url) {
89 $parsed = parse_url($url);
90 if (empty($parsed['path'])) {
91 $parsed['path'] = '/';
92 } else {
93 $parsed['path'] = rtrim($parsed['path'], '/') . '/';
94 }
95 $rebuilt = isset($parsed['scheme']) ? $parsed['scheme'] . '://' : '';
96 $rebuilt .= $parsed['host'] ?? '';
97 $rebuilt .= $parsed['path'];
98 if (!empty($parsed['query'])) {
99 $rebuilt .= '?' . $parsed['query'];
100 }
101 if (!empty($parsed['fragment'])) {
102 $rebuilt .= '#' . $parsed['fragment'];
103 }
104 return $rebuilt;
105 }
106
107 // ============================================================================
108 // Task: gettranslations
109 // ============================================================================
110 if ($task === 'gettranslations') {
111 // Get plugin options for realtime_translations and rewrite settings
112 $opts_raw = $wpdb->get_var("SELECT option_value FROM {$wpdb->options} WHERE option_name = 'gptranslate_options' LIMIT 1");
113 $opts = $opts_raw ? unserialize($opts_raw) : [];
114
115 if (!empty($opts['realtime_translations']) || $retriggerTranslation === 1) {
116 $response['result'] = false;
117 } else {
118 $pageLinkDecoded = urldecode($pageLink);
119
120 if (!empty($opts['rewrite_language_url']) && $opts['rewrite_language_url'] == 1
121 && !empty($opts['rewrite_language_alias']) && $opts['rewrite_language_alias'] == 1) {
122 // Check 8 variants (with/without slash + encoded/decoded)
123 $row = $wpdb->get_row($wpdb->prepare(
124 "SELECT translations, alt_translations, translated_alias, pagelink FROM {$table}" .
125 " WHERE (pagelink = %s OR pagelink = %s OR pagelink = %s OR pagelink = %s OR translated_alias = %s OR translated_alias = %s OR translated_alias = %s OR translated_alias = %s)" .
126 " AND languageoriginal = %s" .
127 " AND languagetranslated = %s" .
128 " AND published = 1",
129 rtrim($pageLink, '/'),
130 rtrim($pageLink, '/') . '/',
131 rtrim($pageLinkDecoded, '/'),
132 rtrim($pageLinkDecoded, '/') . '/',
133 rtrim($pageLink, '/'),
134 rtrim($pageLink, '/') . '/',
135 rtrim($pageLinkDecoded, '/'),
136 rtrim($pageLinkDecoded, '/') . '/',
137 $languageOriginal,
138 $languageTranslated
139 ), ARRAY_A);
140 } else {
141 // Check 4 variants (with/without slash + encoded/decoded)
142 $row = $wpdb->get_row($wpdb->prepare(
143 "SELECT translations, alt_translations, translated_alias, pagelink FROM {$table}" .
144 " WHERE (pagelink = %s OR pagelink = %s OR pagelink = %s OR pagelink = %s)" .
145 " AND languageoriginal = %s" .
146 " AND languagetranslated = %s" .
147 " AND published = 1",
148 rtrim($pageLink, '/'),
149 rtrim($pageLink, '/') . '/',
150 rtrim($pageLinkDecoded, '/'),
151 rtrim($pageLinkDecoded, '/') . '/',
152 $languageOriginal,
153 $languageTranslated
154 ), ARRAY_A);
155 }
156
157 if ($row) {
158 $response['result'] = true;
159 $response['translations'] = json_decode($row['translations'], true) ?: [];
160 $response['alt_translations'] = json_decode($row['alt_translations'], true) ?: [];
161 $response['translated_alias'] = $row['translated_alias'];
162 $response['pagelink_alias'] = $row['pagelink'];
163 } else {
164 $response['result'] = false;
165 }
166 }
167
168 // ============================================================================
169 // Task: getaliastranslation
170 // ============================================================================
171 } elseif ($task === 'getaliastranslation') {
172 try {
173 $row = $wpdb->get_row($wpdb->prepare(
174 "SELECT translated_alias FROM {$table}" .
175 " WHERE (pagelink = %s OR pagelink = %s)" .
176 " AND languageoriginal = %s" .
177 " AND languagetranslated = %s" .
178 " AND published = 1",
179 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $languageOriginal, $languageTranslated
180 ), ARRAY_A);
181
182 if ($row) {
183 $response['result'] = true;
184 $response['translated_alias'] = $row['translated_alias'] ?? '';
185 } else {
186 $response['result'] = false;
187 }
188 } catch (Exception $e) {
189 $response['result'] = false;
190 $response['exception'] = $e->getMessage();
191 }
192
193 // ============================================================================
194 // Task: gettranslatedaliases
195 // ============================================================================
196 } elseif ($task === 'gettranslatedaliases') {
197 try {
198 if ($languageTranslated) {
199 $rows = $wpdb->get_results(
200 $wpdb->prepare(
201 "SELECT pagelink, translated_alias FROM {$table} WHERE languagetranslated = %s AND published = 1",
202 $languageTranslated
203 ),
204 ARRAY_A
205 );
206 } elseif ($languageOriginal) {
207 $rows = $wpdb->get_results(
208 $wpdb->prepare(
209 "SELECT translated_alias AS pagelink, pagelink AS translated_alias FROM {$table} WHERE languageoriginal = %s AND published = 1",
210 $languageOriginal
211 ),
212 ARRAY_A
213 );
214 } else {
215 $response['result'] = false;
216 echo json_encode($response);
217 exit;
218 }
219
220 if ($rows) {
221 $encodedResult = [];
222
223 foreach ($rows as $row) {
224 // Normalize with trailing slash (replaces WP trailingslashit)
225 $pagelink = gpt_light_trailingslashit($row['pagelink']);
226 $translatedAlias = !empty($row['translated_alias']) ? gpt_light_trailingslashit($row['translated_alias']) : '';
227
228 // Encode pagelink path
229 $parsedUrl = parse_url($pagelink);
230 $encodedPagelink = $pagelink;
231
232 if (!empty($parsedUrl['path'])) {
233 $pathParts = explode('/', $parsedUrl['path']);
234 $encodedParts = array_map('rawurlencode', $pathParts);
235 $encodedPath = implode('/', $encodedParts);
236
237 $encodedPagelink = ($parsedUrl['scheme'] ?? '') . '://' . ($parsedUrl['host'] ?? '');
238 $encodedPagelink .= $encodedPath;
239 if (!empty($parsedUrl['query'])) {
240 $encodedPagelink .= '?' . $parsedUrl['query'];
241 }
242 if (!empty($parsedUrl['fragment'])) {
243 $encodedPagelink .= '#' . $parsedUrl['fragment'];
244 }
245 }
246
247 // Encode translated alias path
248 $encodedAlias = $translatedAlias;
249 if (!empty($translatedAlias)) {
250 $parsedAlias = parse_url($translatedAlias);
251 if (!empty($parsedAlias['path'])) {
252 $pathParts = explode('/', $parsedAlias['path']);
253 $encodedParts = array_map('rawurlencode', $pathParts);
254 $encodedPath = implode('/', $encodedParts);
255
256 $encodedAlias = ($parsedAlias['scheme'] ?? '') . '://' . ($parsedAlias['host'] ?? '');
257 $encodedAlias .= $encodedPath;
258 if (!empty($parsedAlias['query'])) {
259 $encodedAlias .= '?' . $parsedAlias['query'];
260 }
261 if (!empty($parsedAlias['fragment'])) {
262 $encodedAlias .= '#' . $parsedAlias['fragment'];
263 }
264 }
265 }
266
267 $encodedResult[$encodedPagelink] = [
268 'pagelink' => $encodedPagelink,
269 'translated_alias' => $encodedAlias
270 ];
271 }
272
273 $response['result'] = true;
274 $response['translated_aliases'] = $encodedResult;
275 } else {
276 $response['result'] = false;
277 }
278 } catch (Exception $e) {
279 $response['result'] = false;
280 $response['exception'] = $e->getMessage();
281 }
282
283 // ============================================================================
284 // Unsupported task: fallback to REST API
285 // ============================================================================
286 } else {
287 http_response_code(400);
288 $response['error'] = 'Unsupported task for lightweight handler';
289 }
290
291 echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
292 exit;
293