PluginProbe
Translate and Go multilingual – Automatic AI translation – wpLingua / 2.14.0
Translate and Go multilingual – Automatic AI translation – wpLingua v2.14.0
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 / slug.php

slug.php in Translate and Go multilingual – Automatic AI translation – wpLingua 2.14.0, at inc/slug.php

629 lines 13.3 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 * Get the original slug from a translation (without "/")
11 *
12 * @param string $slug
13 * @param string $language_id
14 * @param array|false $slugs_translations
15 * @return array Original slug
16 */
17 function wplng_slug_original( $slug, $language_id, $slugs_translations = false ) {
18
19 if ( ! wplng_text_is_translatable( $slug )
20 || wplng_str_contains( $slug, '.' )
21 ) {
22 return $slug;
23 }
24
25 if ( false === $slugs_translations ) {
26 $slugs_translations = wplng_get_slugs();
27 }
28
29 foreach ( $slugs_translations as $slug_translations ) {
30
31 if ( ! isset( $slug_translations['translations'][ $language_id ] )
32 || $slug !== $slug_translations['translations'][ $language_id ]
33 ) {
34 continue;
35 }
36
37 $slug = $slug_translations['source'];
38
39 break;
40 }
41
42 return $slug;
43 }
44
45
46 /**
47 * Get the original path from a translation
48 *
49 * @param string $path
50 * @param string $language_id
51 * @return string Original path
52 */
53 function wplng_slug_original_path( $path, $language_id ) {
54
55 /**
56 * Remove language ID
57 */
58
59 if ( wplng_str_starts_with( $path, '/' . $language_id . '/' ) ) {
60 $path = str_replace( '/' . $language_id . '/', '/', $path );
61 } elseif ( wplng_str_starts_with( $path, $language_id . '/' ) ) {
62 $path = str_replace( $language_id . '/', '/', $path );
63 }
64
65 /**
66 * Return parth if no contains slug
67 */
68
69 if ( '/' === $path || '' === $path ) {
70 return $path;
71 }
72
73 /**
74 * Transform the multi part slug to an slug array
75 */
76
77 $slugs = explode( '/', $path );
78 $slugs = array_filter( $slugs );
79
80 /**
81 * Get the slug translation list and translate $path
82 */
83
84 $slugs_translations = wplng_get_slugs();
85
86 $path_original = '/';
87
88 foreach ( $slugs as $slug ) {
89 $path_original .= wplng_slug_original(
90 $slug,
91 $language_id,
92 $slugs_translations
93 ) . '/';
94
95 }
96
97 /**
98 * Set the first and last '/' to $path_original like $path
99 */
100
101 if ( ! wplng_str_starts_with( $path, '/' ) ) {
102 $path_original = substr( $path_original, 1 );
103 }
104
105 if ( ! wplng_str_ends_with( $path, '/' ) ) {
106 $path_original = substr( $path_original, 0, -1 );
107 }
108
109 return $path_original;
110 }
111
112
113 /**
114 * Get a translated slug
115 *
116 * @param string $slug
117 * @param string $language_id
118 * @param array|false $slugs_translations
119 * @return string Translated slug
120 */
121 function wplng_slug_translate( $slug, $language_id, $slugs_translations = false ) {
122
123 if ( ! wplng_text_is_translatable( $slug )
124 || wplng_str_contains( $slug, '.' )
125 ) {
126 return $slug;
127 }
128
129 if ( false === $slugs_translations ) {
130 $slugs_translations = wplng_get_slugs();
131 }
132
133 /**
134 * Normalize slug to match how sources are stored in cache/DB
135 * (wplng_create_slug applies sanitize_title before storing).
136 * This prevents cache misses and duplicate entries for non-ASCII
137 * slugs (e.g. Japanese/CJK) where the raw URL segment differs
138 * from its sanitized form.
139 */
140 $slug_sanitized = sanitize_title( $slug );
141 if ( '' !== $slug_sanitized ) {
142 $slug = $slug_sanitized;
143 }
144
145 $slug_translation_exist = false;
146
147 foreach ( $slugs_translations as $slug_translations ) {
148
149 if ( $slug !== $slug_translations['source']
150 || ! isset( $slug_translations['translations'][ $language_id ] )
151 ) {
152 continue;
153 }
154
155 $slug = $slug_translations['translations'][ $language_id ];
156
157 $slug_translation_exist = true;
158 break;
159 }
160
161 /**
162 * Slug translation is not in slug cache
163 * Check if exist in DB
164 *
165 * If not exist, create it
166 *
167 * Skip if sanitize_title() returns empty (e.g. pure CJK with no
168 * sanitize_title filter) to avoid costly meta_query DB calls.
169 */
170
171 if ( false === $slug_translation_exist
172 && '' !== $slug_sanitized
173 && false === wplng_get_slug_saved_from_original( $slug )
174 ) {
175 wplng_create_slug( $slug );
176 }
177
178 return $slug;
179 }
180
181
182 /**
183 * Get a translated path
184 *
185 * @param string $path
186 * @param string $language_id
187 * @return string Translated path
188 */
189 function wplng_slug_translate_path( $path, $language_id ) {
190
191 /**
192 * Remove language ID
193 */
194
195 $target = wplng_get_languages_target_ids();
196
197 foreach ( $target as $target_id ) {
198 if ( wplng_str_starts_with( $path, '/' . $target_id . '/' ) ) {
199 $path = str_replace( '/' . $target_id . '/', '/', $path );
200 } elseif ( wplng_str_starts_with( $path, $target_id . '/' ) ) {
201 $path = str_replace( $target_id . '/', '/', $path );
202 }
203 }
204
205 /**
206 * Manage anchor ang $_GET parameters
207 */
208
209 $path = str_replace(
210 array( '#', '?' ),
211 array( '/#', '/?' ),
212 $path
213 );
214
215 /**
216 * Return parth if no contains slug
217 */
218
219 if ( '/' === $path || '' === $path ) {
220 return $path;
221 }
222
223 /**
224 * Transform the multi part slug to an slug array
225 */
226
227 $slugs = explode( '/', $path );
228 $slugs = array_filter( $slugs );
229
230 /**
231 * Get the slug translation list and translate $path
232 */
233
234 $slugs_translations = wplng_get_slugs();
235
236 $path_translated = '/';
237
238 foreach ( $slugs as $slug ) {
239 if ( wplng_text_is_translatable( $slug )
240 && ! wplng_str_starts_with( $slug, '#' )
241 && ! wplng_str_starts_with( $slug, '?' )
242 && ! wplng_str_contains( $slug, '.' )
243 ) {
244 $path_translated .= wplng_slug_translate(
245 $slug,
246 $language_id,
247 $slugs_translations
248 ) . '/';
249 } else {
250 $path_translated .= $slug . '/';
251 }
252 }
253
254 /**
255 * Set the first and last '/' to $path_translated like $path
256 */
257
258 if ( ! wplng_str_starts_with( $path, '/' ) ) {
259 $path_translated = substr( $path_translated, 1 );
260 }
261
262 if ( ! wplng_str_ends_with( $path, '/' ) ) {
263 $path_translated = substr( $path_translated, 0, -1 );
264 }
265
266 return $path_translated;
267 }
268
269
270 /**
271 * Create a wplng_slug
272 *
273 * @param string $slug
274 * @return int|false The post ID or false
275 */
276 function wplng_create_slug( $slug ) {
277
278 if ( is_404()
279 || ! current_user_can( 'edit_posts' )
280 || wplng_str_contains( $slug, '.' )
281 ) {
282 return false;
283 }
284
285 $slug = sanitize_title( $slug );
286
287 if ( '' === $slug
288 || ! wplng_text_is_translatable( $slug )
289 || wplng_is_valid_language_id( $slug )
290 || 'go' === $slug
291 || 'refer' === $slug
292 || 'recommend' === $slug
293 || 'recommends' === $slug
294 || 'wp-includes' === $slug
295 || 'wp-json' === $slug
296 ) {
297 return false;
298 }
299
300 /**
301 * Make the title
302 */
303
304 $tite_max_length = 100;
305 $slug_decoded = urldecode( $slug );
306 $title = mb_substr( $slug_decoded, 0, $tite_max_length );
307 $title = sanitize_title( $title );
308 $title = urldecode( $title );
309
310 if ( mb_strlen( $slug_decoded ) > $tite_max_length ) {
311 $title = '/' . $title . __( '...', 'wplingua' );
312 } else {
313 $title = '/' . $title . '/';
314 }
315
316 /**
317 * Create the post and get this ID
318 */
319
320 $new_post_id = wp_insert_post(
321 array(
322 'post_title' => esc_html( $title ),
323 'post_type' => 'wplng_slug',
324 'post_status' => 'publish',
325 'post_name' => sanitize_title(
326 'wplingua-slug-' . md5( $title ) . '-' . $title
327 ),
328 )
329 );
330
331 if ( is_wp_error( $new_post_id ) || empty( $new_post_id ) ) {
332 return false;
333 }
334
335 /**
336 * Make $translation_meta
337 */
338
339 $languages_target = wplng_get_languages_target_ids();
340 $slug_meta = array();
341
342 foreach ( $languages_target as $target_language ) {
343 $slug_meta[] = array(
344 'language_id' => $target_language,
345 'translation' => $slug,
346 'status' => 'ungenerated',
347 );
348 }
349
350 add_post_meta(
351 $new_post_id,
352 'wplng_slug_original',
353 $slug
354 );
355
356 add_post_meta(
357 $new_post_id,
358 'wplng_slug_md5',
359 md5( $slug )
360 );
361
362 add_post_meta(
363 $new_post_id,
364 'wplng_slug_original_language_id',
365 wplng_get_language_website_id()
366 );
367
368 add_post_meta(
369 $new_post_id,
370 'wplng_slug_translations',
371 wp_json_encode(
372 $slug_meta,
373 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
374 )
375 );
376
377 wplng_clear_slugs_cache();
378
379 return $new_post_id;
380 }
381
382
383 /**
384 * Get the count of published 'wplng_slug' posts.
385 *
386 * This function retrieves the number of posts of type 'wplng_slug'
387 * that are currently published on the site.
388 *
389 * @return int The number of published 'wplng_slug' posts.
390 */
391 function wplng_get_slug_count() {
392
393 global $wplng_slug_count;
394
395 if ( null !== $wplng_slug_count ) {
396 return $wplng_slug_count;
397 }
398
399 $count_posts = wp_count_posts( 'wplng_slug' );
400 $wplng_slug_count = (int) ( $count_posts->publish ?? 0 );
401
402 return $wplng_slug_count;
403 }
404
405
406 /**
407 * Get all saved slugs from a wp_query
408 *
409 * @return array
410 */
411 function wplng_get_slugs_from_query() {
412
413 $slug_to_delete = array();
414 $slugs = array();
415 $args = array(
416 'post_type' => 'wplng_slug',
417 'posts_per_page' => -1,
418 'no_found_rows' => true,
419 'update_post_term_cache' => false,
420 'update_post_meta_cache' => false,
421 'cache_results' => false,
422 'fields' => 'ids', // Only retrieve post IDs
423 'meta_query' => array(
424 array(
425 'key' => 'wplng_slug_original_language_id',
426 'value' => wplng_get_language_website_id(),
427 'compare' => '=',
428 ),
429 ),
430 );
431
432 // Get all slug IDs
433 $post_ids = get_posts( $args );
434
435 if ( empty( $post_ids ) ) {
436 return $slugs;
437 }
438
439 foreach ( $post_ids as $slug_id ) {
440 $meta = get_post_meta( $slug_id );
441
442 // Check for valid source slug
443 if ( ! isset( $meta['wplng_slug_original'][0] )
444 || ! is_string( $meta['wplng_slug_original'][0] )
445 ) {
446 continue;
447 }
448
449 $source = sanitize_title( $meta['wplng_slug_original'][0] );
450
451 // Skip unwanted slugs and mark them for deletion
452 if ( in_array(
453 $source,
454 array(
455 'index-php',
456 'wp-includes',
457 'wp-json',
458 'go',
459 'refer',
460 'recommend',
461 'recommends',
462 ),
463 true
464 )
465 ) {
466 $slug_to_delete[] = $slug_id;
467 continue;
468 }
469
470 // Check for translations and build translation array
471 if ( empty( $meta['wplng_slug_translations'][0] ) ) {
472 continue;
473 }
474
475 $translations = array();
476 $translations_meta = json_decode( $meta['wplng_slug_translations'][0], true );
477
478 if ( ! is_array( $translations_meta ) ) {
479 continue;
480 }
481
482 foreach ( $translations_meta as $translation_meta ) {
483
484 // Validate language ID
485 if ( empty( $translation_meta['language_id'] )
486 || ! wplng_is_valid_language_id( $translation_meta['language_id'] )
487 ) {
488 continue;
489 }
490
491 $language_id = sanitize_key( $translation_meta['language_id'] );
492
493 // Check and validate slug translation
494 if ( empty( $translation_meta['translation'] )
495 || ! is_string( $translation_meta['translation'] )
496 || $translation_meta['translation'] === '[WPLNG_EMPTY]'
497 || $translation_meta['translation'] === $source
498 ) {
499 continue;
500 }
501
502 // Sanitize and add valid translation
503 $translations[ $language_id ] = sanitize_title( $translation_meta['translation'] );
504 }
505
506 // Add source and translations to slugs array
507 $slugs[] = array(
508 'source' => $source,
509 'id' => $slug_id,
510 'translations' => $translations,
511 );
512 }
513
514 // Delete invalid slugs, limit to 32 deletions
515 if ( ! empty( $slug_to_delete ) ) {
516 foreach ( array_slice( $slug_to_delete, 0, 32 ) as $id ) {
517 wp_delete_post( $id, true );
518 }
519 }
520
521 // Cache slugs as a persistent option
522 update_option( 'wplng_cached_slugs', $slugs, false );
523
524 return $slugs;
525 }
526
527
528 /**
529 * Get the slug translations
530 *
531 * @return array Slug translations
532 */
533 function wplng_get_slugs() {
534
535 $slugs_from_cache = get_option( 'wplng_cached_slugs' );
536
537 if ( ! is_array( $slugs_from_cache ) ) {
538 return wplng_get_slugs_from_query();
539 }
540
541 $slugs = array();
542
543 foreach ( $slugs_from_cache as $slug ) {
544
545 if ( empty( $slug['source'] )
546 || ! is_string( $slug['source'] )
547 || ! is_array( $slug['translations'] )
548 ) {
549 continue;
550 }
551
552 $translations = array();
553 $language_target_ids = wplng_get_languages_target_ids();
554
555 foreach ( $slug['translations'] as $language_id => $translation ) {
556 if ( ! in_array( $language_id, $language_target_ids )
557 || ! is_string( $translation )
558 ) {
559 continue;
560 }
561
562 $translations[ $language_id ] = $translation;
563 }
564
565 $slugs[] = array(
566 'source' => $slug['source'],
567 'translations' => $translations,
568 );
569
570 }
571
572 return $slugs;
573 }
574
575
576 /**
577 * Get slug data from original slug
578 *
579 * @param string $original
580 * @return array|false Translation data
581 */
582 function wplng_get_slug_saved_from_original( $original ) {
583
584 $original = trim( strtolower( $original ) );
585
586 if ( empty( $original ) ) {
587 return false;
588 }
589
590 $args = array(
591 'post_type' => 'wplng_slug',
592 'posts_per_page' => -1,
593 'fields' => 'ids',
594 'meta_query' => array(
595 'relation' => 'AND',
596 array(
597 'key' => 'wplng_slug_md5',
598 'value' => md5( $original ),
599 'compare' => '=',
600 ),
601 array(
602 'key' => 'wplng_slug_original_language_id',
603 'value' => wplng_get_language_website_id(),
604 'compare' => '=',
605 ),
606 ),
607 );
608
609 $posts = get_posts( $args );
610
611 if ( empty( $posts ) ) {
612 return false;
613 }
614
615 foreach ( $posts as $post_id ) {
616
617 $meta = get_post_meta( $post_id );
618
619 if ( isset( $meta['wplng_slug_original'][0] )
620 && is_string( $meta['wplng_slug_original'][0] )
621 && $meta['wplng_slug_original'][0] === $original
622 ) {
623 return get_post( $post_id );
624 }
625 }
626
627 return false;
628 }
629