Применение шаблона к произвольному типу записи
Сегодня ко мне обратился клиент, которому нужны были индивидуальные шаблоны для каждой страницы и секции внутри. Я предложил использовать Laravel, но он хотел WordPress, так как это казалось проще в обслуживании (не по моему опыту).
.. Пока всё хорошо. Но я немного запутался.
Я решил, что родительским элементом будет страница, чтобы можно было применять разные шаблоны к каждой странице. Затем у меня есть секция внутри этой страницы, которая не должна быть жестко закодирована. Пользователь должен иметь возможность выбирать макет секции (шаблон), удалять или переупорядочивать секции на текущей странице. И наконец, у меня есть записи, как самые маленькие сущности на сайте. Записи должны отображаться как колонки, как в bootstrap (col-md-2, col-lg-6) и т.д.
Я решил создать произвольный тип записи для использования в качестве секции, но затем прочитал, что типы записей не могут иметь шаблонов, только страницы могут их иметь. Это нарушило мой план, и я потратил 5 часов на поиск решения (безрезультатно). Поэтому мне нужна другая стратегия. Мне нужны шаблоны для двух сущностей.
Может кто-то предложить решение этой проблемы? (Я куплю вам пиво!)
РЕДАКТИРОВАНИЕ:
Для создания своего произвольного типа записи я использую плагин в WordPress под названием 'Custom Post Type UI', конечно, есть и другой способ - вставить короткий фрагмент кода в файл functions.php, но я не буду это здесь рассматривать.

Начиная с WordPress 4.7, пользовательские типы записей поддерживают несколько шаблонов.
Чтобы сделать шаблон доступным для вашего пользовательского типа записи, добавьте этот заголовок в мета-часть файла шаблона:
Template Post Type: post, foo, bar
Например, предположим, что ваш пользовательский тип записи называется "my_events", и вы хотите сделать шаблон "Fullwidth" доступным как для страниц, так и для вашего пользовательского типа записи.
Было:
/**
* Template Name: Fullwidth
*
* Template Description...
**/
Стало:
/**
* Template Name: Fullwidth
* Template Post Type: page, my_events
*
* Template Description...
**/
Подробнее: Шаблоны типов записей в 4.7 из WordPress Core

