PluginProbe
Translate and Go multilingual – Automatic AI translation – wpLingua / 2.16.7
Translate and Go multilingual – Automatic AI translation – wpLingua v2.16.7
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
← All changes | inc/slug.php +628 -617 2.12.32.16.7 View file →
@@ -1,617 +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 - $slug_translation_exist = false;
134 -
135 - foreach ( $slugs_translations as $slug_translations ) {
136 -
137 - if ( $slug !== $slug_translations['source']
138 - || ! isset( $slug_translations['translations'][ $language_id ] )
139 - ) {
140 - continue;
141 - }
142 -
143 - $slug = $slug_translations['translations'][ $language_id ];
144 -
145 - $slug_translation_exist = true;
146 - break;
147 - }
148 -
149 - /**
150 - * Slug translation is not in slug cache
151 - * Check if exist in DB
152 - *
153 - * If not exist, create it
154 - */
155 -
156 - if ( false === $slug_translation_exist
157 - && false === wplng_get_slug_saved_from_original( $slug )
158 - ) {
159 - wplng_create_slug( $slug );
160 - }
161 -
162 - return $slug;
163 -}
164 -
165 -
166 -/**
167 - * Get a translated path
168 - *
169 - * @param string $path
170 - * @param string $language_id
171 - * @return string Translated path
172 - */
173 -function wplng_slug_translate_path( $path, $language_id ) {
174 -
175 - /**
176 - * Remove language ID
177 - */
178 -
179 - $target = wplng_get_languages_target_ids();
180 -
181 - foreach ( $target as $target_id ) {
182 - if ( wplng_str_starts_with( $path, '/' . $target_id . '/' ) ) {
183 - $path = str_replace( '/' . $target_id . '/', '/', $path );
184 - } elseif ( wplng_str_starts_with( $path, $target_id . '/' ) ) {
185 - $path = str_replace( $target_id . '/', '/', $path );
186 - }
187 - }
188 -
189 - /**
190 - * Manage anchor ang $_GET parameters
191 - */
192 -
193 - $path = str_replace(
194 - array( '#', '?' ),
195 - array( '/#', '/?' ),
196 - $path
197 - );
198 -
199 - /**
200 - * Return parth if no contains slug
201 - */
202 -
203 - if ( '/' === $path || '' === $path ) {
204 - return $path;
205 - }
206 -
207 - /**
208 - * Transform the multi part slug to an slug array
209 - */
210 -
211 - $slugs = explode( '/', $path );
212 - $slugs = array_filter( $slugs );
213 -
214 - /**
215 - * Get the slug translation list and translate $path
216 - */
217 -
218 - $slugs_translations = wplng_get_slugs();
219 -
220 - $path_translated = '/';
221 -
222 - foreach ( $slugs as $slug ) {
223 - if ( wplng_text_is_translatable( $slug )
224 - && ! wplng_str_starts_with( $slug, '#' )
225 - && ! wplng_str_starts_with( $slug, '?' )
226 - && ! wplng_str_contains( $slug, '.' )
227 - ) {
228 - $path_translated .= wplng_slug_translate(
229 - $slug,
230 - $language_id,
231 - $slugs_translations
232 - ) . '/';
233 - } else {
234 - $path_translated .= $slug . '/';
235 - }
236 - }
237 -
238 - /**
239 - * Set the first and last '/' to $path_translated like $path
240 - */
241 -
242 - if ( ! wplng_str_starts_with( $path, '/' ) ) {
243 - $path_translated = substr( $path_translated, 1 );
244 - }
245 -
246 - if ( ! wplng_str_ends_with( $path, '/' ) ) {
247 - $path_translated = substr( $path_translated, 0, -1 );
248 - }
249 -
250 - return $path_translated;
251 -}
252 -
253 -
254 -/**
255 - * Create a wplng_slug
256 - *
257 - * @param string $slug
258 - * @return int|false The post ID or false
259 - */
260 -function wplng_create_slug( $slug ) {
261 -
262 - if ( is_404()
263 - || ! current_user_can( 'edit_posts' )
264 - || wplng_str_contains( $slug, '.' )
265 - ) {
266 - return false;
267 - }
268 -
269 - $slug = sanitize_title( $slug );
270 -
271 - if ( '' === $slug
272 - || ! wplng_text_is_translatable( $slug )
273 - || wplng_is_valid_language_id( $slug )
274 - || 'go' === $slug
275 - || 'refer' === $slug
276 - || 'recommend' === $slug
277 - || 'recommends' === $slug
278 - || 'wp-includes' === $slug
279 - || 'wp-json' === $slug
280 - ) {
281 - return false;
282 - }
283 -
284 - /**
285 - * Make the title
286 - */
287 -
288 - $tite_max_length = 100;
289 - $title = mb_substr( $slug, 0, $tite_max_length );
290 - $title = sanitize_title( $slug );
291 - $title = urldecode( $slug );
292 -
293 - if ( strlen( $slug ) > $tite_max_length ) {
294 - $title = '/' . $title . __( '...', 'wplingua' );
295 - } else {
296 - $title = '/' . $title . '/';
297 - }
298 -
299 - /**
300 - * Create the post and get this ID
301 - */
302 -
303 - $new_post_id = wp_insert_post(
304 - array(
305 - 'post_title' => esc_html( $title ),
306 - 'post_type' => 'wplng_slug',
307 - 'post_status' => 'publish',
308 - 'post_name' => sanitize_title(
309 - 'wplingua-slug-' . md5( $title ) . '-' . $title
310 - ),
311 - )
312 - );
313 -
314 - if ( is_wp_error( $new_post_id ) || empty( $new_post_id ) ) {
315 - return false;
316 - }
317 -
318 - /**
319 - * Make $translation_meta
320 - */
321 -
322 - $languages_target = wplng_get_languages_target_ids();
323 - $slug_meta = array();
324 -
325 - foreach ( $languages_target as $target_language ) {
326 - $slug_meta[] = array(
327 - 'language_id' => $target_language,
328 - 'translation' => $slug,
329 - 'status' => 'ungenerated',
330 - );
331 - }
332 -
333 - add_post_meta(
334 - $new_post_id,
335 - 'wplng_slug_original',
336 - $slug
337 - );
338 -
339 - add_post_meta(
340 - $new_post_id,
341 - 'wplng_slug_md5',
342 - md5( $slug )
343 - );
344 -
345 - add_post_meta(
346 - $new_post_id,
347 - 'wplng_slug_original_language_id',
348 - wplng_get_language_website_id()
349 - );
350 -
351 - add_post_meta(
352 - $new_post_id,
353 - 'wplng_slug_translations',
354 - wp_json_encode(
355 - $slug_meta,
356 - JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
357 - )
358 - );
359 -
360 - wplng_clear_slugs_cache();
361 -
362 - return $new_post_id;
363 -}
364 -
365 -
366 -/**
367 - * Get the count of published 'wplng_slug' posts.
368 - *
369 - * This function retrieves the number of posts of type 'wplng_slug'
370 - * that are currently published on the site.
371 - *
372 - * @return int The number of published 'wplng_slug' posts.
373 - */
374 -function wplng_get_slug_count() {
375 -
376 - global $wplng_slug_count;
377 -
378 - if ( null !== $wplng_slug_count ) {
379 - return $wplng_slug_count;
380 - }
381 -
382 - $count_posts = wp_count_posts( 'wplng_slug' );
383 - $wplng_slug_count = (int) ( $count_posts->publish ?? 0 );
384 -
385 - return $wplng_slug_count;
386 -}
387 -
388 -
389 -/**
390 - * Get all saved slugs from a wp_query
391 - *
392 - * @return array
393 - */
394 -function wplng_get_slugs_from_query() {
395 -
396 - $slug_to_delete = array();
397 - $slugs = array();
398 - $args = array(
399 - 'post_type' => 'wplng_slug',
400 - 'posts_per_page' => -1,
401 - 'no_found_rows' => true,
402 - 'update_post_term_cache' => false,
403 - 'update_post_meta_cache' => false,
404 - 'cache_results' => false,
405 - 'fields' => 'ids', // Only retrieve post IDs
406 - 'meta_query' => array(
407 - array(
408 - 'key' => 'wplng_slug_original_language_id',
409 - 'value' => wplng_get_language_website_id(),
410 - 'compare' => '=',
411 - ),
412 - ),
413 - );
414 -
415 - // Get all slug IDs
416 - $post_ids = get_posts( $args );
417 -
418 - if ( empty( $post_ids ) ) {
419 - return $slugs;
420 - }
421 -
422 - foreach ( $post_ids as $slug_id ) {
423 - $meta = get_post_meta( $slug_id );
424 -
425 - // Check for valid source slug
426 - if ( ! isset( $meta['wplng_slug_original'][0] )
427 - || ! is_string( $meta['wplng_slug_original'][0] )
428 - ) {
429 - continue;
430 - }
431 -
432 - $source = sanitize_title( $meta['wplng_slug_original'][0] );
433 -
434 - // Skip unwanted slugs and mark them for deletion
435 - if ( in_array(
436 - $source,
437 - array(
438 - 'index-php',
439 - 'wp-includes',
440 - 'wp-json',
441 - 'go',
442 - 'refer',
443 - 'recommend',
444 - 'recommends',
445 - ),
446 - true
447 - )
448 - ) {
449 - $slug_to_delete[] = $slug_id;
450 - continue;
451 - }
452 -
453 - // Check for translations and build translation array
454 - if ( empty( $meta['wplng_slug_translations'][0] ) ) {
455 - continue;
456 - }
457 -
458 - $translations = array();
459 - $translations_meta = json_decode( $meta['wplng_slug_translations'][0], true );
460 -
461 - if ( ! is_array( $translations_meta ) ) {
462 - continue;
463 - }
464 -
465 - foreach ( $translations_meta as $translation_meta ) {
466 -
467 - // Validate language ID
468 - if ( empty( $translation_meta['language_id'] )
469 - || ! wplng_is_valid_language_id( $translation_meta['language_id'] )
470 - ) {
471 - continue;
472 - }
473 -
474 - $language_id = sanitize_key( $translation_meta['language_id'] );
475 -
476 - // Check and validate slug translation
477 - if ( empty( $translation_meta['translation'] )
478 - || ! is_string( $translation_meta['translation'] )
479 - || $translation_meta['translation'] === '[WPLNG_EMPTY]'
480 - || $translation_meta['translation'] === $source
481 - ) {
482 - continue;
483 - }
484 -
485 - // Sanitize and add valid translation
486 - $translations[ $language_id ] = sanitize_title( $translation_meta['translation'] );
487 - }
488 -
489 - // Add source and translations to slugs array
490 - $slugs[] = array(
491 - 'source' => $source,
492 - 'id' => $slug_id,
493 - 'translations' => $translations,
494 - );
495 - }
496 -
497 - // Delete invalid slugs, limit to 32 deletions
498 - if ( ! empty( $slug_to_delete ) ) {
499 -
500 - foreach ( array_slice( $slug_to_delete, 0, 32 ) as $id ) {
501 - wp_delete_post( $id, true );
502 - }
503 -
504 - // Cache slugs for 30 seconds after deletion
505 - set_transient( 'wplng_cached_slugs', $slugs, 30 );
506 -
507 - } else {
508 - // Cache slugs for a month if no deletions occurred
509 - set_transient( 'wplng_cached_slugs', $slugs, MONTH_IN_SECONDS );
510 - }
511 -
512 - return $slugs;
513 -}
514 -
515 -
516 -/**
517 - * Get the slug translations
518 - *
519 - * @return array Slug translations
520 - */
521 -function wplng_get_slugs() {
522 -
523 - $slugs_from_cache = get_transient( 'wplng_cached_slugs' );
524 -
525 - if ( ! is_array( $slugs_from_cache ) ) {
526 - return wplng_get_slugs_from_query();
527 - }
528 -
529 - $slugs = array();
530 -
531 - foreach ( $slugs_from_cache as $slug ) {
532 -
533 - if ( empty( $slug['source'] )
534 - || ! is_string( $slug['source'] )
535 - || ! is_array( $slug['translations'] )
536 - ) {
537 - continue;
538 - }
539 -
540 - $translations = array();
541 - $language_target_ids = wplng_get_languages_target_ids();
542 -
543 - foreach ( $slug['translations'] as $language_id => $translation ) {
544 - if ( ! in_array( $language_id, $language_target_ids )
545 - || ! is_string( $translation )
546 - ) {
547 - continue;
548 - }
549 -
550 - $translations[ $language_id ] = $translation;
551 - }
552 -
553 - $slugs[] = array(
554 - 'source' => $slug['source'],
555 - 'translations' => $translations,
556 - );
557 -
558 - }
559 -
560 - return $slugs;
561 -}
562 -
563 -
564 -/**
565 - * Get slug data from original slug
566 - *
567 - * @param string $original
568 - * @return array|false Translation data
569 - */
570 -function wplng_get_slug_saved_from_original( $original ) {
571 -
572 - $original = trim( strtolower( $original ) );
573 -
574 - if ( empty( $original ) ) {
575 - return false;
576 - }
577 -
578 - $args = array(
579 - 'post_type' => 'wplng_slug',
580 - 'posts_per_page' => -1,
581 - 'meta_query' => array(
582 - array(
583 - 'key' => 'wplng_slug_md5',
584 - 'value' => md5( $original ),
585 - 'compare' => '=',
586 - ),
587 - ),
588 - 'fields' => 'ids',
589 - 'meta_query' => array(
590 - array(
591 - 'key' => 'wplng_slug_original_language_id',
592 - 'value' => wplng_get_language_website_id(),
593 - 'compare' => '=',
594 - ),
595 - ),
596 - );
597 -
598 - $posts = get_posts( $args );
599 -
600 - if ( empty( $posts ) ) {
601 - return false;
602 - }
603 -
604 - foreach ( $posts as $post_id ) {
605 -
606 - $meta = get_post_meta( $post_id );
607 -
608 - if ( isset( $meta['wplng_slug_original'][0] )
609 - && is_string( $meta['wplng_slug_original'][0] )
610 - && $meta['wplng_slug_original'][0] === $original
611 - ) {
612 - return get_post( $post_id );
613 - }
614 - }
615 -
616 - return false;
617 -}
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 +}