Как добавить числовой слаг для дочерней страницы в WordPress 5.9?

Вопрос или проблема

Я пытаюсь настроить WordPress 5.9, чтобы разрешить числовые ярлыки для дочерних страниц. Взяв ответ 2015 года на подобный вопрос, я использовал обновленный код wp_unique_post_slug, удалил || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug ) и добавил его в качестве следующего хука в functions.php моей темы. Но по какой-то причине я получаю критическую ошибку: “Исчерпан допустимый объем памяти”. Что я могу сделать, чтобы разрешить числовые ярлыки для дочерних страниц?

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;
    }

    $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 ) {
        $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 ) );

        $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;
        }

        $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 ) );

        $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 {
        $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 );

        $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( "https://wordpress.stackexchange.com/", get_option( 'permalink_structure' ) ) ) );
                $postname_index = array_search( '%postname%', $permastructs, true );

                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;
                }
            }
        }

        $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;
        }
    }

    return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
}, 10, 6 );

Обновленный ответ@Quasimodo подтвердил, что его интересуют только записи типа page и что числовые ярлыки — это четырехзначные годы, например 2019.

Что я могу сделать, чтобы разрешить числовые ярлыки для дочерних страниц?

Я бы использовал pre_wp_unique_post_slug хук, который выполняется перед wp_unique_post_slug, что означает фильтрацию ярлыка до того, как будет сгенерирован уникальный (WordPress), и поэтому наш callback должен быть способен генерировать уникальный ярлык, если это необходимо.

Вот пример, опробованный и работающий с 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' );

    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;
}

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;
}

Кстати: просто чтобы вы знали, wp_unique_post_slug хук можно использовать, но ваш callback должен будет возвращать или работать со значением $original_slug (последний параметр из хука) вместо $slug (первый параметр из хука).

Таким образом, приведенный выше пример теперь позволяет нам использовать число в качестве ярлыка для дочерних страниц (тип записи page), однако нам все еще нужно убедиться, что URL/постоянная ссылка работает, как ожидалось — потому что по умолчанию example.com/page-slug/<number> рассматривается как запрос на разбиение на страницы, и WordPress попытается загрузить стр. 2, 3 и т.д. страницы с ярлыком page-slug.

  • Это также объясняет, почему WordPress по умолчанию запрещает использовать число в качестве ярлыка (записи).

Таким образом, шаг 2 для вас будет таким — добавьте пользовательское правило перезаписи, которое гарантирует, что постоянная ссылка example.com/parent-slug/<number> загружает страницу с ярлыком <number> и является дочерней к parent-slug (или какой-либо другой правильный/фактический ярлык):

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' );
}

Или вы можете использовать page_rewrite_rules хук:

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;
}

И помните, есть шаг 3 — обновить правила перезаписи, просто посетив wp-admin → Настройки → Постоянные ссылки.


Что касается этого:

Но по какой-то причине я получаю критическую ошибку: “Исчерпан допустимый объем памяти”.

Это потому, что, как сказал @birgire в своем комментарии, «вы создаете бесконечный цикл, повторно применяя фильтр wp_unique_post_slug в конце вашего обработки».

Таким образом, каждый раз, когда был вызван wp_unique_post_slug() (например, wp_insert_post()),

  1. apply_filters()wp_unique_post_slug()) вызывал вашу обработку
  2. Ваша обработка вызывала apply_filters()
  3. apply_filters() (в вашей обработке) вызывал вашу обработку
  4. Шаги 2 и 3 повторялись снова и снова…

И в итоге PHP выдал критическую ошибку, так как ваш код потреблял слишком много памяти.. :/

Я пробовал это и различные плагины, которые заявляют, что делают то же самое, но не знаю, изменилось ли поведение WP6.x. Мне не удается заставить какие-либо из этих решений работать – они все еще добавляют “-2” к каждому четырехзначному ярлыку, который я пытаюсь использовать.

Если @Quasimodo (https://wordpress.stackexchange.com/users/220387/quasimodo) все еще здесь, могли бы вы проверить, работает ли это на новых версиях WP, пожалуйста?

С уважением, Мадс

Ответ или решение

Для настройки числовых слагов для дочерних страниц в WordPress 5.9 необходимо выполнить несколько шагов. Вопрос сводится к тому, как добавить числовые слаги для дочерних страниц, и при этом избежать критической ошибки "Allowed memory size exhausted". Рассмотрим этот вопрос подробно, применяя подход TEA (теория, пример, применение).

Теория

WordPress изначально не поддерживает использование чисел в качестве слагов страниц, поскольку это может конфликтовать с системами пагинации и другими встроенными механизмами маршрутизации URL. Однако, используя возможности фильтров и хуков WordPress, такие как pre_wp_unique_post_slug, мы можем переписать механизм формирования уникальных слагов и добавить необходимые правила для корректной работы числовых URL.

Проблема, с которой вы столкнулись, заключается в том, что происходит бесконечная рекурсия при применении фильтра wp_unique_post_slug внутри вашей пользовательской функции, что в конечном итоге приводит к превышению лимита памяти.

Пример

  1. Использование фильтра pre_wp_unique_post_slug: Это позволяет перехватывать процесс генерации слага до момента, когда он становится уникальным. Важно, чтобы ваша функция могла генерировать уникальный слаг, если это необходимо.

  2. Добавление Rewrite Rules: Чтобы числовые слаги корректно обрабатывались как часть URL, необходимо добавить пользовательские правила перезаписи (rewrite rules). Это гарантирует, что WordPress обрабатывает числовой слаг как часть маршрута к странице, а не как параметр пагинации.

  3. Избегание рекурсии: Важно убедиться, что ваш код не вызывает функции или фильтры рекурсивно, что вызывает превышение лимита памяти.

Применение

Шаг 1: Настройка вашего functions.php

Добавьте следующее в файл functions.php вашей темы:

add_filter('pre_wp_unique_post_slug', 'handle_numerical_slug_for_pages', 10, 5);
function handle_numerical_slug_for_pages($override_slug, $slug, $post_ID, $post_status, $post_type, $post_parent) {
    if ('page' === $post_type && preg_match('/^\d{4}$/', $slug) && $post_parent) {
        return generate_unique_numeric_slug($slug, $post_ID, $post_type);
    }
    return $override_slug;
}

function generate_unique_numeric_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 = $slug . '-' . $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;
}

Шаг 2: Добавление правила переписи (Rewrite Rule)

Чтобы внедрить поддержку числовых слагов в URL, добавьте следующее:

add_action('init', 'add_custom_page_rewrite_rules');
function add_custom_page_rewrite_rules() {
    add_rewrite_rule(
        '^(.+/\d{4})/?$',
        'index.php?pagename=$matches[1]',
        'top'
    );
}

Шаг 3: Обновление пермалинков

Не забудьте очистить правила переписи URL. Для этого посетите панель администратора WordPress и перейдите в "Настройки" > "Постоянные ссылки". Просто сохраните изменения на этой странице, чтобы обновить правила переписи.

Заключение

Использование числовых слагов может быть полезно для организации контента, особенно если вам приходится иметь дело с данными, представляющими год или другие числовые значения. Предоставленный подход позволяет обойти ограничения WordPress и использовать числовые слаги для дочерних страниц безопасно и эффективно. Убедитесь, что ваш код не вызывает нежелательные рекурсивные процессы, и проверяйте все изменения на тестовой среде, прежде чем применять их на рабочем сайте.

Оцените материал
Добавить комментарий

Капча загружается...