PluginProbe
Translate and Go multilingual – Automatic AI translation – wpLingua / 2.14.2
Translate and Go multilingual – Automatic AI translation – wpLingua v2.14.2
2.16.7 2.16.6 2.16.5 2.16.4 2.16.3 2.16.2 2.16.1 2.16.0 2.15.2 2.15.1 2.15.0 2.14.3 2.14.2 2.14.1 2.14.0 2.13.1 2.13.0 2.12.3 2.12.2 2.12.1 trunk 1.0.3 1.0.4 1.0.5 1.1.0 All 112 releases
wplingua / inc / admin / translation-meta.php

translation-meta.php in Translate and Go multilingual – Automatic AI translation – wpLingua 2.14.2, at inc/admin/translation-meta.php

550 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // If this file is called directly, abort.
4 if ( ! defined( 'WPINC' ) ) {
5 die;
6 }
7
8
9 /**
10 * Add meta box on wpLingua translations
11 *
12 * This function adds a meta box to the wpLingua Translations post type in the
13 * WordPress admin interface. The meta box is used to display and edit the
14 * translation of a post.
15 *
16 * @param object $post The post object.
17 * @return void
18 */
19 function wplng_translation_add_meta_box( $post ) {
20
21 add_meta_box(
22 'wplng_meta_box_translation', // Unique ID for the meta box
23 __( 'Translation', 'wplingua' ), // Title of the meta box
24 'wplng_translation_meta_box_html_output', // Callback function to render the meta box HTML
25 'wplng_translation', // Screen or post type where the meta box appears
26 'normal', // Context where the meta box should appear
27 'low' // Priority within the context
28 );
29 }
30
31
32 /**
33 * Print HTML of translations editor meta box in back office
34 *
35 * This function prints the HTML of the translations editor meta box in the
36 * WordPress admin interface. The meta box is used to display and edit the
37 * translation of a post.
38 *
39 * @param object $post The post object.
40 * @return string HTML The HTML of the meta box.
41 */
42 function wplng_translation_meta_box_html_output( $post ) {
43
44 echo '<div id="wplng-translation-editor">';
45 echo wplng_translation_editor_get_html( $post );
46 echo '</div>';
47 }
48
49
50 /**
51 * Return HTML of translations editor in modal
52 *
53 * @param WP_Post $post The post object.
54 * @return string The HTML of the translations editor in the modal.
55 */
56 function wplng_translation_editor_get_html( $post ) {
57
58 // used later for security
59 $html = wp_nonce_field(
60 basename( __FILE__ ),
61 'wplng_translation_meta_box_nonce',
62 true,
63 false
64 );
65
66 $meta = get_post_meta( $post->ID );
67
68 // Display original text and notice if necesary
69 if ( ! empty( $meta['wplng_translation_original'][0] )
70 && is_string( $meta['wplng_translation_original'][0] )
71 && ! empty( $meta['wplng_translation_original_language_id'][0] )
72 && wplng_is_valid_language_id( $meta['wplng_translation_original_language_id'][0] )
73 ) {
74
75 $language_id = $meta['wplng_translation_original_language_id'][0];
76 $language = wplng_get_language_by_id( $language_id );
77 $alt = __( 'Flag for language: ', 'wplingua' ) . $language['name'];
78 $text_source = wplng_text_esc_displayed( $meta['wplng_translation_original'][0] );
79 $html_notice = '';
80
81 // Make the notice
82 if ( wplng_str_contains( $text_source, '%wplng-search-query%' ) ) {
83 $html_notice .= '<div';
84 $html_notice .= ' class="wplng-help-box"';
85 $html_notice .= ' style="display: block; margin: 15px 0 -10px;"';
86 $html_notice .= '>';
87 $html_notice .= sprintf(
88 esc_html__( 'This text contains the tag %s, which will be replaced by the term the visitor searched for. Please keep it in your translation.', 'wplingua' ),
89 '<u>%wplng-search-query%</u>'
90 );
91 $html_notice .= '</div>';
92 }
93
94 $html .= '<div';
95 $html .= ' id="wplng-original-language" wplng-lang="' . esc_attr( $language_id ) . '"';
96 $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
97 $html .= '>';
98
99 $html .= '<div id="wplng-source-title">';
100 $html .= '<img';
101 $html .= ' src="' . esc_url( $language['flag'] ) . '"';
102 $html .= ' alt="' . esc_attr( $alt ) . '"';
103 $html .= ' class="wplng-flag"';
104 $html .= '>';
105 $html .= esc_html( $language['name'] );
106 $html .= esc_html__( ' - Original text: ', 'wplingua' );
107 $html .= '</div>'; // End #wplng-source-title
108 $html .= '<div id="wplng-source">';
109 $html .= esc_html( $text_source );
110 $html .= '</div>'; // End #wplng-source
111 $html .= '</div>'; // End #wplng-original-language
112 $html .= $html_notice;
113
114 }
115
116 // Foreach translation, display form textarea to edit
117 if ( ! empty( $meta['wplng_translation_translations'][0] )
118 && is_string( $meta['wplng_translation_translations'][0] )
119 ) {
120
121 $translations_data = json_decode( $meta['wplng_translation_translations'][0], true );
122 $languages_target = wplng_get_languages_target_ids();
123 $translations = array();
124
125 if ( empty( $translations_data ) ) {
126 $translations_data = array();
127 }
128
129 foreach ( $languages_target as $language_target ) {
130
131 $is_in = false;
132
133 foreach ( $translations_data as $translation_data ) {
134
135 if ( empty( $translation_data['language_id'] )
136 || ! wplng_is_valid_language_id( $translation_data['language_id'] )
137 || empty( $translation_data['translation'] )
138 || ! is_string( $translation_data['translation'] )
139 || ( $translation_data['language_id'] !== $language_target )
140 ) {
141 continue;
142 }
143
144 $is_in = true;
145 $translations[] = $translation_data;
146
147 }
148
149 if ( ! $is_in ) {
150 $translations[] = array(
151 'language_id' => $language_target,
152 'translation' => '[WPLNG_EMPTY]',
153 'status' => 'ungenerated',
154 );
155 }
156 }
157
158 foreach ( $translations as $translation ) {
159
160 $language_id = $translation['language_id'];
161 $language = wplng_get_language_by_id( $language_id );
162 $textarea = $translation['translation'];
163 $name = 'wplng_translation_' . $language_id;
164 $container_id = 'wplng-translation-' . $language_id;
165 $generate_link = __( 'Regenerate translation', 'wplingua' );
166 $alt = __( 'Flag for language: ', 'wplingua' ) . $language['name'];
167 $class = 'wplng-edit-language';
168 $reviewed_title = __( 'Mark as review', 'wplingua' );
169 $is_reviewed = false;
170
171 if ( '[WPLNG_EMPTY]' === $textarea ) {
172 $textarea = '';
173 }
174
175 switch ( $translation['status'] ) {
176 case 'ungenerated':
177 $generate_link = __( 'Generate translation', 'wplingua' );
178 $class .= ' wplng-status-ungenerated';
179 break;
180
181 case 'generated':
182 $class .= ' wplng-status-generated';
183 break;
184
185 default:
186 if ( is_int( $translation['status'] ) ) {
187
188 $class .= ' wplng-status-reviewed';
189 $is_reviewed = true;
190
191 // Get and check date format
192 $date_format = get_option( 'date_format' );
193 if ( ! is_string( $date_format ) || empty( $date_format ) ) {
194 $date_format = 'F j, Y';
195 }
196
197 // Get and check time format
198 $time_format = get_option( 'time_format' );
199 if ( ! is_string( $time_format ) || empty( $time_format ) ) {
200 $time_format = 'g:i a';
201 }
202
203 $reviewed_title = __( 'Reviewed on ', 'wplingua' );
204 $reviewed_title .= esc_html(
205 gmdate(
206 $date_format,
207 $translation['status']
208 )
209 );
210 $reviewed_title .= ', ' . esc_html(
211 gmdate(
212 $time_format,
213 $translation['status']
214 )
215 );
216 }
217 break;
218 }
219
220 $html .= '<div';
221 $html .= ' id="' . esc_attr( $container_id ) . '"';
222 $html .= ' class="' . esc_attr( $class ) . '"';
223 $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
224 $html .= '>';
225 $html .= '<label for="' . esc_attr( $name ) . '" class="wplng-target-title">';
226 $html .= '<img';
227 $html .= ' src="' . esc_url( $language['flag'] ) . '"';
228 $html .= ' alt="' . esc_attr( $alt ) . '"';
229 $html .= ' class="wplng-flag"';
230 $html .= '>';
231 $html .= esc_html( $language['name'] );
232 $html .= esc_html__( ' - Translation: ', 'wplingua' );
233 $html .= '</label>';
234 $html .= '<textarea';
235 $html .= ' name="' . esc_attr( $name ) . '"';
236 $html .= ' id="' . esc_attr( $name ) . '"';
237 $html .= ' class="wplng-translation-textarea"';
238 $html .= ' lang="' . esc_attr( $language_id ) . '"';
239 $html .= ' spellcheck="false"';
240 $html .= '>';
241 $html .= esc_html( html_entity_decode( $textarea ) );
242 $html .= '</textarea>';
243
244 if ( empty( $translation['status'] ) ) {
245 $html .= '</div>';
246 continue;
247 }
248
249 $html .= '<div class="wplng-translation-footer">';
250 $html .= '<div class="wplng-translation-footer-left">';
251
252 $html .= '<fieldset class="wplng-mark-as-reviewed">';
253
254 $html .= '<input';
255 $html .= ' type="checkbox"';
256 $html .= ' id="wplng_mark_as_reviewed_' . esc_attr( $language_id ) . '"';
257 $html .= ' name="wplng_mark_as_reviewed_' . esc_attr( $language_id ) . '"';
258 $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
259 $html .= checked( $is_reviewed, true, false );
260 $html .= '>';
261
262 $html .= '<label';
263 $html .= ' for="wplng_mark_as_reviewed_' . esc_attr( $language_id ) . '"';
264 $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
265 $html .= ' title="' . esc_attr( $reviewed_title ) . '"';
266 $html .= '>';
267 $html .= esc_html__( 'Is reviewed', 'wplingua' );
268 $html .= '</label>';
269
270 $html .= '</fieldset>';
271
272 $html .= '</div>'; // End .wplng-translation-footer-right
273 $html .= '<div class="wplng-translation-footer-right">';
274
275 $html .= '<span';
276 $html .= ' class="dashicons dashicons-update wplng-spin wplng-generate-spin"';
277 $html .= ' style="display: none;"';
278 $html .= '></span> ';
279
280 $html .= '<a';
281 $html .= ' href="javascript:void(0);"';
282 $html .= ' class="wplng-generate"';
283 $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
284 $html .= '>';
285 $html .= esc_html( $generate_link );
286 $html .= '</a>';
287
288 $html .= '</div>'; // End .wplng-translation-footer-right
289 $html .= '</div>'; // End .wplng-translation-footer
290 $html .= '</div>'; // End .wplng-edit-language
291
292 }
293 }
294
295 if ( ! empty( $meta['wplng_translation_discovery_url'][0] )
296 && is_string( $meta['wplng_translation_discovery_url'][0] )
297 ) {
298 $url = home_url( $meta['wplng_translation_discovery_url'][0] );
299
300 $html .= '<div id="wplng-discovery-url">';
301 $html .= '<strong>';
302 $html .= esc_html__( 'Discovery URL: ', 'wplingua' ) . ' ';
303 $html .= '</strong>';
304 $html .= '<a';
305 $html .= ' href="' . esc_url( $url ) . '"';
306 $html .= '>';
307 $html .= esc_html( $url );
308 $html .= '</a>';
309 $html .= '</div>'; // End #wplng-discovery-url
310 }
311
312 return $html;
313 }
314
315
316 /**
317 * Save meta box data of wpLingua translations
318 *
319 * @param int $post_id
320 * @return void
321 */
322 function wplng_translation_save_meta_boxes_data( $post_id ) {
323
324 // Check if nonce is set
325 if ( ! isset( $_POST['wplng_translation_meta_box_nonce'] ) ) {
326 return false;
327 }
328
329 // Sanitize the nonce
330 $nonce = $_POST['wplng_translation_meta_box_nonce'];
331 $nonce = sanitize_text_field( wp_unslash( $nonce ) );
332
333 // Check for nonce to top xss
334 if ( ! wp_verify_nonce( $nonce, basename( __FILE__ ) ) ) {
335 return false;
336 }
337
338 // check for correct user capabilities - stop internal xss from customers
339 if ( ! current_user_can( 'edit_post', $post_id ) ) {
340 return false;
341 }
342
343 wplng_clear_translations_cache();
344
345 $meta = get_post_meta( $post_id );
346
347 if ( empty( $meta['wplng_translation_translations'][0] ) ) {
348 return false;
349 }
350
351 $languages_target = wplng_get_languages_target_ids();
352 $translations = json_decode(
353 $meta['wplng_translation_translations'][0],
354 true
355 );
356
357 if ( empty( $translations ) ) {
358 $translations = array();
359 }
360
361 foreach ( $languages_target as $language_target ) {
362
363 $is_in = false;
364
365 foreach ( $translations as $translation ) {
366
367 if ( empty( $translation['language_id'] )
368 || ! wplng_is_valid_language_id( $translation['language_id'] )
369 || ! isset( $translation['translation'] )
370 || ! is_string( $translation['translation'] )
371 || ( $translation['language_id'] !== $language_target )
372 ) {
373 continue;
374 }
375
376 $is_in = true;
377 break;
378 }
379
380 if ( ! $is_in ) {
381 $translations[] = array(
382 'language_id' => $language_target,
383 'translation' => '[WPLNG_EMPTY]',
384 'status' => 'ungenerated',
385 );
386 }
387 }
388
389 foreach ( $translations as $key => $translation ) {
390
391 if ( empty( $translation['language_id'] )
392 || ! wplng_is_valid_language_id( $translation['language_id'] )
393 ) {
394 continue;
395 }
396
397 $name = 'wplng_translation_' . $translation['language_id'];
398 $reviewed = 'wplng_mark_as_reviewed_' . $translation['language_id'];
399
400 if ( ! isset( $_POST[ $name ] ) ) {
401 continue;
402 }
403
404 $temp = wplng_text_esc( wp_unslash( $_POST[ $name ] ) );
405
406 if ( empty( $temp ) ) {
407 $temp = '[WPLNG_EMPTY]';
408 $translations[ $key ]['status'] = 'ungenerated';
409 } else {
410
411 if ( $temp !== $translation['translation'] ) {
412 $temp = str_replace( '\\', '', $temp );
413 }
414
415 if ( ! empty( $_POST[ $reviewed ] ) ) {
416 if ( 'false' !== $_POST[ $reviewed ] ) {
417 $translations[ $key ]['status'] = time();
418 } else {
419 $translations[ $key ]['status'] = 'generated';
420 }
421 } else {
422 $translations[ $key ]['status'] = 'generated';
423 }
424 }
425
426 $translations[ $key ]['translation'] = $temp;
427 }
428
429 /**
430 * Save meta: Translation array as JSON
431 */
432
433 $meta_return = update_post_meta(
434 $post_id,
435 'wplng_translation_translations',
436 wp_json_encode(
437 $translations,
438 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
439 )
440 );
441
442 if ( false === $meta_return ) {
443
444 // Try to save it with encoded emoji
445 foreach ( $translations as $key => $translation ) {
446
447 if ( empty( $translation['translation'] )
448 || '[WPLNG_EMPTY]' === $translation['translation']
449 ) {
450 continue;
451 }
452
453 $translations[ $key ]['translation'] = wp_encode_emoji( $translation['translation'] );
454 }
455
456 $meta_return = update_post_meta(
457 $post_id,
458 'wplng_translation_translations',
459 wp_json_encode(
460 $translations,
461 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
462 )
463 );
464
465 }
466
467 return $meta_return;
468 }
469
470
471 /**
472 * wpLingua AJAX function to get translations on CPT edit page
473 *
474 * @return void
475 */
476 function wplng_ajax_generate_translation() {
477
478 /**
479 * Check and sanitize data
480 */
481
482 // Check data
483
484 if ( empty( $_POST['language_source'] )
485 || ! is_string( $_POST['language_source'] )
486 || empty( $_POST['language_target'] )
487 || ! is_string( $_POST['language_target'] )
488 || ! isset( $_POST['text'] )
489 || ! is_string( $_POST['text'] )
490 ) {
491 wp_send_json_error( __( 'Invalid parameters', 'wplingua' ) );
492 return;
493 }
494
495 // Check and sanitize sources and target languages
496
497 $language_source = sanitize_key( $_POST['language_source'] );
498 $language_target = sanitize_key( $_POST['language_target'] );
499
500 if ( ! wplng_is_valid_language_id( $language_source )
501 || ! wplng_is_valid_language_id( $language_target )
502 || $language_source === $language_target
503 ) {
504 wp_send_json_error( __( 'Invalid languages parameters', 'wplingua' ) );
505 return;
506 }
507
508 // Check and sanitize text to translate
509 // (And convert img emoji to emoji)
510
511 $text = wp_kses(
512 wp_unslash( $_POST['text'] ),
513 array(
514 'img' => array(
515 'alt' => array(),
516 ),
517 )
518 );
519
520 $text = preg_replace(
521 '/<img alt=\\"(.*)\\">/U',
522 '$1',
523 $text
524 );
525
526 $text = wplng_text_esc( $text );
527
528 if ( ! wplng_text_is_translatable( $text ) ) {
529 wp_send_json_success( $text );
530 return;
531 }
532
533 /**
534 * Call API and get translation
535 */
536
537 $response = wplng_api_call_translate(
538 array( $text ),
539 $language_source,
540 $language_target
541 );
542
543 if ( ! isset( $response[0] ) ) {
544 wp_send_json_error( __( 'Invalid API response', 'wplingua' ) );
545 return;
546 }
547
548 wp_send_json_success( $response[0] );
549 }
550