Настраиваемое правило перезаписи URL для иерархического типа записей в WordPress

15 янв. 2016 г., 04:43:53
Просмотры: 16.7K
Голосов: 1

Работая над сайтом клиента, я столкнулся со своим злейшим врагом... перезаписью URL в WordPress :(. Я действительно не могу разобраться и после многих дней попыток и поисков у меня ничего не получается.

Что я пытаюсь получить - это постоянные ссылки вида company.com/product/%название-продукта%/ независимо от глубины вложенности /родитель/дочерний/. Мне нужна функциональность иерархических страниц по другим причинам, но это не должно отображаться в URL.

Вот моя настройка:

  • Wordpress 4.4.1
  • Настройки постоянных ссылок установлены на Название записи

  • Я создал иерархический кастомный тип записи product с параметром rewrite отключенным.

    $args = array(
        'label'                 => __( 'Product', 'domain' ),
        'description'           => __( 'Company products', 'domain' ),
        'labels'                => $labels,
        'supports'              => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions', 'page-attributes' ),
        'taxonomies'            => array( 'category', 'post_tag' ),
        'hierarchical'          => true,
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'menu_position'         => 5,
        'show_in_admin_bar'     => true,
        'show_in_nav_menus'     => false,
        'can_export'            => true,
        'has_archive'           => true,
        'exclude_from_search'   => false,
        'publicly_queryable'    => true,
        'rewrite'               => false,
        'capability_type'       => 'page',
    );
    register_post_type( 'product', $args );
    

На этом этапе продукты не имеют красивых постоянных ссылок, они выглядят так и оба варианта работают:

  1. Первый уровень: company.com/?product=my-first-product -> ок
  2. Второй уровень: company.com/?product=my-first-product/child-product -> ок

После этого я зарегистрировал правило перезаписи и permastruct, затем сбросил правила перезаписи, сохранив изменения на странице настроек постоянных ссылок

    function bvt_product_rewrite_rule() {
        add_rewrite_rule( 
            '^product/([^/]+)/?$',
            'index.php?post_type=product&pagename=$matches[1]',
            'top'
        );
        add_permastruct( 'product', '/product/%product%/' );
    }
    add_action( 'init', 'bvt_product_rewrite_rule', 10 );

Теперь постоянные ссылки правильно отображались для страниц первого уровня, но не работали для дочерних страниц. Также оба уровня выдавали ошибку 404.

  1. Первый уровень: company.com/product/my-first-product/ -> 404
  2. Второй уровень: company.com/product/my-first-product/child-product/ -> 404

Я также попробовал решение, приведенное здесь https://wordpress.stackexchange.com/a/101077/86838, где предлагалось удалить родительский слаг из дочерней постоянной ссылки, но у меня все равно ничего не получилось

function bvt_product_flatten_hierarchies( $post_link, $post ) {
    if ( 'product' != $post->post_type ) {
        return $post_link;
    }

    $uri = '';
    foreach ( $post->ancestors as $parent ) {
        $uri = get_post( $parent )->post_name . "/" . $uri;
    }

    return str_replace( $uri, '', $post_link );
}
add_filter( 'post_type_link', 'bvt_product_flatten_hierarchies', 10, 2 );

Я установил два плагина debug-bar и monkeyman-rewrite-analyzer для анализа перезаписи и запросов, но все равно не могу заставить это работать.

Моя последняя попытка была с плагином wp-permastructure, который позволяет установить пользовательскую постоянную ссылку для кастомного типа записи. Он включает настройку permastruct в массиве опций rewrite функции register_post_type.

[...]
'publicly_queryable'    => true,
'rewrite'               => array(
    'permastruct'   => '/%postname%/',
),
'capability_type'       => 'page',
[...]

С этой настройкой и отключенными предыдущими кастомными rewrite/permatruct/flatten_hierarchies, все работало, как указано в описании плагина

  1. Первый уровень: company.com/my-first-product/ -> ок
  2. Второй уровень: company.com/child-product/ -> ок

Но теперь отсутствует уровень /product/, и если я попытаюсь добавить его в конфигурацию 'permastruct' => '/product/%postname%/', это снова приводит к ошибке 404.

Что приводит к моему главному вопросу.

Возможно ли сделать то, что мне нужно, или только то, что я смог получить с помощью последнего плагина?

Заранее спасибо всем, кто уделит время ответить мне и, возможно, спасет меня от моей печальной ситуации. Спасибо

P.S. Я не могу вставить более двух ссылок в тело сообщения, поэтому вот ссылки на упомянутые плагины:

https://wordpress.org/plugins/debug-bar/
https://wordpress.org/plugins/monkeyman-rewrite-analyzer/
https://wordpress.org/plugins/wp-permastructure/
2
Комментарии

Одной из вещей, которые могут мешать перезаписи URL, являются templates. Убедитесь, что ничего не стоит у вас на пути. Лично я бы сначала избегал плагинов и попытался бы заставить перезапись работать с аргументом rewrite для CPT.

Nathan Powell Nathan Powell
15 янв. 2016 г. 04:49:10

Привет, Натан, я согласен с тобой насчёт плагинов. Я всегда стараюсь делать максимально просто, но в этот раз я был в отчаянии.

Kuuak Kuuak
15 янв. 2016 г. 10:54:12
Все ответы на вопрос 1
1

Вы почти у цели. В вашем правиле перезаписи используется неверная переменная запроса - pagename должно быть просто name.

Вот версия, которая работает у меня на свежей установке WordPress 4.4.1 с темой Twenty Sixteen:

function bvt_product_init() {
    $args = array(
        'label'                 => __( 'Product', 'domain' ), // Название типа записи
        'description'           => __( 'Company products', 'domain' ), // Описание
        'supports'              => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions', 'page-attributes' ), // Поддерживаемые возможности
        'taxonomies'            => array( 'category', 'post_tag' ), // Таксономии
        'hierarchical'          => true, // Иерархический тип
        'public'                => true, // Публичный
        'show_ui'               => true, // Показывать в админке
        'show_in_menu'          => true, // Показывать в меню
        'menu_position'         => 5, // Позиция в меню
        'show_in_admin_bar'     => true, // Показывать в админ-баре
        'show_in_nav_menus'     => false, // Показывать в навигационных меню
        'can_export'            => true, // Можно экспортировать
        'has_archive'           => true, // Имеет архив
        'rewrite'               => array( 'slug' => 'product' ), // Перезапись URL
    );
    register_post_type( 'product', $args );

    // Правило перезаписи URL для продуктов
    add_rewrite_rule( 
        '^product/([^/]+)/?$',
        'index.php?post_type=product&name=$matches[1]',
        'top'
    );
}
add_action( 'init', 'bvt_product_init' );

// Функция для уплощения иерархии в URL
function bvt_product_flatten_hierarchies( $post_link, $post ) {
    if ( 'product' != $post->post_type ) {
        return $post_link;
    }
    $uri = '';
    foreach ( $post->ancestors as $parent ) {
        $uri = get_post( $parent )->post_name . "/" . $uri;
    }
    return str_replace( $uri, '', $post_link );
}
add_filter( 'post_type_link', 'bvt_product_flatten_hierarchies', 10, 2 );

Один потенциальный момент, на который стоит обратить внимание: иерархические типы записей позволяют создавать записи с одинаковыми ярлыками (slug), но разными родителями. Обычно это работает, поскольку они запрашиваются по своим родительским/дочерним путям. Без наличия этой родительской/дочерней связи в структуре URL вы можете создать записи, которые никогда нельзя будет запросить на фронтенде, если их ярлык совпадает с уже существующей записью. Просто имейте это в виду.

15 янв. 2016 г. 07:44:05
Комментарии

Привет, Мило, спасибо большое, действительно было очень близко. После замены pagename на name всё заработало. Что касается проблемы с одинаковыми слагами, я решил её, добавив уникальный номер SKU в слаг. Ещё раз спасибо

Kuuak Kuuak
15 янв. 2016 г. 10:56:40