admin管理员组

文章数量:1201412

I am trying to configure WordPress 5.9 to allow numeric slugs for child pages. Drawing from the 2015 answer to a similar question I took the up-to-date code of wp_unique_post_slug, removed || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug ) and then added it as the following hook to my theme's functions.php. But for some reason, I just get a critical error: "Allowed memory size exhausted". What can I do to allow numeric slugs for child pages?

add_filter( 'wp_unique_post_slug', function ( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
    if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true )
        || ( 'inherit' === $post_status && 'revision' === $post_type ) || 'user_request' === $post_type
    ) {
        return $slug;
    }

    /**
     * Filters the post slug before it is generated to be unique.
     *
     * Returning a non-null value will short-circuit the
     * unique slug generation, returning the passed value instead.
     *
     * @since 5.1.0
     *
     * @param string|null $override_slug Short-circuit return value.
     * @param string      $slug          The desired slug (post_name).
     * @param int         $post_ID       Post ID.
     * @param string      $post_status   The post status.
     * @param string      $post_type     Post type.
     * @param int         $post_parent   Post parent ID.
     */
    $override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_ID, $post_status, $post_type, $post_parent );
    if ( null !== $override_slug ) {
        return $override_slug;
    }

    global $wpdb, $wp_rewrite;

    $original_slug = $slug;

    $feeds = $wp_rewrite->feeds;
    if ( ! is_array( $feeds ) ) {
        $feeds = array();
    }

    if ( 'attachment' === $post_type ) {
        // Attachment slugs must be unique across all types.
        $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
        $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );

        /**
         * Filters whether the post slug would make a bad attachment slug.
         *
         * @since 3.1.0
         *
         * @param bool   $bad_slug Whether the slug would be bad as an attachment slug.
         * @param string $slug     The post slug.
         */
        $is_bad_attachment_slug = apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug );

        if ( $post_name_check
            || in_array( $slug, $feeds, true ) || 'embed' === $slug
            || $is_bad_attachment_slug
        ) {
            $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_ID ) );
                $suffix++;
            } while ( $post_name_check );
            $slug = $alt_post_name;
        }
    } elseif ( is_post_type_hierarchical( $post_type ) ) {
        if ( 'nav_menu_item' === $post_type ) {
            return $slug;
        }

        /*
         * Page slugs must be unique within their own trees. Pages are in a separate
         * namespace than posts so page slugs are allowed to overlap post slugs.
         */
        $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d AND post_parent = %d LIMIT 1";
        $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );

        /**
         * Filters whether the post slug would make a bad hierarchical post slug.
         *
         * @since 3.1.0
         *
         * @param bool   $bad_slug    Whether the post slug would be bad in a hierarchical post context.
         * @param string $slug        The post slug.
         * @param string $post_type   Post type.
         * @param int    $post_parent Post parent ID.
         */
        $is_bad_hierarchical_slug = apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent );

        if ( $post_name_check
            || in_array( $slug, $feeds, true ) || 'embed' === $slug
            || $is_bad_hierarchical_slug
        ) {
            $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, $post_parent ) );
                $suffix++;
            } while ( $post_name_check );
            $slug = $alt_post_name;
        }
    } else {
        // Post slugs must be unique across all posts.
        $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
        $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );

        $post = get_post( $post_ID );

        // Prevent new post slugs that could result in URLs that conflict with date archives.
        $conflicts_with_date_archive = false;
        if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
            $slug_num = (int) $slug;

            if ( $slug_num ) {
                $permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
                $postname_index = array_search( '%postname%', $permastructs, true );

                /*
                * Potential date clashes are as follows:
                *
                * - Any integer in the first permastruct position could be a year.
                * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
                * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
                */
                if ( 0 === $postname_index ||
                    ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
                    ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
                ) {
                    $conflicts_with_date_archive = true;
                }
            }
        }

        /**
         * Filters whether the post slug would be bad as a flat slug.
         *
         * @since 3.1.0
         *
         * @param bool   $bad_slug  Whether the post slug would be bad as a flat slug.
         * @param string $slug      The post slug.
         * @param string $post_type Post type.
         */
        $is_bad_flat_slug = apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type );

        if ( $post_name_check
            || in_array( $slug, $feeds, true ) || 'embed' === $slug
            || $conflicts_with_date_archive
            || $is_bad_flat_slug
        ) {
            $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;
        }
    }

    /**
     * Filters the unique post slug.
     *
     * @since 3.3.0
     *
     * @param string $slug          The post slug.
     * @param int    $post_ID       Post ID.
     * @param string $post_status   The post status.
     * @param string $post_type     Post type.
     * @param int    $post_parent   Post parent ID
     * @param string $original_slug The original post slug.
     */
    return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
}, 10, 6 );

I am trying to configure WordPress 5.9 to allow numeric slugs for child pages. Drawing from the 2015 answer to a similar question I took the up-to-date code of wp_unique_post_slug, removed || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug ) and then added it as the following hook to my theme's functions.php. But for some reason, I just get a critical error: "Allowed memory size exhausted". What can I do to allow numeric slugs for child pages?

