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