Как установить отношение родитель-потомок между различными пользовательскими типами записей
Я только что настроил отношение записи/родителя между типом записи "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.
Спасибо! Мэтт

Наконец-то я нашел рабочее решение. Тип записи 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
удалять все дочерние эпизоды? Добавить фильтр в админке для фильтрации эпизодов по родителю? Изменить заголовок эпизода в админке, чтобы отображалось название родительского сериала?

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

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

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

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

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

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

В данном случае нет необходимости в ручном кодировании, вы можете просто использовать этот плагин:
https://wordpress.org/plugins/add-hierarchy-parent-to-post/
Вы даже можете взять код из него. Однако это может быть не полным решением.

Вам нужно будет написать собственный код для разбора URL, так как WordPress должен определять тип записи, которую он пытается получить из базы данных, на основе структуры URL, а ваша структура URL не предоставляет никаких подсказок для этого.
Это не то, что очень легко сделать с помощью API правил перезаписи WordPress, но ничто не мешает вам обойти механизм перезаписи и разобрать URL самостоятельно. Что-то вроде:
- Запустить правила перезаписи WordPress. Если контент найден, отобразить его и завершить выполнение.
- Получить первую часть URL, проверить, есть ли запись с таким ярлыком (slug) и ожидаемым типом записи.
- Перебрать оставшиеся части URL, проверить, существуют ли записи и имеют ли они правильный тип.
- Если все совпадает, отобразить последнюю найденную запись, в противном случае показать страницу 404.
