Commit 579044140f for wordpress.org
commit 579044140f37ef3d219fb89235270bdd6c4f3426
Author: peterwilsoncc <peterwilsoncc@git.wordpress.org>
Date: Wed Sep 23 23:56:46 2026 +0000
Taxonomy: Limit term slugs to 200 characters in `wp_unique_term_slug()`.
Prevents the auto-generation of term slugs that exceed database schema's maximum of 200 characters. This prevents database errors inserting the term.
This is particularly useful for slugs generated for non-latin alphabets as the slug is stored in it's URL encoded form in the database, for example `Категория на продукта` generates a 116 character slug.
* Introduces `wp_truncate_slug()` as a generically named function for truncating the slug of any object
* Deprecates `_truncate_post_slug()` in favour of above
The test suite for `_truncate_post_slug()` is retained (with the addition of expecting a deprecation notice) in order to ensure that future updates do not result in errors.
Props khokansardar, arkaprabhachowdhury, callumbw95, mikejolley, mindctrl, sajib1223, subrataemfluence, swissspidy.
Fixes #46010.
Built from https://develop.svn.wordpress.org/trunk@63909
git-svn-id: http://core.svn.wordpress.org/trunk@63078 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/deprecated.php b/wp-includes/deprecated.php
index 3b78d1610f..d2b987ec18 100644
--- a/wp-includes/deprecated.php
+++ b/wp-includes/deprecated.php
@@ -6532,3 +6532,20 @@ function wp_sanitize_script_attributes( $attributes ) {
}
return $attributes_string;
}
+
+/**
+ * Truncates a post slug.
+ *
+ * @since 3.6.0
+ * @deprecated 7.2.0 Use wp_truncate_slug() instead.
+ * @see wp_truncate_slug()
+ *
+ * @param string $slug The slug to truncate.
+ * @param int $length Optional. Max length of the slug. Default 200 (characters).
+ * @return string The truncated slug.
+ */
+function _truncate_post_slug( $slug, $length = 200 ) {
+ _deprecated_function( __FUNCTION__, '7.2.0', 'wp_truncate_slug()' );
+
+ return wp_truncate_slug( $slug, $length );
+}
diff --git a/wp-includes/formatting.php b/wp-includes/formatting.php
index 5bf001c430..faa94c6f6b 100644
--- a/wp-includes/formatting.php
+++ b/wp-includes/formatting.php
@@ -2400,6 +2400,34 @@ function sanitize_title_with_dashes( $title, $raw_title = '', $context = 'displa
return $title;
}
+/**
+ * Truncates a slug to a given length.
+ *
+ * Non-ASCII slugs are stored percent-encoded, so the slug is truncated on a
+ * character boundary to avoid cutting a percent-encoded sequence in half.
+ *
+ * @since 7.2.0
+ * @access private
+ *
+ * @see utf8_uri_encode()
+ *
+ * @param string $slug The slug to truncate.
+ * @param int $length Optional. Max length of the slug. Default 200 (characters).
+ * @return string The truncated slug.
+ */
+function wp_truncate_slug( $slug, $length = 200 ) {
+ if ( strlen( $slug ) > $length ) {
+ $decoded_slug = urldecode( $slug );
+ if ( $decoded_slug === $slug ) {
+ $slug = substr( $slug, 0, $length );
+ } else {
+ $slug = utf8_uri_encode( $decoded_slug, $length, true );
+ }
+ }
+
+ return rtrim( $slug, '-' );
+}
+
/**
* Ensures a string is a valid SQL 'order by' clause.
*
diff --git a/wp-includes/post.php b/wp-includes/post.php
index d36a44bde2..c268544418 100644
--- a/wp-includes/post.php
+++ b/wp-includes/post.php
@@ -5721,7 +5721,7 @@ function wp_unique_post_slug( $slug, $post_id, $post_status, $post_type, $post_p
) {
$suffix = 2;
do {
- $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
+ $alt_post_name = wp_truncate_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_id ) );
++$suffix;
} while ( $post_name_check );
@@ -5758,7 +5758,7 @@ function wp_unique_post_slug( $slug, $post_id, $post_status, $post_type, $post_p
) {
$suffix = 2;
do {
- $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
+ $alt_post_name = wp_truncate_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_id, $post_parent ) );
++$suffix;
} while ( $post_name_check );
@@ -5814,7 +5814,7 @@ function wp_unique_post_slug( $slug, $post_id, $post_status, $post_type, $post_p
) {
$suffix = 2;
do {
- $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
+ $alt_post_name = wp_truncate_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_id ) );
++$suffix;
} while ( $post_name_check );
@@ -5837,31 +5837,6 @@ function wp_unique_post_slug( $slug, $post_id, $post_status, $post_type, $post_p
return apply_filters( 'wp_unique_post_slug', $slug, $post_id, $post_status, $post_type, $post_parent, $original_slug );
}
-/**
- * Truncates a post slug.
- *
- * @since 3.6.0
- * @access private
- *
- * @see utf8_uri_encode()
- *
- * @param string $slug The slug to truncate.
- * @param int $length Optional. Max length of the slug. Default 200 (characters).
- * @return string The truncated slug.
- */
-function _truncate_post_slug( $slug, $length = 200 ) {
- if ( strlen( $slug ) > $length ) {
- $decoded_slug = urldecode( $slug );
- if ( $decoded_slug === $slug ) {
- $slug = substr( $slug, 0, $length );
- } else {
- $slug = utf8_uri_encode( $decoded_slug, $length, true );
- }
- }
-
- return rtrim( $slug, '-' );
-}
-
/**
* Adds tags to a post.
*
@@ -8726,7 +8701,7 @@ function wp_add_trashed_suffix_to_post_name_for_post( $post ) {
return $post->post_name;
}
add_post_meta( $post->ID, '_wp_desired_post_slug', $post->post_name );
- $post_name = _truncate_post_slug( $post->post_name, 191 ) . '__trashed';
+ $post_name = wp_truncate_slug( $post->post_name, 191 ) . '__trashed';
$wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
clean_post_cache( $post->ID );
return $post_name;
diff --git a/wp-includes/taxonomy.php b/wp-includes/taxonomy.php
index d13a11e655..ff90651e6b 100644
--- a/wp-includes/taxonomy.php
+++ b/wp-includes/taxonomy.php
@@ -3213,9 +3213,15 @@ function wp_remove_object_terms( $object_id, $terms, $taxonomy ) {
* If that still doesn't return a unique slug, then it tries to append a number
* until it finds a number that is truly unique.
*
+ * Appending a parent slug or a number can push the result past the 200 character
+ * limit of the `slug` column in the terms table, so the slug is truncated to make
+ * room for whatever is appended to it.
+ *
* The only purpose for `$term` is for appending a parent, if one exists.
*
* @since 2.3.0
+ * @since 7.2.0 The returned slug is truncated to 200 characters when a parent slug
+ * or a numeric suffix is appended to it.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
@@ -3271,7 +3277,7 @@ function wp_unique_term_slug( $slug, $term ) {
*/
if ( apply_filters( 'wp_unique_term_slug_is_bad_slug', $needs_suffix, $slug, $term ) ) {
if ( $parent_suffix ) {
- $slug .= $parent_suffix;
+ $slug = wp_truncate_slug( $slug . $parent_suffix, 200 );
}
if ( ! empty( $term->term_id ) ) {
@@ -3283,7 +3289,9 @@ function wp_unique_term_slug( $slug, $term ) {
if ( $wpdb->get_var( $query ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$num = 2;
do {
- $alt_slug = $slug . "-$num";
+ // Reserve room for the suffix so the result still fits the 200 character column.
+ $numeric_suffix = "-$num";
+ $alt_slug = wp_truncate_slug( $slug, 200 - strlen( $numeric_suffix ) ) . $numeric_suffix;
++$num;
$slug_check = $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $alt_slug ) );
} while ( $slug_check );
diff --git a/wp-includes/theme-templates.php b/wp-includes/theme-templates.php
index 301820f78f..f82d970c1c 100644
--- a/wp-includes/theme-templates.php
+++ b/wp-includes/theme-templates.php
@@ -87,7 +87,7 @@ function wp_filter_wp_template_unique_post_slug( $override_slug, $slug, $post_id
$suffix = 2;
do {
$query_args = $check_query_args;
- $alt_post_name = _truncate_post_slug( $override_slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
+ $alt_post_name = wp_truncate_slug( $override_slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$query_args['post_name__in'] = array( $alt_post_name );
$query = new WP_Query( $query_args );
++$suffix;
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 993502b95f..bdb8517726 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.2-alpha-63907';
+$wp_version = '7.2-alpha-63909';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.