add_filter( 'wp_unique_post_slug', function ( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
    if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true )
        || ( 'inherit' === $post_status && 'revision' === $post_type ) || 'user_request' === $post_type
    ) {
        return $slug;
    }

    /**
     * Filters the post slug before it is generated to be unique.
     *
     * Returning a non-null value will short-circuit the
     * unique slug generation, returning the passed value instead.
     *
     * @since 5.1.0
     *
     * @param string|null $override_slug Short-circuit return value.
     * @param string      $slug          The desired slug (post_name).
     * @param int         $post_ID       Post ID.
     * @param string      $post_status   The post status.
     * @param string      $post_type     Post type.
     * @param int         $post_parent   Post parent ID.
     */
    $override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_ID, $post_status, $post_type, $post_parent );
    if ( null !== $override_slug ) {
        return $override_slug;
    }

    global $wpdb, $wp_rewrite;

    $original_slug = $slug;

    $feeds = $wp_rewrite->feeds;
    if ( ! is_array( $feeds ) ) {
        $feeds = array();
    }

    if ( 'attachment' === $post_type ) {
        // Attachment slugs must be unique across all types.
        $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
        $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );

        /**
         * Filters whether the post slug would make a bad attachment slug.
         *
         * @since 3.1.0
         *
         * @param bool   $bad_slug Whether the slug would be bad as an attachment slug.
         * @param string $slug     The post slug.
         */
        $is_bad_attachment_slug = apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug );

        if ( $post_name_check
            || in_array( $slug, $feeds, true ) || 'embed' === $slug
            || $is_bad_attachment_slug
        ) {
            $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_ID ) );
                $suffix++;
            } while ( $post_name_check );
            $slug = $alt_post_name;
        }
    } elseif ( is_post_type_hierarchical( $post_type ) ) {
        if ( 'nav_menu_item' === $post_type ) {
            return $slug;
        }

        /*
         * Page slugs must be unique within their own trees. Pages are in a separate
         * namespace than posts so page slugs are allowed to overlap post slugs.
         */
        $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d AND post_parent = %d LIMIT 1";
        $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );

        /**
         * Filters whether the post slug would make a bad hierarchical post slug.
         *
         * @since 3.1.0
         *
         * @param bool   $bad_slug    Whether the post slug would be bad in a hierarchical post context.
         * @param string $slug        The post slug.
         * @param string $post_type   Post type.
         * @param int    $post_parent Post parent ID.
         */
        $is_bad_hierarchical_slug = apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent );

        if ( $post_name_check
            || in_array( $slug, $feeds, true ) || 'embed' === $slug
            || $is_bad_hierarchical_slug
        ) {
            $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, $post_parent ) );
                $suffix++;
            } while ( $post_name_check );
            $slug = $alt_post_name;
        }
    } else {
        // Post slugs must be unique across all posts.
        $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
        $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );

        $post = get_post( $post_ID );

        // Prevent new post slugs that could result in URLs that conflict with date archives.
        $conflicts_with_date_archive = false;
        if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
            $slug_num = (int) $slug;

            if ( $slug_num ) {
                $permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
                $postname_index = array_search( '%postname%', $permastructs, true );

                /*
                * Potential date clashes are as follows:
                *
                * - Any integer in the first permastruct position could be a year.
                * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
                * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
                */
                if ( 0 === $postname_index ||
                    ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
                    ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
                ) {
                    $conflicts_with_date_archive = true;
                }
            }
        }

        /**
         * Filters whether the post slug would be bad as a flat slug.
         *
         * @since 3.1.0
         *
         * @param bool   $bad_slug  Whether the post slug would be bad as a flat slug.
         * @param string $slug      The post slug.
         * @param string $post_type Post type.
         */
        $is_bad_flat_slug = apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type );

        if ( $post_name_check
            || in_array( $slug, $feeds, true ) || 'embed' === $slug
            || $conflicts_with_date_archive
            || $is_bad_flat_slug
        ) {
            $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;
        }
    }

    /**
     * Filters the unique post slug.
     *
     * @since 3.3.0
     *
     * @param string $slug          The post slug.
     * @param int    $post_ID       Post ID.
     * @param string $post_status   The post status.
     * @param string $post_type     Post type.
     * @param int    $post_parent   Post parent ID
     * @param string $original_slug The original post slug.
     */
    return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
}, 10, 6 );
Share Improve this question edited Mar 20, 2022 at 9:01 Quasimodo asked Mar 19, 2022 at 15:21 QuasimodoQuasimodo 637 bronze badges 5
  • Note that you're creating an infinite loop by re-applying the wp_unique_post_slug filter in the end of your callback. – birgire Commented May 16, 2022 at 17:22
  • @birgire I am really new to WordPress coding, how can I change it to avoid the infinite loop? – Quasimodo Commented May 17, 2022 at 5:09
  • 1 Try returning just the $slug instead. – birgire Commented May 17, 2022 at 6:46
  • @birgire Thanks! This does make the critical error disappear, but unfortunately I still cannot create numeric slugs – WordPress keeps appending -2 when changing the permalink. – Quasimodo Commented May 17, 2022 at 7:31
  • Ive not looked into that, just noticed the recursive problem. – birgire Commented May 17, 2022 at 7:40
Add a comment  | 

1 Answer 1

Reset to default 2 +50

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()),

  1. apply_filters() (in wp_unique_post_slug()) called your closure/callback
  2. Your callback called apply_filters()
  3. apply_filters() (in your callback) called your callback
  4. 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.. :/

本文标签: filtersHow to add numeric slug for child page in WordPress 59