Updated Answer — @Quasimodo confirmed that they’re only interested in posts of the page
type and that the numeric slugs are four-digit years, e.g. 2019
.
What can I do to allow numeric slugs for child pages?
I would use the pre_wp_unique_post_slug
hook that runs prior to wp_unique_post_slug
, which means we’ll be filtering the slug before a unique one is generated (by WordPress), and thus our callback must be able to generate a unique slug if necessary.
Here’s an example, tried and tested working with WordPress v5.9.3:
add_filter( 'pre_wp_unique_post_slug', 'my_pre_wp_unique_post_slug', 10, 6 );
function my_pre_wp_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type, $post_parent ) {
$allowed_post_status = array( 'publish', 'private' ); // allows numerical slug only for these statuses
if ( preg_match( '/^\d{4}$/', $slug ) && $post_parent &&
'page' === $post_type && in_array( $post_status, $allowed_post_status )
) {
return my_unique_page_slug( $slug, $post_ID, $post_type );
}
return $override_slug;
}
// This function is based on the code in the wp_unique_post_slug() function, lines
// 5023-5054 in WordPress v5.9.3, except that $check_sql doesn't include the `AND post_parent = %d`,
// which means we only want unique slug for each Page (i.e. post of type 'page').
function my_unique_page_slug( $slug, $post_ID, $post_type="page" ) {
global $wpdb;
$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
if ( $post_name_check ) {
$suffix = 2;
do {
$alt_post_name = _truncate_post_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 );
$slug = $alt_post_name;
}
return $slug;
}
PS: Just so that you know, the wp_unique_post_slug
hook could be used, but your callback would need to return or work on the value of $original_slug
(last parameter from the hook) instead of $slug
(first parameter from the hook).
So the above example would now let us use a number as the slug for child Pages (post type page
), however, we still need to ensure the URL/permalink works as expected — because by default, example.com/page-slug/<number>
is treated as a paginated request and thus WordPress will attempt to load page 2, 3, etc. of the Page with the slug page-slug
.
- The above also explains why WordPress by default disallows number as a (post) slug.
So step 2 for you would be this — add a custom rewrite rule which ensures the permalink example.com/parent-slug/<number>
loads the Page with the slug <number>
and is child of parent-slug
(or whatever the correct/actual slug is):
add_action( 'init', 'my_add_page_rewrite_rules' );
function my_add_page_rewrite_rules() {
add_rewrite_rule(
'^(.+/\d{4})(?:/(\d+))?/?$',
'index.php?pagename=$matches[1]&page=$matches[2]',
'top' );
}
Or you can instead use the page_rewrite_rules
hook:
add_filter( 'page_rewrite_rules', 'my_page_rewrite_rules' );
function my_page_rewrite_rules( $page_rewrite ) {
return array(
'^(.+/\d{4})(?:/(\d+))?/?$' => 'index.php?pagename=$matches[1]&page=$matches[2]',
) + $page_rewrite;
}
And remember, there’s a step 3 — flush the rewrite rules by simply visiting wp-admin
→ Settings → Permalinks.
As for this:
But for some reason, I just get a critical error: “Allowed memory size exhausted”.
That’s because, as @birgire said in his comment, “you’re creating an infinite loop by re-applying the wp_unique_post_slug
filter in the end of your callback“.
So whenever wp_unique_post_slug()
got called (e.g. by wp_insert_post()
),
apply_filters()
(inwp_unique_post_slug()
) called your closure/callback- Your callback called
apply_filters()
apply_filters()
(in your callback) called your callback- Steps 2 & 3 were repeated over and over again…
And eventually, PHP threw a fatal/critical error because your code consumed way too much memory.. :/