Обычно я бы не стал дополнять такой исчерпывающий ответ, как у @Milo, подобной публикацией. Но поскольку у меня уже был готов этот код для другого проекта, я решил поделиться.
Приведенный ниже код реализует все, что @Milo описал в своем ответе, и я успешно использовал его в нескольких проектах.
Вот краткое описание происходящего:
1) Подключение к действию 'add_meta_boxes' для отображения нового пользовательского метабокса на экране редактирования (кроме стандартного типа записи 'page').
2) Подключение к действию 'admin_menu' для удаления стандартного метабокса 'Page Attributes' (кроме стандартного типа записи 'page').
3) Создание пользовательского метабокса, заменяющего функциональность стандартного 'Page Attributes'. Он включает поля для определения РОДИТЕЛЯ, ШАБЛОНА и ПОРЯДКА для любого инициализированного пользовательского типа записи.
4) Подключение к действию 'save_post' для сохранения выбранного шаблона в метаданных записи.
5) Подключение к фильтру 'single_template' для загрузки пользовательского шаблона вместо стандартного шаблона WordPress.
Вот полностью функциональный код для копирования/вставки:
/** Селектор шаблонов для пользовательских типов записей **/
function cpt_add_meta_boxes() {
$post_types = get_post_types();
foreach( $post_types as $ptype ) {
if ( $ptype !== 'page') {
add_meta_box( 'cpt-selector', 'Атрибуты', 'cpt_meta_box', $ptype, 'side', 'core' );
}
}
}
add_action( 'add_meta_boxes', 'cpt_add_meta_boxes' );
function cpt_remove_meta_boxes() {
$post_types = get_post_types();
foreach( $post_types as $ptype ) {
if ( $ptype !== 'page') {
remove_meta_box( 'pageparentdiv', $ptype, 'normal' );
}
}
}
add_action( 'admin_menu' , 'cpt_remove_meta_boxes' );
function cpt_meta_box( $post ) {
$post_meta = get_post_meta( $post->ID );
$templates = wp_get_theme()->get_page_templates();
$post_type_object = get_post_type_object($post->post_type);
if ( $post_type_object->hierarchical ) {
$dropdown_args = array(
'post_type' => $post->post_type,
'exclude_tree' => $post->ID,
'selected' => $post->post_parent,
'name' => 'parent_id',
'show_option_none' => __('(нет родителя)'),
'sort_column' => 'menu_order, post_title',
'echo' => 0,
);
$dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post );
$pages = wp_dropdown_pages( $dropdown_args );
if ( $pages ) {
echo "<p><strong>Родитель</strong></p>";
echo "<label class=\"screen-reader-text\" for=\"parent_id\">Родитель</label>";
echo $pages;
}
}
// Селектор шаблона
echo "<p><strong>Шаблон</strong></p>";
echo "<select id=\"cpt-selector\" name=\"_wp_page_template\"><option value=\"default\">Шаблон по умолчанию</option>";
foreach ( $templates as $template_filename => $template_name ) {
if ( $post->post_type == strstr( $template_filename, '-', true) ) {
if ( isset($post_meta['_wp_page_template'][0]) && ($post_meta['_wp_page_template'][0] == $template_filename) ) {
echo "<option value=\"$template_filename\" selected=\"selected\">$template_name</option>";
} else {
echo "<option value=\"$template_filename\">$template_name</option>";
}
}
}
echo "</select>";
// Порядок страницы
echo "<p><strong>Порядок</strong></p>";
echo "<p><label class=\"screen-reader-text\" for=\"menu_order\">Порядок</label><input name=\"menu_order\" type=\"text\" size=\"4\" id=\"menu_order\" value=\"". esc_attr($post->menu_order) . "\" /></p>";
}
function save_cpt_template_meta_data( $post_id ) {
if ( isset( $_REQUEST['_wp_page_template'] ) ) {
update_post_meta( $post_id, '_wp_page_template', $_REQUEST['_wp_page_template'] );
}
}
add_action( 'save_post' , 'save_cpt_template_meta_data' );
function custom_single_template($template) {
global $post;
$post_meta = ( $post ) ? get_post_meta( $post->ID ) : null;
if ( isset($post_meta['_wp_page_template'][0]) && ( $post_meta['_wp_page_template'][0] != 'default' ) ) {
$template = get_template_directory() . '/' . $post_meta['_wp_page_template'][0];
}
return $template;
}
add_filter( 'single_template', 'custom_single_template' );
/** Конец селектора шаблонов для пользовательских типов записей **/
Единственное допущение, которое я сделал - это следование лучшим практикам именования шаблонов:
типзаписи-названиешаблона.php
Например, вы можете определить несколько пользовательских шаблонов для типа записи "Мероприятие", используя следующее соглашение об именах в вашей теме:
мероприятие-стандартный.php
мероприятие-навесьдень.php
мероприятие-повторяющееся.php
Этот код достаточно умен, чтобы показывать шаблоны "мероприятие" только для типа записи "Мероприятие". Другими словами, шаблон с названием "раздел-видео.php" никогда не будет виден для типа записи "Мероприятие". Вместо этого этот шаблон будет отображаться как опция для типа записи "Раздел".
Чтобы отключить эту функцию, вам просто нужно удалить условную логику из кода выше:
if ( $post->post_type == strstr( $template_filename, '-', true) ) { }

