term_id : (int) $target->ID; $stored = 'term' === $type ? $target->slug : $target->post_name; $keys = qtrad_slugs_meta_keys( $lang ); if ( '' === $slug || $slug === $stored ) { if ( metadata_exists( $type, $id, $keys[1] ) ) { // An empty value hides the slug an older plugin left, without deleting it. update_metadata( $type, $id, $keys[0], '' ); } else { delete_metadata( $type, $id, $keys[0] ); } return ''; } if ( qtrad_slugs_get( $type, $id, $lang ) === $slug ) { return $slug; } $slug = qtrad_slugs_unique( $type, $target, $lang, $slug ); update_metadata( $type, $id, $keys[0], wp_slash( $slug ) ); return $slug; } /** Whether another post or term of the same kind already answers to that slug in the language. */ function qtrad_slugs_is_taken( $type, $target, $lang, $slug ) { global $wpdb, $wp_rewrite; $reserved = array_merge( is_array( $wp_rewrite->feeds ) ? $wp_rewrite->feeds : array(), array( 'embed', $wp_rewrite->pagination_base, $wp_rewrite->comments_pagination_base ) ); if ( in_array( $slug, $reserved, true ) ) { return true; } $keys = qtrad_slugs_meta_keys( $lang ); // Stored and translated slugs are compared in one lookup, which no core API offers. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching if ( 'term' === $type ) { return (bool) $wpdb->get_var( $wpdb->prepare( "SELECT t.term_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = t.term_id LEFT JOIN {$wpdb->termmeta} m ON m.term_id = t.term_id AND m.meta_key IN (%s, %s) WHERE tt.taxonomy = %s AND t.term_id <> %d AND ( t.slug = %s OR m.meta_value = %s ) LIMIT 1", $keys[0], $keys[1], $target->taxonomy, $target->term_id, $slug, $slug ) ); } if ( is_post_type_hierarchical( $target->post_type ) ) { return (bool) $wpdb->get_var( $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} m ON m.post_id = p.ID AND m.meta_key IN (%s, %s) WHERE p.post_type = %s AND p.post_parent = %d AND p.ID <> %d AND p.post_status NOT IN ('trash', 'auto-draft') AND ( p.post_name = %s OR m.meta_value = %s ) LIMIT 1", $keys[0], $keys[1], $target->post_type, $target->post_parent, $target->ID, $slug, $slug ) ); } return (bool) $wpdb->get_var( $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} m ON m.post_id = p.ID AND m.meta_key IN (%s, %s) WHERE p.post_type = %s AND p.ID <> %d AND p.post_status NOT IN ('trash', 'auto-draft') AND ( p.post_name = %s OR m.meta_value = %s ) LIMIT 1", $keys[0], $keys[1], $target->post_type, $target->ID, $slug, $slug ) ); // phpcs:enable } /** A free slug, with -2, -3 and so on appended the way WordPress does for its own slugs. */ function qtrad_slugs_unique( $type, $target, $lang, $slug ) { $wanted = $slug; for ( $number = 2; $number < 100 && qtrad_slugs_is_taken( $type, $target, $lang, $slug ); $number++ ) { $slug = substr( $wanted, 0, 200 - strlen( '-' . $number ) ) . '-' . $number; } return $slug; } /** One labelled field per language. Slugs are shown decoded, as WordPress shows its own. */ function qtrad_slugs_fields( $type, $id, $name, $prefix ) { foreach ( qtrad_enabled_languages() as $lang ) { $field = $prefix . $lang; $value = $id ? qtrad_slugs_get( $type, $id, $lang ) : ''; echo '
';
echo '
' . esc_html__( 'The last part of the address in each language. Leave a field empty to use the WordPress slug in that language.', 'qtrad' ) . '
'; qtrad_slugs_fields( 'post', $post->ID, 'qtrad_slugs', 'qtrad-slug-' ); } function qtrad_slugs_save_meta_box( $post_id ) { if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) || ! current_user_can( 'edit_post', $post_id ) || empty( $_POST['qtrad_slugs_post_id'] ) || (int) $_POST['qtrad_slugs_post_id'] !== (int) $post_id || ! isset( $_POST['qtrad_slugs_nonce'] ) || ! is_string( $_POST['qtrad_slugs_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['qtrad_slugs_nonce'] ) ), 'qtrad_slugs_' . $post_id ) || empty( $_POST['qtrad_slugs'] ) || ! is_array( $_POST['qtrad_slugs'] ) ) { return; } $post = get_post( $post_id ); if ( ! $post ) { return; } foreach ( qtrad_enabled_languages() as $lang ) { if ( isset( $_POST['qtrad_slugs'][ $lang ] ) && is_string( $_POST['qtrad_slugs'][ $lang ] ) ) { qtrad_slugs_set( 'post', $post, $lang, sanitize_title( wp_unslash( $_POST['qtrad_slugs'][ $lang ] ) ) ); } } } /** Slug fields follow the term name translations, which carry the nonce. */ function qtrad_slugs_register_term_fields() { if ( ! get_option( 'permalink_structure' ) ) { return; } foreach ( get_taxonomies( array( 'show_ui' => true ) ) as $taxonomy ) { if ( is_taxonomy_viewable( $taxonomy ) ) { add_action( $taxonomy . '_edit_form_fields', 'qtrad_slugs_term_edit_fields', 11 ); add_action( $taxonomy . '_add_form_fields', 'qtrad_slugs_term_add_fields', 11 ); } } } function qtrad_slugs_term_edit_fields( $term ) { echo '' . esc_html__( 'Leave a field empty to use the WordPress slug in that language.', 'qtrad' ) . '
'; echo '' . esc_html__( 'Translated slugs', 'qtrad' ) . '
'; qtrad_slugs_fields( 'term', 0, 'qtrad_term_slugs', 'qtrad-term-slug-' ); echo '| ' . esc_html__( 'Base', 'qtrad' ) . ' | '; foreach ( $enabled as $lang ) { echo '' . esc_html( qtrad_language_name( $lang ) ) . ' | '; } echo '
|---|---|
' . esc_html( qtrad_use_current( $object['label'] ) ) . ' ' . esc_html( $object['base'] ) . ' | ';
foreach ( $enabled as $lang ) {
/* translators: 1: post type or taxonomy name, 2: language name */
$label = sprintf( __( '%1$s, %2$s', 'qtrad' ), qtrad_use_current( $object['label'] ), qtrad_language_name( $lang ) );
echo ''; } echo ' |
' . esc_html__( 'The first part of the address of a post type or taxonomy, such as "category". Leave a field empty to keep the base shown in its row.', 'qtrad' ) . '
'; echo '