Как установить отношение родитель-потомок между различными пользовательскими типами записей

14 мар. 2015 г., 00:20:15
Просмотры: 27.4K
Голосов: 23

Я только что настроил отношение записи/родителя между типом записи "episodes" и типом записи "cartoon-series".

Я использовал этот код для добавления мета-бокса для назначения родителя из другого типа записи:

add_action('admin_menu', function() {
    remove_meta_box('pageparentdiv', 'episodes', 'normal');
});
add_action('add_meta_boxes', function() {
    add_meta_box('episodes-parent', 'Мультсериалы', 'episodes_attributes_meta_box', 'episodes', 'side', 'default');
});

function episodes_attributes_meta_box($post) {
    $post_type_object = get_post_type_object($post->post_type);
    if ( $post_type_object->hierarchical ) {
        $pages = wp_dropdown_pages(array('post_type' => 'cartoon-series', 'selected' => $post->post_parent, 'name' => 'parent_id', 'show_option_none' => __('(без родителя)'), 'sort_column'=> 'menu_order, post_title', 'echo' => 0));
        if ( ! empty($pages) ) {
            echo $pages;
        } // проверка на пустые страницы
    } // проверка иерархии
}

Это сработало на экране администратора, позволяя мне установить сериал как родителя для эпизода, но когда я пытаюсь просмотреть запись, я получаю ошибку 404. Структура URL такая:

domain/episodes/series-name/episode-name

URL для сериала:

domain/cartoon-series/series-name

Я хотел бы, чтобы URL для эпизода был:

domain/cartoon-series/series-name/episode-name

Что я упускаю? Возможно ли сделать весь тип записи потомком другого типа записи? Тогда я мог бы даже получить URL для списка эпизодов:

domain/cartoon-series/series-name/episodes

Спасибо! Мэтт


Как запрошено, вот код для двух пользовательских типов записей:

$labels = array(
    "name" => "Мультсериалы",
    "singular_name" => "Мультсериал",
    "menu_name" => "Мультсериалы",
    "all_items" => "Все мультсериалы",
    "add_new" => "Добавить новый",
    "add_new_item" => "Добавить новый мультсериал",
    "edit" => "Редактировать",
    "edit_item" => "Редактировать мультсериал",
    "new_item" => "Новый мультсериал",
    "view" => "Просмотр",
    "view_item" => "Просмотреть мультсериал",
    "search_items" => "Поиск мультсериалов",
    "not_found" => "Мультсериалы не найдены",
    "not_found_in_trash" => "В корзине мультсериалы не найдены",
    "parent" => "Родительский мультсериал",
    );

$args = array(
    "labels" => $labels,
    "description" => "",
    "public" => true,
    "show_ui" => true,
    "has_archive" => true,
    "show_in_menu" => true,
    "exclude_from_search" => false,
    "capability_type" => "post",
    "map_meta_cap" => true,
    "hierarchical" => true,
    "rewrite" => array( "slug" => "cartoon-series", "with_front" => true ),
    "query_var" => true,
    "supports" => array( "title", "revisions", "thumbnail" ),           );
register_post_type( "cartoon-series", $args );

$labels = array(
    "name" => "Эпизоды",
    "singular_name" => "Эпизод",
    );

$args = array(
    "labels" => $labels,
    "description" => "",
    "public" => true,
    "show_ui" => true,
    "has_archive" => true,
    "show_in_menu" => true,
    "exclude_from_search" => false,
    "capability_type" => "post",
    "map_meta_cap" => true,
    "hierarchical" => true,
    "rewrite" => array( "slug" => "episodes", "with_front" => true ),
    "query_var" => true,
    "supports" => array( "title", "revisions", "thumbnail" ),           );
register_post_type( "episodes", $args );

Я использую плагин CPT UI, поэтому я не могу редактировать этот код напрямую. Это просто экспортированный код, который предоставляет CPT UI.

У меня нет другого кода, который связывает эти два CPT. Возможно, это то, что я упускаю. Я просто нашел этот код в интернете, который размещает метабокс на странице для создания связи. Этого недостаточно для выполнения задачи? Похоже, он устанавливает post_parent.

Спасибо! Мэтт

4
Комментарии

Извините, но я ошибся. Родительско-дочерняя связь установлена правильно. Метабокс не использует метаполе (это меня сначала сбило с толку), он использует переменную запроса parent_id и не требует дополнительного кода для установки связи. Проблема в том, что WordPress не распознаёт сгенерированный URL. Я пытался найти правило перезаписи, которое бы заставило это работать, но без успеха. Сейчас я исследую возможные решения.

cybmeta cybmeta
14 мар. 2015 г. 20:19:46

