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