Для удобства использования функции я отмечаю ваш ответ как 'Лучший ответ', потому что он очень плавно объясняет 'как применить шаблон к пользовательскому типу записи'. И, конечно же, содержит очень полезный фрагмент кода, который я могу просто скопировать и вставить ( истинная мечта ) в мой functions.php.
Спасибо, dswebsme за такой подробный ответ. Дайте мне номер банковского счета - я куплю вам пиво! [:
Удачи!

Небольшая правка: в последней функции в этой строке: $template = get_template_directory() . '/' . $post_meta['_wp_page_template'][0]; вместо использования get_template_directory() используйте get_stylesheet_directory(), например: $template = get_stylesheet_directory() . '/' . $post_meta['_wp_page_template'][0]; Я использую дочернюю тему, и get_template_directory() будет возвращать директорию родительской темы, поэтому если вы поместите свой пользовательский шаблон в дочернюю тему, сервер не найдет его и выдаст ошибку PHP, тогда как get_stylesheet_directory() вернет правильную директорию шаблона. Отличный код, спасибо.

Я знаю, что это старый вопрос, но хотел добавить свой ответ о том, что помогло мне применить шаблоны к пользовательским типам записей.
Я создал свой пользовательский тип записи вручную (с помощью собственного плагина), а не используя плагин Custom Post Type UI.
Код плагина выглядит так:
<?php
/**
* Plugin Name: [Ваш плагин для типа записи]
*/
defined('ABSPATH') or die(''); // Запрещает доступ к файлу через его URL в браузере
function yourPostTypeFunction() {
register_post_type('your-post-type', array(
'labels'=>array(
'name'=>__('Страницы типа записи'),
'singular_name'=>__('Страница типа записи')
),
'description'=>'Ваше описание',
'public'=>true,
'hierarchical'=>true, // Позволяет отношения родитель-потомок
'show_in_rest'=>true,
'supports'=>array( // Функции, которые должен поддерживать тип записи
'title',
'editor',
'thumbnail',
'revisions',
'page-attributes' /* Необходимо для отображения метабокса установки отношений родитель-потомок, если вы не используете метод поля заголовка 'Template Post Type' */
),
'has_archive'=>true,
)
);
}
add_action('init', 'yourPostTypeFunction');
function CPTFlushPermalinks() {
yourPostTypeFunction();
flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'CPTFlushPermalinks');
?>
Подробности о всех параметрах для register_post_type()
(и почему в конце стоит flush_rewrite_rules()
) можно узнать здесь. Многие параметры установлены по умолчанию, и их не нужно добавлять.
Для меня было важно, чтобы 'hierarchical'
было установлено в true
, а 'supports'
включало 'page-attributes'
.
После этого я создал файл шаблона. Код выглядит примерно так:
<?php
/*
* Template Name: [Ваш шаблон]
*/
// ... содержимое страницы (в основном скопировано из page.php)
?>
Обратите внимание, что я опустил поле "Template Post Type", появившееся после WordPress 4.7? Это потому, что это поле предназначено для управления видимостью шаблона в метабоксе 'Атрибуты страницы' для определенных типов записей. Указанные типы записей смогут выбирать шаблон в своем метабоксе 'Атрибуты страницы'. (Если вы используете это, вам не понадобится поддержка 'page-attributes'
в вашем типе записи — метабокс будет создан автоматически.) Без этого только записи типа 'page' могут выбирать разные шаблоны в своем метабоксе 'Атрибуты страницы'.
Это не то, что я хотел, так как мне нужно было, чтобы мой шаблон автоматически применялся ко всем страницам с данным типом записи. Это достигается путем именования шаблона "single-[название типа записи].php".
Существуют другие схемы именования для других целей, перечисленные здесь.
Это будет работать для отдельных страниц типа записи. Я не знаю, как насчет подразделов, о которых спрашивалось в исходном вопросе; там, вероятно, потребуются условные операторы в других шаблонах, например, "if ('your-post-type' === get_post_type()) { ...
".
Вы можете свободно переименовывать пользовательский шаблон через его поле "Template Name" без влияния на страницы. Если вы хотите переименовать файл, вы можете это сделать, но затем вам нужно будет зайти в базу данных, найти имя файла и переименовать его экземпляры на новое имя.
Также плагин Post Type Switcher полезен для переключения записей между типами, включая массовое изменение. У меня он работал безупречно, без ошибок.
Ответ dswebsme был уместен для версий WordPress до того, как система позволила не-'страницам' выбирать пользовательские шаблоны в метабоксе 'Атрибуты страницы'.

Пользовательские типы записей могут иметь выбираемые шаблоны, вам просто нужно реализовать их самостоятельно. WordPress делает это для типа записи "page", сохраняя слаг шаблона в метаданных записи, а затем проверяя наличие этого значения при загрузке шаблона на фронтенде в соответствии с иерархией шаблонов.
Основной процесс будет состоять в том, чтобы добавить метабокс к вашему пользовательскому типу записи, позволяя пользователям выбирать шаблон (возможно, используя get_page_templates
для построения списка).
Второй шаг — добавить фильтр к single_template
, чтобы загружать выбранный шаблон при просмотре этой записи на фронтенде.
Если вы посмотрите в файлах ядра wp-includes/template.php
и wp-includes/post-template.php
, вы увидите код, который использует WordPress (и где применяется фильтр), и сможете адаптировать его под свои нужды. get_queried_object
даст вам post_type
объекта внутри фильтра, а также ID, чтобы вы могли получить метаданные записи.
РЕДАКТИРОВАНИЕ —
Вот пример фильтра для типа записи post
, который загружает то, что указано в метаполе my_template
(например, whatever.php
). Вы можете протестировать это, создав новую запись и введя имя файла в этом поле с помощью стандартного метабокса "Произвольные поля". Вы можете модифицировать это для своего типа записи (изменив 'post'
) и своей схемы хранения и именования файлов.
function wpd_post_type_template( $template ){
$object = get_queried_object();
if( ! empty( $object->post_type )
&& 'post' == $object->post_type
&& $slug = get_post_meta( $object->ID, 'my_template', true ) ){
if( $custom_template = locate_template( $slug, false ) ){
$template = $custom_template;
}
}
return $template;
}
add_filter( 'single_template', 'wpd_post_type_template' ) ;

Это был мой первоначальный вариант.
Я реализовал метабокс с выпадающим списком всех доступных шаблонов в папке моей темы. Теперь я буду исследовать с подсказками, которые вы мне дали. Но если у вас есть какой-то учебник в виду, пожалуйста, не стесняйтесь поделиться им здесь со мной.
Спасибо!