После некоторых исследований я пришёл к выводу, что заставить это работать так, как вы хотите, скорее всего не получится. Кажется, невозможно сделать один тип записи родителем для другого типа записи. Вернее, технически это возможно — ваш код действительно устанавливает такую связь, но просмотр дочерней записи не работает во фронтенде. Я пробовал правила перезаписи и хуки в pre_get_posts для изменения запроса безрезультатно, здесь задействовано что-то более сложное, что мне не удалось понять. Это как если бы кот был родителем собаки. Я предлагаю использовать либо один иерархический тип записи, либо установить связь с помощью метаполей.

cybmeta cybmeta
14 мар. 2015 г. 21:12:59

Мне кажется, что один иерархический тип записи идеально подходит для вашей ситуации.

cybmeta cybmeta
14 мар. 2015 г. 21:19:12

Я действительно стараюсь НЕ усложнять это. Если есть более элегантное решение, я весь во внимании. Я новичок в WordPress в целом и пока неплохо справлялся, но эта задача поставила меня в тупик. Обычно я бы просто сделал мультсериал категорией и назначил бы его эпизоду. Проблема в том, что у меня есть и другие вложенные данные, помимо эпизодов, которые должны находиться под мультсериалом. Поэтому кажется, что мультсериал тоже должен быть CPT. Это сложно! :-D Можете объяснить, что вы имеете в виду под использованием только одного иерархического типа записи?

Mattaton Mattaton
14 мар. 2015 г. 22:40:15
Все ответы на вопрос 3
7
18

Наконец-то я нашел рабочее решение. Тип записи cartoon-series можно зарегистрировать как обычно, но тип записей для эпизодов не может быть иерархическим (я думаю, WordPress ожидает, что родительский контент будет того же типа, что и дочерний, если связь установлена через post_parent в таблице базы данных wp_posts).

При регистрации эпизодов правило перезаписи должно быть установлено на нужный слаг, то есть cartoon-series/%series_name%. Затем мы можем фильтровать ссылки эпизодов, чтобы заменить %series_name% на фактическое название родительского типа записи cartoon-series, и добавить правило перезаписи, чтобы WordPress понимал, когда запрашивается тип записи cartoon-series, а когда - эпизоды.

add_action('init', function(){
    $labels = array(
        "name" => "Мультсериалы",
        "singular_name" => "Мультсериал",
        "menu_name" => "Мультсериалы",
        "all_items" => "Все мультсериалы",
        "add_new" => "Добавить новый",
        "add_new_item" => "Добавить новый мультсериал",
        "edit" => "Редактировать",
        "edit_item" => "Редактировать мультсериал",
        "new_item" => "Новый мультсериал",
        "view" => "Просмотр",
        "view_item" => "Просмотр мультсериала",
        "search_items" => "Поиск мультсериалов",
        "not_found" => "Мультсериалы не найдены",
        "not_found_in_trash" => "В корзине нет мультсериалов",
        "parent" => "Родительский мультсериал",
    );

    $args = array(
        "labels" => $labels,
         "description" => "",
        "public" => true,
        "show_ui" => true,
        "has_archive" => true,
        "show_in_menu" => true,
        "exclude_from_search" => false,
        "capability_type" => "post",
        "map_meta_cap" => true,
        "hierarchical" => true,
        "rewrite" => array( "slug" => "cartoon-series", "with_front" => true ),
        "query_var" => true,
        "supports" => array( "title", "revisions", "thumbnail" )
    );

    register_post_type( "cartoon-series", $args );

    $labels = array(
        "name" => "Эпизоды",
        "singular_name" => "Эпизод",
    );

    $args = array(
        "labels" => $labels,
        "description" => "",
        "public" => true,
        "show_ui" => true,
        "has_archive" => true,
        "show_in_menu" => true,
        "exclude_from_search" => false,
        "capability_type" => "post",
        "map_meta_cap" => true,
        "hierarchical" => false,
        "rewrite" => array( "slug" => "cartoon-series/%series_name%", "with_front" => true ),
        "query_var" => true,
        "supports" => array( "title", "revisions", "thumbnail" )
    );

    register_post_type( "episodes", $args );

});

add_action('add_meta_boxes', function() {
    add_meta_box('episodes-parent', 'Мультсериал', 'episodes_attributes_meta_box', 'episodes', 'side', 'default');
});

function episodes_attributes_meta_box($post) {
        $pages = wp_dropdown_pages(array('post_type' => 'cartoon-series', 'selected' => $post->post_parent, 'name' => 'parent_id', 'show_option_none' => __('(нет родителя)'), 'sort_column'=> 'menu_order, post_title', 'echo' => 0));
        if ( ! empty($pages) ) {
            echo $pages;
        } // проверка на пустые страницы
}

