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/slug-meta.php +572 -572 2.16.52.16.8 View file →
@@ -1,572 +1,572 @@
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 -}
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 +}