PluginProbe ʕ •ᴥ•ʔ
GPTranslate – Multilingual AI Translation for WordPress: Automatically Translate Websites / 2.21
GPTranslate – Multilingual AI Translation for WordPress: Automatically Translate Websites v2.21
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 / gptranslate.php
gptranslate Last commit date
assets 5 months ago flags 5 months ago language 5 months ago gptranslate.php 5 months ago multilang-routing.php 5 months ago readme.txt 5 months ago serverside-translations.php 5 months ago settings.php 5 months ago simplehtmldom.php 5 months ago uninstall.php 5 months ago
gptranslate.php
2578 lines
1 <?php
2 /*
3 Plugin Name: GPTranslate
4 Plugin URI: https://gptranslate.storejextensions.org/
5 Description: GPTranslate for Wordpress is the revolutionary multilanguage solution to automatically translate your Wordpress website thanks to the power of Artificial Intelligence like ChatGPT, Deepseek, Gemini and more. ⚠️GPTranslate FREE Mode active
6 Author: JExtensions Store
7 Version: 2.21
8 Author URI: https://storejextensions.org
9 License: GPLv2 or later
10 License URI: https://www.gnu.org/licenses/gpl-2.0.html
11 */
12
13 if (!defined('ABSPATH')) exit;
14
15 class GPTranslate {
16 private static $instance = null;
17 private $table_name;
18 private $version;
19
20 private function isSelected($current, $value) {
21 return selected($current, $value, false);
22 }
23
24 public static $pluginVersion = '2.21';
25
26 /**
27 * Class constructor and settings inizializer with register_setting
28 *
29 * @access public
30 */
31 public function __construct() {
32 global $wpdb;
33 $this->table_name = $wpdb->prefix . 'gptranslate';
34
35 $this->version = '2.21';
36
37 $settings = get_option ( 'gptranslate_options', [ ] );
38
39 // Server-side plugin exclusion check for frontend pages
40 if (! is_admin ()) {
41 $page_exclusions_raw = $settings ['page_exclusions'] ?? '';
42
43 if (! empty ( $page_exclusions_raw )) {
44
45 // Normalize input: newlines -> commas, collapse multiple commas
46 $patterns = explode ( ',', trim ( preg_replace ( '/,+/', ',', str_ireplace ( [
47 "\r",
48 "\n"
49 ], ',', $page_exclusions_raw ) ), ',' ) );
50
51 // Normalize current request URI (PATH ONLY)
52 $request_uri = $_SERVER ['REQUEST_URI'] ?? '/';
53 $request_path = parse_url ( $request_uri, PHP_URL_PATH ) ?: '/';
54 $request_path = '/' . trim ( $request_path, '/' );
55
56 // Split path into segments
57 $path_segments = array_values ( array_filter ( explode ( '/', trim ( $request_path, '/' ) ) ) );
58
59 // Remove subfolder installation segment if configured
60 if (! empty ( $settings ['subfolder_installation'] ) && ! empty ( $path_segments )) {
61 array_shift ( $path_segments );
62 }
63
64 // Remove language slug if present (use configured languages)
65 $languages = (isset ( $settings ['languages'] ) && is_array ( $settings ['languages'] )) ? array_map ( 'strtolower', $settings ['languages'] ) : [ ];
66
67 if (! empty ( $path_segments ) && in_array ( strtolower ( $path_segments [0] ), $languages, true )) {
68 array_shift ( $path_segments );
69 }
70
71 // HOME detection:
72 // After removing subfolder + language, nothing left = HOME
73 $is_home_request = empty ( $path_segments );
74
75 // 1) Explicit HOME exclusion via "home" or "/"
76 if ($is_home_request) {
77 foreach ( $patterns as $pattern ) {
78 $pattern = strtolower ( trim ( $pattern ) );
79 if ($pattern === 'home' || $pattern === '/') {
80 return; // Skip plugin execution
81 }
82 }
83 }
84
85 // 2) Normal URL-based exclusion (full URL substring match)
86 $current_url = esc_url_raw ( ((! empty ( $_SERVER ['HTTPS'] ) && $_SERVER ['HTTPS'] !== 'off') ? 'https://' : 'http://') . ($_SERVER ['HTTP_HOST'] ?? '') . $request_uri );
87
88 foreach ( $patterns as $pattern ) {
89 $pattern = trim ( $pattern );
90
91 // Skip empty and home patterns (already handled)
92 if ($pattern === '' || $pattern === '/' || strtolower ( $pattern ) === 'home') {
93 continue;
94 }
95
96 // Case-insensitive substring match
97 if (stripos ( $current_url, $pattern ) !== false) {
98 return; // Skip plugin execution
99 }
100 }
101 }
102 }
103
104 // Include various functions like multilanguage URLs, hreflang tag, HTML lang attribute rewriting
105 require_once plugin_dir_path(__FILE__) . 'multilang-routing.php';
106
107 if ( isset($settings ['serverside_translations']) && $settings ['serverside_translations'] == 1 ) {
108 require_once plugin_dir_path(__FILE__) . 'serverside-translations.php';
109 }
110
111 register_activation_hook ( __FILE__, [
112 $this,
113 'activate_plugin'
114 ] );
115
116 add_action ( 'admin_init', function () {
117 // Disable WordPress emoji script and styles
118 remove_action('wp_head', 'print_emoji_detection_script', 7);
119 remove_action('admin_print_scripts', 'print_emoji_detection_script');
120 remove_action('wp_print_styles', 'print_emoji_styles');
121 remove_action('admin_print_styles', 'print_emoji_styles');
122 remove_filter('the_content_feed', 'wp_staticize_emoji');
123 remove_filter('comment_text_rss', 'wp_staticize_emoji');
124 remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
125
126 function gptranslate_sanitize_options( $options ) {
127 $clean = [];
128 $clean = $options;
129 return $clean;
130 }
131 register_setting ( 'gptranslate_settings', 'gptranslate_options', [
132 'sanitize_callback' => 'gptranslate_sanitize_options'
133 ]);
134
135 // Register record deletion
136 $page = sanitize_key($_GET['page'] ?? '');
137 $action = sanitize_key($_GET['action'] ?? '');
138 $nonce = isset($_GET['_gptranslate_nonce']) ? wp_unslash($_GET['_gptranslate_nonce']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
139 $translation_id = isset($_GET['translation_id']) ? (int) $_GET['translation_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
140
141 if ($page === 'gptranslate' &&
142 $action === 'delete_translation' &&
143 $translation_id &&
144 wp_verify_nonce($nonce, 'gptranslate_delete_' . $translation_id)
145 ) {
146 $this->gptranslate_handle_deletion($translation_id);
147 exit;
148 }
149
150 if (isset($_GET['action']) && sanitize_key($_GET['action']) == 'toggle_published') {
151 // Toggle published state
152 $id = isset($_GET['translation_id']) ? (int) $_GET['translation_id'] : 0;
153
154 if (!$id || !isset($_GET['_gptranslate_nonce']) || !wp_verify_nonce(wp_unslash($_GET['_gptranslate_nonce']), 'gptranslate_toggle_' . $id)) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
155 wp_die('Invalid nonce.');
156 }
157
158 global $wpdb;
159 $table = $wpdb->prefix . 'gptranslate';
160
161 // Toggle published flag
162 $current = $wpdb->get_var($wpdb->prepare("SELECT published FROM $table WHERE id = %d", $id)); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
163 $new = ($current == 1) ? 0 : 1;
164
165 $wpdb->update($table, ['published' => $new], ['id' => $id]); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
166
167 wp_redirect(admin_url('admin.php?page=gptranslate&action=state_toggled'));
168 exit;
169 }
170
171 // Check for plugin update
172 function check_for_gptranslate_update($currentVersion) {
173 // Start the session if not already started
174 if (session_status() === PHP_SESSION_NONE) {
175 session_start();
176 }
177
178 // Reset session vars after update
179 if (isset($_SESSION['gptranslate_update_version']) && version_compare($currentVersion, $_SESSION['gptranslate_update_version'], '>=')) {
180 unset($_SESSION['gptranslate_update_version']);
181 unset($_SESSION['gptranslate_update_checked']);
182 }
183
184 // Check if the update check has been done in this session
185 if (!isset($_SESSION['gptranslate_update_checked']) || $_SESSION['gptranslate_update_checked'] !== true) {
186
187 // Perform the remote XML check
188 $remote_url = 'https://storejextensions.org/updates/gptranslatewp_updater.xml';
189 $response = wp_remote_get($remote_url);
190
191 if (!is_wp_error($response)) {
192 $body = wp_remote_retrieve_body($response);
193 if (!empty($body)) {
194 $xml = simplexml_load_string($body);
195 if ($xml && !empty($xml->update->version)) {
196 $updateversion = (string)$xml->update->version;
197 if (version_compare($updateversion, $currentVersion, '>')) {
198 // Store the update info in session (version, and flag that update is available)
199 $_SESSION['gptranslate_update_version'] = $updateversion;
200 }
201 $_SESSION['gptranslate_update_checked'] = true;
202 }
203 }
204 }
205 }
206
207 // If update is available, show the notice
208 if (isset($_SESSION['gptranslate_update_version']) && sanitize_text_field($_SESSION['gptranslate_update_version'])) {
209 add_action('admin_notices', function () {
210 echo '<div class="notice notice-warning is-dismissible">';
211 echo '<p>An update for <strong><a href="https://storejextensions.org/extensions/gptranslate.html" target="_blank">GPTranslate</a></strong> is available. The new version <strong>' . esc_html(sanitize_text_field($_SESSION['gptranslate_update_version'])) . '</strong> can be downloaded from your reserved area if you have a valid subscription and license for the full version.</p>'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
212 echo '</div>';
213 });
214 }
215 }
216 //check_for_gptranslate_update($this->version);
217 } );
218
219 // Post admin notices after actions
220 add_action( 'admin_notices', function() {
221 if ( isset( $_GET['page'], $_GET['deleted'] ) && sanitize_key($_GET['page']) === 'gptranslate' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
222 if ( (int) $_GET['deleted'] === 1 ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
223 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_DELETED')) . '</p></div>';
224 } elseif ( (int) $_GET['deleted'] === 0 ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
225 echo '<div class="notice notice-error is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_DELETED_ERROR')) . '</p></div>';
226 }
227 }
228
229 if ( isset( $_GET['page'], $_GET['action'] ) && sanitize_key($_GET['page']) === 'gptranslate' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
230 if ( $_GET['action'] === 'state_toggled' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
231 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_STATE_UPDATED_SUCCESSFULLY')) . '</p></div>';
232 }
233 }
234
235 if (isset($_GET['imported']) && $_GET['imported'] == '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
236 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_IMPORTED_SUCCESSFULLY')) . '</p></div>';
237 }
238
239 if (isset($_GET['settingsimported']) && $_GET['settingsimported'] == '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
240 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_SETTINGS_IMPORTED_SUCCESSFULLY')) . '</p></div>';
241 }
242 });
243
244 // Add hook for admin menu links
245 add_action ( 'admin_menu', [
246 $this,
247 'admin_menu'
248 ] );
249
250 // Add hook for record saving/deleting
251 add_action ( 'admin_post_save_gptranslate_record', [
252 $this,
253 'save_record'
254 ] );
255
256 add_action( 'admin_post_save_gptranslate_record_and_close', [
257 $this,
258 'save_record'
259 ]);
260
261 add_action('admin_post_cancel_gptranslate_record', [
262 $this,
263 'save_record'
264 ]);
265
266 // Add hook for adding main frontend app scripts
267 add_action ( 'wp_enqueue_scripts', [
268 $this,
269 'enqueue_frontend_scripts'
270 ] );
271 }
272
273 /**
274 * Singleton class instance
275 *
276 * @access public
277 */
278 public static function get_instance() {
279 if (null === self::$instance) {
280 self::$instance = new static();
281 }
282 return self::$instance;
283 }
284
285 /**
286 * Activation plugin hook with db table creation
287 *
288 * @access public
289 */
290 public function activate_plugin() {
291 global $wpdb;
292 $charset_collate = $wpdb->get_charset_collate();
293
294 $sql = "CREATE TABLE " . $this->table_name . " (
295 id int UNSIGNED NOT NULL AUTO_INCREMENT,
296 pagelink varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
297 translated_alias varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
298 translations mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
299 alt_translations mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
300 languageoriginal char(20) NOT NULL,
301 languagetranslated char(20) NOT NULL,
302 published tinyint NOT NULL DEFAULT '1',
303 translate_date datetime DEFAULT NULL,
304 translation_engine varchar(20) NOT NULL,
305 PRIMARY KEY (id)
306 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
307
308 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
309 dbDelta($sql);
310
311 // Valori di default
312 $default_options = [
313 'google_translate_engine' => '1',
314 'chatgpt_apikey' => '',
315 'chatgpt_model' => 'gpt-3.5-turbo',
316 'chatgpt_request_message' => "Compile this JSON object key-value pairs adding the translation into '{{target}}' language to the empty value from the original '{{source}}' language of the key and return me only a parsable JSON object without any surrounding characters, preserve and return in the JSON object the key in the original '{{source}}' language within double quotes: '{{translations}}'. Pay attention to not skip any key and translate all keys. Return only a parsable JSON object with no surrounding text, explanations, or markdown formatting. Ensure the response is valid JSON and can be parsed directly.",
317 'chatgpt_request_conversation_mode' => 'user',
318 'language' => 'en',
319 'max_translations_per_request' => '100',
320 'max_characters_per_request' => '2048',
321 'detect_browser_language' => '0',
322 'autotranslate_detected_language' => '0',
323 'always_detect_autotranslated_language' => '0',
324 'auto_set_language_direction' => '0',
325 'serverside_translations' => '0',
326 'serverside_translations_method' => 'regex',
327 'serverside_translations_caseinsensitive' => '1',
328 'serverside_translations_matchquotes' => '1',
329 'serverside_translations_urldecode' => '1',
330 'serverside_translations_language_switching_mode' => 'url',
331 'serverside_translations_ignore_querystring' => '0',
332 'serverside_translations_urlencode_space' => '0',
333 'css_selector_serverside_leafnodes_excluded' => '',
334 'detect_current_language' => '0',
335 'detect_default_language' => '0',
336 'rewrite_language_url' => '0',
337 'rewrite_language_alias' => '0',
338 'rewrite_language_alias_original_language' => '0',
339 'rewrite_page_links' => '0',
340 'omit_prefix_original_language' => '0',
341 'excluded_alias_slugs' => '',
342 'rewrite_default_language_url' => '0',
343 'translate_metadata' => '0',
344 'set_html_lang' => '0',
345 'add_canonical' => '0',
346 'add_alternate' => '0',
347 'translate_placeholders' => '0',
348 'translate_altimages' => '0',
349 'css_selector_classes_translate_altimages_excluded' => '',
350 'translate_srcimages' => '0',
351 'translate_titles' => '0',
352 'translate_values' => '0',
353 'metadata_chosen_engine' => '0',
354 'metadata_words_leafnodes_excluded' => '',
355 'default_language_first' => '0',
356 'css_selector_leafnodes_excluded' => 'a.nturl,.gt-lang-code',
357 'words_leafnodes_excluded' => '',
358 'words_leafnodes_excluded_bylanguage_repeatable' => '[]',
359 'words_min_length' => '',
360 'flatten_inner_formatting_tags' => '0',
361 'flatten_inner_formatting_tags_to_remove' => 'span,b,strong,i,em,u,font',
362 'wrap_excluded_words' => '0',
363 'crawler_timeout' => '30',
364 'crawler_exclusions' => '',
365 'page_exclusions' => '',
366 'chatgpt_gtranslate_request_delay' => '0',
367 'initial_translation_delay' => '0',
368 'realtime_translations' => '0',
369 'css_selector_realtime_translations_retrigger' => '',
370 'realtime_translations_retrigger_events' => ['click'],
371 'realtime_translations_retrigger_events_delay' => '200',
372 'realtime_translations_retrigger_force_google' => '0',
373 'translations_export_format' => '.csv',
374 'ignore_querystring' => '0',
375 'enable_indexer' => '0',
376 'storage_type' => 'session',
377 'subfolder_installation' => '0',
378 'alt_flags' => [],
379 'languages' => ['en', 'es', 'de', 'it', 'fr'],
380 'excluded_languages' => [],
381 'enable_reader' => '0',
382 'responsivevoice_apikey' => 'PEVOFBma',
383 'responsivevoice_language_gender' => 'auto',
384 'responsivevoice_volume_tts' => '100',
385 'responsivevoice_voice_speed' => 'normal',
386 'mainpage_selector' => '*[name*=main], *[class*=main], *[id*=main], *[id*=container], *[class*=container]',
387 'elements_toexclude_custom' => '',
388 'proxy_responsive_loading_script' => '1',
389 'proxy_responsive_reading_mode' => 'native',
390 'chunksize' => '200',
391 'widget_text_color' => '#000000',
392 'widget_background_color' => '#FFFFFF',
393 'popup_border_radius' => '0',
394 'popup_fontsize' => '20',
395 'popup_iconsize' => '32',
396 'popup_shadow' => '1',
397 'disable_toast_popups' => '0',
398 'widget_opacity' => '1.0',
399 'float_position' => 'bottom-left',
400 'float_switcher_open_direction' => 'top',
401 'flag_style' => '2d',
402 'flag_loading' => 'local',
403 'show_language_titles' => '1',
404 'enable_dropdown' => '1',
405 'equal_widths' => '0',
406 'reader_button_position' => 'top',
407 'widget_max_height' => '260',
408 'wrapper_selector' => '.gptranslate_wrapper',
409 'draggable_widget' => '0',
410 'disable_control' => '0',
411 'custom_css' => '',
412 'disable_bootstrap_css' => '0'
413 ];
414
415 // Se l'opzione non è ancora presente, la crea
416 if (get_option('gptranslate_options') === false) {
417 add_option('gptranslate_options', $default_options);
418 }
419
420 }
421
422 /**
423 * Function to add admin menu for both settings and translations management
424 *
425 * @access public
426 */
427 public function admin_menu() {
428 add_menu_page('GPTranslate', 'GPTranslate', 'manage_options', 'gptranslate', [$this, 'records_page'], 'dashicons-translation');
429
430 // Add a submenu that matches the main menu to prevent duplication and allow renaming
431 add_submenu_page('gptranslate', esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS')), esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS')), 'manage_options', 'gptranslate', [$this, 'records_page']);
432
433 // Now you can safely add a differently named submenu
434 add_submenu_page('gptranslate', esc_html($this->loadTranslations('PLG_GPTRANSLATE_SETTINGS_MENU_TITLE')), esc_html($this->loadTranslations('PLG_GPTRANSLATE_SETTINGS_MENU_TITLE')), 'manage_options', 'gptranslate-settings', [$this, 'settings_page']);
435 }
436
437 /**
438 * Load the configuration settings page held in the settings.php file
439 *
440 * @access public
441 */
442 public function settings_page() {
443 require_once 'settings.php';
444
445 echo '<script>
446 const PLG_GPTRANSLATE_MOVE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_MOVE')) . '";
447 const PLG_GPTRANSLATE_REMOVE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_REMOVE')) . '";
448 </script>';
449 }
450
451 /**
452 * Translation records pages, list and edit
453 *
454 * @access public
455 */
456 public function records_page() {
457 global $wpdb;
458
459 // Edit record
460 if (isset($_GET['action']) && sanitize_key($_GET['action']) == 'edit') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
461 $id = isset($_GET['edit']) ? (int) $_GET['edit'] : 0;
462 $nonce = isset($_GET['_gptranslate_nonce']) ? wp_unslash($_GET['_gptranslate_nonce']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
463 $opts = get_option( 'gptranslate_options', [] );
464
465 if ( ! wp_verify_nonce( $nonce, 'gptranslate_edit_' . $id ) ) {
466 wp_die( esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_SECURITY_ERROR')), 'Error', [ 'response' => 403 ] );
467 }
468
469 $record = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$this->table_name} WHERE id = %d", $id)); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
470
471 $translationsArray = json_decode($record->translations, true) ?? [];
472 $altTranslationsArray = json_decode($record->alt_translations, true) ?? [];
473
474 uksort($translationsArray, function($a, $b) {
475 return strlen($b) - strlen($a);
476 });
477 uksort($altTranslationsArray, function($a, $b) {
478 return strlen($b) - strlen($a);
479 });
480
481 // Path relativo o assoluto all'immagine della bandiera
482 $flagUrlOriginal = plugins_url('flags/svg/' . esc_attr($record->languageoriginal) . '.svg', __FILE__);
483 $flagUrlTranslated = plugins_url('flags/svg/' . esc_attr($record->languagetranslated) . '.svg', __FILE__);
484
485 $pubIcon = $record->published ? '<img src="' . plugins_url('assets/images/published.png', __FILE__) . '" alt="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_PUBLISHED_GENERIC')) . '" title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_UNPUBLISH')) . '">' // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
486 : '<img src="' . plugins_url('assets/images/unpublished.png', __FILE__) . '" alt="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_UTRANSLATIONS_SHORT_CHART')) . '" title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_PUBLISH')) . '">'; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
487 $rewriteAliasRow = '';
488 if ($opts ['rewrite_language_alias'] == 1) {
489 $rewriteAliasRow = '<tr>
490 <th scope="row"><label for="translated_alias" title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATED_ALIAS_DESC')) . '">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATED_ALIAS')) . '</label></th>
491 <td><input type="text" id="translated_alias" name="translated_alias" value="' . esc_attr($record->translated_alias) . '" class="regular-text code"></td>
492 </tr>';
493 }
494
495 echo '<h1><img class="gptranslate-plugin-icon" src="data:image/png;base64,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"/> ' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_EDIT_TRANSLATION_HEADER')) . '</h1><div class="wrap">'; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
496 echo '<form method="post" id="edit-translations" action="admin-post.php">
497 <p>
498 <input type="submit" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_SAVE')) . '" class="button button-primary" data-action="save_gptranslate_record">
499 <input type="submit" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_SAVEANDECLOSE')) . '" class="button button-primary" data-action="save_gptranslate_record_and_close">
500 <input type="submit" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_CANCEL')) . '" class="button button-primary" data-action="cancel_gptranslate_record">
501 </p>
502 <input type="hidden" name="languagetranslated" id="languagetranslated" value="' . esc_attr($record->languagetranslated) . '">
503 <input type="hidden" name="action" id="form_action" value="save_gptranslate_record">
504 <input type="hidden" name="id" value="' . (int) $record->id . '">
505
506 <table class="form-table">
507 <tr>
508 <th scope="row"><label for="pagelink" title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_LINK_PAGE_DESC')) . '">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LINK_PAGE')) . '</label></th>
509 <td><input type="text" id="pagelink" name="pagelink" value="' . esc_attr($record->pagelink) . '" class="regular-text code"></td>
510 </tr>' .
511 $rewriteAliasRow .
512 '<tr><th scope="row"><label>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_ORIGINAL')) . '</label></th><td>' . '<img src="' . esc_url($flagUrlOriginal) . '" alt="flag" style="width: 16px; vertical-align:middle; margin-right:5px;">' . ' ' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . strtoupper($record->languageoriginal))) . '</td></tr>' . // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
513 '<tr><th scope="row"><label>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_TRANSLATED')) . '</label></th><td>' . '<img src="' . esc_url($flagUrlTranslated) . '" alt="flag" style="width: 16px; vertical-align:middle; margin-right:5px;">' . ' ' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . strtoupper($record->languagetranslated))) . '</td></tr>' . // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
514 '<tr><th scope="row"><label>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_PUBLISHED')) . '</label></th><td>' . wp_kses_post($pubIcon) . '</td></tr>' .
515 '<tr><th scope="row"><label>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_DATE')) . '</label></th><td>' . esc_html( date_i18n('l, d F Y \a\t H:i', strtotime( get_date_from_gmt($record->translate_date) ) ) ) . '</td></tr>' .
516 '<tr><th scope="row"><label>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_CHATGPT_TRANSLATION_ENGINE')) . '</label></th><td><span class="gpt-label">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_CHATGPT_TRANSLATION_ENGINE_' . strtoupper(esc_attr($record->translation_engine)) . '_ENGINE')) . '</span></td></tr>
517
518 <tr>
519 <th scope="row"><label title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS_DESC')) . '">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS')) . '</label></th>
520 <td>
521 <div class="gptcard gptcard-default">
522 <div class="gptcard-header">
523 <div class="accordion-toggle">
524 <div class="input-group">
525 <span class="gpt-label" aria-label="Filter">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_FILTER')) . '</span>
526 <input type="text" name="search" value="" class="text_area">
527 <button class="btn btn-primary btn-sm" data-role="search-translations" onclick="return false;">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_GO')) . '</button>
528 <button class="btn btn-primary btn-sm" data-role="reset-search" onclick="return false;">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_RESET')) . '</button>
529 </div>
530 </div>
531 </div>
532 <div class="gptcard-body gptcard-block ps-3 accordion-body accordion-inner">
533 <button type="button" class="btn btn-success btn-adder" data-addtype="translations">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ADD_TRANSLATION')) . '</button>
534 <textarea name="translations_json" id="translations_json" hidden>' . esc_textarea(json_encode($translationsArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) . '</textarea>
535 <div id="translations-container"></div>
536 </div>
537 </div>
538 </td>
539 </tr>
540
541 <tr class="alt_translations_' . (int)$opts['translate_altimages'] . '">
542 <th scope="row"><label title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_ALT_TRANSLATIONS_DESC')) . '">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ALT_TRANSLATIONS')) . '</label></th>
543 <td>
544 <div class="gptcard gptcard-default">
545 <div class="gptcard-header">
546 <div class="accordion-toggle">
547 <div class="input-group">
548 <span class="gpt-label" aria-label="Filter">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_FILTER')) . '</span>
549 <input type="text" name="search" value="" class="text_area">
550 <button class="btn btn-primary btn-sm" data-role="search-translations" onclick="return false;">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_GO')) . '</button>
551 <button class="btn btn-primary btn-sm" data-role="reset-search" onclick="return false;">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_RESET')) . '</button>
552 </div>
553 </div>
554 </div>
555 <div class="gptcard-body gptcard-block ps-3 accordion-body accordion-inner">
556 <button type="button" class="btn btn-success btn-adder" data-addtype="alt-translations">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ADD_ALT_TRANSLATION')) . '</button>
557 <textarea name="alt_translations_json" id="alt_translations_json" hidden>' . esc_textarea(json_encode($altTranslationsArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) . '</textarea>
558 <div id="alt-translations-container"></div>
559 </div>
560 </div>
561 </td>
562 </tr>
563 </table>' .
564 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- nonce field is safe
565 wp_nonce_field('gptranslate_save_record_action', '_gptranslate_nonce') .
566 '</form>';
567
568 echo '<script>
569 const initialTranslations = ' . (json_encode($translationsArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}') . ';
570 const initialAltTranslations = ' . (json_encode($altTranslationsArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}') . ';
571
572 const PLG_GPTRANSLATE_ORIGINAL_TEXT = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_ORIGINAL_TEXT')) . '";
573 const PLG_GPTRANSLATE_TRANSLATED_TEXT = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATED_TEXT')) . '";
574 const PLG_GPTRANSLATE_DELETE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_DELETE')) . '";
575 const PLG_GPTRANSLATE_MOVE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_MOVE')) . '";
576 const PLG_GPTRANSLATE_REMOVE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_REMOVE')) . '";
577 const PLG_GPTRANSLATE_SYNC = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC')) . '";
578 const PLG_GPTRANSLATE_SYNC_TITLE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC_TITLE')) . '";
579 const PLG_GPTRANSLATE_SYNC_DESC = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC_DESC')) . '";
580 const PLG_GPTRANSLATE_SYNC_COMPLETED = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC_COMPLETED')) . '";
581 const PLG_GPTRANSLATE_SYNC_ERROR = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC_ERROR')) . '";
582 const gptServerSideLink = "' . esc_url_raw(rest_url('gptranslate/v1/request')) . '";
583 </script>';
584 } else {
585 // FREE period
586 // echo '<div class="notice notice-info is-dismissible"><p>You’re currently using the full version of GPTranslate – completely FREE during the initial launch period! Enjoy unlimited AI-powered translations and all PRO features at no cost. 🚀</p></div>';
587
588 // UPGRADE period
589 echo '<div class="notice notice-warning is-dismissible"><p>⚠️ GPTranslate runs in FREE Mode with usage limits. To unlock unlimited translations and advanced features, upgrade to the <a href="https://gptranslate.storejextensions.org/" target="_blank"><strong>PRO version</strong></a>. Current FREE Plan: translate up to <strong>2500 words</strong>, read aloud up to <strong>100 words</strong> per page and crawl up to <strong>10 pages</strong>. Don’t lose AI power – <a href="https://gptranslate.storejextensions.org/" target="_blank">Upgrade Now</a>.</p></div>';
590
591 // List records
592 echo '<h1><img class="gptranslate-plugin-icon" src="data:image/png;base64,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"/> ' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS_HEADER')) . '</h1><div class="wrap">'; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
593
594 // Filters section
595 $opts = get_option( 'gptranslate_options', [] );
596 $languageFilter = esc_attr( sanitize_text_field( wp_unslash( $_GET['language'] ?? '' ) ) );
597 $languages = $opts['languages'] ?? [];
598
599 // Paginate records
600 $records_per_page = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 10;
601 $valid_per_page_options = [5, 10, 25, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 9999999];
602 if (!in_array($records_per_page, $valid_per_page_options)) {
603 $records_per_page = 10;
604 }
605
606 $searchFilter = sanitize_text_field( wp_unslash( $_GET['s'] ?? '' ) );
607 $engineFilter = sanitize_key( wp_unslash( $_GET['engine'] ?? '' ) );
608 $publishedFilter = sanitize_key( wp_unslash( $_GET['published'] ?? '' ) );
609 $exactMatchFilter = isset($_GET['exactmatch']) && $_GET['exactmatch'] == '1' ? true : false;
610
611 echo
612 '<form method="get" class="form-filter-container">' .
613 '<div class="left-filter-container">' .
614 '<input type="text" name="s" id="search-input" value="' . (isset($_GET['s']) ? esc_attr(sanitize_text_field( wp_unslash($_GET['s']))) : '') . '" placeholder="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_SEARCH')) . '" />' .
615 '<button type="button" class="button" onclick="this.form.submit();">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_GO')) . '</button>' .
616 '<button type="button" class="button" onclick="document.getElementById(\'search-input\').value=\'\'; this.form.submit();">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_RESET')) . '</button>' .
617 '<label class="button"><input type="checkbox" name="exactmatch" value="1" ' . checked($exactMatchFilter, true, false) . ' title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_EXACT_MATCH')) . '"> ' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_EXACT_MATCH_LABEL')) . '</label>' .
618 '</div>' .
619 '<div class="right-filter-container">' .
620 '<input type="hidden" name="_gptranslate_nonce" value="' . esc_attr(wp_create_nonce('gptranslate_filter_action')) . '" />' .
621 '<select name="published">' .
622 '<option value="">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_ALL')) . '</option>' .
623 '<option value="1" ' . esc_html($this->isSelected(esc_attr((isset($_GET['published']) ? esc_attr(sanitize_key( wp_unslash($_GET['published']))) : '')), '1')) . '>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_PUBLISHED_GENERIC')) . '</option>' .
624 '<option value="0" ' . esc_html($this->isSelected(esc_attr((isset($_GET['published']) ? esc_attr(sanitize_key( wp_unslash($_GET['published']))) : '')), '0')) . '>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_UTRANSLATIONS_SHORT_CHART')) . '</option>' .
625 '</select>' .
626 '<select name="language">' .
627 '<option value="">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_TRANSLATED')) . '</option>';
628
629
630 // Loop languages
631 foreach ($languages as $lang) {
632 echo "<option value='" . esc_attr($lang) . "'" . esc_html($this->isSelected($languageFilter, $lang)) . ">" . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . strtoupper($lang))) . "</option>";
633 }
634
635 echo
636 '</select>' .
637 '<select name="engine">' .
638 '<option value="">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_CHATGPT_TRANSLATION_ENGINE')) . '</option>' .
639 '<option value="gtranslate" ' . esc_html($this->isSelected(sanitize_key(wp_unslash($_GET['engine'] ?? '')), 'gtranslate')) . '>Google AI</option>' .
640 '<option value="chatgpt" ' . esc_html($this->isSelected(sanitize_key(wp_unslash($_GET['engine'] ?? '')), 'chatgpt')) . '>ChatGPT</option>' .
641 '<option value="gemini" ' . esc_html($this->isSelected(sanitize_key(wp_unslash($_GET['engine'] ?? '')), 'gemini')) . '>Gemini</option>' .
642 '<option value="deepseek" ' . esc_html($this->isSelected(sanitize_key(wp_unslash($_GET['engine'] ?? '')), 'deepseek')) . '>DeepSeek</option>' .
643 '<option value="googlecloud" ' . esc_html($this->isSelected(sanitize_key(wp_unslash($_GET['engine'] ?? '')), 'googlecloud')) . '>Google Cloud</option>' .
644 '</select>' .
645 '<select name="per_page">' .
646 '<option value="5" ' . esc_html($this->isSelected($records_per_page, 5)) . '>5</option>' .
647 '<option value="10" ' . esc_html($this->isSelected($records_per_page, 10)) . '>10</option>' .
648 '<option value="25" ' . esc_html($this->isSelected($records_per_page, 25)) . '>25</option>' .
649 '<option value="50" ' . esc_html($this->isSelected($records_per_page, 50)) . '>50</option>' .
650 '<option value="100" ' . esc_html($this->isSelected($records_per_page, 100)) . '>100</option>' .
651 '<option value="200" ' . esc_html($this->isSelected($records_per_page, 200)) . '>200</option>' .
652 '<option value="500" ' . esc_html($this->isSelected($records_per_page, 500)) . '>500</option>' .
653 '<option value="1000" ' . esc_html($this->isSelected($records_per_page, 1000)) . '>1000</option>' .
654 '<option value="2000" ' . esc_html($this->isSelected($records_per_page, 2000)) . '>2000</option>' .
655 '<option value="5000" ' . esc_html($this->isSelected($records_per_page, 5000)) . '>5000</option>' .
656 '<option value="10000" ' . esc_html($this->isSelected($records_per_page, 10000)) . '>10000</option>' .
657 '<option value="9999999" ' . esc_html($this->isSelected($records_per_page, 9999999)) . '>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ALL')) . '</option>' .
658 '</select>' .
659 '<input type="submit" class="button" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_FILTER')) . '" />' .
660 '</div>' .
661 '<input type="hidden" name="page" value="gptranslate" />' .
662 '</form>';
663
664 // Bottoni Import/Export
665 echo '<div class="action-buttons-toolbar">';
666 echo '<button class="button button-primary" id="bulk-delete-btn">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_DELETE')) . '</button>';
667 echo '<button class="button button-primary" id="toggle-migration">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_BTNS')) . '</button>';
668
669 // Export CSV/XLIFF
670 $exportFormat = (isset($opts['translations_export_format']) && $opts['translations_export_format'] == '.xliff') ? 'gptranslate_export_translations_xliff' : 'gptranslate_export_translations_csv';
671 echo '<form method="post" action="' . esc_attr(admin_url('admin-post.php')) . '" style="display:inline;margin-right:10px;">';
672 if($exportFormat == 'gptranslate_export_translations_csv') {
673 wp_nonce_field('gptranslate_export_csv', 'gptranslate_export_csv_nonce');
674 } else {
675 wp_nonce_field('gptranslate_export_xliff', 'gptranslate_export_xliff_nonce');
676 }
677 echo '<input type="hidden" name="action" value="' . $exportFormat . '">';
678 echo '<input type="submit" class="button button-primary" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_EXPORT_TRANSLATIONS')) . '">';
679 echo '</form>';
680
681 // Import CSV/XLIFF
682 $importFormat = (isset($opts['translations_export_format']) && $opts['translations_export_format'] == '.xliff') ? 'gptranslate_import_translations_xliff' : 'gptranslate_import_translations_csv';
683 echo '<input type="button" class="button button-primary button-import" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_IMPORT_TRANSLATIONS')) . '">';
684 echo '<form method="post" action="' . esc_attr(admin_url('admin-post.php')) . '" enctype="multipart/form-data" style="display:inline;">';
685 if($importFormat == 'gptranslate_import_translations_csv') {
686 wp_nonce_field('gptranslate_import_csv', 'gptranslate_import_csv_nonce');
687 $acceptFormat = '.csv';
688 } else {
689 wp_nonce_field('gptranslate_import_xliff', 'gptranslate_import_xliff_nonce');
690 $acceptFormat = '.xliff';
691 }
692 echo '<input type="hidden" name="action" value="' . $importFormat . '">';
693 echo '<input type="file" name="import_file" class="toggle-import hidden" accept="' . $acceptFormat . '" required>';
694 echo '<input type="submit" class="button button-primary toggle-import hidden" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_META_CONFIRM')) . '">';
695 echo '</form>';
696
697 echo '</div>';
698
699 echo '<div id="migraterow" class="hidden">
700 <span class="input-group">
701 <label for="migratetranslations_currentdomain"><strong>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_META_PREVIOUS_DOMAIN')) . '</strong></label>
702 <input type="text" class="form-control" id="migratetranslations_currentdomain" name="migratetranslations_currentdomain" value="">
703 </span>
704 <span class="input-group">
705 <label for="migratetranslations_newdomain"><strong>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_META_NEW_DOMAIN')) . '</strong></label>
706 <input type="text" class="form-control" id="migratetranslations_newdomain" name="migratetranslations_newdomain" value="">
707 </span>
708 <button class="button button-primary" id="migrationconfirm">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_META_CONFIRM')) . '</button>
709 <button class="button" id="migrationcancel">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_RESET')) . '</button>
710 </div>
711 <input type="button" class="button button-warning button-crawler" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER')) . '">
712
713 <script>
714 const PLG_GPTRANSLATE_MIGRATION_SUCCESS = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_MIGRATION_SUCCESS')) . '";
715 const PLG_GPTRANSLATE_MIGRATION_FAILED = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_MIGRATION_FAILED')) . '";
716 const PLG_GPTRANSLATE_UNKNOWN_ERROR = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_UNKNOWN_ERROR')) . '";
717 const PLG_GPTRANSLATE_NETWORK_ERROR = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_NETWORK_ERROR')) . '";
718 const PLG_GPTRANSLATE_BULK_DELETE_CONFIRM = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_CONFIRM')) . '";
719 const PLG_GPTRANSLATE_BULK_DELETE_SELECT_ONE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_SELECT_ONE')) . '";
720 const PLG_GPTRANSLATE_BULK_DELETE_SUCCESS = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_SUCCESS')) . '";
721 const PLG_GPTRANSLATE_BULK_DELETE_ERROR = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_ERROR')) . '";
722 const PLG_GPTRANSLATE_BULK_DELETE_NETWORK = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_NETWORK')) . '";
723 const PLG_GPTRANSLATE_CRAWLER = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER')) . '";
724 const PLG_GPTRANSLATE_CRAWLER_DIALOG_TITLE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_DIALOG_TITLE')) . '";
725 const PLG_GPTRANSLATE_CRAWLER_TARGET_LINK = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_TARGET_LINK')) . '";
726 const PLG_GPTRANSLATE_CRAWLER_CHOOSE_TARGET_LINK = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_CHOOSE_TARGET_LINK')) . '";
727 const PLG_GPTRANSLATE_CRAWLER_START = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_START')) . '";
728 const PLG_GPTRANSLATE_CRAWLER_START_DESC = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_START_DESC')) . '";
729 const PLG_GPTRANSLATE_CRAWLER_STARTED = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_STARTED')) . '";
730 const PLG_GPTRANSLATE_CRAWLER_CURRENT_STATUS_RUNNING = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_CURRENT_STATUS_RUNNING')) . '";
731 const PLG_GPTRANSLATE_CRAWLER_FOOTER = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_FOOTER')) . '";
732 const PLG_GPTRANSLATE_CRAWLER_CURRENT_STATUS_IDLE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_CURRENT_STATUS_IDLE')) . '";
733 const PLG_GPTRANSLATE_CRAWLER_NO_URLS_PROCESSED = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_NO_URLS_PROCESSED')) . '";
734 const PLG_GPTRANSLATE_CRAWLER_STOP = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_STOP')) . '";
735 const PLG_GPTRANSLATE_CRAWLER_STOP_DESC = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_STOP_DESC')) . '";
736 const PLG_GPTRANSLATE_CRAWLER_STARTING = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_STARTING')) . '";
737 const PLG_GPTRANSLATE_CRAWLER_STOPPING = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_STOPPING')) . '";
738 const PLG_GPTRANSLATE_CRAWLER_STOPPED = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_STOPPED')) . '";
739 const PLG_GPTRANSLATE_CRAWLER_COMPLETED = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_COMPLETED')) . '";
740 const PLG_GPTRANSLATE_CRAWLER_LOADING = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_LOADING')) . '";
741 const PLG_GPTRANSLATE_CRAWLER_TRANSLATING = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_TRANSLATING')) . '";
742 const PLG_GPTRANSLATE_CRAWLER_PAGE_COMPLETED = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_PAGE_COMPLETED')) . '";
743 const PLG_GPTRANSLATE_CRAWLER_REFRESHING = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_REFRESHING')) . '";
744 const PLG_GPTRANSLATE_CRAWLER_CURRENT_STATUS_NOLANG_SELECTOR = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_CURRENT_STATUS_NOLANG_SELECTOR')) . '";
745 const PLG_GPTRANSLATE_CRAWLER_EXPORT_XMLSITEMAP = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_EXPORT_XMLSITEMAP')) . '";
746 const PLG_GPTRANSLATE_CRAWLER_SINGLE_URL_OPTION = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_CRAWLER_SINGLE_URL_OPTION')) . '";
747 var gptranslateBaseCrawlerHome = "' . esc_js( trailingslashit( get_site_url() ) ) . '";
748 var gptranslateDefaultLanguage = "' . $opts['language'] . '";
749 var gptranslateCrawlerTimeout = "' . (isset($opts['crawler_timeout']) ? $opts['crawler_timeout'] : '30') . '";
750 var gptranslateCrawlerExclusions = "' . esc_js(trim(preg_replace('/,+/', ',', str_ireplace(["\r", "\n"], ",", (isset($opts['crawler_exclusions']) ? $opts['crawler_exclusions'] : ''))), ',')) . '";
751 var gptranslateRewriteLanguageUrl = ' . (int)$opts['rewrite_language_url'] . ';
752 var gptranslateOmitPrefixOriginalLanguage = ' . (isset($opts['omit_prefix_original_language']) ? (int)$opts['omit_prefix_original_language'] : 0) . ';
753 var gptVersionNumeric = ' . 0 . ';
754 </script>';
755
756
757 echo '<table class="widefat fixed striped">';
758 echo '<thead><tr>';
759 echo '<th style="width: 1%"><input class="form-check-input" autocomplete="off" type="checkbox" id="checkall"></th>';
760 echo '<th style="width: 2%;;white-space:nowrap">ID</th>';
761 if($opts['rewrite_language_alias'] == 1) {
762 echo '<th style="width: 18%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LINK_PAGE')) . '</th>';
763 echo '<th style="width: 18%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATED_ALIAS')) . '</th>';
764 echo '<th style="width: 18%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_ORIGINAL_TRANSLATED')) . '</th>';
765 } else {
766 echo '<th style="width: 25%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LINK_PAGE')) . '</th>';
767 echo '<th style="width: 20%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_ORIGINAL_TRANSLATED')) . '</th>';
768 }
769 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_ORIGINAL')) . '</th>';
770 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_TRANSLATED')) . '</th>';
771 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_PUBLISHED_GENERIC')) . '</th>';
772 echo '<th style="width:5%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS_ENGINE')) . '</th>';
773 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS_DATE')) . '</th>';
774 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ACTIONS')) . '</th>';
775 echo '</tr></thead>';
776
777 echo '<tbody>';
778
779 $current_page = isset($_GET['paged']) && is_numeric($_GET['paged']) ? (int)$_GET['paged'] : 1;
780 $offset = ($current_page - 1) * $records_per_page;
781 $sql_count = "SELECT COUNT(*) FROM {$this->table_name} WHERE 1=1";
782
783 // Add dynamic filters
784 if (!empty($searchFilter)) {
785 if ($exactMatchFilter) {
786 // Ricerca esatta
787 $sql_count .= " AND (pagelink = '" . esc_sql($searchFilter) . "' OR translated_alias = '" . esc_sql($searchFilter) . "')";
788 } else {
789 // Ricerca LIKE (comportamento attuale)
790 $sql_count .= " AND (pagelink LIKE '%" . esc_sql($searchFilter) . "%' OR translated_alias LIKE '%" . esc_sql($searchFilter) . "%' OR translations LIKE '%" . esc_sql($searchFilter) . "%'" . " OR alt_translations LIKE '%" . esc_sql($searchFilter) . "%'" . ")";
791 }
792 }
793 if ($publishedFilter !== '') {
794 $sql_count .= " AND published = '" . esc_sql($publishedFilter) . "'";
795 }
796 if (!empty($languageFilter)) {
797 $sql_count .= " AND languagetranslated = '" . esc_sql($languageFilter) . "'";
798 }
799 if (!empty($engineFilter)) {
800 $sql_count .= " AND translation_engine = '" . esc_sql($engineFilter) . "'";
801 }
802 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Dynamic query built with placeholders, safely prepared
803 $total_records = $wpdb->get_var($sql_count); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
804 $total_pages = ceil($total_records / $records_per_page);
805
806 // Load records count with filtering
807 $sql_data = "SELECT * FROM {$this->table_name} WHERE 1=1";
808
809 // Add dynamic filters
810 if (!empty($searchFilter)) {
811 if ($exactMatchFilter) {
812 // Ricerca esatta
813 $sql_data .= " AND (pagelink = '" . esc_sql($searchFilter) . "' OR translated_alias = '" . esc_sql($searchFilter) . "')";
814 } else {
815 // Ricerca LIKE (comportamento attuale)
816 $sql_data .= " AND (pagelink LIKE '%" . esc_sql($searchFilter) . "%' OR translated_alias LIKE '%" . esc_sql($searchFilter) . "%' OR translations LIKE '%" . esc_sql($searchFilter) . "%'" . " OR alt_translations LIKE '%" . esc_sql($searchFilter) . "%'" . ")";
817 }
818 }
819 if ($publishedFilter !== '') {
820 $sql_data .= " AND published = '" . esc_sql($publishedFilter) . "'";
821 }
822 if (!empty($languageFilter)) {
823 $sql_data .= " AND languagetranslated = '" . esc_sql($languageFilter) . "'";
824 }
825 if (!empty($engineFilter)) {
826 $sql_data .= " AND translation_engine = '" . esc_sql($engineFilter) . "'";
827 }
828 $sql_data .= " ORDER BY translate_date DESC LIMIT $records_per_page OFFSET $offset";
829
830 // Load records with filtering and pagination
831 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Dynamic query built with placeholders, safely prepared
832 $records = $wpdb->get_results($sql_data); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
833
834 // Message for no translations
835 if (!count($records) && stripos($sql_data, "AND") === false) {
836 echo '<div class="notice notice-success is-dismissible" id="notranslations-notice">';
837 echo '<p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_NO_TRANSLATIONS')) . '</p>';
838 echo '</div>';
839 ?>
840 <div id="gptranslate-zero-state" class="gptranslate-zero-state">
841 <div class="gptranslate-zero-icon">
842 <svg fill="#222222" height="96px" width="96px" version="1.1" id="XMLID_275_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
843 <g id="language">
844 <g>
845 <path d="M12,24C5.4,24,0,18.6,0,12S5.4,0,12,0s12,5.4,12,12S18.6,24,12,24z M9.5,17c0.6,3.1,1.7,5,2.5,5s1.9-1.9,2.5-5H9.5z
846 M16.6,17c-0.3,1.7-0.8,3.3-1.4,4.5c2.3-0.8,4.3-2.4,5.5-4.5H16.6z M3.3,17c1.2,2.1,3.2,3.7,5.5,4.5c-0.6-1.2-1.1-2.8-1.4-4.5H3.3
847 z M16.9,15h4.7c0.2-0.9,0.4-2,0.4-3s-0.2-2.1-0.5-3h-4.7c0.2,1,0.2,2,0.2,3S17,14,16.9,15z M9.2,15h5.7c0.1-0.9,0.2-1.9,0.2-3
848 S15,9.9,14.9,9H9.2C9.1,9.9,9,10.9,9,12C9,13.1,9.1,14.1,9.2,15z M2.5,15h4.7c-0.1-1-0.1-2-0.1-3s0-2,0.1-3H2.5C2.2,9.9,2,11,2,12
849 S2.2,14.1,2.5,15z M16.6,7h4.1c-1.2-2.1-3.2-3.7-5.5-4.5C15.8,3.7,16.3,5.3,16.6,7z M9.5,7h5.1c-0.6-3.1-1.7-5-2.5-5
850 C11.3,2,10.1,3.9,9.5,7z M3.3,7h4.1c0.3-1.7,0.8-3.3,1.4-4.5C6.5,3.3,4.6,4.9,3.3,7z"/>
851 </g>
852 </g>
853 </svg>
854 </div>
855
856 <div class="gptranslate-zero-title">
857 <?php echo esc_html($this->loadTranslations('PLG_GPTRANSLATE_ZERO_TITLE')); ?>
858 </div>
859
860 <p class="gptranslate-zero-desc">
861 <?php echo esc_html($this->loadTranslations('PLG_GPTRANSLATE_ZERO_DESC')); ?>
862 </p>
863
864 <button id="gptranslate-start-crawler" class="button button-primary button-hero">
865 <span class="dashicons-before dashicons-translation" aria-hidden="true"></span>
866 <?php echo esc_html($this->loadTranslations('PLG_GPTRANSLATE_ZERO_BUTTON')); ?>
867 </button>
868
869 <p class="gptranslate-zero-hint">
870 <?php echo esc_html($this->loadTranslations('PLG_GPTRANSLATE_ZERO_HINT')); ?>
871 </p>
872 </div>
873 <?php
874 }
875
876 foreach ( $records as $r ) {
877 // Build a short summary of the translations
878 $tr = json_decode( $r->translations, true );
879 if ( is_array( $tr ) ) {
880 $pairs = array_map(
881 function( $k, $v ) {
882 return esc_html( $k ) . '' . esc_html( $v );
883 },
884 array_keys( $tr ),
885 array_values( $tr )
886 );
887 $short = implode( ', ', $pairs );
888 $short = mb_substr( $short, 0, 80 ) . ( mb_strlen( $short ) > 80 ? '' : '' );
889 } else {
890 $short = '';
891 }
892
893 // Escape all output
894 $id = (int) $r->id;
895 $link = wp_nonce_url( admin_url( "admin.php?page=gptranslate&action=edit&edit={$id}" ), 'gptranslate_edit_' . $id, '_gptranslate_nonce' );
896 $deleteLink = wp_nonce_url( admin_url( "admin.php?page=gptranslate&action=delete_translation&translation_id={$id}" ), 'gptranslate_delete_' . $id, '_gptranslate_nonce' );
897 $origLang = esc_html( strtoupper( $r->languageoriginal ) );
898 $transLang = esc_html( strtoupper( $r->languagetranslated ) );
899 $pub = $r->published ? esc_html($this->loadTranslations('PLG_GPTRANSLATE_YES')) : esc_html($this->loadTranslations('PLG_GPTRANSLATE_NO'));
900 $engine = esc_html( $r->translation_engine );
901 $date = esc_html( $r->translate_date );
902
903 $langOriginal = esc_attr($r->languageoriginal);
904
905 // Path relativo o assoluto all'immagine della bandiera
906 $flagUrlOriginal = plugins_url('flags/svg/' . $r->languageoriginal . '.svg', __FILE__);
907 $flagOriginal = '<img src="' . esc_url($flagUrlOriginal) . '" alt="flag" style="width: 16px; vertical-align:middle; margin-right:4px;">'; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
908 $flagUrlTranslated = plugins_url('flags/svg/' . $r->languagetranslated . '.svg', __FILE__);
909 $flagTranslated = '<img src="' . esc_url($flagUrlTranslated) . '" alt="flag" style="width: 16px; vertical-align:middle; margin-right:4px;">'; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
910
911 $togglePublishedUrl = wp_nonce_url(
912 admin_url("admin.php?page=gptranslate&action=toggle_published&translation_id={$id}"),
913 'gptranslate_toggle_' . $id,
914 '_gptranslate_nonce'
915 );
916
917 $pubIcon = $r->published ? '<img src="' . plugins_url('assets/images/published.png', __FILE__) . '" alt="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_PUBLISHED_GENERIC')) . '" title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_UNPUBLISH')) . '">' // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
918 : '<img src="' . plugins_url('assets/images/unpublished.png', __FILE__) . '" alt="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_UTRANSLATIONS_SHORT_CHART')) . '" title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_PUBLISH')) . '">'; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
919
920
921 $pub = $r->published ? "<a href='{$togglePublishedUrl}' class='gpt-toggle gpt-published' title='" . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_UNPUBLISH')) . "'>" . $pubIcon . "</a>"
922 : "<a href='{$togglePublishedUrl}' class='gpt-toggle gpt-unpublished' title='" . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_PUBLISH')) . "'>" . $pubIcon . "</a>";
923
924 $local_date = get_date_from_gmt($date);
925
926 echo '<tr>';
927 echo "<td style='width: 1%'><input class='form-check-input' autocomplete='off' type='checkbox' id='cb0' name='gptid[]' value='" . esc_attr($r->id) . "'></td>";
928 echo "<td style='width: 2%;white-space:nowrap'>". esc_html($r->id) . "</td>";
929 echo "<td><a href='" . esc_attr( $link ) . "'><span class='icon-style' aria-hidden='true'>📝</span>" . esc_html( $r->pagelink ) . "</a><a class='doublesize-icon' href='" . esc_attr( $r->pagelink ) . "' target='_blank'>↗</a></td>";
930 if($opts['rewrite_language_alias'] == 1) {
931 echo $r->translated_alias ? "<td><a href='" . esc_attr( $r->translated_alias ) . "'>" . esc_html( $r->translated_alias ) . "</a><a class='doublesize-icon' href='" . esc_attr( $r->translated_alias ) . "' target='_blank'>↗</a></td>" : '<td>-</td>';
932 }
933 echo "<td>" . esc_html($short) . "</td>";
934 echo "<td>" . wp_kses_post($flagOriginal) . " " . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . strtoupper($langOriginal))) . "</td>";
935 echo "<td>" . wp_kses_post($flagTranslated) . " " . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . strtoupper(esc_attr($r->languagetranslated)))) . "</td>";
936 echo "<td>" . wp_kses_post($pub) . "</td>";
937 echo "<td><span class='gpt-label'>" . esc_html($this->loadTranslations('PLG_GPTRANSLATE_CHATGPT_TRANSLATION_ENGINE_' . strtoupper($engine) . '_ENGINE')) . "</span></td>";
938 echo "<td>" . esc_html( date_i18n('l, d F Y \a\t H:i', strtotime($local_date)) ) . "</td>";
939 echo "<td><a href='" . esc_attr($link) . "'>" . esc_html($this->loadTranslations('PLG_GPTRANSLATE_EDIT')) . "</a> | <a href='" . esc_attr($deleteLink) . "' onclick=\"return confirm('" . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_DELETE_CONFIRM')) . "');\">" . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_DELETE')) . "</a></td>";
940 echo '</tr>';
941 }
942 echo '</tbody>';
943 echo '</table>';
944
945 if ($total_pages > 1) {
946 echo '<div class="tablenav"><div class="tablenav-pages">';
947 for ($i = 1; $i <= $total_pages; $i++) {
948 $url = add_query_arg(array_merge($_GET, ['paged' => $i]), admin_url('admin.php'));
949 $class = ($i == $current_page) ? "class='current-page button'" : "class='button'";
950 echo "<a " . wp_kses_post($class) . " href='" . esc_url($url) . "'>" . esc_html($i) . "</a> ";
951 }
952 echo '</div></div>';
953 }
954 }
955
956 echo '</div>';
957 echo '</div>';
958 }
959
960 /**
961 * Load language file and translations
962 * @return array
963 */
964 public function loadTranslations($key) {
965 // Text translations
966 static $adminLanguageStrings = null;
967
968 if(!$adminLanguageStrings) {
969 $adminLanguageFile = dirname(__FILE__) . "/language/en-GB/gptranslate.ini";
970 if(file_exists($adminLanguageFile)) {
971 $adminLanguageStrings = parse_ini_file($adminLanguageFile, false, INI_SCANNER_NORMAL);
972 }
973 }
974
975 if(array_key_exists($key, $adminLanguageStrings)) {
976 return $adminLanguageStrings[$key];
977 }
978
979 return $key;
980 }
981
982 /**
983 * Load language file and translations
984 * @return array
985 */
986 public static function loadTranslation($key) {
987 // Text translations
988 static $adminLanguageStrings = null;
989
990 if(!$adminLanguageStrings) {
991 $adminLanguageFile = dirname(__FILE__) . "/language/en-GB/gptranslate.ini";
992 if(file_exists($adminLanguageFile)) {
993 $adminLanguageStrings = parse_ini_file($adminLanguageFile, false, INI_SCANNER_NORMAL);
994 }
995 }
996
997 if(array_key_exists($key, $adminLanguageStrings)) {
998 return $adminLanguageStrings[$key];
999 }
1000
1001 return $key;
1002 }
1003
1004 /**
1005 * Save translation record
1006 *
1007 * @access public
1008 */
1009 public function save_record() {
1010 global $wpdb;
1011
1012 // Retrieve and sanitize basic inputs
1013 $id = isset ( $_POST ['id'] ) ? intval ( $_POST ['id'] ) : 0;
1014 $formAction = isset ( $_POST ['action'] ) ? sanitize_key ( $_POST ['action'] ) : '';
1015
1016 if ( !isset($_POST['_gptranslate_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_gptranslate_nonce'])), 'gptranslate_save_record_action') ) {
1017 wp_die(esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_SECURITY_ERROR')), 'gptranslate');
1018 }
1019
1020 // Handle cancel action
1021 if ($formAction === 'cancel_gptranslate_record') {
1022 wp_redirect ( admin_url ( 'admin.php?page=gptranslate' ) );
1023 exit ();
1024 }
1025
1026 // Sanitize pagelink
1027 $pagelink = isset ( $_POST ['pagelink'] ) ? sanitize_text_field ( wp_unslash ( $_POST ['pagelink'] ) ) : '';
1028
1029 // Sanitize translated_alias
1030 $translatedAlias = isset ( $_POST ['translated_alias'] ) ? sanitize_text_field ( wp_unslash ( $_POST ['translated_alias'] ) ) : '';
1031
1032 // Process and sanitize translations JSON
1033 $raw_translations = filter_input( INPUT_POST, 'translations_json', FILTER_UNSAFE_RAW );
1034 $raw_translations = is_string($raw_translations) ? $raw_translations : '[]';
1035
1036 $decoded_translations = json_decode ( $raw_translations, true );
1037 if (! is_array ( $decoded_translations )) {
1038 wp_die ( esc_html($this->loadTranslations('PLG_GPTRANSLATE_INVALID_JSON_TRANSLATIONS')), esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_ERROR')), [
1039 'response' => 400
1040 ] );
1041 }
1042 $clean_translations = $decoded_translations;
1043 $sanitized_translations_json = wp_json_encode ( $clean_translations );
1044
1045 // Process and sanitize alternative translations JSON
1046 $raw_alt = filter_input( INPUT_POST, 'alt_translations_json', FILTER_UNSAFE_RAW );
1047 $raw_alt = is_string($raw_translations) ? $raw_alt : '[]';
1048
1049 $decoded_alt = json_decode ( $raw_alt, true );
1050 if (! is_array ( $decoded_alt )) {
1051 wp_die ( esc_html($this->loadTranslations('PLG_GPTRANSLATE_INVALID_JSON_ALTTRANSLATIONS')), esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_ERROR')), [
1052 'response' => 400
1053 ] );
1054 }
1055 $clean_alt = $decoded_alt;
1056 $sanitized_alt_json = wp_json_encode ( $clean_alt );
1057
1058 // Update database record
1059 $wpdb->update ( $this->table_name, [ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1060 'pagelink' => $pagelink,
1061 'translated_alias' => $translatedAlias,
1062 'translations' => $sanitized_translations_json,
1063 'alt_translations' => $sanitized_alt_json
1064 ], [
1065 'id' => $id
1066 ], [
1067 '%s',
1068 '%s',
1069 '%s'
1070 ], [
1071 '%d'
1072 ] );
1073
1074 // Redirect based on action
1075 if ($formAction === 'save_gptranslate_record_and_close') {
1076 wp_redirect ( admin_url ( 'admin.php?page=gptranslate' ) );
1077 } else {
1078 $url = admin_url( 'admin.php?page=gptranslate&action=edit&edit=' . $id );
1079 $url = wp_nonce_url( $url, 'gptranslate_edit_' . $id, '_gptranslate_nonce' );
1080 wp_redirect( html_entity_decode( $url ) );
1081 }
1082 exit ();
1083 }
1084
1085 public function gptranslate_handle_deletion() {
1086 // 1) Verify nonce
1087 $id = isset( $_GET['translation_id'] ) ? intval( $_GET['translation_id'] ) : 0;
1088 $nonce = isset( $_GET['_gptranslate_nonce'] ) ? wp_unslash( $_GET['_gptranslate_nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1089
1090 if ( ! wp_verify_nonce( $nonce, 'gptranslate_delete_' . $id ) ) {
1091 wp_die( esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_SECURITY_ERROR')), 'Error', [ 'response' => 403 ] );
1092 }
1093
1094 // 2) Delete the row
1095 global $wpdb;
1096 $table = $wpdb->prefix . 'gptranslate';
1097 $deleted = $wpdb->delete( $table, [ 'id' => $id ], [ '%d' ] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1098
1099 // 3) Redirect back with a message
1100 $redirect_url = add_query_arg(
1101 [
1102 'page' => 'gptranslate',
1103 'deleted' => $deleted ? '1' : '0',
1104 ],
1105 admin_url( 'admin.php' )
1106 );
1107 wp_redirect( $redirect_url );
1108 exit;
1109 }
1110
1111
1112 /**
1113 * Add main app frontend script
1114 *
1115 * @access public
1116 */
1117 public function enqueue_frontend_scripts() {
1118 add_filter('script_loader_tag', function($tag, $handle) {
1119 if ($handle === 'gptranslate-responsivevoice') {
1120 return str_replace('<script ', '<script defer ', $tag);
1121 }
1122
1123 if ($handle === 'gptranslate-jsonrepair') {
1124 return str_replace('<script ', '<script type="module" ', $tag);
1125 }
1126
1127 if ($handle === 'gptranslate-bstoast') {
1128 return str_replace('<script ', '<script type="module" ', $tag);
1129 }
1130 if ($handle === 'gptranslate-main') {
1131 // 1) prendi i valori raw
1132 $raw_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
1133 $raw_host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
1134
1135 // 2) unslash WP
1136 $unslashed_uri = wp_unslash ( $raw_uri );
1137 $unslashed_host = wp_unslash ( $raw_host );
1138
1139 // 3) sanitizza URI come URL
1140 $orig_url = esc_url_raw ( $unslashed_uri );
1141 // - accetta path e query, rimuove caratteri pericolosi
1142
1143 // 4) sanitizza host
1144 // a) rimuovi tag e control chars
1145 $host_clean = sanitize_text_field ( $unslashed_host );
1146 // b) mantieni solo [a-z0-9.-] per sicurezza
1147 $orig_domain = preg_replace ( '/[^a-z0-9.-]/i', '', $host_clean );
1148
1149 // 5) infine escape per attributo HTML
1150 return str_replace ( '<script ', sprintf ( '<script type="module" data-gt-orig-url="%s" data-gt-orig-domain="%s" data-gt-widget-id="1" ', esc_attr ( $orig_url ), esc_attr ( $orig_domain ) ), $tag );
1151 }
1152
1153 return $tag;
1154 }, 10, 2);
1155
1156 $settings = get_option("gptranslate_options");
1157
1158 // Excluded languages check
1159 $excludedLangs = isset($settings['excluded_languages']) ? (array) $settings['excluded_languages'] : [];
1160 if (!empty($excludedLangs) && defined ( 'GPTRANSLATE_CURRENT_LANG' )) {
1161 if (in_array(GPTRANSLATE_CURRENT_LANG, $excludedLangs, true)) {
1162 return;
1163 }
1164 }
1165
1166 // Move default language to the first one in the list
1167 if($settings ['default_language_first']) {
1168 if(!isset($settings ['languages'])) {
1169 $settings ['languages'] = array_map ( 'strtolower', [
1170 'AF',
1171 'SQ',
1172 'AM',
1173 'AR',
1174 'HY',
1175 'AZ',
1176 'EU',
1177 'BE',
1178 'BN',
1179 'BS',
1180 'BG',
1181 'CA',
1182 'CEB',
1183 'NY',
1184 'ZH',
1185 'CO',
1186 'HR',
1187 'CS',
1188 'DA',
1189 'NL',
1190 'EN',
1191 'EO',
1192 'ET',
1193 'TL',
1194 'FI',
1195 'FR',
1196 'FY',
1197 'GL',
1198 'KA',
1199 'DE',
1200 'EL',
1201 'GU',
1202 'HT',
1203 'HA',
1204 'HAW',
1205 'IW',
1206 'HI',
1207 'HMN',
1208 'HU',
1209 'IS',
1210 'IG',
1211 'ID',
1212 'GA',
1213 'IT',
1214 'JA',
1215 'JW',
1216 'KN',
1217 'KK',
1218 'KM',
1219 'KO',
1220 'KU',
1221 'KY',
1222 'LO',
1223 'LA',
1224 'LV',
1225 'LT',
1226 'LB',
1227 'MK',
1228 'MG',
1229 'MS',
1230 'ML',
1231 'MT',
1232 'MI',
1233 'MR',
1234 'MN',
1235 'MY',
1236 'NE',
1237 'NO',
1238 'PS',
1239 'FA',
1240 'PL',
1241 'PT',
1242 'PA',
1243 'RO',
1244 'RU',
1245 'SM',
1246 'GD',
1247 'SR',
1248 'ST',
1249 'SN',
1250 'SD',
1251 'SI',
1252 'SK',
1253 'SL',
1254 'SO',
1255 'ES',
1256 'SU',
1257 'SW',
1258 'SV',
1259 'TG',
1260 'TA',
1261 'TE',
1262 'TH',
1263 'TR',
1264 'UK',
1265 'UR',
1266 'UZ',
1267 'VI',
1268 'CY',
1269 'XH',
1270 'YI',
1271 'YO',
1272 'ZU',
1273 'ZT'
1274 ] );
1275 }
1276 $defaultLanguageKeyIndex = array_search($settings ['language'], $settings ['languages']);
1277 if ($defaultLanguageKeyIndex !== false) {
1278 // Remove the 'de' element from its current position
1279 $defaultLanguage = $settings ['languages'][$defaultLanguageKeyIndex];
1280 unset($settings ['languages'][$defaultLanguageKeyIndex]);
1281
1282 // Re-index the array to maintain numerical indexes
1283 $settings ['languages'] = array_values($settings ['languages']);
1284
1285 // Add 'de' to the beginning of the array
1286 array_unshift($settings ['languages'], $defaultLanguage);
1287 }
1288 }
1289
1290 // build alt_flags array
1291 $alt_flags = array ();
1292 $raw_alt_flags = isset($settings ['alt_flags']) ? $settings ['alt_flags'] : [];
1293 foreach ( $raw_alt_flags as $country ) {
1294 if ($country == 'usa' || $country == 'canada' || $country == 'ireland')
1295 $alt_flags ['en'] = $country;
1296 elseif ($country == 'brazil')
1297 $alt_flags ['pt'] = $country;
1298 elseif ($country == 'mexico' or $country == 'argentina' or $country == 'colombia')
1299 $alt_flags ['es'] = $country;
1300 elseif ($country == 'quebec')
1301 $alt_flags ['fr'] = $country;
1302 elseif ($country == 'taiwan')
1303 $alt_flags ['zh'] = $country;
1304 elseif ($country == 'hongkong')
1305 $alt_flags ['zt'] = $country;
1306 }
1307
1308 // Build float position variables
1309 $float_position = $settings ['float_position'];
1310 if($float_position != 'inline'){
1311 list ( $switcher_vertical_position, $switcher_horizontal_position ) = explode ( '-', $float_position );
1312 } else {
1313 list ( $switcher_vertical_position, $switcher_horizontal_position ) = ['inline', 'inline'];
1314 }
1315
1316 // Set local flags path
1317 $flagsPath = trailingslashit(plugins_url('flags', __FILE__));
1318
1319 // Ensure a default array value
1320 if(!isset($settings['realtime_translations_retrigger_events'])) {
1321 $settings['realtime_translations_retrigger_events'] = ['click'];
1322 $settings['realtime_translations_retrigger_events_delay'] = 200;
1323 }
1324 if(!isset ( $settings ['realtime_translations_retrigger_force_google'] )) {
1325 $settings ['realtime_translations_retrigger_force_google'] = 0;
1326 }
1327 if(!isset ( $settings ['translate_srcimages'] )) {
1328 $settings ['translate_srcimages'] = 0;
1329 }
1330 if(!isset ( $settings ['css_selector_realtime_translations_retrigger'] )) {
1331 $settings ['css_selector_realtime_translations_retrigger'] = '';
1332 }
1333 if(!isset($settings ['serverside_translations_language_switching_mode'])) {
1334 $settings ['serverside_translations_language_switching_mode'] = 'url';
1335 }
1336 if(!isset($settings ['excluded_alias_slugs'])) {
1337 $settings ['excluded_alias_slugs'] = '';
1338 }
1339 if(!isset($settings ['popup_shadow'])) {
1340 $settings ['popup_shadow'] = 1;
1341 }
1342 if(!isset($settings ['wrap_excluded_words'])) {
1343 $settings ['wrap_excluded_words'] = 1;
1344 }
1345
1346 wp_register_script('gptranslate-main-inline', '', [], $this->version, true);
1347 wp_enqueue_script('gptranslate-main-inline');
1348
1349 // Example: $settings is an array like in Joomla
1350 $base64Encode = 'base' . 64 . '_encode';
1351 $key = $settings['chatgpt_apikey'];
1352 $key = strrev($key);
1353 $secret = 'gptranslate';
1354 $out = '';
1355 for ($i = 0; $i < strlen($key); $i++) {
1356 $out .= chr(ord($key[$i]) ^ ord($secret[$i % strlen($secret)]));
1357 }
1358 $encoded = $base64Encode($out);
1359
1360 $ajaxEndpoint = esc_url_raw(rest_url('gptranslate/v1/request'));
1361
1362 $inlineScript = 'var gptServerSideLink = "' . esc_js($ajaxEndpoint) . '";
1363 var gptApiKey = "' . esc_js(hash( 'sha256', get_site_url() )) . '";
1364 var gptLiveSite = "' . esc_js(get_site_url()) . '";
1365 var gptStorage = ' . ($settings['storage_type'] === 'session' ? 'window.sessionStorage' : 'window.localStorage') . ';
1366 var gptMaxTranslationsPerRequest = ' . (int)$settings['max_translations_per_request'] . ';
1367 var maxCharactersPerRequest = ' . (int)$settings['max_characters_per_request'] . ';
1368 var gptRewriteLanguageUrl = ' . (int)$settings['rewrite_language_url'] . ';
1369 var gptOmitPrefixOriginalLanguage = ' . (isset($settings['omit_prefix_original_language']) ? (int)$settings['omit_prefix_original_language'] : 0) . ';
1370 var gptExcludedAliasSlugs = "' . esc_js(rtrim($settings['excluded_alias_slugs'], ', ')) . '";
1371 var gptRewriteLanguageAlias = ' . (int)$settings['rewrite_language_alias'] . ';
1372 var gptRewriteLanguageAliasOriginalLanguage = ' . (int)$settings['rewrite_language_alias_original_language'] . ';
1373 var gptAutoSetLanguageDirection = ' . (int)$settings['auto_set_language_direction'] . ';
1374 var gptServersideTranslations = ' . (int)$settings['serverside_translations'] . ';
1375 var gptServersideTranslationsLanguageSwitchingMode = "' . $settings ['serverside_translations_language_switching_mode'] . '";
1376 var gptRewritePageLinks = ' . (int)$settings['rewrite_page_links'] . ';
1377 var gptTranslateMetadata = ' . (int)$settings['translate_metadata'] . ';
1378 var gptTranslatePlaceholders = ' . (int)$settings['translate_placeholders'] . ';
1379 var gptTranslateAltImages = ' . (int)$settings['translate_altimages'] . ';
1380 var chatgptClassesAltimagesExcluded = "' . esc_js(str_ireplace('"', '', $settings['css_selector_classes_translate_altimages_excluded'])) . '";
1381 var gptTranslateSrcImages = ' . (int)$settings['translate_srcimages'] . ';
1382 var gptTranslateTitles = ' . (int)$settings['translate_titles'] . ';
1383 var gptTranslateValues = ' . (int)$settings['translate_values'] . ';
1384 var gptMetadataChosenEngine = ' . (isset($settings['metadata_chosen_engine']) ? (int)$settings['metadata_chosen_engine'] : 0) . ';
1385 var chatgptMetadataWordsLeafnodesExcluded = "' . esc_js(rtrim($settings['metadata_words_leafnodes_excluded'], ', ')) . '";
1386 var gptSetHtmlLang = ' . (int)$settings['set_html_lang'] . ';
1387 var gptAddCanonical = ' . (int)$settings['add_canonical'] . ';
1388 var gptAddAlternate = ' . (int)$settings['add_alternate'] . ';
1389 var gptSubfolderInstallation = ' . (int)$settings['subfolder_installation'] . ';
1390 var gptIgnoreQuerystring = ' . (int)$settings['ignore_querystring'] . ';
1391 var gptChatgptGtranslateRequestDelay = ' . (int)$settings['chatgpt_gtranslate_request_delay'] . ';
1392 var gptInitialTranslationDelay = ' . (int)$settings['initial_translation_delay'] . ';
1393 var gptCssSelectorRealtimeTranslationsRetrigger = "' . trim(str_ireplace('"', '', $settings ['css_selector_realtime_translations_retrigger'])) . '";
1394 var chatgptApiKey = "' . esc_js($encoded) . '";
1395 var chatgptApiModel = "' . esc_js($settings['chatgpt_model']) . '";
1396 var chatgptRequestMessage = "' . str_ireplace("\'", "'", esc_js(str_ireplace(['"' , "\r", "\n"], ['' , ' ', ' '], $settings['chatgpt_request_message']))) . '";
1397 var chatgptRequestConversationMode = "' . esc_js($settings['chatgpt_request_conversation_mode']) . '";
1398 var chatgptEnableReader = ' . (int)$settings['enable_reader'] . ';
1399 var chatgptResponsivevoiceLanguageGender = "' . esc_js($settings['responsivevoice_language_gender']) . '";
1400 var chatgptResponsivevoiceApiKey = "' . esc_js($settings['responsivevoice_apikey']) . '";
1401 var chatgptResponsivevoiceReadingMode = "' . esc_js($settings['proxy_responsive_reading_mode']) . '";
1402 var chatgptChunksize = "' . esc_js($settings['chunksize']) . '";
1403 var chatgptCssSelectorLeafnodesExcluded = "' . esc_js(trim(preg_replace('/,+/', ',', str_ireplace(["\r", "\n"], ",", $settings['css_selector_leafnodes_excluded'])), ',')) . '";
1404 var chatgptWordsLeafnodesExcluded = "' . esc_js(rtrim($settings['words_leafnodes_excluded'], ', ')) . '";
1405 var chatgptWordsMinLength = "' . (int)$settings['words_min_length'] . '";
1406 var chatgptFlattenInnerFormattingTags = ' . (int)($settings['flatten_inner_formatting_tags'] ?? 0) . ';
1407 var chatgptFlattenInnerFormattingTagsToRemove = "' . esc_js(str_ireplace('"', '', trim(trim(preg_replace('/,+/', ',', str_ireplace(["\r", "\n"], ",", ($settings['flatten_inner_formatting_tags_to_remove'] ?? 'strong,em,u,b,i,span'))), ',')))) . '";
1408 var chatgptWrapExcludedWords = ' . (int)$settings['wrap_excluded_words'] . ';
1409 var chatgptMainpageSelector = "' . esc_js($settings['mainpage_selector']) . '";
1410 var chatgptElementsToExcludeCustom = "' . esc_js(trim($settings['elements_toexclude_custom'])) . '";
1411 var chatgptPopupFontsize = ' . (int)$settings['popup_fontsize'] . ';
1412 var chatgptDraggableWidget = ' . (int)$settings['draggable_widget'] . ';
1413 var gptAudioVolume = ' . (float)$settings['responsivevoice_volume_tts'] . ';
1414 var gptVoiceSpeed = "' . esc_js($settings['responsivevoice_voice_speed']) . '";
1415 var gTranslateEngine = ' . (($settings['google_translate_engine'] == 1 || !trim($settings['chatgpt_apikey'])) ? 1 : 0) . ';
1416 var gptRealtimeTranslationsRetriggerForceGoogle = ' . (int)$settings['realtime_translations_retrigger_force_google'] . ';
1417 var translateEngineValue = "' . esc_js($settings['google_translate_engine']) . '";
1418 var gptPopupShadow = ' . (int)$settings ['popup_shadow'] . ';
1419 var gptDisableControl = ' . (int)$settings['disable_control'] . ';
1420 var gptThemeUri = "' . get_stylesheet_directory_uri() . '";
1421 var gptVersionNumeric = ' . 0 . ';
1422 var svgIconArrow = \'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 285 285"><path d="M282 76.5l-14.2-14.3a9 9 0 0 0-13.1 0L142.5 174.4 30.3 62.2a9 9 0 0 0-13.2 0L3 76.5a9 9 0 0 0 0 13.1l133 133a9 9 0 0 0 13.1 0l133-133a9 9 0 0 0 0-13z" style="fill:%23' . ltrim($settings['widget_text_color'], '#') . '"/></svg>\';';
1423
1424 // Inject it AFTER gptranslate-main-inline
1425 wp_add_inline_script('gptranslate-main-inline', $inlineScript);
1426
1427 wp_register_script('gptranslate-js-specs', '', [], $this->version, true);
1428 wp_enqueue_script('gptranslate-js-specs');
1429 wp_add_inline_script('gptranslate-js-specs', 'window.gptranslateSettings = window.gptranslateSettings || {};
1430 window.gptranslateSettings["1"] = {
1431 "default_language": "' . $settings['language'] . '",
1432 "languages": ' . json_encode($settings['languages']) . ',
1433 "wrapper_selector": "' . $settings['wrapper_selector'] . '",
1434 "float_switcher_open_direction": "' . $settings['float_switcher_open_direction'] . '",
1435 "detect_browser_language": ' . (int)$settings['detect_browser_language'] . ',
1436 "detect_current_language": ' . (int)$settings['detect_current_language'] . ',
1437 "detect_default_language": ' . (int)$settings['detect_default_language'] . ',
1438 "autotranslate_detected_language": ' . (int)$settings['autotranslate_detected_language'] . ',
1439 "always_detect_autotranslated_language": ' . (int)$settings['always_detect_autotranslated_language'] . ',
1440 "widget_text_color": "' . $settings['widget_text_color'] . '",
1441 "show_language_titles": ' . (int)$settings['show_language_titles'] . ',
1442 "enable_dropdown": ' . (int)$settings['enable_dropdown'] . ',
1443 "equal_widths": ' . (int)$settings['equal_widths'] . ',
1444 "reader_button_position": "' . $settings['reader_button_position'] . '",
1445 "custom_css": "' . addslashes(preg_replace('/\s+/', ' ', str_replace(["\r", "\n"], ' ', (string) $settings['custom_css']))) . '",
1446 "alt_flags": ' . json_encode($alt_flags). ',
1447 "realtime_translations_retrigger_events": ' . json_encode($settings['realtime_translations_retrigger_events']) . ',
1448 "realtime_translations_retrigger_events_delay": ' . (int)$settings['realtime_translations_retrigger_events_delay'] . ',
1449 "switcher_horizontal_position": "' . $switcher_horizontal_position . '",
1450 "switcher_vertical_position": "' . $switcher_vertical_position . '",
1451 "flags_location": "' . esc_js($flagsPath) . '",
1452 "flag_loading": "' . $settings['flag_loading'] . '",
1453 "flag_style": "' . $settings['flag_style'] . '",
1454 "widget_max_height": ' . (int)$settings['widget_max_height'] . '
1455 };');
1456
1457 $languageStringsScript = '';
1458
1459 // Generic translations
1460 $labels = [
1461 'TRANSLATING',
1462 'TRANSLATING_WAIT',
1463 'TRANSLATING_COMPLETE',
1464 'READING_INPROGRESS',
1465 'READING_END',
1466 'READING_EMPTY'
1467 ];
1468
1469 foreach ($labels as $label) {
1470 $languageStringsScript .= 'var PLG_GPTRANSLATE_' . $label . '="' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_' . $label)) . '";' . PHP_EOL;
1471 }
1472
1473 $languages = [
1474 'AF', 'SQ', 'AM', 'AR', 'HY', 'AZ', 'EU', 'BE', 'BN', 'BS', 'BG', 'CA', 'CEB', 'NY',
1475 'ZH', 'CO', 'HR', 'CS', 'DA', 'NL', 'EN', 'EO', 'ET', 'TL', 'FI', 'FR',
1476 'FY', 'GL', 'KA', 'DE', 'EL', 'GU', 'HT', 'HA', 'HAW', 'IW', 'HI', 'HMN', 'HU',
1477 'IS', 'IG', 'ID', 'GA', 'IT', 'JA', 'JW', 'KN', 'KK', 'KM', 'KO', 'KU', 'KY', 'LO',
1478 'LA', 'LV', 'LT', 'LB', 'MK', 'MG', 'MS', 'ML', 'MT', 'MI', 'MR', 'MN', 'MY', 'NE',
1479 'NO', 'PS', 'FA', 'PL', 'PT', 'PA', 'RO', 'RU', 'SM', 'GD', 'SR', 'ST', 'SN', 'SD',
1480 'SI', 'SK', 'SL', 'SO', 'ES', 'SU', 'SW', 'SV', 'TG', 'TA', 'TE', 'TH', 'TR', 'UK',
1481 'UR', 'UZ', 'VI', 'CY', 'XH', 'YI', 'YO', 'ZU', 'ZT'
1482 ];
1483
1484
1485 foreach ($languages as $lang) {
1486 $languageStringsScript .= 'var PLG_GPTRANSLATE_LANGUAGE_NAME_' . $lang . '="' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . $lang)) . '";' . PHP_EOL;
1487 }
1488
1489 wp_register_script('gptranslate-js-language-strings', '', [], $this->version, true);
1490 wp_enqueue_script('gptranslate-js-language-strings');
1491 wp_add_inline_script('gptranslate-js-language-strings', $languageStringsScript);
1492
1493
1494 // Dictionary
1495 $words_leafnodes_excluded_bylanguage_repeatable = $settings['words_leafnodes_excluded_bylanguage_repeatable'];
1496 if ($words_leafnodes_excluded_bylanguage_repeatable) {
1497 if (is_string($words_leafnodes_excluded_bylanguage_repeatable)) {
1498 $words_leafnodes_excluded_bylanguage_repeatable = json_decode($words_leafnodes_excluded_bylanguage_repeatable, true);
1499 }
1500
1501 // Ora convertiamo l'array normale nel formato con chiavi tipo words_leafnodes_excluded_bylanguage_repeatable0, 1, 2...
1502 $formatted = [];
1503 foreach ($words_leafnodes_excluded_bylanguage_repeatable as $index => $row) {
1504 $formatted["words_leafnodes_excluded_bylanguage_repeatable{$index}"] = [
1505 'words_leafnodes_excluded_bylanguage' => $row['word'] ?? '',
1506 'words_leafnodes_excluded_bylanguage_language_original' => $row['langOriginal'] ?? '*',
1507 'words_leafnodes_excluded_bylanguage_language_target' => $row['langTranslated'] ?? '*',
1508 'words_leafnodes_excluded_bylanguage_translation' => $row['optionalTranslation'] ?? ''
1509 ];
1510 }
1511
1512 // Correctly formatted JSON encode
1513 $formatted_json = json_encode($formatted, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
1514
1515 // Inietto i dati PRIMA che venga eseguito gptranslate.js
1516 wp_register_script('gptranslate-js-word-leafones-excluded-language', '', [], $this->version, true);
1517 wp_enqueue_script('gptranslate-js-word-leafones-excluded-language');
1518 wp_add_inline_script(
1519 'gptranslate-js-word-leafones-excluded-language',
1520 'var chatgptWordsLeafnodesExcludedByLanguage = ' . $formatted_json . ';'
1521 );
1522 }
1523
1524 // Local or remote script
1525 if($settings['proxy_responsive_loading_script'] == 1) {
1526 wp_enqueue_script('gptranslate-responsivevoice', plugin_dir_url(__FILE__) . 'assets/js/responsivevoice.js', [], $this->version, true);
1527 } else {
1528 wp_enqueue_script('gptranslate-responsivevoice', 'https://code.responsivevoice.org/responsivevoice.js?key=' . $settings ['responsivevoice_apikey'], [], $this->version, true);
1529 }
1530
1531 wp_enqueue_script('gptranslate-jsonrepair', plugin_dir_url(__FILE__) . 'assets/js/jsonrepair/index.js', [], $this->version, true);
1532 wp_enqueue_script('gptranslate-main', plugin_dir_url(__FILE__) . 'assets/js/gptranslate.js', [], $this->version, true);
1533
1534 // Enqueue Bootstrap component
1535 if(!$settings['disable_bootstrap_css']) {
1536 wp_enqueue_script('gptranslate-bstoast', plugin_dir_url(__FILE__) . 'assets/js/toast.min.js', [], $this->version, true);
1537 wp_enqueue_style(
1538 'bootstrap-css',
1539 plugin_dir_url(__FILE__) . 'assets/css/bootstrap.min.css',
1540 [],
1541 '5.3.2'
1542 );
1543 } else {
1544 // Add custom CSS only to replicate the toast and progress styles of Bootstrap
1545 wp_register_style('gptranslate-bootstrap-style', false, [], $this->version);
1546 wp_enqueue_style('gptranslate-bootstrap-style');
1547 wp_add_inline_style('gptranslate-bootstrap-style', '
1548 .progress-gptranslate,.progress-gptranslate-reading{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:0.25rem}
1549 .progress-gptranslate .toast,.progress-gptranslate-reading .toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border:1px solid rgba(0,0,0,0.1);box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15);border-radius:0.25rem}
1550 .progress-gptranslate .toast.show,.progress-gptranslate-reading .toast.show{display:block}
1551 .progress-gptranslate .toast-header,.progress-gptranslate-reading .toast-header{display:flex;align-items:center;padding:0.5rem 0.75rem;color:#6c757d;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,0.05);border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}
1552 .progress-gptranslate .toast-body,.progress-gptranslate-reading .toast-body{padding:0.75rem}
1553 .progress-gptranslate .progress-bar,.progress-gptranslate-reading .progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width 0.6s ease}
1554 .progress-gptranslate .progress-bar-striped,.progress-gptranslate-reading .progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}
1555 .progress-gptranslate .progress-bar-animated,.progress-gptranslate-reading .progress-bar-animated{animation:progress-bar-stripes-gptranslate 1s linear infinite}
1556 @keyframes progress-bar-stripes-gptranslate{0%{background-position-x:1rem}}
1557 .progress-gptranslate .btn-close,.progress-gptranslate-reading .btn-close{box-sizing:content-box;width:1em;height:1em;padding:0.25em 0.25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 16 16\' fill=\'%23000\'%3e%3cpath d=\'M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z\'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:0.25rem;opacity:0.5;cursor:pointer}
1558 .progress-gptranslate .btn-close:hover,.progress-gptranslate-reading .btn-close:hover{color:#000;text-decoration:none;opacity:0.75}
1559 .progress-gptranslate .me-auto,.progress-gptranslate-reading .me-auto{margin-right:auto!important}
1560 html[dir="rtl"] .progress-gptranslate .me-auto,html[dir="rtl"] .progress-gptranslate-reading .me-auto{margin-right:unset !important;margin-left:auto!important}
1561 .progress-gptranslate .text-muted,.progress-gptranslate-reading .text-muted{color:#6c757d!important}
1562 .progress-gptranslate .bg-primary,.progress-gptranslate-reading .bg-primary{background-color:#0d6efd!important}
1563 .progress-gptranslate .bg-secondary,.progress-gptranslate-reading .bg-secondary{background-color:#6c757d!important}
1564 .progress-gptranslate .bg-success,.progress-gptranslate-reading .bg-success{background-color:#198754!important}
1565 .progress-gptranslate .bg-danger,.progress-gptranslate-reading .bg-danger{background-color:#dc3545!important}
1566 .progress-gptranslate .bg-warning,.progress-gptranslate-reading .bg-warning{background-color:#ffc107!important;color:#000!important}
1567 .progress-gptranslate .bg-info,.progress-gptranslate-reading .bg-info{background-color:#0dcaf0!important;color:#000!important}
1568 .progress-gptranslate .bg-light,.progress-gptranslate-reading .bg-light{background-color:#f8f9fa!important;color:#000!important}
1569 .progress-gptranslate .bg-dark,.progress-gptranslate-reading .bg-dark{background-color:#212529!important}
1570 ');
1571
1572 // Closer for the toast element
1573 wp_register_script('gptranslate-toast-dismiss', false, [], $this->version, true);
1574 wp_enqueue_script('gptranslate-toast-dismiss');
1575 wp_add_inline_script('gptranslate-toast-dismiss', '
1576 document.addEventListener("DOMContentLoaded", function() {
1577 document.addEventListener("click", function(e) {
1578 if (e.target.matches(".btn-close[data-bs-dismiss=\"toast\"]") ||
1579 e.target.closest(".btn-close[data-bs-dismiss=\"toast\"]")) {
1580 const btnClose = e.target.matches(".btn-close") ? e.target : e.target.closest(".btn-close");
1581 const toast = btnClose.closest(".toast");
1582 if (toast) {
1583 toast.classList.remove("show");
1584 const progressContainer = toast.closest(".progress-gptranslate, .progress-gptranslate-reading");
1585 if (progressContainer) {
1586 progressContainer.remove();
1587 }
1588 }
1589 }
1590 });
1591 });
1592 ');
1593 }
1594
1595 // Registra un handle CSS vuoto se necessario
1596 wp_register_style('gptranslate-dynamic-css', false, [], $this->version);
1597 wp_enqueue_style('gptranslate-dynamic-css');
1598
1599 // Prepara lo stile dinamico
1600 $dynamic_css = 'div.gpt_float_switcher .gt-selected, div.gpt_float_switcher, div.gpt_options { background-color: ' . (!empty($settings['widget_background_color']) ? esc_attr($settings['widget_background_color']) : '#FFFFFF') . '; }' .
1601 'div.gpt_float_switcher, div.gpt_float_switcher div.gt-selected div.gpt-current-lang, div.gpt_float_switcher div.gpt_options a { color: ' . (!empty($settings['widget_text_color']) ? esc_attr($settings['widget_text_color']) : '#000000') . '; font-size: ' . intval($settings['popup_fontsize']) . 'px; }' .
1602 'div.gpt_float_switcher { border-radius: ' . intval($settings['popup_border_radius']) . 'px; }' .
1603 'div.gpt_float_switcher img, svg.svg-inline--fa { box-sizing: border-box; width: ' . intval($settings['popup_iconsize']) . 'px; }';
1604
1605 if (!empty($settings['disable_toast_popups']) && $settings['disable_toast_popups'] == 1) {
1606 $dynamic_css .= '.progress.progress-gptranslate,.progress.progress-gptranslate-reading{ display: none !important; }';
1607 }
1608
1609 // Opacity del background widget (solo se diverso da 1.0)
1610 if (!empty($settings['widget_opacity']) && floatval($settings['widget_opacity']) != 1.0) {
1611 $bgColor = !empty($settings['widget_background_color']) ? esc_attr($settings['widget_background_color']) : '#FFFFFF';
1612 $opacity = floatval($settings['widget_opacity']);
1613 $alphaHex = str_pad(dechex(round($opacity * 255)), 2, '0', STR_PAD_LEFT);
1614 $bgColorWithAlpha = $bgColor . strtoupper($alphaHex);
1615
1616 $dynamic_css .= 'div.gpt_float_switcher .gt-selected, div.gpt_float_switcher, div.gpt_options { background-color: ' . $bgColorWithAlpha . ' !important; }';
1617 }
1618
1619 // Inietta il CSS inline
1620 wp_add_inline_style('gptranslate-dynamic-css', $dynamic_css);
1621
1622 // --- Load theme RTL stylesheet if available ---
1623 if ( ! empty( $settings['auto_set_language_direction'] ) && is_rtl() ) {
1624 // Common file names
1625 $rtl_candidates = array(
1626 get_stylesheet_directory() . '/style-rtl.css',
1627 get_stylesheet_directory() . '/rtl.css'
1628 );
1629
1630 $rtl_file = '';
1631 foreach ( $rtl_candidates as $candidate ) {
1632 if ( file_exists( $candidate ) ) {
1633 $rtl_file = $candidate;
1634 break;
1635 }
1636 }
1637
1638 if ( $rtl_file ) {
1639 $rtl_uri = str_replace(
1640 get_stylesheet_directory(),
1641 get_stylesheet_directory_uri(),
1642 $rtl_file
1643 );
1644
1645 wp_enqueue_style(
1646 'theme-rtl',
1647 $rtl_uri,
1648 array(),
1649 filemtime( $rtl_file )
1650 );
1651 }
1652 }
1653 }
1654 }
1655
1656 // Force WordPress.org update check on plugin activation
1657 register_activation_hook( __FILE__, function() {
1658 if ( function_exists('wp_update_plugins') ) {
1659 wp_update_plugins();
1660 }
1661 });
1662
1663 // Schedule a daily update check
1664 add_action( 'gptranslate_daily_update_check', function() {
1665 if ( function_exists('wp_update_plugins') ) {
1666 wp_update_plugins();
1667 }
1668 });
1669
1670 if ( ! wp_next_scheduled( 'gptranslate_daily_update_check' ) ) {
1671 wp_schedule_event( time(), 'daily', 'gptranslate_daily_update_check' );
1672 }
1673
1674 // 🧹 Cleanup scheduled event on deactivation
1675 register_deactivation_hook( __FILE__, function() {
1676 wp_clear_scheduled_hook( 'gptranslate_daily_update_check' );
1677 });
1678
1679 /**
1680 * Global function to add links to WP
1681 *
1682 * @access public
1683 */
1684 add_filter('plugin_action_links_' . plugin_basename(__FILE__), function($links) {
1685 $settings_link = '<a href="admin.php?page=gptranslate">' . esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_SETTINGS_MENU_TITLE')) . '</a>';
1686 array_unshift($links, $settings_link);
1687 return $links;
1688 });
1689
1690 /**
1691 * Add main admin scripts for example to manage records add/delete functions
1692 *
1693 * @access public
1694 */
1695 add_action('admin_enqueue_scripts', function() {
1696 if(isset($_GET['page']) && strpos(sanitize_key($_GET['page']), 'gptranslate') !== false) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1697 // Enqueue JS
1698 wp_enqueue_script ( 'gptranslate-js', plugin_dir_url ( __FILE__ ) . 'assets/js/admin.js', [ ], GPTranslate::$pluginVersion, true );
1699 wp_enqueue_script ( 'gptranslate-js-select2', plugin_dir_url ( __FILE__ ) . 'assets/js/select2.min.js', [ 'jquery' ], GPTranslate::$pluginVersion, true );
1700
1701 if(sanitize_key($_GET['page']) != 'gptranslate-settings' && !isset($_GET['action'])) {
1702 wp_enqueue_script ( 'crawler-js', plugin_dir_url ( __FILE__ ) . 'assets/js/crawler.js', [ ], GPTranslate::$pluginVersion, true );
1703 }
1704
1705 // Enqueue CSS
1706 wp_enqueue_style ( 'gptranslate-css', plugin_dir_url ( __FILE__ ) . 'assets/css/admin.css', [ ], GPTranslate::$pluginVersion );
1707 wp_enqueue_style ( 'gptranslate-css-select2', plugin_dir_url ( __FILE__ ) . 'assets/css/select2.min.css', [ ], GPTranslate::$pluginVersion );
1708
1709 if(sanitize_key($_GET['page']) != 'gptranslate-settings' && !isset($_GET['action'])) {
1710 wp_enqueue_style ( 'crawler-css', plugin_dir_url ( __FILE__ ) . 'assets/css/crawler.css', [ ], GPTranslate::$pluginVersion );
1711 }
1712
1713 wp_localize_script('gptranslate-js', 'gptranslate_vars', [
1714 'ajaxurl' => admin_url('admin-ajax.php'),
1715 'nonce' => wp_create_nonce('gptranslate_migrate_translations'),
1716 'deletenonce' => wp_create_nonce('gptranslate_delete_translations'),
1717 'gptApiKey' => hash( 'sha256', get_site_url() )
1718 ]);
1719 }
1720 });
1721
1722 add_action('wp_ajax_gptranslate_bulk_delete', function () {
1723 if (!current_user_can('manage_options') || !check_ajax_referer('gptranslate_delete_translations', '_wpnonce', false)) {
1724 wp_send_json_error(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1725 }
1726
1727 global $wpdb;
1728 $ids = isset($_POST['gptid']) ? array_map('intval', (array) $_POST['gptid']) : [];
1729
1730 if (empty($ids)) {
1731 wp_send_json_error('No records selected');
1732 }
1733
1734 $table = $wpdb->prefix . 'gptranslate';
1735 $in = implode(',', array_fill(0, count($ids), '%d'));
1736 $sql = "DELETE FROM {$table} WHERE id IN ($in)";
1737 $result = $wpdb->query($wpdb->prepare($sql, ...$ids)); // phpcs:ignore
1738
1739 if ($result === false) {
1740 wp_send_json_error('Database error');
1741 }
1742
1743 wp_send_json_success();
1744 });
1745
1746 // Handle Export CSV
1747 add_action('admin_post_gptranslate_export_translations_csv', function () {
1748 if (!current_user_can('manage_options') || !check_admin_referer('gptranslate_export_csv', 'gptranslate_export_csv_nonce')) {
1749 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1750 }
1751
1752 global $wpdb;
1753 $table = $wpdb->prefix . 'gptranslate';
1754
1755 $records = $wpdb->get_results("SELECT * FROM $table ORDER BY translate_date DESC", ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1756
1757 if (!$records) {
1758 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_NOTRANSLATIONS')));
1759 }
1760
1761 $localDate = get_date_from_gmt(gmdate('Y-m-d'));
1762 $fileDate = ( date_i18n('Y-m-d', strtotime($localDate)) );
1763
1764 header('Content-Type: text/csv');
1765 header('Content-Disposition: attachment; filename="gptranslate-translations-' . $fileDate . '.csv"');
1766
1767 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1768 $output = fopen('php://output', 'w');
1769
1770 // Intestazioni CSV
1771 fputcsv($output, array_keys($records[0]), ",", '"', "\\");
1772
1773 foreach ($records as $record) {
1774 fputcsv($output, $record, ",", '"', "\\");
1775 }
1776
1777 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1778 fclose($output);
1779 exit;
1780 });
1781
1782 // Handle Import CSV
1783 add_action('admin_post_gptranslate_import_translations_csv', function () {
1784 if (!current_user_can('manage_options') || !check_admin_referer('gptranslate_import_csv', 'gptranslate_import_csv_nonce')) {
1785 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1786 }
1787
1788 if (!isset($_FILES['import_file'], $_FILES['import_file']['error'], $_FILES['import_file']['tmp_name']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
1789 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UPLOAD_FAILED')));
1790 }
1791
1792 $tmp_name = $_FILES['import_file']['tmp_name']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1793 if (!is_uploaded_file($tmp_name)) {
1794 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_FAILED_UPLOADED_FILE')));
1795 }
1796
1797 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1798 $file = fopen($tmp_name, 'r');
1799 if (!$file) {
1800 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_FAILED_UPLOADED_FILE')));
1801 }
1802
1803 $headers = fgetcsv($file);
1804 if (!$headers) {
1805 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_INVALID_CSV_FORMAT')));
1806 }
1807
1808 global $wpdb;
1809 $table = $wpdb->prefix . 'gptranslate';
1810
1811 while (($row = fgetcsv($file)) !== false) {
1812 $countHeaders = count($headers);
1813 $countRow = count($row);
1814 // Invalid combine
1815 if($countHeaders != $countRow) {
1816 continue;
1817 }
1818 $record = array_combine($headers, $row);
1819 if (empty($record['pagelink'])) {
1820 continue; // skip if no primary key
1821 }
1822
1823 $pagelink = sanitize_text_field($record['pagelink']);
1824 $exists = $wpdb->get_var($wpdb->prepare(
1825 "SELECT id FROM $table WHERE pagelink = %s AND languageoriginal = %s AND languagetranslated = %s",
1826 $pagelink, $record['languageoriginal'], $record['languagetranslated']
1827 ));// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1828
1829 $data = [
1830 'translated_alias' => $record['translated_alias'],
1831 'translations' => $record['translations'],
1832 'alt_translations' => $record['alt_translations'],
1833 'languageoriginal' => sanitize_text_field($record['languageoriginal']),
1834 'languagetranslated' => sanitize_text_field($record['languagetranslated']),
1835 'published' => isset($record['published']) ? (int)$record['published'] : 1,
1836 'translate_date' => $record['translate_date'],
1837 'translation_engine' => sanitize_text_field($record['translation_engine']),
1838 ];
1839
1840 if ($exists) {
1841 $wpdb->update( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1842 $table,
1843 $data,
1844 ['pagelink' => $pagelink, 'languageoriginal' => sanitize_text_field($record['languageoriginal']), 'languagetranslated' => sanitize_text_field($record['languagetranslated'])],
1845 ['%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s'],
1846 ['%s','%s','%s']
1847 );
1848 } else {
1849 $data['pagelink'] = $pagelink;
1850 $wpdb->insert( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1851 $table,
1852 $data,
1853 ['%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s']
1854 );
1855 }
1856 }
1857
1858 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1859 fclose($file);
1860
1861 wp_redirect(admin_url('admin.php?page=gptranslate&imported=1'));
1862 exit;
1863 });
1864
1865 // Handle Export XLIFF
1866 add_action('admin_post_gptranslate_export_translations_xliff', function () {
1867 if (!current_user_can('manage_options') || !check_admin_referer('gptranslate_export_xliff', 'gptranslate_export_xliff_nonce')) {
1868 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1869 }
1870
1871 global $wpdb;
1872 $table = $wpdb->prefix . 'gptranslate';
1873
1874 $records = $wpdb->get_results("SELECT * FROM $table ORDER BY translate_date DESC", ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1875
1876 if (!$records) {
1877 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_NOTRANSLATIONS')));
1878 }
1879
1880 $localDate = get_date_from_gmt(gmdate('Y-m-d'));
1881 $fileDate = (date_i18n('Y-m-d', strtotime($localDate)));
1882
1883 header('Content-Type: application/xml; charset=utf-8');
1884 header('Content-Disposition: attachment; filename="gptranslate-translations-' . $fileDate . '.xliff"');
1885
1886 $xml = new SimpleXMLElement('<xliff/>');
1887 $xml->addAttribute('version', '1.2');
1888
1889 foreach ($records as $record) {
1890 $file = $xml->addChild('file');
1891 $file->addAttribute('source-language', $record['languageoriginal']);
1892 $file->addAttribute('target-language', $record['languagetranslated']);
1893 $file->addAttribute('datatype', 'html');
1894 $file->addAttribute('original', $record['pagelink']);
1895
1896 $body = $file->addChild('body');
1897
1898 $translations = json_decode($record['translations'], true) ?: [];
1899 foreach ($translations as $source => $target) {
1900 $unit = $body->addChild('trans-unit');
1901 $unit->addAttribute('id', md5($source));
1902 $unit->addChild('source', htmlspecialchars($source));
1903 $unit->addChild('target', htmlspecialchars($target));
1904 }
1905 }
1906
1907 echo $xml->asXML();
1908 exit;
1909 });
1910
1911 // Handle Import XLIFF
1912 add_action('admin_post_gptranslate_import_translations_xliff', function () {
1913 if (!current_user_can('manage_options') || !check_admin_referer('gptranslate_import_xliff', 'gptranslate_import_xliff_nonce')) {
1914 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1915 }
1916
1917 if (!isset($_FILES['import_file'], $_FILES['import_file']['error'], $_FILES['import_file']['tmp_name']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
1918 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UPLOAD_FAILED')));
1919 }
1920
1921 $tmp_name = $_FILES['import_file']['tmp_name']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1922 if (!is_uploaded_file($tmp_name)) {
1923 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_FAILED_UPLOADED_FILE')));
1924 }
1925
1926 $xml = simplexml_load_file($tmp_name);
1927 if (!$xml) {
1928 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_INVALID_XLIFF_FORMAT')));
1929 }
1930
1931 global $wpdb;
1932 $table = $wpdb->prefix . 'gptranslate';
1933
1934 foreach ($xml->file as $file) {
1935 $sourceLang = (string)$file['source-language'];
1936 $targetLang = (string)$file['target-language'];
1937 $pagelink = (string)$file['original'];
1938
1939 $translations = [];
1940 foreach ($file->body->{'trans-unit'} as $unit) {
1941 $src = (string)$unit->source;
1942 $tgt = (string)$unit->target;
1943 $translations[$src] = $tgt;
1944 }
1945
1946 $json_translations = wp_json_encode($translations);
1947
1948 $exists = $wpdb->get_var($wpdb->prepare(
1949 "SELECT id FROM $table WHERE pagelink = %s AND languageoriginal = %s AND languagetranslated = %s",
1950 $pagelink, $sourceLang, $targetLang
1951 )); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1952
1953 if ($exists) {
1954 // �
1955 Update only the translations and date, keep everything else intact
1956 $wpdb->update(
1957 $table,
1958 [
1959 'translations' => $json_translations,
1960 'translate_date' => current_time('mysql'),
1961 ],
1962 ['id' => $exists]
1963 ); // phpcs:ignore
1964 } else {
1965 // �
1966 Insert full record only if it doesn't exist
1967 $data = [
1968 'pagelink' => $pagelink,
1969 'translated_alias' => '',
1970 'translations' => $json_translations,
1971 'alt_translations' => '[]',
1972 'languageoriginal' => $sourceLang,
1973 'languagetranslated'=> $targetLang,
1974 'published' => 1,
1975 'translate_date' => current_time('mysql'),
1976 'translation_engine'=> 'chatgpt',
1977 ];
1978 $wpdb->insert($table, $data); // phpcs:ignore
1979 }
1980 }
1981
1982 wp_redirect(admin_url('admin.php?page=gptranslate&imported=1'));
1983 exit;
1984 });
1985
1986 // Handle Export XML Sitemap
1987 add_action('admin_post_gptranslate_export_xml_sitemap', function () {
1988 if (!current_user_can('manage_options') || !isset($_POST['gptranslate_export_xml_sitemap']) || $_POST['gptranslate_export_xml_sitemap'] !== 'b62d18a19b') {
1989 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1990 }
1991
1992 global $wpdb;
1993 $table = $wpdb->prefix . 'gptranslate';
1994
1995 // Solo record con translated_alias non vuoto
1996 $records = $wpdb->get_results("
1997 SELECT *
1998 FROM $table
1999 WHERE translated_alias IS NOT NULL
2000 AND translated_alias != ''
2001 ORDER BY translate_date DESC
2002 ", ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2003
2004 $localDate = get_date_from_gmt(gmdate('Y-m-d'));
2005 $fileDate = date_i18n('Y-m-d', strtotime($localDate));
2006
2007 $dom = new DOMDocument('1.0', 'UTF-8');
2008 $dom->formatOutput = true;
2009
2010 $urlset = $dom->createElement('urlset');
2011 $urlset->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');
2012 $dom->appendChild($urlset);
2013
2014 foreach ($records as $record) {
2015 $url = $dom->createElement('url');
2016 $loc = $dom->createElement('loc');
2017 $loc->appendChild($dom->createTextNode($record['translated_alias']));
2018 $url->appendChild($loc);
2019
2020 if (!empty($record['translate_date'])) {
2021 $lastmod = $dom->createElement('lastmod', date('c', strtotime($record['translate_date'])));
2022 $url->appendChild($lastmod);
2023 }
2024
2025 $urlset->appendChild($url);
2026 }
2027
2028 header('Content-Type: application/xml; charset=UTF-8');
2029 header('Content-Disposition: attachment; filename="gptranslate-sitemap-' . $fileDate . '.xml"');
2030 echo $dom->saveXML();
2031 exit;
2032 });
2033
2034 // Register Ajax handler
2035 add_action('wp_ajax_gptranslate_migrate_translations', function() {
2036 if (!current_user_can('manage_options')) {
2037 wp_send_json_error(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
2038 }
2039
2040 check_ajax_referer('gptranslate_migrate_translations');
2041
2042 global $wpdb;
2043 $table = $wpdb->prefix . 'gptranslate';
2044 $old = isset($_POST['old_domain']) ? sanitize_text_field(wp_unslash($_POST['old_domain'])) : '';
2045 $new = isset($_POST['new_domain']) ? sanitize_text_field(wp_unslash($_POST['new_domain'])) : '';
2046
2047 if (empty($old) || empty($new)) {
2048 wp_send_json_error(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_MISSING_DOMAIN_VALUES')));
2049 }
2050
2051 $query = $wpdb->prepare(
2052 "UPDATE $table SET pagelink = REPLACE(pagelink, %s, %s), translated_alias = REPLACE(translated_alias, %s, %s)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
2053 $old, $new, $old, $new
2054 );
2055
2056 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Dynamic query built with placeholders, safely prepared
2057 $result = $wpdb->query($query); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2058
2059 if ($result === false) {
2060 wp_send_json_error(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_DATABASE_ERROR')));
2061 } else {
2062 wp_send_json_success();
2063 }
2064 });
2065
2066 function gptranslate_export_settings() {
2067 if ( ! current_user_can( 'manage_options' ) || !check_admin_referer('gptranslate_export_settings', 'gptranslate_export_settings_nonce')) {
2068 wp_die( esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')) );
2069 }
2070
2071 $options = get_option( 'gptranslate_options', [] );
2072
2073 $localDate = get_date_from_gmt(gmdate('Y-m-d'));
2074 $fileDate = ( date_i18n('Y-m-d', strtotime($localDate)) );
2075
2076 header( 'Content-Type: application/json' );
2077 header( 'Content-Disposition: attachment; filename="gptranslate-settings-' . $fileDate . '.json"' );
2078 echo wp_json_encode( $options );
2079 exit;
2080 }
2081 add_action( 'admin_post_gptranslate_export_settings', 'gptranslate_export_settings' );
2082
2083 function gptranslate_import_settings() {
2084 if ( ! current_user_can( 'manage_options' ) || !check_admin_referer('gptranslate_import_settings', 'gptranslate_import_settings_nonce')) {
2085 wp_die( esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')) );
2086 }
2087
2088 if ( empty( $_FILES['gptranslate_settings_file']['tmp_name'] ) ) {
2089 wp_die( esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UPLOAD_FAILED')) );
2090 }
2091
2092 $content = file_get_contents( $_FILES['gptranslate_settings_file']['tmp_name'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2093 $decoded = json_decode( $content, true );
2094
2095 if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $decoded ) ) {
2096 wp_die( esc_html($this->loadTranslations('PLG_GPTRANSLATE_INVALID_JSON_TRANSLATIONS') ) );
2097 }
2098
2099 // Optional: sanitize known values, or just update if you're confident of source
2100 update_option( 'gptranslate_options', $decoded );
2101
2102 wp_safe_redirect( admin_url( 'admin.php?page=gptranslate-settings&settingsimported=1' ) );
2103 exit;
2104 }
2105 add_action( 'admin_post_gptranslate_import_settings', 'gptranslate_import_settings' );
2106
2107 add_action('wp_footer', function () {
2108 // Add the default target container if default CSS selector
2109 $settings = get_option("gptranslate_options");
2110
2111 // Disable interface
2112 if($settings ['disable_control']) {
2113 $settings ['wrapper_selector'] = '';
2114 }
2115
2116 if ($settings ['wrapper_selector'] == '.gptranslate_wrapper') {
2117 echo '<div class="gptranslate_wrapper" id="gpt-wrapper"></div>';
2118 }
2119 });
2120
2121 // POST API REST Translations storage
2122 add_action('rest_api_init', function () {
2123 register_rest_route('gptranslate/v1', '/request', [
2124 'methods' => 'POST',
2125 'callback' => 'gpt_handle_request',
2126 'permission_callback' => 'gptranslate_public_permission'
2127 ]);
2128 });
2129
2130 add_filter('plugin_action_links_' . plugin_basename(__FILE__), function ($links) {
2131 // Remove the default 'Settings' item
2132 unset($links[0]);
2133
2134 $settings_link = '<a href="' . esc_url(admin_url('admin.php?page=gptranslate-settings')) . '">' . esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_SETTINGS_MENU_TITLE')) . '</a>';
2135 array_unshift($links, $settings_link);
2136
2137 $translations_link = '<a href="' . esc_url(admin_url('admin.php?page=gptranslate')) . '">' . esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_TRANSLATIONS')) . '</a>';
2138 array_unshift($links, $translations_link);
2139
2140 return $links;
2141 });
2142
2143 /*
2144 // Remove any WP update for the free version over the paid full one
2145 add_filter('auto_update_plugin', function($update, $item) {
2146 if (isset($item->slug) && $item->slug === 'gptranslate') {
2147 return false;
2148 }
2149 return $update;
2150 }, 10, 2);
2151
2152
2153 add_filter('site_transient_update_plugins', function($transient) {
2154 if (isset($transient->response['gptranslate/gptranslate.php'])) {
2155 unset($transient->response['gptranslate/gptranslate.php']);
2156 }
2157 return $transient;
2158 });
2159 */
2160
2161 /**
2162 * Permission callback public API
2163 * @param WP_REST_Request $request
2164 * @return bool|WP_Error
2165 */
2166 function gptranslate_public_permission( WP_REST_Request $request ) {
2167 // 1) Controllo chiave API inviata via header
2168 $headerApiKey = $request->get_header('x-gptranslate-key');
2169 $restApiKey = hash( 'sha256', get_site_url() );
2170 if ( $headerApiKey != $restApiKey) {
2171 return new WP_Error( 'rest_forbidden', esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_FORBIDDEN_APIKEY')), [ 'status' => 403 ] );
2172 }
2173 return true;
2174 }
2175
2176 /**
2177 * Normalize WP URL
2178 * @param string $url
2179 * @return string
2180 */
2181 function gpt_trailingslashit_url($url) {
2182 $parsed = wp_parse_url($url);
2183 if (empty($parsed['path'])) {
2184 $parsed['path'] = '/';
2185 } else {
2186 $parsed['path'] = trailingslashit(untrailingslashit($parsed['path']));
2187 }
2188
2189 $rebuilt = isset($parsed['scheme']) ? $parsed['scheme'] . '://' : '';
2190 $rebuilt .= $parsed['host'] ?? '';
2191 $rebuilt .= $parsed['path'];
2192 if (!empty($parsed['query'])) {
2193 $rebuilt .= '?' . $parsed['query'];
2194 }
2195 if (!empty($parsed['fragment'])) {
2196 $rebuilt .= '#' . $parsed['fragment'];
2197 }
2198
2199 return $rebuilt;
2200 }
2201
2202 /**
2203 * Callback per GET/STORE translations via REST.
2204 * Frontend API
2205 *
2206 * @param WP_REST_Request $request
2207 * @return WP_REST_Response
2208 */
2209 function gpt_handle_request( WP_REST_Request $request ) {
2210 global $wpdb;
2211
2212 $table = $wpdb->prefix . 'gptranslate';
2213
2214 $params = $request->get_json_params();
2215
2216 if(!$params) {
2217 $params = $request->get_body_params();
2218 }
2219
2220 // Sanitize input params
2221 $task = sanitize_text_field( $params['task'] ?? '' );
2222 $pageLink = sanitize_text_field( $params['pagelink'] ?? '' );
2223 $translatedAlias = sanitize_text_field( $params['translated_alias'] ?? '' );
2224 $languageOriginal = sanitize_text_field( $params['language_original'] ?? '' );
2225 $languageTranslated = sanitize_text_field( $params['language_translated'] ?? '' );
2226 $translationEngine = sanitize_text_field( $params['translation_engine'] ?? '' );
2227 $retriggerTranslation = (int) sanitize_text_field( $params['retrigger'] ?? false );
2228
2229 $now = current_time( 'mysql' );
2230
2231 $response = [ 'result' => false ];
2232
2233 if ( $task === 'storetranslations' ) {
2234 // Ensure there is not a mismatching insert with the same languages
2235 if($languageOriginal == $languageTranslated) {
2236 $response['result'] = true;
2237 return rest_ensure_response( $response );
2238 }
2239
2240 // Fetch raw param (could be array or JSON string)
2241 $rawFull = $params['translations'] ?? '[]';
2242 $rawAlt = $params['alt_translations'] ?? '[]';
2243
2244 // If it’s already a string (JSON), use it directly.
2245 // If it’s an array (unlikely with FormData), JSON encode it.
2246 if ( is_string( $rawFull ) && json_decode( $rawFull ) !== null ) {
2247 $fullTranslations = $rawFull;
2248 } else {
2249 $fullTranslations = wp_json_encode( (array) $rawFull );
2250 }
2251
2252 if ( is_string( $rawAlt ) && json_decode( $rawAlt ) !== null ) {
2253 $altTranslations = $rawAlt;
2254 } else {
2255 $altTranslations = wp_json_encode( (array) $rawAlt );
2256 }
2257
2258 // Verifica se esiste già
2259 $existing_id = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2260 "SELECT id FROM {$table}" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
2261 "\n WHERE (pagelink = %s OR pagelink = %s)" .
2262 "\n AND languageoriginal = %s" .
2263 "\n AND languagetranslated = %s",
2264 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $languageOriginal, $languageTranslated
2265 ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Dynamic query built with placeholders, safely prepared
2266
2267 // Only if it is a retrigger then ignore the db processing
2268 if($retriggerTranslation !== 1) {
2269 if ( $existing_id ) {
2270 // UPDATE
2271 $updated = $wpdb->update( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2272 $table,
2273 [
2274 'translations' => $fullTranslations,
2275 'alt_translations' => $altTranslations,
2276 'translated_alias' => $translatedAlias,
2277 'translate_date' => $now,
2278 'translation_engine' => $translationEngine,
2279 ],
2280 [ 'id' => (int) $existing_id ],
2281 [ '%s', '%s', '%s', '%s', '%s' ],
2282 [ '%d' ]
2283 );
2284 $response['result'] = ( $updated !== false );
2285 } else {
2286 // INSERT
2287 $inserted = $wpdb->insert( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2288 $table,
2289 [
2290 'pagelink' => $pageLink,
2291 'translations' => $fullTranslations,
2292 'alt_translations' => $altTranslations,
2293 'translated_alias' => $translatedAlias,
2294 'languageoriginal' => $languageOriginal,
2295 'languagetranslated' => $languageTranslated,
2296 'published' => 1,
2297 'translate_date' => $now,
2298 'translation_engine' => $translationEngine,
2299 ],
2300 [ '%s','%s','%s','%s','%s','%s','%d','%s','%s' ]
2301 );
2302 $response['result'] = ( $inserted !== false );
2303 }
2304 } else {
2305 $response['result'] = true;
2306 }
2307 } elseif ( $task === 'gettranslations' ) {
2308 $opts = get_option( 'gptranslate_options', [] );
2309 if ( ! empty( $opts['realtime_translations'] ) || $retriggerTranslation === 1) {
2310 $response['result'] = false;
2311 } else {
2312 if ($opts['rewrite_language_url'] == 1 && $opts['rewrite_language_alias'] == 1) {
2313 $row = $wpdb->get_row( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2314 "SELECT translations, alt_translations, translated_alias, pagelink FROM {$table}" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
2315 "\n WHERE (pagelink = %s OR pagelink = %s OR translated_alias = %s OR translated_alias = %s)" .
2316 "\n AND languageoriginal = %s" .
2317 "\n AND languagetranslated = %s" .
2318 "\n AND published = 1",
2319 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $languageOriginal, $languageTranslated
2320 ), ARRAY_A );
2321 } else {
2322 $row = $wpdb->get_row( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2323 "SELECT translations, alt_translations, translated_alias, pagelink FROM {$table}" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
2324 "\n WHERE (pagelink = %s OR pagelink = %s)" .
2325 "\n AND languageoriginal = %s" .
2326 "\n AND languagetranslated = %s" .
2327 "\n AND published = 1",
2328 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $languageOriginal, $languageTranslated
2329 ), ARRAY_A );
2330 }
2331
2332 if ( $row ) {
2333 $response['result'] = true;
2334 $response['translations'] = json_decode( $row['translations'], true ) ?: [];
2335 $response['alt_translations'] = json_decode( $row['alt_translations'], true ) ?: [];
2336 $response['translated_alias'] = $row['translated_alias'];
2337 $response['pagelink_alias'] = $row['pagelink'];
2338 } else {
2339 $response['result'] = false;
2340 }
2341 }
2342 } elseif ($task == 'getaliastranslation') {
2343 // Always perform a new realtime translation if the option is enabled
2344 try {
2345 $row = $wpdb->get_row( $wpdb->prepare(
2346 "SELECT translated_alias FROM {$table}" .
2347 "\n WHERE (pagelink = %s OR pagelink = %s)" .
2348 "\n AND languageoriginal = %s" .
2349 "\n AND languagetranslated = %s" .
2350 "\n AND published = 1",
2351 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $languageOriginal, $languageTranslated
2352 ), ARRAY_A );
2353
2354 if ( $row ) {
2355 $response['result'] = true;
2356 $response['translated_alias'] = $row['translated_alias'] ?? '';
2357 } else {
2358 $response['result'] = false;
2359 }
2360 } catch ( Exception $e ) {
2361 $response['result'] = false;
2362 $response['exception'] = $e->getMessage();
2363 }
2364 } elseif ( $task === 'syncTranslation' ) {
2365 $original = wp_unslash( $params['original'] ?? '' );
2366 $translated = wp_unslash( $params['translated'] ?? '' );
2367 $languageTranslated = sanitize_text_field( $params['language_translated'] ?? '' );
2368 $translationType = sanitize_text_field( $params['translation_type'] ?? 'translations' ); // default to 'translations'
2369
2370 // Recupera tutti i record nella lingua target
2371 $rows = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2372 "SELECT id, {$translationType}, languagetranslated" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
2373 "\n FROM {$table}" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
2374 "\n WHERE languagetranslated = %s",
2375 $languageTranslated
2376 ) );
2377
2378 $updatedCount = 0;
2379
2380 foreach ( $rows as $row ) {
2381 $currentTranslations = json_decode( $row->$translationType, true );
2382
2383 if ( is_array( $currentTranslations ) && array_key_exists( $original, $currentTranslations ) ) {
2384 // Aggiorna la traduzione e salva
2385 $currentTranslations[ $original ] = $translated;
2386 $jsonUpdated = wp_json_encode( $currentTranslations );
2387
2388 $success = $wpdb->update( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2389 $table,
2390 [ $translationType => $jsonUpdated, 'translate_date' => $now ],
2391 [ 'id' => $row->id ],
2392 [ '%s', '%s' ],
2393 [ '%d' ]
2394 );
2395
2396 if ( $success !== false ) {
2397 $updatedCount++;
2398 }
2399 }
2400 }
2401
2402 $response['result'] = $updatedCount > 0;
2403 } elseif ($task == 'gettranslatedaliases') {
2404 try {
2405 if ($languageTranslated) {
2406 $rows = $wpdb->get_results(
2407 $wpdb->prepare(
2408 "SELECT pagelink, translated_alias
2409 FROM {$table}
2410 WHERE languagetranslated = %s
2411 AND published = 1",
2412 $languageTranslated
2413 ),
2414 ARRAY_A
2415 );
2416 } elseif ($languageOriginal) {
2417 $rows = $wpdb->get_results(
2418 $wpdb->prepare(
2419 "SELECT translated_alias AS pagelink, pagelink AS translated_alias
2420 FROM {$table}
2421 WHERE languageoriginal = %s
2422 AND published = 1",
2423 $languageOriginal
2424 ),
2425 ARRAY_A
2426 );
2427 } else {
2428 $response['result'] = false;
2429 echo wp_json_encode($response);
2430 exit;
2431 }
2432
2433 if ($rows) {
2434 $encodedResult = [];
2435
2436 foreach ($rows as $row) {
2437
2438 // Normalizza WordPress-style (CON trailing slash)
2439 $pagelink = gpt_trailingslashit_url($row['pagelink']);
2440 $translatedAlias = !empty($row['translated_alias']) ? gpt_trailingslashit_url($row['translated_alias']) : '';
2441
2442 // Encode pagelink (solo path)
2443 $parsedUrl = wp_parse_url($pagelink);
2444 $encodedPagelink = $pagelink;
2445
2446 if (!empty($parsedUrl['path'])) {
2447 $pathParts = explode('/', $parsedUrl['path']);
2448 $encodedParts = array_map('rawurlencode', $pathParts);
2449 $encodedPath = implode('/', $encodedParts);
2450
2451 $encodedPagelink = ($parsedUrl['scheme'] ?? '') . '://' . ($parsedUrl['host'] ?? '');
2452 $encodedPagelink .= $encodedPath;
2453 if (!empty($parsedUrl['query'])) {
2454 $encodedPagelink .= '?' . $parsedUrl['query'];
2455 }
2456 if (!empty($parsedUrl['fragment'])) {
2457 $encodedPagelink .= '#' . $parsedUrl['fragment'];
2458 }
2459 }
2460
2461 // Encode translated alias
2462 $encodedAlias = $translatedAlias;
2463 if (!empty($translatedAlias)) {
2464 $parsedAlias = wp_parse_url($translatedAlias);
2465 if (!empty($parsedAlias['path'])) {
2466 $pathParts = explode('/', $parsedAlias['path']);
2467 $encodedParts = array_map('rawurlencode', $pathParts);
2468 $encodedPath = implode('/', $encodedParts);
2469
2470 $encodedAlias = ($parsedAlias['scheme'] ?? '') . '://' . ($parsedAlias['host'] ?? '');
2471 $encodedAlias .= $encodedPath;
2472 if (!empty($parsedAlias['query'])) {
2473 $encodedAlias .= '?' . $parsedAlias['query'];
2474 }
2475 if (!empty($parsedAlias['fragment'])) {
2476 $encodedAlias .= '#' . $parsedAlias['fragment'];
2477 }
2478 }
2479 }
2480
2481 $encodedResult[$encodedPagelink] = [
2482 'pagelink' => $encodedPagelink,
2483 'translated_alias' => $encodedAlias
2484 ];
2485 }
2486
2487 $response['result'] = true;
2488 $response['translated_aliases'] = $encodedResult;
2489
2490 } else {
2491 $response['result'] = false;
2492 }
2493
2494 } catch (Exception $e) {
2495 $response['result'] = false;
2496 $response['exception'] = $e->getMessage();
2497 }
2498 } elseif ( $task === 'deepseektranslations' ) {
2499 try {
2500 // Read raw JSON payload sent from JS
2501 $rawInput = file_get_contents( 'php://input' );
2502 $requestData = json_decode( $rawInput, true );
2503
2504 if ( ! $requestData || empty( $requestData['messages'] ) ) {
2505 throw new Exception( 'Invalid DeepSeek request payload' );
2506 }
2507
2508 // Get plugin options
2509 $opts = get_option( 'gptranslate_options', [] );
2510
2511 $deepseekApiKey = $opts['chatgpt_apikey'] ?? '';
2512 $deepseekModel = $opts['chatgpt_model'] ?? 'deepseek-chat';
2513
2514 if ( empty( $deepseekApiKey ) ) {
2515 throw new Exception( 'DeepSeek API key not configured' );
2516 }
2517
2518 // Fixed DeepSeek parameters (server controlled)
2519 $payload = [
2520 'model' => $deepseekModel,
2521 'messages' => $requestData['messages'],
2522 'max_tokens' => 4096,
2523 'temperature' => 0.5,
2524 ];
2525
2526 // Call DeepSeek API
2527 $ch = curl_init( 'https://api.deepseek.com/v1/chat/completions' );
2528 curl_setopt_array( $ch, [
2529 CURLOPT_POST => true,
2530 CURLOPT_RETURNTRANSFER => true,
2531 CURLOPT_HTTPHEADER => [
2532 'Content-Type: application/json',
2533 'Authorization: Bearer ' . $deepseekApiKey,
2534 ],
2535 CURLOPT_POSTFIELDS => wp_json_encode( $payload ),
2536 CURLOPT_TIMEOUT => 60,
2537 ] );
2538
2539 $apiResponse = curl_exec( $ch );
2540
2541 if ( $apiResponse === false ) {
2542 throw new Exception( 'DeepSeek cURL error: ' . curl_error( $ch ) );
2543 }
2544
2545 $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
2546 curl_close( $ch );
2547
2548 if ( $httpCode >= 400 ) {
2549 throw new Exception( 'DeepSeek API HTTP error: ' . $httpCode );
2550 }
2551
2552 // IMPORTANT:
2553 // Return DeepSeek response EXACTLY as the API returns it
2554 header( 'Content-Type: application/json; charset=utf-8' );
2555 echo $apiResponse;
2556 exit;
2557
2558 } catch ( Exception $e ) {
2559 // FALLBACK SAFE RESPONSE (OpenAI-compatible)
2560 header('Content-Type: application/json; charset=utf-8');
2561 echo wp_json_encode([
2562 'choices' => [[
2563 'finish_reason' => 'error',
2564 'message' => [
2565 'role' => 'assistant',
2566 'content' => ''
2567 ]
2568 ]]
2569 ]);
2570 exit;
2571 }
2572 }
2573
2574 return rest_ensure_response( $response );
2575 }
2576
2577 // Instantiate and run the app
2578 GPTranslate::get_instance();