PluginProbe
GPTranslate – Multilingual AI Translation Agent for WordPress: Translate Your Site with AI / 2.14.1
GPTranslate – Multilingual AI Translation Agent for WordPress: Translate Your Site with AI v2.14.1
2.34.5 2.34.4 2.34.3 2.34.2 2.34.1 2.34 2.33.10 2.33.9 2.33.8 2.33.7 2.33.6 2.33.5 2.33.2 2.32.10 2.33 2.33.1 2.32.6 2.32.7 2.32.8 trunk 2.10.3 2.10.4 2.10.5 2.10.6 2.11 All 57 releases
gptranslate / gptranslate.php

gptranslate.php in GPTranslate – Multilingual AI Translation Agent for WordPress: Translate Your Site with AI 2.14.1, at gptranslate.php

1,753 lines 136.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: GPTranslate
4 Plugin URI: https://storejextensions.org/extensions/gptranslate.html
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.14.1
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.14.1';
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.14.1';
36
37 $settings = get_option ( 'gptranslate_options', [ ] );
38
39 // Include various functions like multilanguage URLs, hreflang tag, HTML lang attribute rewriting
40 require_once plugin_dir_path(__FILE__) . 'multilang-routing.php';
41
42 if ( isset($settings ['serverside_translations']) && $settings ['serverside_translations'] == 1 ) {
43 require_once plugin_dir_path(__FILE__) . 'serverside-translations.php';
44 }
45
46 register_activation_hook ( __FILE__, [
47 $this,
48 'activate_plugin'
49 ] );
50
51 add_action ( 'admin_init', function () {
52 // Disable WordPress emoji script and styles
53 remove_action('wp_head', 'print_emoji_detection_script', 7);
54 remove_action('admin_print_scripts', 'print_emoji_detection_script');
55 remove_action('wp_print_styles', 'print_emoji_styles');
56 remove_action('admin_print_styles', 'print_emoji_styles');
57 remove_filter('the_content_feed', 'wp_staticize_emoji');
58 remove_filter('comment_text_rss', 'wp_staticize_emoji');
59 remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
60
61 function gptranslate_sanitize_options( $options ) {
62 $clean = [];
63 $clean = $options;
64 return $clean;
65 }
66 register_setting ( 'gptranslate_settings', 'gptranslate_options', [
67 'sanitize_callback' => 'gptranslate_sanitize_options'
68 ]);
69
70 // Register record deletion
71 $page = sanitize_key($_GET['page'] ?? '');
72 $action = sanitize_key($_GET['action'] ?? '');
73 $nonce = isset($_GET['_gptranslate_nonce']) ? wp_unslash($_GET['_gptranslate_nonce']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
74 $translation_id = isset($_GET['translation_id']) ? (int) $_GET['translation_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
75
76 if ($page === 'gptranslate' &&
77 $action === 'delete_translation' &&
78 $translation_id &&
79 wp_verify_nonce($nonce, 'gptranslate_delete_' . $translation_id)
80 ) {
81 $this->gptranslate_handle_deletion($translation_id);
82 wp_redirect(admin__rewrit('admin.php?page=gptranslate&deleted=1'));
83 exit;
84 }
85
86 if (isset($_GET['action']) && sanitize_key($_GET['action']) == 'toggle_published') {
87 // Toggle published state
88 $id = isset($_GET['translation_id']) ? (int) $_GET['translation_id'] : 0;
89
90 if (!$id || !isset($_GET['_gptranslate_nonce']) || !wp_verify_nonce(wp_unslash($_GET['_gptranslate_nonce']), 'gptranslate_toggle_' . $id)) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
91 wp_die('Invalid nonce.');
92 }
93
94 global $wpdb;
95 $table = $wpdb->prefix . 'gptranslate';
96
97 // Toggle published flag
98 $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
99 $new = ($current == 1) ? 0 : 1;
100
101 $wpdb->update($table, ['published' => $new], ['id' => $id]); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
102
103 wp_redirect(admin_url('admin.php?page=gptranslate&action=state_toggled'));
104 exit;
105 }
106
107 // Check for plugin update
108 function check_for_gptranslate_update($currentVersion) {
109 // Start the session if not already started
110 if (session_status() === PHP_SESSION_NONE) {
111 session_start();
112 }
113
114 // Reset session vars after update
115 if (isset($_SESSION['gptranslate_update_version']) && version_compare($currentVersion, $_SESSION['gptranslate_update_version'], '>=')) {
116 unset($_SESSION['gptranslate_update_version']);
117 unset($_SESSION['gptranslate_update_checked']);
118 }
119
120 // Check if the update check has been done in this session
121 if (!isset($_SESSION['gptranslate_update_checked']) || $_SESSION['gptranslate_update_checked'] !== true) {
122
123 // Perform the remote XML check
124 $remote_url = 'https://storejextensions.org/updates/gptranslatewp_updater.xml';
125 $response = wp_remote_get($remote_url);
126
127 if (!is_wp_error($response)) {
128 $body = wp_remote_retrieve_body($response);
129 if (!empty($body)) {
130 $xml = simplexml_load_string($body);
131 if ($xml && !empty($xml->update->version)) {
132 $updateversion = (string)$xml->update->version;
133 if (version_compare($updateversion, $currentVersion, '>')) {
134 // Store the update info in session (version, and flag that update is available)
135 $_SESSION['gptranslate_update_version'] = $updateversion;
136 }
137 $_SESSION['gptranslate_update_checked'] = true;
138 }
139 }
140 }
141 }
142
143 // If update is available, show the notice
144 if (isset($_SESSION['gptranslate_update_version']) && sanitize_text_field($_SESSION['gptranslate_update_version'])) {
145 add_action('admin_notices', function () {
146 echo '<div class="notice notice-warning is-dismissible">';
147 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
148 echo '</div>';
149 });
150 }
151 }
152 //check_for_gptranslate_update($this->version);
153 } );
154
155 // Post admin notices after actions
156 add_action( 'admin_notices', function() {
157 if ( isset( $_GET['page'], $_GET['deleted'] ) && sanitize_key($_GET['page']) === 'gptranslate' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
158 if ( (int) $_GET['deleted'] === 1 ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
159 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_DELETED')) . '</p></div>';
160 } elseif ( (int) $_GET['deleted'] === 0 ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
161 echo '<div class="notice notice-error is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_DELETED_ERROR')) . '</p></div>';
162 }
163 }
164
165 if ( isset( $_GET['page'], $_GET['action'] ) && sanitize_key($_GET['page']) === 'gptranslate' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
166 if ( $_GET['action'] === 'state_toggled' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
167 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_STATE_UPDATED_SUCCESSFULLY')) . '</p></div>';
168 }
169 }
170
171 if (isset($_GET['imported']) && $_GET['imported'] == '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
172 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_IMPORTED_SUCCESSFULLY')) . '</p></div>';
173 }
174
175 if (isset($_GET['settingsimported']) && $_GET['settingsimported'] == '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
176 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_SETTINGS_IMPORTED_SUCCESSFULLY')) . '</p></div>';
177 }
178 });
179
180 // Add hook for admin menu links
181 add_action ( 'admin_menu', [
182 $this,
183 'admin_menu'
184 ] );
185
186 // Add hook for record saving/deleting
187 add_action ( 'admin_post_save_gptranslate_record', [
188 $this,
189 'save_record'
190 ] );
191
192 add_action( 'admin_post_save_gptranslate_record_and_close', [
193 $this,
194 'save_record'
195 ]);
196
197 add_action('admin_post_cancel_gptranslate_record', [
198 $this,
199 'save_record'
200 ]);
201
202 // Add hook for adding main frontend app scripts
203 add_action ( 'wp_enqueue_scripts', [
204 $this,
205 'enqueue_frontend_scripts'
206 ] );
207 }
208
209 /**
210 * Singleton class instance
211 *
212 * @access public
213 */
214 public static function get_instance() {
215 if (null === self::$instance) {
216 self::$instance = new static();
217 }
218 return self::$instance;
219 }
220
221 /**
222 * Activation plugin hook with db table creation
223 *
224 * @access public
225 */
226 public function activate_plugin() {
227 global $wpdb;
228 $charset_collate = $wpdb->get_charset_collate();
229
230 $sql = "CREATE TABLE " . $this->table_name . " (
231 id int UNSIGNED NOT NULL AUTO_INCREMENT,
232 pagelink varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
233 translated_alias varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
234 translations mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
235 alt_translations mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
236 languageoriginal char(20) NOT NULL,
237 languagetranslated char(20) NOT NULL,
238 published tinyint NOT NULL DEFAULT '1',
239 translate_date datetime DEFAULT NULL,
240 translation_engine varchar(20) NOT NULL,
241 PRIMARY KEY (id)
242 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
243
244 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
245 dbDelta($sql);
246
247 // Valori di default
248 $default_options = [
249 'google_translate_engine' => '1',
250 'chatgpt_apikey' => '',
251 'chatgpt_model' => 'gpt-3.5-turbo',
252 '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.",
253 'chatgpt_request_conversation_mode' => 'user',
254 'language' => 'en',
255 'max_translations_per_request' => '100',
256 'max_characters_per_request' => '2048',
257 'detect_browser_language' => '0',
258 'autotranslate_detected_language' => '0',
259 'always_detect_autotranslated_language' => '0',
260 'auto_set_language_direction' => '0',
261 'serverside_translations' => '0',
262 'serverside_translations_method' => 'regex',
263 'serverside_translations_caseinsensitive' => '1',
264 'serverside_translations_matchquotes' => '1',
265 'serverside_translations_urldecode' => '0',
266 'serverside_translations_ignore_querystring' => '0',
267 'serverside_translations_urlencode_space' => '0',
268 'css_selector_serverside_leafnodes_excluded' => '',
269 'detect_current_language' => '0',
270 'detect_default_language' => '0',
271 'rewrite_language_url' => '0',
272 'rewrite_language_alias' => '0',
273 'rewrite_language_alias_original_language' => '0',
274 'rewrite_page_links' => '0',
275 'rewrite_default_language_url' => '0',
276 'translate_metadata' => '0',
277 'set_html_lang' => '0',
278 'add_canonical' => '0',
279 'add_alternate' => '0',
280 'translate_placeholders' => '0',
281 'translate_altimages' => '0',
282 'css_selector_classes_translate_altimages_excluded' => '',
283 'translate_titles' => '0',
284 'translate_values' => '0',
285 'default_language_first' => '0',
286 'css_selector_leafnodes_excluded' => 'a.nturl',
287 'words_leafnodes_excluded' => '',
288 'words_leafnodes_excluded_bylanguage_repeatable' => '[]',
289 'words_min_length' => '',
290 'chatgpt_gtranslate_request_delay' => '0',
291 'initial_translation_delay' => '0',
292 'realtime_translations' => '0',
293 'ignore_querystring' => '0',
294 'enable_indexer' => '0',
295 'storage_type' => 'session',
296 'subfolder_installation' => '0',
297 'alt_flags' => [],
298 'languages' => ['en', 'es', 'de', 'it', 'fr'],
299 'enable_reader' => '0',
300 'responsivevoice_apikey' => 'WLXcRhDN',
301 'responsivevoice_language_gender' => 'auto',
302 'responsivevoice_volume_tts' => '100',
303 'responsivevoice_voice_speed' => 'normal',
304 'mainpage_selector' => '*[name*=main], *[class*=main], *[id*=main], *[id*=container], *[class*=container]',
305 'elements_toexclude_custom' => '',
306 'proxy_responsive_loading_script' => '1',
307 'proxy_responsive_reading_mode' => 'native',
308 'chunksize' => '200',
309 'widget_text_color' => '#000000',
310 'widget_background_color' => '#FFFFFF',
311 'popup_border_radius' => '0',
312 'popup_fontsize' => '20',
313 'popup_iconsize' => '32',
314 'float_position' => 'bottom-left',
315 'float_switcher_open_direction' => 'top',
316 'flag_style' => '2d',
317 'flag_loading' => 'local',
318 'show_language_titles' => '1',
319 'enable_dropdown' => '1',
320 'equal_widths' => '0',
321 'reader_button_position' => 'top',
322 'widget_max_height' => '260',
323 'wrapper_selector' => '.gptranslate_wrapper',
324 'draggable_widget' => '0',
325 'disable_control' => '0',
326 'custom_css' => '',
327 'disable_bootstrap_css' => '0'
328 ];
329
330 // Se l'opzione non è ancora presente, la crea
331 if (get_option('gptranslate_options') === false) {
332 add_option('gptranslate_options', $default_options);
333 }
334
335 }
336
337 /**
338 * Function to add admin menu for both settings and translations management
339 *
340 * @access public
341 */
342 public function admin_menu() {
343 add_menu_page('GPTranslate', 'GPTranslate', 'manage_options', 'gptranslate', [$this, 'records_page'], 'dashicons-translation');
344
345 // Add a submenu that matches the main menu to prevent duplication and allow renaming
346 add_submenu_page('gptranslate', esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS')), esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS')), 'manage_options', 'gptranslate', [$this, 'records_page']);
347
348 // Now you can safely add a differently named submenu
349 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']);
350 }
351
352 /**
353 * Load the configuration settings page held in the settings.php file
354 *
355 * @access public
356 */
357 public function settings_page() {
358 require_once 'settings.php';
359
360 echo '<script>
361 const PLG_GPTRANSLATE_MOVE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_MOVE')) . '";
362 const PLG_GPTRANSLATE_REMOVE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_REMOVE')) . '";
363 </script>';
364 }
365
366 /**
367 * Translation records pages, list and edit
368 *
369 * @access public
370 */
371 public function records_page() {
372 global $wpdb;
373
374 // Edit record
375 if (isset($_GET['action']) && sanitize_key($_GET['action']) == 'edit') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
376 $id = isset($_GET['edit']) ? (int) $_GET['edit'] : 0;
377 $nonce = isset($_GET['_gptranslate_nonce']) ? wp_unslash($_GET['_gptranslate_nonce']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
378 $opts = get_option( 'gptranslate_options', [] );
379
380 if ( ! wp_verify_nonce( $nonce, 'gptranslate_edit_' . $id ) ) {
381 wp_die( esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_SECURITY_ERROR')), 'Error', [ 'response' => 403 ] );
382 }
383
384 $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
385
386 $translationsArray = json_decode($record->translations, true) ?? [];
387 $altTranslationsArray = json_decode($record->alt_translations, true) ?? [];
388
389 uksort($translationsArray, function($a, $b) {
390 return strlen($b) - strlen($a);
391 });
392 uksort($altTranslationsArray, function($a, $b) {
393 return strlen($b) - strlen($a);
394 });
395
396 // Path relativo o assoluto all'immagine della bandiera
397 $flagUrlOriginal = plugins_url('flags/svg/' . esc_attr($record->languageoriginal) . '.svg', __FILE__);
398 $flagUrlTranslated = plugins_url('flags/svg/' . esc_attr($record->languagetranslated) . '.svg', __FILE__);
399
400 $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
401 : '<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
402 $rewriteAliasRow = '';
403 if ($opts ['rewrite_language_alias'] == 1) {
404 $rewriteAliasRow = '<tr>
405 <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>
406 <td><input type="text" id="translated_alias" name="translated_alias" value="' . esc_attr($record->translated_alias) . '" class="regular-text code"></td>
407 </tr>';
408 }
409
410 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
411 echo '<form method="post" id="edit-translations" action="admin-post.php">
412 <p>
413 <input type="submit" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_SAVE')) . '" class="button button-primary" data-action="save_gptranslate_record">
414 <input type="submit" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_SAVEANDECLOSE')) . '" class="button button-primary" data-action="save_gptranslate_record_and_close">
415 <input type="submit" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_CANCEL')) . '" class="button button-primary" data-action="cancel_gptranslate_record">
416 </p>
417 <input type="hidden" name="languagetranslated" id="languagetranslated" value="' . esc_attr($record->languagetranslated) . '">
418 <input type="hidden" name="action" id="form_action" value="save_gptranslate_record">
419 <input type="hidden" name="id" value="' . (int) $record->id . '">
420
421 <table class="form-table">
422 <tr>
423 <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>
424 <td><input type="text" id="pagelink" name="pagelink" value="' . esc_attr($record->pagelink) . '" class="regular-text code"></td>
425 </tr>' .
426 $rewriteAliasRow .
427 '<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
428 '<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
429 '<tr><th scope="row"><label>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_PUBLISHED')) . '</label></th><td>' . wp_kses_post($pubIcon) . '</td></tr>' .
430 '<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>' .
431 '<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>
432
433 <tr>
434 <th scope="row"><label title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS_DESC')) . '">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS')) . '</label></th>
435 <td>
436 <div class="gptcard gptcard-default">
437 <div class="gptcard-header">
438 <div class="accordion-toggle">
439 <div class="input-group">
440 <span class="gpt-label" aria-label="Filter">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_FILTER')) . '</span>
441 <input type="text" name="search" value="" class="text_area">
442 <button class="btn btn-primary btn-sm" data-role="search-translations" onclick="return false;">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_GO')) . '</button>
443 <button class="btn btn-primary btn-sm" data-role="reset-search" onclick="return false;">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_RESET')) . '</button>
444 </div>
445 </div>
446 </div>
447 <div class="gptcard-body gptcard-block ps-3 accordion-body accordion-inner">
448 <button type="button" class="btn btn-success btn-adder" data-addtype="translations">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ADD_TRANSLATION')) . '</button>
449 <textarea name="translations_json" id="translations_json" hidden>' . esc_textarea(json_encode($translationsArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) . '</textarea>
450 <div id="translations-container"></div>
451 </div>
452 </div>
453 </td>
454 </tr>
455
456 <tr>
457 <th scope="row"><label title="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_ALT_TRANSLATIONS_DESC')) . '">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ALT_TRANSLATIONS')) . '</label></th>
458 <td>
459 <div class="gptcard gptcard-default">
460 <div class="gptcard-header">
461 <div class="accordion-toggle">
462 <div class="input-group">
463 <span class="gpt-label" aria-label="Filter">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_FILTER')) . '</span>
464 <input type="text" name="search" value="" class="text_area">
465 <button class="btn btn-primary btn-sm" data-role="search-translations" onclick="return false;">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_GO')) . '</button>
466 <button class="btn btn-primary btn-sm" data-role="reset-search" onclick="return false;">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_RESET')) . '</button>
467 </div>
468 </div>
469 </div>
470 <div class="gptcard-body gptcard-block ps-3 accordion-body accordion-inner">
471 <button type="button" class="btn btn-success btn-adder" data-addtype="alt-translations">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ADD_ALT_TRANSLATION')) . '</button>
472 <textarea name="alt_translations_json" id="alt_translations_json" hidden>' . esc_textarea(json_encode($altTranslationsArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) . '</textarea>
473 <div id="alt-translations-container"></div>
474 </div>
475 </div>
476 </td>
477 </tr>
478 </table>' .
479 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- nonce field is safe
480 wp_nonce_field('gptranslate_save_record_action', '_gptranslate_nonce') .
481 '</form>';
482
483 echo '<script>
484 const initialTranslations = ' . (json_encode($translationsArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}') . ';
485 const initialAltTranslations = ' . (json_encode($altTranslationsArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}') . ';
486
487 const PLG_GPTRANSLATE_ORIGINAL_TEXT = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_ORIGINAL_TEXT')) . '";
488 const PLG_GPTRANSLATE_TRANSLATED_TEXT = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATED_TEXT')) . '";
489 const PLG_GPTRANSLATE_DELETE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_DELETE')) . '";
490 const PLG_GPTRANSLATE_MOVE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_MOVE')) . '";
491 const PLG_GPTRANSLATE_REMOVE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_REMOVE')) . '";
492 const PLG_GPTRANSLATE_SYNC = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC')) . '";
493 const PLG_GPTRANSLATE_SYNC_TITLE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC_TITLE')) . '";
494 const PLG_GPTRANSLATE_SYNC_DESC = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC_DESC')) . '";
495 const PLG_GPTRANSLATE_SYNC_COMPLETED = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC_COMPLETED')) . '";
496 const PLG_GPTRANSLATE_SYNC_ERROR = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_SYNC_ERROR')) . '";
497 const gptServerSideLink = "' . esc_url_raw(rest_url('gptranslate/v1/request')) . '";
498 </script>';
499 } else {
500 // FREE period
501 // 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>';
502
503 // UPGRADE period
504 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://storejextensions.org/extensions/gptranslate.html" target="_blank"><strong>PRO version</strong></a>. Current FREE Plan: translate up to <strong>500 words</strong> and read aloud up to <strong>100 words</strong> per page. Don’t lose AI power – <a href="https://storejextensions.org/extensions/gptranslate.html" target="_blank">Upgrade Now</a>.</p></div>';
505
506 // List records
507 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
508
509 // Filters section
510 $opts = get_option( 'gptranslate_options', [] );
511 $languageFilter = esc_attr( sanitize_text_field( wp_unslash( $_GET['language'] ?? '' ) ) );
512 $languages = $opts['languages'] ?? [];
513
514 // Paginate records
515 $records_per_page = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 10;
516 $valid_per_page_options = [5, 10, 25, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 9999999];
517 if (!in_array($records_per_page, $valid_per_page_options)) {
518 $records_per_page = 10;
519 }
520
521 $searchFilter = sanitize_text_field( wp_unslash( $_GET['s'] ?? '' ) );
522 $engineFilter = sanitize_key( wp_unslash( $_GET['engine'] ?? '' ) );
523 $publishedFilter = sanitize_key( wp_unslash( $_GET['published'] ?? '' ) );
524
525 echo
526 '<form method="get" class="form-filter-container">' .
527 '<div class="left-filter-container">' .
528 '<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')) . '" />' .
529 '<button type="button" class="button" onclick="this.form.submit();">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_GO')) . '</button>' .
530 '<button type="button" class="button" onclick="document.getElementById(\'search-input\').value=\'\'; this.form.submit();">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_RESET')) . '</button>' .
531 '</div>' .
532 '<div class="right-filter-container">' .
533 '<input type="hidden" name="_gptranslate_nonce" value="' . esc_attr(wp_create_nonce('gptranslate_filter_action')) . '" />' .
534 '<select name="published">' .
535 '<option value="">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATION_ALL')) . '</option>' .
536 '<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>' .
537 '<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>' .
538 '</select>' .
539 '<select name="language">' .
540 '<option value="">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_TRANSLATED')) . '</option>';
541
542
543 // Loop languages
544 foreach ($languages as $lang) {
545 echo "<option value='" . esc_attr($lang) . "'" . esc_html($this->isSelected($languageFilter, $lang)) . ">" . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . strtoupper($lang))) . "</option>";
546 }
547
548 echo
549 '</select>' .
550 '<select name="engine">' .
551 '<option value="">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_CHATGPT_TRANSLATION_ENGINE')) . '</option>' .
552 '<option value="gtranslate" ' . esc_html($this->isSelected(sanitize_key(wp_unslash($_GET['engine'] ?? '')), 'gtranslate')) . '>Google AI</option>' .
553 '<option value="chatgpt" ' . esc_html($this->isSelected(sanitize_key(wp_unslash($_GET['engine'] ?? '')), 'chatgpt')) . '>ChatGPT</option>' .
554 '<option value="gemini" ' . esc_html($this->isSelected(sanitize_key(wp_unslash($_GET['engine'] ?? '')), 'Gemini')) . '>Gemini</option>' .
555 '<option value="deepseek" ' . esc_html($this->isSelected(sanitize_key(wp_unslash($_GET['engine'] ?? '')), 'DeepSeek')) . '>DeepSeek</option>' .
556 '</select>' .
557 '<select name="per_page">' .
558 '<option value="5" ' . esc_html($this->isSelected($records_per_page, 5)) . '>5</option>' .
559 '<option value="10" ' . esc_html($this->isSelected($records_per_page, 10)) . '>10</option>' .
560 '<option value="25" ' . esc_html($this->isSelected($records_per_page, 25)) . '>25</option>' .
561 '<option value="50" ' . esc_html($this->isSelected($records_per_page, 50)) . '>50</option>' .
562 '<option value="100" ' . esc_html($this->isSelected($records_per_page, 100)) . '>100</option>' .
563 '<option value="200" ' . esc_html($this->isSelected($records_per_page, 200)) . '>200</option>' .
564 '<option value="500" ' . esc_html($this->isSelected($records_per_page, 500)) . '>500</option>' .
565 '<option value="1000" ' . esc_html($this->isSelected($records_per_page, 1000)) . '>1000</option>' .
566 '<option value="2000" ' . esc_html($this->isSelected($records_per_page, 2000)) . '>2000</option>' .
567 '<option value="5000" ' . esc_html($this->isSelected($records_per_page, 5000)) . '>5000</option>' .
568 '<option value="10000" ' . esc_html($this->isSelected($records_per_page, 10000)) . '>10000</option>' .
569 '<option value="9999999" ' . esc_html($this->isSelected($records_per_page, 9999999)) . '>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ALL')) . '</option>' .
570 '</select>' .
571 '<input type="submit" class="button" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_FILTER')) . '" />' .
572 '</div>' .
573 '<input type="hidden" name="page" value="gptranslate" />' .
574 '</form>';
575
576 // Bottoni Import/Export
577 echo '<div class="action-buttons-toolbar">';
578 echo '<button class="button button-primary" id="bulk-delete-btn">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_DELETE')) . '</button>';
579 echo '<button class="button button-primary" id="toggle-migration">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_BTNS')) . '</button>';
580
581 // Export CSV
582 echo '<form method="post" action="' . esc_attr(admin_url('admin-post.php')) . '" style="display:inline;margin-right:10px;">';
583 wp_nonce_field('gptranslate_export_csv', 'gptranslate_export_csv_nonce');
584 echo '<input type="hidden" name="action" value="gptranslate_export_translations_csv">';
585 echo '<input type="submit" class="button button-primary" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_EXPORT_TRANSLATIONS')) . '">';
586 echo '</form>';
587
588 // Import CSV
589 echo '<input type="button" class="button button-primary button-import" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_IMPORT_TRANSLATIONS')) . '">';
590 echo '<form method="post" action="' . esc_attr(admin_url('admin-post.php')) . '" enctype="multipart/form-data" style="display:inline;">';
591 wp_nonce_field('gptranslate_import_csv', 'gptranslate_import_csv_nonce');
592 echo '<input type="hidden" name="action" value="gptranslate_import_translations_csv">';
593 echo '<input type="file" name="import_file" class="toggle-import hidden" accept=".csv" required>';
594 echo '<input type="submit" class="button button-primary toggle-import hidden" value="' . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_META_CONFIRM')) . '">';
595 echo '</form>';
596
597 echo '</div>';
598
599 echo '<div id="migraterow" class="hidden">
600 <span class="input-group">
601 <label for="migratetranslations_currentdomain"><strong>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_META_PREVIOUS_DOMAIN')) . '</strong></label>
602 <input type="text" class="form-control" id="migratetranslations_currentdomain" name="migratetranslations_currentdomain" value="">
603 </span>
604 <span class="input-group">
605 <label for="migratetranslations_newdomain"><strong>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_META_NEW_DOMAIN')) . '</strong></label>
606 <input type="text" class="form-control" id="migratetranslations_newdomain" name="migratetranslations_newdomain" value="">
607 </span>
608 <button class="button button-primary" id="migrationconfirm">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_MIGRATE_META_CONFIRM')) . '</button>
609 <button class="button" id="migrationcancel">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_RESET')) . '</button>
610 </div>
611 <script>
612 const PLG_GPTRANSLATE_MIGRATION_SUCCESS = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_MIGRATION_SUCCESS')) . '";
613 const PLG_GPTRANSLATE_MIGRATION_FAILED = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_MIGRATION_FAILED')) . '";
614 const PLG_GPTRANSLATE_UNKNOWN_ERROR = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_UNKNOWN_ERROR')) . '";
615 const PLG_GPTRANSLATE_NETWORK_ERROR = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_NETWORK_ERROR')) . '";
616 const PLG_GPTRANSLATE_BULK_DELETE_CONFIRM = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_CONFIRM')) . '";
617 const PLG_GPTRANSLATE_BULK_DELETE_SELECT_ONE = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_SELECT_ONE')) . '";
618 const PLG_GPTRANSLATE_BULK_DELETE_SUCCESS = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_SUCCESS')) . '";
619 const PLG_GPTRANSLATE_BULK_DELETE_ERROR = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_ERROR')) . '";
620 const PLG_GPTRANSLATE_BULK_DELETE_NETWORK = "' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_BULK_DELETE_NETWORK')) . '";
621 </script>';
622
623
624 echo '<table class="widefat fixed striped">';
625 echo '<thead><tr>';
626 echo '<th style="width: 1%"><input class="form-check-input" autocomplete="off" type="checkbox" id="checkall"></th>';
627 echo '<th style="width: 2%;;white-space:nowrap">ID</th>';
628 if($opts['rewrite_language_alias'] == 1) {
629 echo '<th style="width: 18%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LINK_PAGE')) . '</th>';
630 echo '<th style="width: 18%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATED_ALIAS')) . '</th>';
631 echo '<th style="width: 18%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_ORIGINAL_TRANSLATED')) . '</th>';
632 } else {
633 echo '<th style="width: 25%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LINK_PAGE')) . '</th>';
634 echo '<th style="width: 20%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_ORIGINAL_TRANSLATED')) . '</th>';
635 }
636 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_ORIGINAL')) . '</th>';
637 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_TRANSLATED')) . '</th>';
638 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_PUBLISHED_GENERIC')) . '</th>';
639 echo '<th style="width:5%">' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS_ENGINE')) . '</th>';
640 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_TRANSLATIONS_DATE')) . '</th>';
641 echo '<th>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_ACTIONS')) . '</th>';
642 echo '</tr></thead>';
643
644 echo '<tbody>';
645
646 $current_page = isset($_GET['paged']) && is_numeric($_GET['paged']) ? (int)$_GET['paged'] : 1;
647 $offset = ($current_page - 1) * $records_per_page;
648 $sql_count = "SELECT COUNT(*) FROM {$this->table_name} WHERE 1=1";
649
650 // Add dynamic filters
651 if (!empty($searchFilter)) {
652 $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) . "%'" . ")";
653 }
654 if ($publishedFilter !== '') {
655 $sql_count .= " AND published = '" . esc_sql($publishedFilter) . "'";
656 }
657 if (!empty($languageFilter)) {
658 $sql_count .= " AND languagetranslated = '" . esc_sql($languageFilter) . "'";
659 }
660 if (!empty($engineFilter)) {
661 $sql_count .= " AND translation_engine = '" . esc_sql($engineFilter) . "'";
662 }
663 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Dynamic query built with placeholders, safely prepared
664 $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
665 $total_pages = ceil($total_records / $records_per_page);
666
667 // Load records count with filtering
668 $sql_data = "SELECT * FROM {$this->table_name} WHERE 1=1";
669
670 // Add dynamic filters
671 if (!empty($searchFilter)) {
672 $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) . "%'" . ")";
673 }
674 if ($publishedFilter !== '') {
675 $sql_data .= " AND published = '" . esc_sql($publishedFilter) . "'";
676 }
677 if (!empty($languageFilter)) {
678 $sql_data .= " AND languagetranslated = '" . esc_sql($languageFilter) . "'";
679 }
680 if (!empty($engineFilter)) {
681 $sql_data .= " AND translation_engine = '" . esc_sql($engineFilter) . "'";
682 }
683 $sql_data .= " ORDER BY translate_date DESC LIMIT $records_per_page OFFSET $offset";
684
685 // Load records with filtering and pagination
686 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Dynamic query built with placeholders, safely prepared
687 $records = $wpdb->get_results($sql_data); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
688
689 // Message for no translations
690 if (!count($records) && stripos($sql_data, "AND") === false) {
691 echo '<div class="notice notice-success is-dismissible" id="notranslations-notice">';
692 echo '<p>' . esc_html($this->loadTranslations('PLG_GPTRANSLATE_NO_TRANSLATIONS')) . '</p>';
693 echo '</div>';
694 }
695
696 foreach ( $records as $r ) {
697 // Build a short summary of the translations
698 $tr = json_decode( $r->translations, true );
699 if ( is_array( $tr ) ) {
700 $pairs = array_map(
701 function( $k, $v ) {
702 return esc_html( $k ) . '' . esc_html( $v );
703 },
704 array_keys( $tr ),
705 array_values( $tr )
706 );
707 $short = implode( ', ', $pairs );
708 $short = mb_substr( $short, 0, 80 ) . ( mb_strlen( $short ) > 80 ? '' : '' );
709 } else {
710 $short = '';
711 }
712
713 // Escape all output
714 $id = (int) $r->id;
715 $link = wp_nonce_url( admin_url( "admin.php?page=gptranslate&action=edit&edit={$id}" ), 'gptranslate_edit_' . $id, '_gptranslate_nonce' );
716 $deleteLink = wp_nonce_url( admin_url( "admin.php?page=gptranslate&action=delete_translation&translation_id={$id}" ), 'gptranslate_delete_' . $id, '_gptranslate_nonce' );
717 $origLang = esc_html( strtoupper( $r->languageoriginal ) );
718 $transLang = esc_html( strtoupper( $r->languagetranslated ) );
719 $pub = $r->published ? esc_html($this->loadTranslations('PLG_GPTRANSLATE_YES')) : esc_html($this->loadTranslations('PLG_GPTRANSLATE_NO'));
720 $engine = esc_html( $r->translation_engine );
721 $date = esc_html( $r->translate_date );
722
723 $langOriginal = esc_attr($r->languageoriginal);
724
725 // Path relativo o assoluto all'immagine della bandiera
726 $flagUrlOriginal = plugins_url('flags/svg/' . $r->languageoriginal . '.svg', __FILE__);
727 $flagOriginal = '<img src="' . esc_url($flagUrlOriginal) . '" alt="flag" style="width: 16px; vertical-align:middle; margin-right:4px;">'; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
728 $flagUrlTranslated = plugins_url('flags/svg/' . $r->languagetranslated . '.svg', __FILE__);
729 $flagTranslated = '<img src="' . esc_url($flagUrlTranslated) . '" alt="flag" style="width: 16px; vertical-align:middle; margin-right:4px;">'; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
730
731 $togglePublishedUrl = wp_nonce_url(
732 admin_url("admin.php?page=gptranslate&action=toggle_published&translation_id={$id}"),
733 'gptranslate_toggle_' . $id,
734 '_gptranslate_nonce'
735 );
736
737 $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
738 : '<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
739
740
741 $pub = $r->published ? "<a href='{$togglePublishedUrl}' class='gpt-toggle gpt-published' title='" . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_UNPUBLISH')) . "'>" . $pubIcon . "</a>"
742 : "<a href='{$togglePublishedUrl}' class='gpt-toggle gpt-unpublished' title='" . esc_attr($this->loadTranslations('PLG_GPTRANSLATE_PUBLISH')) . "'>" . $pubIcon . "</a>";
743
744 $local_date = get_date_from_gmt($date);
745
746 echo '<tr>';
747 echo "<td style='width: 1%'><input class='form-check-input' autocomplete='off' type='checkbox' id='cb0' name='gptid[]' value='" . esc_attr($r->id) . "'></td>";
748 echo "<td style='width: 2%;white-space:nowrap'>". esc_html($r->id) . "</td>";
749 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>";
750 if($opts['rewrite_language_alias'] == 1) {
751 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>';
752 }
753 echo "<td>" . esc_html($short) . "</td>";
754 echo "<td>" . wp_kses_post($flagOriginal) . " " . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . strtoupper($langOriginal))) . "</td>";
755 echo "<td>" . wp_kses_post($flagTranslated) . " " . esc_html($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . strtoupper(esc_attr($r->languagetranslated)))) . "</td>";
756 echo "<td>" . wp_kses_post($pub) . "</td>";
757 echo "<td><span class='gpt-label'>" . esc_html($this->loadTranslations('PLG_GPTRANSLATE_CHATGPT_TRANSLATION_ENGINE_' . strtoupper($engine) . '_ENGINE')) . "</span></td>";
758 echo "<td>" . esc_html( date_i18n('l, d F Y \a\t H:i', strtotime($local_date)) ) . "</td>";
759 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>";
760 echo '</tr>';
761 }
762 echo '</tbody>';
763 echo '</table>';
764
765 if ($total_pages > 1) {
766 echo '<div class="tablenav"><div class="tablenav-pages">';
767 for ($i = 1; $i <= $total_pages; $i++) {
768 $url = add_query_arg(array_merge($_GET, ['paged' => $i]), admin_url('admin.php'));
769 $class = ($i == $current_page) ? "class='current-page button'" : "class='button'";
770 echo "<a " . wp_kses_post($class) . " href='" . esc_url($url) . "'>" . esc_html($i) . "</a> ";
771 }
772 echo '</div></div>';
773 }
774 }
775
776 echo '</div>';
777 echo '</div>';
778 }
779
780 /**
781 * Load language file and translations
782 * @return array
783 */
784 public function loadTranslations($key) {
785 // Text translations
786 static $adminLanguageStrings = null;
787
788 if(!$adminLanguageStrings) {
789 $adminLanguageFile = dirname(__FILE__) . "/language/en-GB/gptranslate.ini";
790 if(file_exists($adminLanguageFile)) {
791 $adminLanguageStrings = parse_ini_file($adminLanguageFile, false, INI_SCANNER_NORMAL);
792 }
793 }
794
795 if(array_key_exists($key, $adminLanguageStrings)) {
796 return $adminLanguageStrings[$key];
797 }
798
799 return $key;
800 }
801
802 /**
803 * Load language file and translations
804 * @return array
805 */
806 public static function loadTranslation($key) {
807 // Text translations
808 static $adminLanguageStrings = null;
809
810 if(!$adminLanguageStrings) {
811 $adminLanguageFile = dirname(__FILE__) . "/language/en-GB/gptranslate.ini";
812 if(file_exists($adminLanguageFile)) {
813 $adminLanguageStrings = parse_ini_file($adminLanguageFile, false, INI_SCANNER_NORMAL);
814 }
815 }
816
817 if(array_key_exists($key, $adminLanguageStrings)) {
818 return $adminLanguageStrings[$key];
819 }
820
821 return $key;
822 }
823
824 /**
825 * Save translation record
826 *
827 * @access public
828 */
829 public function save_record() {
830 global $wpdb;
831
832 // Retrieve and sanitize basic inputs
833 $id = isset ( $_POST ['id'] ) ? intval ( $_POST ['id'] ) : 0;
834 $formAction = isset ( $_POST ['action'] ) ? sanitize_key ( $_POST ['action'] ) : '';
835
836 if ( !isset($_POST['_gptranslate_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_gptranslate_nonce'])), 'gptranslate_save_record_action') ) {
837 wp_die(esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_SECURITY_ERROR')), 'gptranslate');
838 }
839
840 // Handle cancel action
841 if ($formAction === 'cancel_gptranslate_record') {
842 wp_redirect ( admin_url ( 'admin.php?page=gptranslate' ) );
843 exit ();
844 }
845
846 // Sanitize pagelink
847 $pagelink = isset ( $_POST ['pagelink'] ) ? sanitize_text_field ( wp_unslash ( $_POST ['pagelink'] ) ) : '';
848
849 // Sanitize translated_alias
850 $translatedAlias = isset ( $_POST ['translated_alias'] ) ? sanitize_text_field ( wp_unslash ( $_POST ['translated_alias'] ) ) : '';
851
852 // Process and sanitize translations JSON
853 $raw_translations = filter_input( INPUT_POST, 'translations_json', FILTER_UNSAFE_RAW );
854 $raw_translations = is_string($raw_translations) ? $raw_translations : '[]';
855
856 $decoded_translations = json_decode ( $raw_translations, true );
857 if (! is_array ( $decoded_translations )) {
858 wp_die ( esc_html($this->loadTranslations('PLG_GPTRANSLATE_INVALID_JSON_TRANSLATIONS')), esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_ERROR')), [
859 'response' => 400
860 ] );
861 }
862 $clean_translations = $decoded_translations;
863 $sanitized_translations_json = wp_json_encode ( $clean_translations );
864
865 // Process and sanitize alternative translations JSON
866 $raw_alt = filter_input( INPUT_POST, 'alt_translations_json', FILTER_UNSAFE_RAW );
867 $raw_alt = is_string($raw_translations) ? $raw_alt : '[]';
868
869 $decoded_alt = json_decode ( $raw_alt, true );
870 if (! is_array ( $decoded_alt )) {
871 wp_die ( esc_html($this->loadTranslations('PLG_GPTRANSLATE_INVALID_JSON_ALTTRANSLATIONS')), esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_ERROR')), [
872 'response' => 400
873 ] );
874 }
875 $clean_alt = $decoded_alt;
876 $sanitized_alt_json = wp_json_encode ( $clean_alt );
877
878 // Update database record
879 $wpdb->update ( $this->table_name, [ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
880 'pagelink' => $pagelink,
881 'translated_alias' => $translatedAlias,
882 'translations' => $sanitized_translations_json,
883 'alt_translations' => $sanitized_alt_json
884 ], [
885 'id' => $id
886 ], [
887 '%s',
888 '%s',
889 '%s'
890 ], [
891 '%d'
892 ] );
893
894 // Redirect based on action
895 if ($formAction === 'save_gptranslate_record_and_close') {
896 wp_redirect ( admin_url ( 'admin.php?page=gptranslate' ) );
897 } else {
898 $url = admin_url( 'admin.php?page=gptranslate&action=edit&edit=' . $id );
899 $url = wp_nonce_url( $url, 'gptranslate_edit_' . $id, '_gptranslate_nonce' );
900 wp_redirect( html_entity_decode( $url ) );
901 }
902 exit ();
903 }
904
905 public function gptranslate_handle_deletion() {
906 // 1) Verify nonce
907 $id = isset( $_GET['translation_id'] ) ? intval( $_GET['translation_id'] ) : 0;
908 $nonce = isset( $_GET['_gptranslate_nonce'] ) ? wp_unslash( $_GET['_gptranslate_nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
909
910 if ( ! wp_verify_nonce( $nonce, 'gptranslate_delete_' . $id ) ) {
911 wp_die( esc_html($this->loadTranslations('PLG_GPTRANSLATE_GENERIC_SECURITY_ERROR')), 'Error', [ 'response' => 403 ] );
912 }
913
914 // 2) Delete the row
915 global $wpdb;
916 $table = $wpdb->prefix . 'gptranslate';
917 $deleted = $wpdb->delete( $table, [ 'id' => $id ], [ '%d' ] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
918
919 // 3) Redirect back with a message
920 $redirect_url = add_query_arg(
921 [
922 'page' => 'gptranslate',
923 'deleted' => $deleted ? '1' : '0',
924 ],
925 admin_url( 'admin.php' )
926 );
927 wp_redirect( $redirect_url );
928 exit;
929 }
930
931
932 /**
933 * Add main app frontend script
934 *
935 * @access public
936 */
937 public function enqueue_frontend_scripts() {
938 add_filter('script_loader_tag', function($tag, $handle) {
939 if ($handle === 'gptranslate-responsivevoice') {
940 return str_replace('<script ', '<script defer ', $tag);
941 }
942
943 if ($handle === 'gptranslate-jsonrepair') {
944 return str_replace('<script ', '<script type="module" ', $tag);
945 }
946
947 if ($handle === 'gptranslate-bstoast') {
948 return str_replace('<script ', '<script type="module" ', $tag);
949 }
950 if ($handle === 'gptranslate-main') {
951 // 1) prendi i valori raw
952 $raw_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
953 $raw_host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
954
955 // 2) unslash WP
956 $unslashed_uri = wp_unslash ( $raw_uri );
957 $unslashed_host = wp_unslash ( $raw_host );
958
959 // 3) sanitizza URI come URL
960 $orig_url = esc_url_raw ( $unslashed_uri );
961 // - accetta path e query, rimuove caratteri pericolosi
962
963 // 4) sanitizza host
964 // a) rimuovi tag e control chars
965 $host_clean = sanitize_text_field ( $unslashed_host );
966 // b) mantieni solo [a-z0-9.-] per sicurezza
967 $orig_domain = preg_replace ( '/[^a-z0-9.-]/i', '', $host_clean );
968
969 // 5) infine escape per attributo HTML
970 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 );
971 }
972
973 return $tag;
974 }, 10, 2);
975
976 $settings = get_option("gptranslate_options");
977
978 // Move default language to the first one in the list
979 if($settings ['default_language_first']) {
980 $defaultLanguageKeyIndex = array_search($settings ['language'], $settings ['languages']);
981 if ($defaultLanguageKeyIndex !== false) {
982 // Remove the 'de' element from its current position
983 $defaultLanguage = $settings ['languages'][$defaultLanguageKeyIndex];
984 unset($settings ['languages'][$defaultLanguageKeyIndex]);
985
986 // Re-index the array to maintain numerical indexes
987 $settings ['languages'] = array_values($settings ['languages']);
988
989 // Add 'de' to the beginning of the array
990 array_unshift($settings ['languages'], $defaultLanguage);
991 }
992 }
993
994 // build alt_flags array
995 $alt_flags = array ();
996 $raw_alt_flags = isset($settings ['alt_flags']) ? $settings ['alt_flags'] : [];
997 foreach ( $raw_alt_flags as $country ) {
998 if ($country == 'usa' or $country == 'canada')
999 $alt_flags ['en'] = $country;
1000 elseif ($country == 'brazil')
1001 $alt_flags ['pt'] = $country;
1002 elseif ($country == 'mexico' or $country == 'argentina' or $country == 'colombia')
1003 $alt_flags ['es'] = $country;
1004 elseif ($country == 'quebec')
1005 $alt_flags ['fr'] = $country;
1006 }
1007
1008 // Build float position variables
1009 $float_position = $settings ['float_position'];
1010 if($float_position != 'inline'){
1011 list ( $switcher_vertical_position, $switcher_horizontal_position ) = explode ( '-', $float_position );
1012 } else {
1013 list ( $switcher_vertical_position, $switcher_horizontal_position ) = ['inline', 'inline'];
1014 }
1015
1016 // Set local flags path
1017 $flagsPath = trailingslashit(plugins_url('flags', __FILE__));
1018
1019 wp_register_script('gptranslate-main-inline', '', [], $this->version, true);
1020 wp_enqueue_script('gptranslate-main-inline');
1021
1022 // Example: $settings is an array like in Joomla
1023 $base64Encode = 'base' . 64 . '_encode';
1024 $key = $settings['chatgpt_apikey'];
1025 $key = strrev($key);
1026 $secret = 'gptranslate';
1027 $out = '';
1028 for ($i = 0; $i < strlen($key); $i++) {
1029 $out .= chr(ord($key[$i]) ^ ord($secret[$i % strlen($secret)]));
1030 }
1031 $encoded = $base64Encode($out);
1032
1033 $ajaxEndpoint = esc_url_raw(rest_url('gptranslate/v1/request'));
1034
1035 $inlineScript = 'var gptServerSideLink = "' . esc_js($ajaxEndpoint) . '";
1036 var gptApiKey = "' . esc_js(hash( 'sha256', get_site_url() )) . '";
1037 var gptLiveSite = "' . esc_js(get_site_url()) . '";
1038 var gptStorage = ' . ($settings['storage_type'] === 'session' ? 'window.sessionStorage' : 'window.localStorage') . ';
1039 var gptMaxTranslationsPerRequest = ' . (int)$settings['max_translations_per_request'] . ';
1040 var maxCharactersPerRequest = ' . (int)$settings['max_characters_per_request'] . ';
1041 var gptRewriteLanguageUrl = ' . (int)$settings['rewrite_language_url'] . ';
1042 var gptOmitPrefixOriginalLanguage = ' . (isset($settings['omit_prefix_original_language']) ? (int)$settings['omit_prefix_original_language'] : 0) . ';
1043 var gptRewriteLanguageAlias = ' . (int)$settings['rewrite_language_alias'] . ';
1044 var gptRewriteLanguageAliasOriginalLanguage = ' . (int)$settings['rewrite_language_alias_original_language'] . ';
1045 var gptAutoSetLanguageDirection = ' . (int)$settings['auto_set_language_direction'] . ';
1046 var gptServersideTranslations = ' . (int)$settings['serverside_translations'] . ';
1047 var gptRewritePageLinks = ' . (int)$settings['rewrite_page_links'] . ';
1048 var gptTranslateMetadata = ' . (int)$settings['translate_metadata'] . ';
1049 var gptTranslatePlaceholders = ' . (int)$settings['translate_placeholders'] . ';
1050 var gptTranslateAltImages = ' . (int)$settings['translate_altimages'] . ';
1051 var chatgptClassesAltimagesExcluded = "' . esc_js(str_ireplace('"', '', $settings['css_selector_classes_translate_altimages_excluded'])) . '";
1052 var gptTranslateTitles = ' . (int)$settings['translate_titles'] . ';
1053 var gptTranslateValues = ' . (int)$settings['translate_values'] . ';
1054 var gptSetHtmlLang = ' . (int)$settings['set_html_lang'] . ';
1055 var gptAddCanonical = ' . (int)$settings['add_canonical'] . ';
1056 var gptAddAlternate = ' . (int)$settings['add_alternate'] . ';
1057 var gptSubfolderInstallation = ' . (int)$settings['subfolder_installation'] . ';
1058 var gptIgnoreQuerystring = ' . (int)$settings['ignore_querystring'] . ';
1059 var gptChatgptGtranslateRequestDelay = ' . (int)$settings['chatgpt_gtranslate_request_delay'] . ';
1060 var gptInitialTranslationDelay = ' . (int)$settings['initial_translation_delay'] . ';
1061 var chatgptApiKey = "' . esc_js($encoded) . '";
1062 var chatgptApiModel = "' . esc_js($settings['chatgpt_model']) . '";
1063 var chatgptRequestMessage = "' . str_ireplace("\'", "'", esc_js(str_ireplace(['"' , "\r", "\n"], ['' , ' ', ' '], $settings['chatgpt_request_message']))) . '";
1064 var chatgptRequestConversationMode = "' . esc_js($settings['chatgpt_request_conversation_mode']) . '";
1065 var chatgptEnableReader = ' . (int)$settings['enable_reader'] . ';
1066 var chatgptResponsivevoiceLanguageGender = "' . esc_js($settings['responsivevoice_language_gender']) . '";
1067 var chatgptResponsivevoiceApiKey = "' . esc_js($settings['responsivevoice_apikey']) . '";
1068 var chatgptResponsivevoiceReadingMode = "' . esc_js($settings['proxy_responsive_reading_mode']) . '";
1069 var chatgptChunksize = "' . esc_js($settings['chunksize']) . '";
1070 var chatgptCssSelectorLeafnodesExcluded = "' . esc_js(trim(preg_replace('/,+/', ',', str_ireplace(["\r", "\n"], ",", $settings['css_selector_leafnodes_excluded'])), ',')) . '";
1071 var chatgptWordsLeafnodesExcluded = "' . esc_js(rtrim($settings['words_leafnodes_excluded'], ', ')) . '";
1072 var chatgptWordsMinLength = "' . (int)$settings['words_min_length'] . '";
1073 var chatgptMainpageSelector = "' . esc_js($settings['mainpage_selector']) . '";
1074 var chatgptElementsToExcludeCustom = "' . esc_js(trim($settings['elements_toexclude_custom'])) . '";
1075 var chatgptPopupFontsize = ' . (int)$settings['popup_fontsize'] . ';
1076 var chatgptDraggableWidget = ' . (int)$settings['draggable_widget'] . ';
1077 var gptAudioVolume = ' . (float)$settings['responsivevoice_volume_tts'] . ';
1078 var gptVoiceSpeed = "' . esc_js($settings['responsivevoice_voice_speed']) . '";
1079 var gTranslateEngine = ' . (($settings['google_translate_engine'] == 1 || !trim($settings['chatgpt_apikey'])) ? 1 : 0) . ';
1080 var translateEngineValue = "' . esc_js($settings['google_translate_engine']) . '";
1081 var gptDisableControl = ' . (int)$settings['disable_control'] . ';
1082 var gptVersionNumeric = ' . 0 . ';
1083 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:#' . ltrim($settings['widget_text_color'], '#') . '"/></svg>\';';
1084
1085 // Inject it AFTER gptranslate-main-inline
1086 wp_add_inline_script('gptranslate-main-inline', $inlineScript);
1087
1088 wp_register_script('gptranslate-js-specs', '', [], $this->version, true);
1089 wp_enqueue_script('gptranslate-js-specs');
1090 wp_add_inline_script('gptranslate-js-specs', 'window.gptranslateSettings = window.gptranslateSettings || {};
1091 window.gptranslateSettings["1"] = {
1092 "default_language": "' . $settings['language'] . '",
1093 "languages": ' . json_encode($settings['languages']) . ',
1094 "wrapper_selector": "' . $settings['wrapper_selector'] . '",
1095 "float_switcher_open_direction": "' . $settings['float_switcher_open_direction'] . '",
1096 "detect_browser_language": ' . (int)$settings['detect_browser_language'] . ',
1097 "detect_current_language": ' . (int)$settings['detect_current_language'] . ',
1098 "detect_default_language": ' . (int)$settings['detect_default_language'] . ',
1099 "autotranslate_detected_language": ' . (int)$settings['autotranslate_detected_language'] . ',
1100 "always_detect_autotranslated_language": ' . (int)$settings['always_detect_autotranslated_language'] . ',
1101 "widget_text_color": "' . $settings['widget_text_color'] . '",
1102 "show_language_titles": ' . (int)$settings['show_language_titles'] . ',
1103 "enable_dropdown": ' . (int)$settings['enable_dropdown'] . ',
1104 "equal_widths": ' . (int)$settings['equal_widths'] . ',
1105 "reader_button_position": "' . $settings['reader_button_position'] . '",
1106 "custom_css": "' . $settings['custom_css'] . '",
1107 "alt_flags": ' . json_encode($alt_flags). ',
1108 "switcher_horizontal_position": "' . $switcher_horizontal_position . '",
1109 "switcher_vertical_position": "' . $switcher_vertical_position . '",
1110 "flags_location": "' . esc_js($flagsPath) . '",
1111 "flag_loading": "' . $settings['flag_loading'] . '",
1112 "flag_style": "' . $settings['flag_style'] . '",
1113 "widget_max_height": ' . (int)$settings['widget_max_height'] . '
1114 };');
1115
1116 $languageStringsScript = '';
1117
1118 // Generic translations
1119 $labels = [
1120 'TRANSLATING',
1121 'TRANSLATING_WAIT',
1122 'TRANSLATING_COMPLETE',
1123 'READING_INPROGRESS',
1124 'READING_END',
1125 'READING_EMPTY'
1126 ];
1127
1128 foreach ($labels as $label) {
1129 $languageStringsScript .= 'var PLG_GPTRANSLATE_' . $label . '="' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_' . $label)) . '";' . PHP_EOL;
1130 }
1131
1132 $languages = [
1133 'AF', 'SQ', 'AM', 'AR', 'HY', 'AZ', 'EU', 'BE', 'BN', 'BS', 'BG', 'CA', 'CEB', 'NY',
1134 'ZH', 'CO', 'HR', 'CS', 'DA', 'NL', 'EN', 'EO', 'ET', 'TL', 'FI', 'FR',
1135 'FY', 'GL', 'KA', 'DE', 'EL', 'GU', 'HT', 'HA', 'HAW', 'IW', 'HI', 'HMN', 'HU',
1136 'IS', 'IG', 'ID', 'GA', 'IT', 'JA', 'JW', 'KN', 'KK', 'KM', 'KO', 'KU', 'KY', 'LO',
1137 'LA', 'LV', 'LT', 'LB', 'MK', 'MG', 'MS', 'ML', 'MT', 'MI', 'MR', 'MN', 'MY', 'NE',
1138 'NO', 'PS', 'FA', 'PL', 'PT', 'PA', 'RO', 'RU', 'SM', 'GD', 'SR', 'ST', 'SN', 'SD',
1139 'SI', 'SK', 'SL', 'SO', 'ES', 'SU', 'SW', 'SV', 'TG', 'TA', 'TE', 'TH', 'TR', 'UK',
1140 'UR', 'UZ', 'VI', 'CY', 'XH', 'YI', 'YO', 'ZU'
1141 ];
1142
1143
1144 foreach ($languages as $lang) {
1145 $languageStringsScript .= 'var PLG_GPTRANSLATE_LANGUAGE_NAME_' . $lang . '="' . esc_js($this->loadTranslations('PLG_GPTRANSLATE_LANGUAGE_NAME_' . $lang)) . '";' . PHP_EOL;
1146 }
1147
1148 wp_register_script('gptranslate-js-language-strings', '', [], $this->version, true);
1149 wp_enqueue_script('gptranslate-js-language-strings');
1150 wp_add_inline_script('gptranslate-js-language-strings', $languageStringsScript);
1151
1152
1153 // Dictionary
1154 $words_leafnodes_excluded_bylanguage_repeatable = $settings['words_leafnodes_excluded_bylanguage_repeatable'];
1155 if ($words_leafnodes_excluded_bylanguage_repeatable) {
1156 if (is_string($words_leafnodes_excluded_bylanguage_repeatable)) {
1157 $words_leafnodes_excluded_bylanguage_repeatable = json_decode($words_leafnodes_excluded_bylanguage_repeatable, true);
1158 }
1159
1160 // Ora convertiamo l'array normale nel formato con chiavi tipo words_leafnodes_excluded_bylanguage_repeatable0, 1, 2...
1161 $formatted = [];
1162 foreach ($words_leafnodes_excluded_bylanguage_repeatable as $index => $row) {
1163 $formatted["words_leafnodes_excluded_bylanguage_repeatable{$index}"] = [
1164 'words_leafnodes_excluded_bylanguage' => $row['word'] ?? '',
1165 'words_leafnodes_excluded_bylanguage_language_original' => $row['langOriginal'] ?? '*',
1166 'words_leafnodes_excluded_bylanguage_language_target' => $row['langTranslated'] ?? '*',
1167 'words_leafnodes_excluded_bylanguage_translation' => $row['optionalTranslation'] ?? ''
1168 ];
1169 }
1170
1171 // Correctly formatted JSON encode
1172 $formatted_json = json_encode($formatted, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
1173
1174 // Inietto i dati PRIMA che venga eseguito gptranslate.js
1175 wp_register_script('gptranslate-js-word-leafones-excluded-language', '', [], $this->version, true);
1176 wp_enqueue_script('gptranslate-js-word-leafones-excluded-language');
1177 wp_add_inline_script(
1178 'gptranslate-js-word-leafones-excluded-language',
1179 'var chatgptWordsLeafnodesExcludedByLanguage = ' . $formatted_json . ';'
1180 );
1181 }
1182
1183 // Local or remote script
1184 if($settings['proxy_responsive_loading_script'] == 1) {
1185 wp_enqueue_script('gptranslate-responsivevoice', plugin_dir_url(__FILE__) . 'assets/js/responsivevoice.js', [], $this->version, true);
1186 } else {
1187 wp_enqueue_script('gptranslate-responsivevoice', 'https://code.responsivevoice.org/responsivevoice.js?key=' . $settings ['responsivevoice_apikey'], [], $this->version, true);
1188 }
1189
1190 wp_enqueue_script('gptranslate-jsonrepair', plugin_dir_url(__FILE__) . 'assets/js/jsonrepair/index.js', [], $this->version, true);
1191 wp_enqueue_script('gptranslate-main', plugin_dir_url(__FILE__) . 'assets/js/gptranslate.js', [], $this->version, true);
1192
1193 // Enqueue Bootstrap component
1194 if(!$settings['disable_bootstrap_css']) {
1195 wp_enqueue_script('gptranslate-bstoast', plugin_dir_url(__FILE__) . 'assets/js/toast.min.js', [], $this->version, true);
1196 wp_enqueue_style(
1197 'bootstrap-css',
1198 plugin_dir_url(__FILE__) . 'assets/css/bootstrap.min.css',
1199 [],
1200 '5.3.2'
1201 );
1202 }
1203
1204 // Registra un handle CSS vuoto se necessario
1205 wp_register_style('gptranslate-dynamic-css', false, [], $this->version);
1206 wp_enqueue_style('gptranslate-dynamic-css');
1207
1208 // Prepara lo stile dinamico
1209 $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') . '; }' .
1210 '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; }' .
1211 'div.gpt_float_switcher { border-radius: ' . intval($settings['popup_border_radius']) . 'px; }' .
1212 'div.gpt_float_switcher img, svg.svg-inline--fa { box-sizing: border-box; width: ' . intval($settings['popup_iconsize']) . 'px; }';
1213
1214 // Inietta il CSS inline
1215 wp_add_inline_style('gptranslate-dynamic-css', $dynamic_css);
1216 }
1217 }
1218
1219 // Force WordPress.org update check on plugin activation
1220 register_activation_hook( __FILE__, function() {
1221 if ( function_exists('wp_update_plugins') ) {
1222 wp_update_plugins();
1223 }
1224 });
1225
1226 // Schedule a daily update check
1227 add_action( 'gptranslate_daily_update_check', function() {
1228 if ( function_exists('wp_update_plugins') ) {
1229 wp_update_plugins();
1230 }
1231 });
1232
1233 if ( ! wp_next_scheduled( 'gptranslate_daily_update_check' ) ) {
1234 wp_schedule_event( time(), 'daily', 'gptranslate_daily_update_check' );
1235 }
1236
1237 // 🧹 Cleanup scheduled event on deactivation
1238 register_deactivation_hook( __FILE__, function() {
1239 wp_clear_scheduled_hook( 'gptranslate_daily_update_check' );
1240 });
1241
1242 /**
1243 * Global function to add links to WP
1244 *
1245 * @access public
1246 */
1247 add_filter('plugin_action_links_' . plugin_basename(__FILE__), function($links) {
1248 $settings_link = '<a href="admin.php?page=gptranslate">' . esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_SETTINGS_MENU_TITLE')) . '</a>';
1249 array_unshift($links, $settings_link);
1250 return $links;
1251 });
1252
1253 /**
1254 * Add main admin scripts for example to manage records add/delete functions
1255 *
1256 * @access public
1257 */
1258 add_action('admin_enqueue_scripts', function() {
1259 if(isset($_GET['page']) && strpos(sanitize_key($_GET['page']), 'gptranslate') !== false) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1260 // Enqueue JS
1261 wp_enqueue_script ( 'gptranslate-js', plugin_dir_url ( __FILE__ ) . 'assets/js/admin.js', [ ], GPTranslate::$pluginVersion, true );
1262
1263 // Enqueue CSS
1264 wp_enqueue_style ( 'gptranslate-css', plugin_dir_url ( __FILE__ ) . 'assets/css/admin.css', [ ], GPTranslate::$pluginVersion );
1265
1266 wp_localize_script('gptranslate-js', 'gptranslate_vars', [
1267 'ajaxurl' => admin_url('admin-ajax.php'),
1268 'nonce' => wp_create_nonce('gptranslate_migrate_translations'),
1269 'deletenonce' => wp_create_nonce('gptranslate_delete_translations'),
1270 'gptApiKey' => hash( 'sha256', get_site_url() )
1271 ]);
1272 }
1273 });
1274
1275 add_action('wp_ajax_gptranslate_bulk_delete', function () {
1276 if (!current_user_can('manage_options') || !check_ajax_referer('gptranslate_delete_translations', '_wpnonce', false)) {
1277 wp_send_json_error(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1278 }
1279
1280 global $wpdb;
1281 $ids = isset($_POST['gptid']) ? array_map('intval', (array) $_POST['gptid']) : [];
1282
1283 if (empty($ids)) {
1284 wp_send_json_error('No records selected');
1285 }
1286
1287 $table = $wpdb->prefix . 'gptranslate';
1288 $in = implode(',', array_fill(0, count($ids), '%d'));
1289 $sql = "DELETE FROM {$table} WHERE id IN ($in)";
1290 $result = $wpdb->query($wpdb->prepare($sql, ...$ids)); // phpcs:ignore
1291
1292 if ($result === false) {
1293 wp_send_json_error('Database error');
1294 }
1295
1296 wp_send_json_success();
1297 });
1298
1299 // Handle Export CSV
1300 add_action('admin_post_gptranslate_export_translations_csv', function () {
1301 if (!current_user_can('manage_options') || !check_admin_referer('gptranslate_export_csv', 'gptranslate_export_csv_nonce')) {
1302 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1303 }
1304
1305 global $wpdb;
1306 $table = $wpdb->prefix . 'gptranslate';
1307
1308 $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
1309
1310 if (!$records) {
1311 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_NOTRANSLATIONS')));
1312 }
1313
1314 $localDate = get_date_from_gmt(gmdate('Y-m-d'));
1315 $fileDate = ( date_i18n('Y-m-d', strtotime($localDate)) );
1316
1317 header('Content-Type: text/csv');
1318 header('Content-Disposition: attachment; filename="gptranslate-translations-' . $fileDate . '.csv"');
1319
1320 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1321 $output = fopen('php://output', 'w');
1322
1323 // Intestazioni CSV
1324 fputcsv($output, array_keys($records[0]));
1325
1326 foreach ($records as $record) {
1327 fputcsv($output, $record);
1328 }
1329
1330 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1331 fclose($output);
1332 exit;
1333 });
1334
1335 // Handle Import CSV
1336 add_action('admin_post_gptranslate_import_translations_csv', function () {
1337 if (!current_user_can('manage_options') || !check_admin_referer('gptranslate_import_csv', 'gptranslate_import_csv_nonce')) {
1338 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1339 }
1340
1341 if (!isset($_FILES['import_file'], $_FILES['import_file']['error'], $_FILES['import_file']['tmp_name']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
1342 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UPLOAD_FAILED')));
1343 }
1344
1345 $tmp_name = $_FILES['import_file']['tmp_name']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1346 if (!is_uploaded_file($tmp_name)) {
1347 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_FAILED_UPLOADED_FILE')));
1348 }
1349
1350 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1351 $file = fopen($tmp_name, 'r');
1352 if (!$file) {
1353 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_FAILED_UPLOADED_FILE')));
1354 }
1355
1356 $headers = fgetcsv($file);
1357 if (!$headers) {
1358 wp_die(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_INVALID_CSV_FORMAT')));
1359 }
1360
1361 global $wpdb;
1362 $table = $wpdb->prefix . 'gptranslate';
1363
1364 while (($row = fgetcsv($file)) !== false) {
1365 $countHeaders = count($headers);
1366 $countRow = count($row);
1367 // Invalid combine
1368 if($countHeaders != $countRow) {
1369 continue;
1370 }
1371 $record = array_combine($headers, $row);
1372 if (empty($record['pagelink'])) {
1373 continue; // skip if no primary key
1374 }
1375
1376 $pagelink = sanitize_text_field($record['pagelink']);
1377 $exists = $wpdb->get_var($wpdb->prepare("SELECT id FROM $table WHERE pagelink = %s", $pagelink)); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1378
1379 $data = [
1380 'translated_alias' => $record['translated_alias'],
1381 'translations' => $record['translations'],
1382 'alt_translations' => $record['alt_translations'],
1383 'languageoriginal' => sanitize_text_field($record['languageoriginal']),
1384 'languagetranslated' => sanitize_text_field($record['languagetranslated']),
1385 'published' => isset($record['published']) ? (int)$record['published'] : 1,
1386 'translate_date' => $record['translate_date'],
1387 'translation_engine' => sanitize_text_field($record['translation_engine']),
1388 ];
1389
1390 if ($exists) {
1391 $wpdb->update( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1392 $table,
1393 $data,
1394 ['pagelink' => $pagelink],
1395 ['%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s'],
1396 ['%s']
1397 );
1398 } else {
1399 $data['pagelink'] = $pagelink;
1400 $wpdb->insert( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1401 $table,
1402 $data,
1403 ['%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s']
1404 );
1405 }
1406 }
1407
1408 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1409 fclose($file);
1410
1411 wp_redirect(admin_url('admin.php?page=gptranslate&imported=1'));
1412 exit;
1413 });
1414
1415
1416 // Register Ajax handler
1417 add_action('wp_ajax_gptranslate_migrate_translations', function() {
1418 if (!current_user_can('manage_options')) {
1419 wp_send_json_error(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')));
1420 }
1421
1422 check_ajax_referer('gptranslate_migrate_translations');
1423
1424 global $wpdb;
1425 $table = $wpdb->prefix . 'gptranslate';
1426 $old = isset($_POST['old_domain']) ? sanitize_text_field(wp_unslash($_POST['old_domain'])) : '';
1427 $new = isset($_POST['new_domain']) ? sanitize_text_field(wp_unslash($_POST['new_domain'])) : '';
1428
1429 if (empty($old) || empty($new)) {
1430 wp_send_json_error(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_MISSING_DOMAIN_VALUES')));
1431 }
1432
1433 $query = $wpdb->prepare(
1434 "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
1435 $old, $new, $old, $new
1436 );
1437
1438 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Dynamic query built with placeholders, safely prepared
1439 $result = $wpdb->query($query); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1440
1441 if ($result === false) {
1442 wp_send_json_error(esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_DATABASE_ERROR')));
1443 } else {
1444 wp_send_json_success();
1445 }
1446 });
1447
1448 function gptranslate_export_settings() {
1449 if ( ! current_user_can( 'manage_options' ) || !check_admin_referer('gptranslate_export_settings', 'gptranslate_export_settings_nonce')) {
1450 wp_die( esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')) );
1451 }
1452
1453 $options = get_option( 'gptranslate_options', [] );
1454
1455 $localDate = get_date_from_gmt(gmdate('Y-m-d'));
1456 $fileDate = ( date_i18n('Y-m-d', strtotime($localDate)) );
1457
1458 header( 'Content-Type: application/json' );
1459 header( 'Content-Disposition: attachment; filename="gptranslate-settings-' . $fileDate . '.json"' );
1460 echo wp_json_encode( $options );
1461 exit;
1462 }
1463 add_action( 'admin_post_gptranslate_export_settings', 'gptranslate_export_settings' );
1464
1465 function gptranslate_import_settings() {
1466 if ( ! current_user_can( 'manage_options' ) || !check_admin_referer('gptranslate_import_settings', 'gptranslate_import_settings_nonce')) {
1467 wp_die( esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UNAUTHORIZED_REQUEST')) );
1468 }
1469
1470 if ( empty( $_FILES['gptranslate_settings_file']['tmp_name'] ) ) {
1471 wp_die( esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_UPLOAD_FAILED')) );
1472 }
1473
1474 $content = file_get_contents( $_FILES['gptranslate_settings_file']['tmp_name'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1475 $decoded = json_decode( $content, true );
1476
1477 if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $decoded ) ) {
1478 wp_die( esc_html($this->loadTranslations('PLG_GPTRANSLATE_INVALID_JSON_TRANSLATIONS') ) );
1479 }
1480
1481 // Optional: sanitize known values, or just update if you're confident of source
1482 update_option( 'gptranslate_options', $decoded );
1483
1484 wp_safe_redirect( admin_url( 'admin.php?page=gptranslate-settings&settingsimported=1' ) );
1485 exit;
1486 }
1487 add_action( 'admin_post_gptranslate_import_settings', 'gptranslate_import_settings' );
1488
1489 add_action('wp_footer', function () {
1490 // Add the default target container if default CSS selector
1491 $settings = get_option("gptranslate_options");
1492
1493 // Disable interface
1494 if($settings ['disable_control']) {
1495 $settings ['wrapper_selector'] = '';
1496 }
1497
1498 if ($settings ['wrapper_selector'] == '.gptranslate_wrapper') {
1499 echo '<div class="gptranslate_wrapper" id="gpt-wrapper"></div>';
1500 }
1501 });
1502
1503 // POST API REST Translations storage
1504 add_action('rest_api_init', function () {
1505 register_rest_route('gptranslate/v1', '/request', [
1506 'methods' => 'POST',
1507 'callback' => 'gpt_handle_request',
1508 'permission_callback' => 'gptranslate_public_permission'
1509 ]);
1510 });
1511
1512 add_filter('plugin_action_links_' . plugin_basename(__FILE__), function ($links) {
1513 // Remove the default 'Settings' item
1514 unset($links[0]);
1515
1516 $settings_link = '<a href="' . esc_url(admin_url('admin.php?page=gptranslate-settings')) . '">' . esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_SETTINGS_MENU_TITLE')) . '</a>';
1517 array_unshift($links, $settings_link);
1518
1519 $translations_link = '<a href="' . esc_url(admin_url('admin.php?page=gptranslate')) . '">' . esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_TRANSLATIONS')) . '</a>';
1520 array_unshift($links, $translations_link);
1521
1522 return $links;
1523 });
1524
1525 /*
1526 // Remove any WP update for the free version over the paid full one
1527 add_filter('auto_update_plugin', function($update, $item) {
1528 if (isset($item->slug) && $item->slug === 'gptranslate') {
1529 return false;
1530 }
1531 return $update;
1532 }, 10, 2);
1533
1534
1535 add_filter('site_transient_update_plugins', function($transient) {
1536 if (isset($transient->response['gptranslate/gptranslate.php'])) {
1537 unset($transient->response['gptranslate/gptranslate.php']);
1538 }
1539 return $transient;
1540 });
1541 */
1542
1543 /**
1544 * Permission callback public API
1545 * @param WP_REST_Request $request
1546 * @return bool|WP_Error
1547 */
1548 function gptranslate_public_permission( WP_REST_Request $request ) {
1549 // 1) Controllo chiave API inviata via header
1550 $headerApiKey = $request->get_header('x-gptranslate-key');
1551 $restApiKey = hash( 'sha256', get_site_url() );
1552 if ( $headerApiKey != $restApiKey) {
1553 return new WP_Error( 'rest_forbidden', esc_html(GPTranslate::loadTranslation('PLG_GPTRANSLATE_FORBIDDEN_APIKEY')), [ 'status' => 403 ] );
1554 }
1555 return true;
1556 }
1557
1558 /**
1559 * Callback per GET/STORE translations via REST.
1560 * Frontend API
1561 *
1562 * @param WP_REST_Request $request
1563 * @return WP_REST_Response
1564 */
1565 function gpt_handle_request( WP_REST_Request $request ) {
1566 global $wpdb;
1567
1568 $table = $wpdb->prefix . 'gptranslate';
1569
1570 $params = $request->get_json_params();
1571
1572 if(!$params) {
1573 $params = $request->get_body_params();
1574 }
1575
1576 // Sanitize input params
1577 $task = sanitize_text_field( $params['task'] ?? '' );
1578 $pageLink = sanitize_text_field( $params['pagelink'] ?? '' );
1579 $translatedAlias = sanitize_text_field( $params['translated_alias'] ?? '' );
1580 $languageOriginal = sanitize_text_field( $params['language_original'] ?? '' );
1581 $languageTranslated = sanitize_text_field( $params['language_translated'] ?? '' );
1582 $translationEngine = sanitize_text_field( $params['translation_engine'] ?? '' );
1583
1584 $now = current_time( 'mysql' );
1585
1586 $response = [ 'result' => false ];
1587
1588 if ( $task === 'storetranslations' ) {
1589 // Fetch raw param (could be array or JSON string)
1590 $rawFull = $params['translations'] ?? '[]';
1591 $rawAlt = $params['alt_translations'] ?? '[]';
1592
1593 // If it’s already a string (JSON), use it directly.
1594 // If it’s an array (unlikely with FormData), JSON encode it.
1595 if ( is_string( $rawFull ) && json_decode( $rawFull ) !== null ) {
1596 $fullTranslations = $rawFull;
1597 } else {
1598 $fullTranslations = wp_json_encode( (array) $rawFull );
1599 }
1600
1601 if ( is_string( $rawAlt ) && json_decode( $rawAlt ) !== null ) {
1602 $altTranslations = $rawAlt;
1603 } else {
1604 $altTranslations = wp_json_encode( (array) $rawAlt );
1605 }
1606
1607 // Verifica se esiste già
1608 $existing_id = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1609 "SELECT id FROM {$table}" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
1610 "\n WHERE (pagelink = %s OR pagelink = %s)" .
1611 "\n AND languageoriginal = %s" .
1612 "\n AND languagetranslated = %s",
1613 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $languageOriginal, $languageTranslated
1614 ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Dynamic query built with placeholders, safely prepared
1615
1616 if ( $existing_id ) {
1617 // UPDATE
1618 $updated = $wpdb->update( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1619 $table,
1620 [
1621 'translations' => $fullTranslations,
1622 'alt_translations' => $altTranslations,
1623 'translated_alias' => $translatedAlias,
1624 'translate_date' => $now,
1625 'translation_engine' => $translationEngine,
1626 ],
1627 [ 'id' => (int) $existing_id ],
1628 [ '%s', '%s', '%s', '%s', '%s' ],
1629 [ '%d' ]
1630 );
1631 $response['result'] = ( $updated !== false );
1632 } else {
1633 // INSERT
1634 $inserted = $wpdb->insert( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1635 $table,
1636 [
1637 'pagelink' => $pageLink,
1638 'translations' => $fullTranslations,
1639 'alt_translations' => $altTranslations,
1640 'translated_alias' => $translatedAlias,
1641 'languageoriginal' => $languageOriginal,
1642 'languagetranslated' => $languageTranslated,
1643 'published' => 1,
1644 'translate_date' => $now,
1645 'translation_engine' => $translationEngine,
1646 ],
1647 [ '%s','%s','%s','%s','%s','%s','%d','%s','%s' ]
1648 );
1649 $response['result'] = ( $inserted !== false );
1650 }
1651 } elseif ( $task === 'gettranslations' ) {
1652 $opts = get_option( 'gptranslate_options', [] );
1653 if ( ! empty( $opts['realtime_translations'] ) ) {
1654 $response['result'] = false;
1655 } else {
1656 if ($opts['rewrite_language_url'] == 1 && $opts['rewrite_language_alias'] == 1) {
1657 $row = $wpdb->get_row( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1658 "SELECT translations, alt_translations, translated_alias, pagelink FROM {$table}" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
1659 "\n WHERE (pagelink = %s OR pagelink = %s OR translated_alias = %s OR translated_alias = %s)" .
1660 "\n AND languageoriginal = %s" .
1661 "\n AND languagetranslated = %s" .
1662 "\n AND published = 1",
1663 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $languageOriginal, $languageTranslated
1664 ), ARRAY_A );
1665 } else {
1666 $row = $wpdb->get_row( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1667 "SELECT translations, alt_translations, translated_alias, pagelink FROM {$table}" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
1668 "\n WHERE (pagelink = %s OR pagelink = %s)" .
1669 "\n AND languageoriginal = %s" .
1670 "\n AND languagetranslated = %s" .
1671 "\n AND published = 1",
1672 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $languageOriginal, $languageTranslated
1673 ), ARRAY_A );
1674 }
1675
1676 if ( $row ) {
1677 $response['result'] = true;
1678 $response['translations'] = json_decode( $row['translations'], true ) ?: [];
1679 $response['alt_translations'] = json_decode( $row['alt_translations'], true ) ?: [];
1680 $response['translated_alias'] = $row['translated_alias'];
1681 $response['pagelink_alias'] = $row['pagelink'];
1682 } else {
1683 $response['result'] = false;
1684 }
1685 }
1686 } elseif ($task == 'getaliastranslation') {
1687 // Always perform a new realtime translation if the option is enabled
1688 try {
1689 $row = $wpdb->get_row( $wpdb->prepare(
1690 "SELECT translated_alias FROM {$table}" .
1691 "\n WHERE (pagelink = %s OR pagelink = %s)" .
1692 "\n AND languageoriginal = %s" .
1693 "\n AND languagetranslated = %s" .
1694 "\n AND published = 1",
1695 rtrim($pageLink, '/'), rtrim($pageLink, '/') . '/', $languageOriginal, $languageTranslated
1696 ), ARRAY_A );
1697
1698 if ( $row ) {
1699 $response['result'] = true;
1700 $response['translated_alias'] = $row['translated_alias'] ?? '';
1701 } else {
1702 $response['result'] = false;
1703 }
1704 } catch ( Exception $e ) {
1705 $response['result'] = false;
1706 $response['exception'] = $e->getMessage();
1707 }
1708 } elseif ( $task === 'syncTranslation' ) {
1709 $original = wp_unslash( $params['original'] ?? '' );
1710 $translated = wp_unslash( $params['translated'] ?? '' );
1711 $languageTranslated = sanitize_text_field( $params['language_translated'] ?? '' );
1712 $translationType = sanitize_text_field( $params['translation_type'] ?? 'translations' ); // default to 'translations'
1713
1714 // Recupera tutti i record nella lingua target
1715 $rows = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1716 "SELECT id, {$translationType}, languagetranslated" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
1717 "\n FROM {$table}" . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query built with placeholders, safely prepared
1718 "\n WHERE languagetranslated = %s",
1719 $languageTranslated
1720 ) );
1721
1722 $updatedCount = 0;
1723
1724 foreach ( $rows as $row ) {
1725 $currentTranslations = json_decode( $row->$translationType, true );
1726
1727 if ( is_array( $currentTranslations ) && array_key_exists( $original, $currentTranslations ) ) {
1728 // Aggiorna la traduzione e salva
1729 $currentTranslations[ $original ] = $translated;
1730 $jsonUpdated = wp_json_encode( $currentTranslations );
1731
1732 $success = $wpdb->update( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1733 $table,
1734 [ $translationType => $jsonUpdated, 'translate_date' => $now ],
1735 [ 'id' => $row->id ],
1736 [ '%s', '%s' ],
1737 [ '%d' ]
1738 );
1739
1740 if ( $success !== false ) {
1741 $updatedCount++;
1742 }
1743 }
1744 }
1745
1746 $response['result'] = $updatedCount > 0;
1747 }
1748
1749 return rest_ensure_response( $response );
1750 }
1751
1752 // Instantiate and run the app
1753 GPTranslate::get_instance();