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 / slug-meta.php

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

573 lines 15.5 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 for wpLingua Slugs
11 *
12 * This function adds a meta box to the wpLingua Slugs post type
13 * in the WordPress admin interface. The meta box is used to
14 * display and edit slug information for translations.
15 *
16 * @param WP_Post $post The post object.
17 * @return void
18 */
19 function wplng_slug_add_meta_box( $post ) {
20
21 add_meta_box(
22 'wplng_meta_box_slug', // Unique ID for the meta box
23 __( 'Slug', 'wplingua' ), // Title of the meta box
24 'wplng_slug_meta_box_html_output', // Callback function to render the meta box HTML
25 'wplng_slug', // 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 slugs editor meta box in back office
34 *
35 * This function prints the HTML of the slugs editor meta box in the WordPress admin interface.
36 *
37 * @param object $post The post object.
38 * @return string HTML The HTML of the meta box.
39 */
40 function wplng_slug_meta_box_html_output( $post ) {
41
42 echo '<div id="wplng-slug-editor">';
43 echo wplng_slug_editor_get_html( $post );
44 echo '</div>';
45 }
46
47
48 /**
49 * This function prints the HTML of the slugs editor meta box in the WordPress admin interface.
50 *
51 * @param WP_Post $post The post object.
52 * @return string HTML The HTML of the meta box.
53 */
54 function wplng_slug_editor_get_html( $post ) {
55
56 // Used later for security
57 $html = wp_nonce_field(
58 basename( __FILE__ ),
59 'wplng_slug_meta_box_nonce',
60 true,
61 false
62 );
63
64 $meta = get_post_meta( $post->ID );
65
66 // Display original text
67 if ( ! empty( $meta['wplng_slug_original'][0] )
68 && is_string( $meta['wplng_slug_original'][0] )
69 && ! empty( $meta['wplng_slug_original_language_id'][0] )
70 && wplng_is_valid_language_id( $meta['wplng_slug_original_language_id'][0] )
71 ) {
72
73 $language_id = $meta['wplng_slug_original_language_id'][0];
74 $language = wplng_get_language_by_id( $language_id );
75 $alt = __( 'Flag for language: ', 'wplingua' ) . $language['name'];
76
77 $slug = $meta['wplng_slug_original'][0];
78 $slug = sanitize_title( $slug );
79 $slug = urldecode( $slug );
80
81 $html .= '<div id="wplng-original-language" wplng-lang="' . esc_attr( $language_id ) . '">';
82 $html .= '<div id="wplng-source-title">';
83 $html .= '<img';
84 $html .= ' src="' . esc_url( $language['flag'] ) . '"';
85 $html .= ' alt="' . esc_attr( $alt ) . '"';
86 $html .= ' class="wplng-flag"';
87 $html .= '>';
88 $html .= esc_html( $language['name'] );
89 $html .= esc_html__( ' - Original slug: ', 'wplingua' );
90 $html .= '</div>'; // End #wplng-source-title
91 $html .= '<div id="wplng-source">/';
92 $html .= esc_html( $slug );
93 $html .= '/</div>'; // End #wplng-source
94 $html .= '</div>'; // End #wplng-original-language
95
96 }
97
98 // Foreach translation, display form textarea to edit
99 if ( ! empty( $meta['wplng_slug_translations'][0] )
100 && is_string( $meta['wplng_slug_translations'][0] )
101 ) {
102
103 $translations_data = json_decode( $meta['wplng_slug_translations'][0], true );
104 $languages_target = wplng_get_languages_target_ids();
105 $translations = array();
106
107 if ( empty( $translations_data ) ) {
108 $translations_data = array();
109 }
110
111 foreach ( $languages_target as $language_target ) {
112
113 $is_in = false;
114
115 foreach ( $translations_data as $translation_data ) {
116
117 if ( empty( $translation_data['language_id'] )
118 || ! wplng_is_valid_language_id( $translation_data['language_id'] )
119 || empty( $translation_data['translation'] )
120 || ! is_string( $translation_data['translation'] )
121 || ( $translation_data['language_id'] !== $language_target )
122 ) {
123 continue;
124 }
125
126 $is_in = true;
127
128 $translations[ $translation_data['language_id'] ] = array(
129 'language_id' => $translation_data['language_id'],
130 'translation' => $translation_data['translation'],
131 'status' => $translation_data['status'],
132 );
133
134 break;
135 }
136
137 if ( ! $is_in ) {
138 $translations[] = array(
139 'language_id' => $language_target,
140 'translation' => '[WPLNG_EMPTY]',
141 'status' => 'ungenerated',
142 );
143 }
144 }
145
146 foreach ( $translations as $translation ) {
147
148 $language_id = $translation['language_id'];
149 $language = wplng_get_language_by_id( $language_id );
150 $slug_input = $translation['translation'];
151 $name = 'wplng_slug_' . $language_id;
152 $container_id = 'wplng-slug-' . $language_id;
153 $generate_link = __( 'Regenerate translation', 'wplingua' );
154 $alt = __( 'Flag for language: ', 'wplingua' ) . $language['name'];
155 $class = 'wplng-edit-language';
156 $reviewed_title = __( 'Mark as review', 'wplingua' );
157 $is_reviewed = false;
158
159 if ( '[WPLNG_EMPTY]' === $slug_input ) {
160 $slug_input = $meta['wplng_slug_original'][0];
161 }
162
163 $slug_input = urldecode(
164 sanitize_title(
165 $slug_input
166 )
167 );
168
169 switch ( $translation['status'] ) {
170 case 'ungenerated':
171 $generate_link = __( 'Generate translation', 'wplingua' );
172 $class .= ' wplng-status-ungenerated';
173 break;
174
175 case 'generated':
176 $class .= ' wplng-status-generated';
177 break;
178
179 default:
180 if ( is_int( $translation['status'] ) ) {
181
182 $class .= ' wplng-status-reviewed';
183 $is_reviewed = true;
184
185 // Get and check date format
186 $date_format = get_option( 'date_format' );
187 if ( ! is_string( $date_format ) || empty( $date_format ) ) {
188 $date_format = 'F j, Y';
189 }
190
191 // Get and check time format
192 $time_format = get_option( 'time_format' );
193 if ( ! is_string( $time_format ) || empty( $time_format ) ) {
194 $time_format = 'g:i a';
195 }
196
197 $reviewed_title = __( 'Reviewed on ', 'wplingua' );
198 $reviewed_title .= esc_html(
199 gmdate(
200 $date_format,
201 $translation['status']
202 )
203 );
204 $reviewed_title .= ', ' . esc_html(
205 gmdate(
206 $time_format,
207 $translation['status']
208 )
209 );
210 }
211 break;
212 }
213
214 $html .= '<div';
215 $html .= ' id="' . esc_attr( $container_id ) . '"';
216 $html .= ' class="' . esc_attr( $class ) . '"';
217 $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
218 $html .= '>';
219 $html .= '<label for="' . esc_attr( $name ) . '" class="wplng-target-title">';
220 $html .= '<img';
221 $html .= ' src="' . esc_url( $language['flag'] ) . '"';
222 $html .= ' alt="' . esc_attr( $alt ) . '"';
223 $html .= ' class="wplng-flag"';
224 $html .= '>';
225 $html .= esc_html( $language['name'] );
226 $html .= esc_html__( ' - Slug translation: ', 'wplingua' );
227 $html .= '</label>';
228 $html .= '<input';
229 $html .= ' type="text"';
230 $html .= ' name="' . esc_attr( $name ) . '"';
231 $html .= ' id="' . esc_attr( $name ) . '"';
232 $html .= ' class="wplng-slug-input"';
233 $html .= ' lang="' . esc_attr( $language_id ) . '"';
234 $html .= ' spellcheck="false"';
235 $html .= ' value="/' . esc_attr( $slug_input ) . '/"';
236 $html .= '>';
237
238 if ( empty( $translation['status'] ) ) {
239 $html .= '</div>';
240 continue;
241 }
242
243 $html .= '<div class="wplng-slug-footer">';
244 $html .= '<div class="wplng-slug-footer-left">';
245
246 $html .= '<fieldset class="wplng-mark-as-reviewed">';
247
248 $html .= '<input';
249 $html .= ' type="checkbox"';
250 $html .= ' id="wplng_mark_as_reviewed_' . esc_attr( $language_id ) . '"';
251 $html .= ' name="wplng_mark_as_reviewed_' . esc_attr( $language_id ) . '"';
252 $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
253 $html .= checked( $is_reviewed, true, false );
254 $html .= '>';
255
256 $html .= '<label';
257 $html .= ' for="wplng_mark_as_reviewed_' . esc_attr( $language_id ) . '"';
258 $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
259 $html .= ' title="' . esc_attr( $reviewed_title ) . '"';
260 $html .= '>';
261 $html .= esc_html__( 'Is reviewed', 'wplingua' );
262 $html .= '</label>';
263
264 $html .= '</fieldset>';
265
266 $html .= '</div>'; // End .wplng-translation-footer-right
267 $html .= '<div class="wplng-slug-footer-right">';
268
269 $html .= '<span';
270 $html .= ' class="dashicons dashicons-update wplng-spin wplng-generate-spin"';
271 $html .= ' style="display: none;"';
272 $html .= '></span> ';
273
274 $html .= '<a';
275 $html .= ' href="javascript:void(0);"';
276 $html .= ' class="wplng-generate"';
277 $html .= ' wplng-lang="' . esc_attr( $language_id ) . '"';
278 $html .= '>';
279 $html .= esc_html( $generate_link );
280 $html .= '</a>';
281
282 $html .= '</div>'; // End .wplng-translation-footer-right
283 $html .= '</div>'; // End .wplng-translation-footer
284 $html .= '</div>'; // End .wplng-edit-language
285
286 }
287 }
288
289 return $html;
290 }
291
292
293 /**
294 * Save meta box data of wpLingua translations
295 *
296 * This function is responsible for saving the slug translations
297 * from the meta box in the WordPress admin interface.
298 *
299 * @param int $post_id The ID of the post being saved.
300 * @return bool Returns true if meta data is successfully updated, false otherwise.
301 */
302 function wplng_slug_save_meta_boxes_data( $post_id ) {
303
304 // Check if nonce is set
305 if ( ! isset( $_POST['wplng_slug_meta_box_nonce'] ) ) {
306 return false;
307 }
308
309 // Sanitize the nonce
310 $nonce = $_POST['wplng_slug_meta_box_nonce'];
311 $nonce = sanitize_text_field( wp_unslash( $nonce ) );
312
313 // Check for nonce to prevent 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 $meta = get_post_meta( $post_id );
324
325 if ( empty( $meta['wplng_slug_translations'][0] )
326 || empty( $meta['wplng_slug_original'][0] )
327 ) {
328 return false;
329 }
330
331 $slug_original = sanitize_title( $meta['wplng_slug_original'][0] );
332 $languages_target = wplng_get_languages_target_ids();
333 $translations = json_decode(
334 $meta['wplng_slug_translations'][0],
335 true
336 );
337
338 if ( empty( $translations ) ) {
339 $translations = array();
340 }
341
342 // Loop through all target languages and save the slug translations
343 foreach ( $languages_target as $language_target ) {
344
345 $is_in = false;
346
347 foreach ( $translations as $translation ) {
348
349 if ( empty( $translation['language_id'] )
350 || ! wplng_is_valid_language_id( $translation['language_id'] )
351 || ! isset( $translation['translation'] )
352 || ! is_string( $translation['translation'] )
353 || ( $translation['language_id'] !== $language_target )
354 ) {
355 continue;
356 }
357
358 $is_in = true;
359 break;
360 }
361
362 if ( ! $is_in ) {
363 $translations[] = array(
364 'language_id' => $language_target,
365 'translation' => '[WPLNG_EMPTY]',
366 'status' => 'ungenerated',
367 );
368 }
369 }
370
371 // Save the slug translations - this part is run after the translations have been validated
372 foreach ( $translations as $key => $translation ) {
373
374 if ( empty( $translation['language_id'] )
375 || ! wplng_is_valid_language_id( $translation['language_id'] )
376 ) {
377 continue;
378 }
379
380 $name = 'wplng_slug_' . $translation['language_id'];
381 $reviewed = 'wplng_mark_as_reviewed_' . $translation['language_id'];
382
383 if ( ! isset( $_POST[ $name ] ) ) {
384 continue;
385 }
386
387 $temp = sanitize_title( $_POST[ $name ] );
388
389 if ( empty( $temp ) || $slug_original === $temp ) {
390 $temp = '[WPLNG_EMPTY]';
391 } elseif ( $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 // Check if another slug is translated with the same string
406
407 if ( '[WPLNG_EMPTY]' !== $temp ) {
408
409 $saved_slugs = wplng_get_slugs();
410 $has_same_slugs = true;
411 $same_slugs_counter = 0;
412
413 while ( $has_same_slugs ) {
414
415 $has_same_slugs = false;
416
417 foreach ( $saved_slugs as $saved_slug ) {
418
419 if ( $slug_original === $saved_slug['source'] ) {
420 continue;
421 }
422
423 $saved_slug_translated = $saved_slug['source'];
424
425 if ( isset( $saved_slug['translations'][ $translation['language_id'] ] ) ) {
426 $saved_slug_translated = $saved_slug['translations'][ $translation['language_id'] ];
427 }
428
429 if ( $temp !== $saved_slug_translated ) {
430 continue;
431 }
432
433 $has_same_slugs = true;
434 ++$same_slugs_counter;
435
436 $temp = preg_replace(
437 '#(.*)-(\d*)$#',
438 '$1',
439 $temp
440 );
441
442 $temp = $temp . '-' . $same_slugs_counter;
443
444 break;
445
446 }
447 }
448 }
449
450 $translations[ $key ]['translation'] = $temp;
451 }
452
453 wplng_clear_slugs_cache();
454
455 // Save the translations in the post meta in JSON.
456 return true === update_post_meta(
457 $post_id,
458 'wplng_slug_translations',
459 wp_json_encode(
460 $translations,
461 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
462 )
463 );
464 }
465
466
467
468
469
470 /**
471 * wpLingua AJAX function to get slugs on CPT edit page
472 *
473 * This function processes an AJAX request to generate a slug for a custom post type (CPT) edit page.
474 * It validates and sanitizes input data, checks language parameters, and calls an external API
475 * to translate the given text into a slug format.
476 *
477 * @return void
478 */
479 function wplng_ajax_generate_slug() {
480
481 /**
482 * Check and sanitize data
483 */
484
485 if ( empty( $_POST['language_source'] )
486 || ! is_string( $_POST['language_source'] )
487 || empty( $_POST['language_target'] )
488 || ! is_string( $_POST['language_target'] )
489 || ! isset( $_POST['text'] )
490 || ! is_string( $_POST['text'] )
491 ) {
492 wp_send_json_error( __( 'Invalid parameters', 'wplingua' ) );
493 return;
494 }
495
496 // Check and sanitize source and target languages
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 /**
509 * Check and sanitize text to translate
510 */
511
512 $text = wp_unslash( $_POST['text'] );
513 $text = wplng_text_esc( $text );
514
515 // Replace certain characters for slug compatibility
516 $text = str_replace(
517 array( '/', '-', '_' ),
518 array( '', ' ', ' ' ),
519 $text
520 );
521
522 // Remove img emoji added by WordPress
523 $text = wp_kses(
524 $text,
525 array(
526 'img' => array(
527 'alt' => array(),
528 ),
529 )
530 );
531
532 // Remove image alt attributes from text
533 $text = preg_replace(
534 '/<img alt=\\"(.*)\\">/U',
535 '',
536 $text
537 );
538
539 // Check if text is translatable
540 if ( ! wplng_text_is_translatable( $text ) ) {
541 wp_send_json_success(
542 sanitize_title(
543 '/' . $text . '/'
544 )
545 );
546 return;
547 }
548
549 /**
550 * Call API and get translation
551 */
552
553 $response = wplng_api_call_translate(
554 array( $text ),
555 $language_source,
556 $language_target
557 );
558
559 // Validate API response
560 if ( ! isset( $response[0] ) ) {
561 wp_send_json_error( __( 'Invalid API response', 'wplingua' ) );
562 return;
563 }
564
565 // Process and return translated slug
566 $response = $response[0];
567 $response = sanitize_title( $response );
568 $response = urldecode( $response );
569 $response = '/' . $response . '/';
570
571 wp_send_json_success( $response );
572 }
573