PluginProbe
Translate and boost SEO – Automatic AI translation – wpLingua / 2.16.8
Translate and boost SEO – Automatic AI translation – wpLingua v2.16.8
2.16.8 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 All 113 releases
← All changes | inc/admin/translation-meta.php +549 -529 2.12.22.16.8 View file →
@@ -1,529 +1,549 @@
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
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 -
79 - $html .= '<div id="wplng-original-language" wplng-lang="' . esc_attr( $language_id ) . '">';
80 - $html .= '<div id="wplng-source-title">';
81 - $html .= '<img';
82 - $html .= ' src="' . esc_url( $language['flag'] ) . '"';
83 - $html .= ' alt="' . esc_attr( $alt ) . '"';
84 - $html .= ' class="wplng-flag"';
85 - $html .= '>';
86 - $html .= esc_html( $language['name'] );
87 - $html .= esc_html__( ' - Original text: ', 'wplingua' );
88 - $html .= '</div>'; // End #wplng-source-title
89 - $html .= '<div id="wplng-source">';
90 - $html .= esc_html( wplng_text_esc_displayed( $meta['wplng_translation_original'][0] ) );
91 - $html .= '</div>'; // End #wplng-source
92 - $html .= '</div>'; // End #wplng-original-language
93 -
94 - }
95 -
96 - // Foreach translation, display form textarea to edit
97 - if ( ! empty( $meta['wplng_translation_translations'][0] )
98 - && is_string( $meta['wplng_translation_translations'][0] )
99 - ) {
100 -
101 - $translations_data = json_decode( $meta['wplng_translation_translations'][0], true );
102 - $languages_target = wplng_get_languages_target_ids();
103 - $translations = array();
104 -
105 - if ( empty( $translations_data ) ) {
106 - $translations_data = array();
107 - }
108 -
109 - foreach ( $languages_target as $language_target ) {
110 -
111 - $is_in = false;
112 -
113 - foreach ( $translations_data as $translation_data ) {
114 -
115 - if ( empty( $translation_data['language_id'] )
116 - || ! wplng_is_valid_language_id( $translation_data['language_id'] )
117 - || empty( $translation_data['translation'] )
118 - || ! is_string( $translation_data['translation'] )
119 - || ( $translation_data['language_id'] !== $language_target )
120 - ) {
121 - continue;
122 - }
123 -
124 - $is_in = true;
125 - $translations[] = $translation_data;
126 -
127 - }
128 -
129 - if ( ! $is_in ) {
130 - $translations[] = array(
131 - 'language_id' => $language_target,
132 - 'translation' => '[WPLNG_EMPTY]',
133 - 'status' => 'ungenerated',
134 - );
135 - }
136 - }
137 -
138 - foreach ( $translations as $translation ) {
139 -
140 - $language_id = $translation['language_id'];
141 - $language = wplng_get_language_by_id( $language_id );
142 - $textarea = $translation['translation'];
143 - $name = 'wplng_translation_' . $language_id;
144 - $container_id = 'wplng-translation-' . $language_id;
145 - $generate_link = __( 'Regenerate translation', 'wplingua' );
146 - $alt = __( 'Flag for language: ', 'wplingua' ) . $language['name'];
147 - $class = 'wplng-edit-language';
148 - $reviewed_title = __( 'Mark as review', 'wplingua' );
149 - $is_reviewed = false;
150 -
151 - if ( '[WPLNG_EMPTY]' === $textarea ) {
152 - $textarea = '';
153 - }
154 -
155 - switch ( $translation['status'] ) {
156 - case 'ungenerated':
157 - $generate_link = __( 'Generate translation', 'wplingua' );
158 - $class .= ' wplng-status-ungenerated';
159 - break;
160 -
161 - case 'generated':
162 - $class .= ' wplng-status-generated';
163 - break;
164 -
165 - default:
166 - if ( is_int( $translation['status'] ) ) {
167 -
168 - $class .= ' wplng-status-reviewed';
169 - $is_reviewed = true;
170 -
171 - // Get and check date format
172 - $date_format = get_option( 'date_format' );
173 - if ( ! is_string( $date_format ) || empty( $date_format ) ) {
174 - $date_format = 'F j, Y';
175 - }
176 -
177 - // Get and check time format
178 - $time_format = get_option( 'time_format' );
179 - if ( ! is_string( $time_format ) || empty( $time_format ) ) {
180 - $time_format = 'g:i a';
181 - }
182 -
183 - $reviewed_title = __( 'Reviewed on ', 'wplingua' );
184 - $reviewed_title .= esc_html(
185 - gmdate(
186 - $date_format,
187 - $translation['status']
188 - )
189 - );
190 - $reviewed_title .= ', ' . esc_html(
191 - gmdate(
192 - $time_format,
193 - $translation['status']
194 - )
195 - );
196 - }
197 - break;
198 - }
199 -
200 - $html .= '<div';
201 - $html .= ' id="' . esc_attr( $container_id ) . '"';
202 - $html .= ' class="' . esc_attr( $class ) . '"';
203 - $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
204 - $html .= '>';
205 - $html .= '<label for="' . esc_attr( $name ) . '" class="wplng-target-title">';
206 - $html .= '<img';
207 - $html .= ' src="' . esc_url( $language['flag'] ) . '"';
208 - $html .= ' alt="' . esc_attr( $alt ) . '"';
209 - $html .= ' class="wplng-flag"';
210 - $html .= '>';
211 - $html .= esc_html( $language['name'] );
212 - $html .= esc_html__( ' - Translation: ', 'wplingua' );
213 - $html .= '</label>';
214 - $html .= '<textarea';
215 - $html .= ' name="' . esc_attr( $name ) . '"';
216 - $html .= ' id="' . esc_attr( $name ) . '"';
217 - $html .= ' class="wplng-translation-textarea"';
218 - $html .= ' lang="' . esc_attr( $language_id ) . '"';
219 - $html .= ' spellcheck="false"';
220 - $html .= '>';
221 - $html .= esc_html( html_entity_decode( $textarea ) );
222 - $html .= '</textarea>';
223 -
224 - if ( empty( $translation['status'] ) ) {
225 - $html .= '</div>';
226 - continue;
227 - }
228 -
229 - $html .= '<div class="wplng-translation-footer">';
230 - $html .= '<div class="wplng-translation-footer-left">';
231 -
232 - $html .= '<fieldset class="wplng-mark-as-reviewed">';
233 -
234 - $html .= '<input';
235 - $html .= ' type="checkbox"';
236 - $html .= ' id="wplng_mark_as_reviewed_' . esc_attr( $language_id ) . '"';
237 - $html .= ' name="wplng_mark_as_reviewed_' . esc_attr( $language_id ) . '"';
238 - $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
239 - $html .= checked( $is_reviewed, true, false );
240 - $html .= '>';
241 -
242 - $html .= '<label';
243 - $html .= ' for="wplng_mark_as_reviewed_' . esc_attr( $language_id ) . '"';
244 - $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
245 - $html .= ' title="' . esc_attr( $reviewed_title ) . '"';
246 - $html .= '>';
247 - $html .= esc_html__( 'Is reviewed', 'wplingua' );
248 - $html .= '</label>';
249 -
250 - $html .= '</fieldset>';
251 -
252 - $html .= '</div>'; // End .wplng-translation-footer-right
253 - $html .= '<div class="wplng-translation-footer-right">';
254 -
255 - $html .= '<span';
256 - $html .= ' class="dashicons dashicons-update wplng-spin wplng-generate-spin"';
257 - $html .= ' style="display: none;"';
258 - $html .= '></span> ';
259 -
260 - $html .= '<a';
261 - $html .= ' href="javascript:void(0);"';
262 - $html .= ' class="wplng-generate"';
263 - $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
264 - $html .= '>';
265 - $html .= esc_html( $generate_link );
266 - $html .= '</a>';
267 -
268 - $html .= '</div>'; // End .wplng-translation-footer-right
269 - $html .= '</div>'; // End .wplng-translation-footer
270 - $html .= '</div>'; // End .wplng-edit-language
271 -
272 - }
273 - }
274 -
275 - if ( ! empty( $meta['wplng_translation_discovery_url'][0] )
276 - && is_string( $meta['wplng_translation_discovery_url'][0] )
277 - ) {
278 - $url = home_url( $meta['wplng_translation_discovery_url'][0] );
279 -
280 - $html .= '<div id="wplng-discovery-url">';
281 - $html .= '<strong>';
282 - $html .= esc_html__( 'Discovery URL: ' ) . ' ';
283 - $html .= '</strong>';
284 - $html .= '<a';
285 - $html .= ' href="' . esc_url( $url ) . '"';
286 - $html .= '>';
287 - $html .= esc_html( $url );
288 - $html .= '</a>';
289 - $html .= '</div>'; // End #wplng-discovery-url
290 - }
291 -
292 - return $html;
293 -}
294 -
295 -
296 -/**
297 - * Save meta box data of wpLingua translations
298 - *
299 - * @param int $post_id
300 - * @return void
301 - */
302 -function wplng_translation_save_meta_boxes_data( $post_id ) {
303 -
304 - // Check if nonce is set
305 - if ( ! isset( $_POST['wplng_translation_meta_box_nonce'] ) ) {
306 - return false;
307 - }
308 -
309 - // Sanitize the nonce
310 - $nonce = $_POST['wplng_translation_meta_box_nonce'];
311 - $nonce = sanitize_text_field( wp_unslash( $nonce ) );
312 -
313 - // Check for nonce to top xss
314 - if ( ! wp_verify_nonce( $nonce, basename( __FILE__ ) ) ) {
315 - return false;
316 - }
317 -
318 - // check for correct user capabilities - stop internal xss from customers
319 - if ( ! current_user_can( 'edit_post', $post_id ) ) {
320 - return false;
321 - }
322 -
323 - wplng_clear_translations_cache();
324 -
325 - $meta = get_post_meta( $post_id );
326 -
327 - if ( empty( $meta['wplng_translation_translations'][0] ) ) {
328 - return false;
329 - }
330 -
331 - $languages_target = wplng_get_languages_target_ids();
332 - $translations = json_decode(
333 - $meta['wplng_translation_translations'][0],
334 - true
335 - );
336 -
337 - if ( empty( $translations ) ) {
338 - $translations = array();
339 - }
340 -
341 - foreach ( $languages_target as $language_target ) {
342 -
343 - $is_in = false;
344 -
345 - foreach ( $translations as $translation ) {
346 -
347 - if ( empty( $translation['language_id'] )
348 - || ! wplng_is_valid_language_id( $translation['language_id'] )
349 - || ! isset( $translation['translation'] )
350 - || ! is_string( $translation['translation'] )
351 - || ( $translation['language_id'] !== $language_target )
352 - ) {
353 - continue;
354 - }
355 -
356 - $is_in = true;
357 - break;
358 - }
359 -
360 - if ( ! $is_in ) {
361 - $translations[] = array(
362 - 'language_id' => $language_target,
363 - 'translation' => '[WPLNG_EMPTY]',
364 - 'status' => 'ungenerated',
365 - );
366 - }
367 - }
368 -
369 - foreach ( $translations as $key => $translation ) {
370 -
371 - if ( empty( $translation['language_id'] )
372 - || ! wplng_is_valid_language_id( $translation['language_id'] )
373 - ) {
374 - continue;
375 - }
376 -
377 - $name = 'wplng_translation_' . $translation['language_id'];
378 - $reviewed = 'wplng_mark_as_reviewed_' . $translation['language_id'];
379 -
380 - if ( ! isset( $_POST[ $name ] ) ) {
381 - continue;
382 - }
383 -
384 - $temp = stripslashes( wplng_text_esc( $_POST[ $name ] ) );
385 -
386 - if ( empty( $temp ) ) {
387 - $temp = '[WPLNG_EMPTY]';
388 - $translations[ $key ]['status'] = 'ungenerated';
389 - } else {
390 -
391 - if ( $temp !== $translation['translation'] ) {
392 - $temp = str_replace( '\\', '', $temp );
393 - }
394 -
395 - if ( ! empty( $_POST[ $reviewed ] ) ) {
396 - if ( 'false' !== $_POST[ $reviewed ] ) {
397 - $translations[ $key ]['status'] = time();
398 - } else {
399 - $translations[ $key ]['status'] = 'generated';
400 - }
401 - } else {
402 - $translations[ $key ]['status'] = 'generated';
403 - }
404 - }
405 -
406 - $translations[ $key ]['translation'] = esc_html( $temp );
407 - }
408 -
409 - /**
410 - * Save meta: Translation array as JSON
411 - */
412 -
413 - $meta_return = update_post_meta(
414 - $post_id,
415 - 'wplng_translation_translations',
416 - wp_json_encode(
417 - $translations,
418 - JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
419 - )
420 - );
421 -
422 - if ( false === $meta_return ) {
423 -
424 - // Try to save it with encoded emoji
425 - foreach ( $translations as $key => $translation ) {
426 -
427 - if ( empty( $translation['translation'] )
428 - || '[WPLNG_EMPTY]' === $translation['translation']
429 - ) {
430 - continue;
431 - }
432 -
433 - $translations[ $key ]['translation'] = wp_encode_emoji( $translation['translation'] );
434 - }
435 -
436 - $meta_return = update_post_meta(
437 - $post_id,
438 - 'wplng_translation_translations',
439 - wp_json_encode(
440 - $translations,
441 - JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
442 - )
443 - );
444 -
445 - }
446 -
447 - return $meta_return;
448 -}
449 -
450 -
451 -/**
452 - * wpLingua AJAX function to get translations on CPT edit page
453 - *
454 - * @return void
455 - */
456 -function wplng_ajax_generate_translation() {
457 -
458 - /**
459 - * Check and sanitize data
460 - */
461 -
462 - // Check data
463 -
464 - if ( empty( $_POST['language_source'] )
465 - || ! is_string( $_POST['language_source'] )
466 - || empty( $_POST['language_target'] )
467 - || ! is_string( $_POST['language_target'] )
468 - || ! isset( $_POST['text'] )
469 - || ! is_string( $_POST['text'] )
470 - ) {
471 - wp_send_json_error( __( 'Invalid parameters', 'wplingua' ) );
472 - return;
473 - }
474 -
475 - // Check and sanitize sources and target languages
476 -
477 - $language_source = sanitize_key( $_POST['language_source'] );
478 - $language_target = sanitize_key( $_POST['language_target'] );
479 -
480 - if ( ! wplng_is_valid_language_id( $language_source )
481 - || ! wplng_is_valid_language_id( $language_target )
482 - || $language_source === $language_target
483 - ) {
484 - wp_send_json_error( __( 'Invalid languages parameters', 'wplingua' ) );
485 - return;
486 - }
487 -
488 - // Check and sanitize text to translate
489 - // (And convert img emoji to emoji)
490 -
491 - $text = wp_kses(
492 - $_POST['text'],
493 - array(
494 - 'img' => array(
495 - 'alt' => array(),
496 - ),
497 - )
498 - );
499 -
500 - $text = preg_replace(
501 - '/<img alt=\\"(.*)\\">/U',
502 - '$1',
503 - $text
504 - );
505 -
506 - $text = wplng_text_esc( $text );
507 -
508 - if ( ! wplng_text_is_translatable( $text ) ) {
509 - wp_send_json_success( $text );
510 - return;
511 - }
512 -
513 - /**
514 - * Call API and get translation
515 - */
516 -
517 - $response = wplng_api_call_translate(
518 - array( $text ),
519 - $language_source,
520 - $language_target
521 - );
522 -
523 - if ( ! isset( $response[0] ) ) {
524 - wp_send_json_error( __( 'Invalid API response', 'wplingua' ) );
525 - return;
526 - }
527 -
528 - wp_send_json_success( $response[0] );
529 -}
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 +}