add_action( 'init', function() {

    add_rewrite_rule( '^cartoon-series/(.*)/([^/]+)/?$','index.php?episodes=$matches[2]','top' );

});

add_filter( 'post_type_link', function( $link, $post ) {
    if ( 'episodes' == get_post_type( $post ) ) {
        // Получаем название родительского мультсериала
        if( $post->post_parent ) {
            $parent = get_post( $post->post_parent );
            if( !empty($parent->post_name) ) {
                return str_replace( '%series_name%', $parent->post_name, $link );
            }
        } else {
            // Это, кажется, не работает. Предназначено для создания красивых постоянных ссылок,
            // когда у эпизода нет родителя, но, похоже, потребуются
            // дополнительные правила перезаписи
            //return str_replace( '/%series_name%', '', $link );
        }

    }
    return $link;
}, 10, 2 );

ВАЖНО: Не забудьте сбросить правила перезаписи после сохранения этого кода и перед тестированием. Перейдите в wp-admin/options-permalink.php и нажмите "Сохранить", чтобы перегенерировать правила перезаписи.

ВАЖНО 2: Вероятно, потребуется добавить дополнительные правила перезаписи, например, для работы с пагинацией. Также решение может потребовать доработки для полноценной работы: например, при удалении cartoon-series удалять все дочерние эпизоды? Добавить фильтр в админке для фильтрации эпизодов по родителю? Изменить заголовок эпизода в админке, чтобы отображалось название родительского сериала?

14 мар. 2015 г. 22:44:56
Комментарии

Спасибо, что разобрались в этом! Кажется, что опубликованный вами код удаляет название мультсериала из URL. Вместо замены %series_name% на название эпизода, %series_name% должен быть названием родителя эпизода. Название эпизода должно идти после этого. По какой-то причине у меня не заполняется метабокс "Cartoon Series", чтобы выбрать родителя. Поэтому я и подумал, что эпизоды должны быть иерархическими. Пытаюсь понять, почему так происходит.

Mattaton Mattaton
14 мар. 2015 г. 22:58:24

Да, эпизоды должны быть иерархическими, чтобы метабокс "Cartoon Series" мог заполниться.

Mattaton Mattaton
14 мар. 2015 г. 22:59:40

Сделал эпизоды иерархическими, чтобы можно было установить родителя, но URL стал еще хуже. С таким slug, как вы предложили, название сериала появляется в URL дважды. Вместо domain/episodes/series-name/episode-name, как было раньше, получается domain/episodes/series-name/series-name/episode-name

Mattaton Mattaton
14 мар. 2015 г. 23:03:40

Как я уже говорил, эпизоды не могут быть иерархическими. Я изменил код метабокса, чтобы он заполнялся неиерархическими типами записей. Используйте точный код, который я опубликовал, я его протестировал, и он работает. Если вы используете другой код, я не могу знать, в чем проблема. Просто скопируйте и вставьте код из ответа и протестируйте его. Возможно, вам потребуется отключить плагин CPT UI или, по крайней мере, удалить пользовательские типы записей из плагина, так как они регистрируются в коде.

cybmeta cybmeta
14 мар. 2015 г. 23:06:57

Ах, мои извинения, я быстро просмотрел и подумал, что эта часть одинаковая. Вы правы, страница теперь загружается, и URL выглядит корректно.

Mattaton Mattaton
14 мар. 2015 г. 23:10:58

Если это ответило на ваш вопрос, пожалуйста, отметьте ответ как принятый, поставив галочку под стрелками голосования в верхнем левом углу ответа.

cybmeta cybmeta
14 мар. 2015 г. 23:14:03

Даже спустя 8 лет это всё ещё отлично работает! Спасибо!

Gavin Gavin
19 июн. 2023 г. 19:20:39
Показать остальные 2 комментариев
0

В данном случае нет необходимости в ручном кодировании, вы можете просто использовать этот плагин:

https://wordpress.org/plugins/add-hierarchy-parent-to-post/

Вы даже можете взять код из него. Однако это может быть не полным решением.

7 февр. 2017 г. 10:29:43
0
-1

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

Это не то, что очень легко сделать с помощью API правил перезаписи WordPress, но ничто не мешает вам обойти механизм перезаписи и разобрать URL самостоятельно. Что-то вроде:

  1. Запустить правила перезаписи WordPress. Если контент найден, отобразить его и завершить выполнение.
  2. Получить первую часть URL, проверить, есть ли запись с таким ярлыком (slug) и ожидаемым типом записи.
  3. Перебрать оставшиеся части URL, проверить, существуют ли записи и имеют ли они правильный тип.
  4. Если все совпадает, отобразить последнюю найденную запись, в противном случае показать страницу 404.
14 мар. 2015 г. 21:28:55