Aplicar plantilla a tipo de publicación personalizada
Hoy tuve un cliente que quería una plantilla personalizada para cada página y sección dentro de ella. Propuse Laravel como solución, pero él quería WordPress, ya que parece más fácil de mantener (no según mi experiencia).
... Hasta ahora todo bien. Estoy un poco confundido ahí.
Así que decidí que mi entidad principal sería la página, para poder aplicar diferentes plantillas a cada página. Luego tengo una sección dentro de esta página, que no debería estar hardcodeada. El usuario debería tener la capacidad de elegir el diseño de la sección (plantilla), y eliminar o reordenar secciones dentro de la página actual. Y finalmente tengo las publicaciones ya que son la entidad más pequeña en el sitio web. Las publicaciones se renderizarían como columnas al estilo bootstrap (col-md-2, col-lg-6), etc...
Decidí crear un tipo de publicación personalizada para usarla como sección, pero luego leí que los tipos de publicación no pueden tener plantilla, solo las páginas pueden tenerlas. Esto comprometió mi plan hasta ahora y pasé 5 horas investigando una solución (sin salida). Así que necesito otra estrategia para hacer esto. Necesito plantillas para dos entidades.
¿Puede alguien sugerir una solución a este problema? (¡Te compraré una cerveza!)
EDICIÓN:
Para crear mi propio tipo de publicación personalizada, uso un plugin en WordPress llamado 'Custom Post Type UI', por supuesto hay otra manera, agregando un pequeño fragmento de código en tu archivo functions.php, pero no lo cubriré aquí.

A partir de WordPress 4.7, se agregó soporte para múltiples plantillas en los Tipos de Entrada Personalizados (Custom Post Types).
Para hacer que una plantilla esté disponible para tu Tipo de Entrada Personalizado, debes agregar este encabezado en la sección de metadatos del archivo de plantilla:
Template Post Type: post, foo, bar
Por ejemplo, asumamos que tu Tipo de Entrada Personalizado se llama "my_events" y deseas hacer disponible una plantilla llamada "Ancho Completo" tanto para Páginas como para tu Tipo de Entrada Personalizado.
Esto:
/**
* Template Name: Fullwidth
*
* Template Description...
**/
Se convierte en esto:
/**
* Template Name: Fullwidth
* Template Post Type: page, my_events
*
* Template Description...
**/
Más información: Plantillas para Tipos de Entrada en 4.7 del núcleo de WordPress

Normalmente no seguiría una respuesta tan contundente como la que proporcionó @Milo con una publicación como esta. Como ya tenía este código escrito para otro proyecto, pensé en compartirlo.
El código a continuación hace todo lo que @Milo resumió en su respuesta y lo he utilizado en varios proyectos con gran éxito.
Aquí está la hoja de trucos de lo que está pasando:
1) Conectar a la acción 'add_meta_boxes' para renderizar un nuevo metabox personalizado en la pantalla de edición (excepto en el tipo de publicación nativo 'page').
2) Conectar a la acción 'admin_menu' para eliminar el metabox 'Page Attributes' existente (excepto en el tipo de publicación nativo 'page').
3) Construir el metabox personalizado para reemplazar la funcionalidad del metabox nativo 'Page Attributes'. Esto incluye campos para definir el PADRE, PLANTILLA y ORDEN de cualquier tipo de publicación personalizada que hayas inicializado.
4) Conectar a la acción 'save_post' para guardar tu selección de plantilla en los metadatos de la publicación.
5) Conectar al filtro 'single_template' para cargar tu plantilla personalizada en lugar de la plantilla predeterminada de WordPress.
Aquí está el código completamente funcional para copiar/pegar:
/** Selector de Plantillas para Custom Post Types **/
function cpt_add_meta_boxes() {
$post_types = get_post_types();
foreach( $post_types as $ptype ) {
if ( $ptype !== 'page') {
add_meta_box( 'cpt-selector', 'Atributos', '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' => __('(sin padre)'),
'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>Padre</strong></p>";
echo "<label class=\"screen-reader-text\" for=\"parent_id\">Padre</label>";
echo $pages;
}
}
// Selector de Plantilla
echo "<p><strong>Plantilla</strong></p>";
echo "<select id=\"cpt-selector\" name=\"_wp_page_template\"><option value=\"default\">Plantilla Predeterminada</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>";
// Orden de página
echo "<p><strong>Orden</strong></p>";
echo "<p><label class=\"screen-reader-text\" for=\"menu_order\">Orden</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' );
/** FIN Selector de Plantillas para Custom Post Types **/
La única suposición que he hecho aquí es que tus plantillas siguen una convención de nomenclatura recomendada de:
posttype-nombredelaplantilla.php
Como ejemplo, podrías definir algunas plantillas personalizadas para un tipo de publicación personalizado "Evento" usando la siguiente convención de nomenclatura dentro de tu tema:
evento-estandar.php
evento-todoeldia.php
evento-recurrente.php
Este código es lo suficientemente inteligente como para permitir que solo las plantillas de "evento" se apliquen al tipo de publicación Evento. En otras palabras, una plantilla llamada "seccion-video.php" nunca sería visible para el tipo de publicación Evento. Esa plantilla aparecería como una opción en el tipo de publicación "Sección".
Para eliminar esta característica simplemente necesitas eliminar la lógica condicional del código anterior:
if ( $post->post_type == strstr( $template_filename, '-', true) ) { }

Para el uso de la función, marco tu respuesta como 'Mejor Respuesta', porque explica de manera bastante fluida 'cómo aplicar una plantilla a un tipo de publicación personalizado'. Y, por supuesto, proporciona un fragmento de código bastante útil que puedo copiar y pegar ( el sueño hecho realidad ) en mi functions.php.
Gracias, dswebsme por publicar una respuesta tan detallada. ¡Dame tu cuenta bancaria y te invito una cerveza! [:
¡Saludos!

Solo una pequeña edición, en la última función en esta línea: $template = get_template_directory() . '/' . $post_meta['_wp_page_template'][0]; en lugar de usar get_template_directory(), usa get_stylesheet_directory(), ej: $template = get_stylesheet_directory() . '/' . $post_meta['_wp_page_template'][0]; Estoy usando un tema hijo y get_template_directory() recuperará el directorio del tema padre, así que si colocas tu plantilla personalizada en la ubicación del tema hijo, el servidor no la encontrará y dará un error php, mientras que get_stylesheet_directory() devolverá el directorio correcto de la plantilla. Buen código, gracias

Sé que esta es una pregunta antigua, pero quería añadir mi respuesta sobre lo que me ayudó a aplicar plantillas a tipos de contenido personalizados.
Creé mi tipo de contenido personalizado manualmente (con mi propio plugin) en lugar de usar el plugin Custom Post Type UI.
El código del plugin se ve así:
<?php
/**
* Plugin Name: [Tu plugin de tipo de contenido]
*/
defined('ABSPATH') or die(''); // Previene el acceso al archivo a través de su URL en el navegador
function tuFuncionTipoContenido() {
register_post_type('tu-tipo-contenido', array(
'labels'=>array(
'name'=>__('Páginas de tipo de contenido'),
'singular_name'=>__('Página de tipo de contenido')
),
'description'=>'Tu descripción',
'public'=>true,
'hierarchical'=>true, // Permite relaciones padre-hijo
'show_in_rest'=>true,
'supports'=>array( // Características que el tipo de contenido debe soportar
'title',
'editor',
'thumbnail',
'revisions',
'page-attributes' /* Necesario para mostrar el metabox para establecer relaciones padre-hijo
si no estás usando el método del campo de encabezado 'Template Post Type' */
),
'has_archive'=>true,
)
);
}
add_action('init', 'tuFuncionTipoContenido');
function CPTFlushPermalinks() {
tuFuncionTipoContenido();
flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'CPTFlushPermalinks');
?>
Consulta aquí para detalles sobre todas las opciones de register_post_type()
(y por qué hay un flush_rewrite_rules()
al final). Muchas están configuradas como valores predeterminados que no necesitas añadir.
Lo importante para mí fue que 'hierarchical'
estuviera configurado como true
y que 'supports'
incluyera 'page-attributes'
.
Después de esto, creé mi archivo de plantilla. El código se ve algo así:
<?php
/*
* Template Name: [Tu plantilla]
*/
// ... contenido de la página (mayormente copiado de page.php)
?>
¿Notaste que omití el campo "Template Post Type" posterior a WordPress 4.7? Esto es porque este campo es para permitir la visibilidad de una plantilla en el metabox 'Atributos de página' para tipos de contenido específicos. Los tipos de contenido listados podrán elegir la plantilla en su metabox de Atributos de página. (Si haces esto, no necesitarás el soporte 'page-attributes'
en tu tipo de contenido – el metabox se creará automáticamente.) Sin él, solo los contenidos del tipo 'página' pueden elegir diferentes plantillas en su metabox 'Atributos de página'.
Esto no es lo que yo quería, ya que deseaba que mi plantilla se aplicara inmediatamente a todas las páginas con el tipo de contenido. Esto se logra nombrando la plantilla "single-[nombre del tipo de contenido].php".
Hay otros esquemas de nombres que puedes usar para otros propósitos, listados aquí.
Esto funcionará para páginas individuales del tipo de contenido. No sé sobre subsecciones, como preguntaba la pregunta original; allí probablemente necesitarías declaraciones condicionales en tus otras plantillas, como "if ('tu-tipo-contenido' === get_post_type()) { ...
".
Puedes renombrar libremente la plantilla personalizada a través de su campo "Template Name" sin afectar ninguna página. Si deseas renombrar el archivo, puedes hacerlo, pero luego debes ir a tu base de datos, buscar el nombre del archivo y renombrar las instancias al nuevo nombre.
Además, el plugin Post Type Switcher es útil para cambiar contenidos entre tipos de contenido, incluso en masa. Funcionó perfectamente para mí sin errores.
La respuesta de dswebsme fue una respuesta apropiada de antes de que WordPress permitiera que contenidos que no son 'páginas' eligieran plantillas personalizadas en el cuadro 'Atributos de página'.

Los tipos de entradas personalizados pueden tener plantillas seleccionables, solo tendrás que implementarlas tú mismo. La forma en que WordPress lo hace internamente con el tipo de entrada de página es guardando un slug de plantilla en los metadatos de la entrada, luego verificando si existe un valor allí cuando la plantilla se carga en el front-end según la jerarquía.
El proceso básico sería agregar un meta box a tu tipo de entrada personalizado para permitir a los usuarios seleccionar una plantilla (quizás usando get_page_templates
para construir una lista).
El segundo paso es agregar un filtro a single_template
para cargar la plantilla seleccionada cuando ese objeto se visualiza en el front-end.
Si miras dentro de los archivos principales wp-includes/template.php
y wp-includes/post-template.php
, puedes ver el código que WordPress usa (y dónde se aplica el filtro) y adaptarlo a tus necesidades. get_queried_object
te dará el post_type
del objeto dentro del filtro, así como el ID para permitirte obtener los metadatos de la entrada.
EDIT -
Aquí hay un ejemplo de filtro para el tipo de entrada post
que carga lo que esté en la meta clave my_template
(como whatever.php
). Puedes probarlo creando una nueva entrada e ingresando un nombre de archivo bajo esa clave usando el meta box nativo de Campos Personalizados. Puedes modificar esto para tu tipo personalizado (cambia 'post'
), y cualquier esquema que estés usando para almacenar y nombrar archivos.
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' ) ;

Este fue inicialmente mi primer intento.
Implementé la meta caja con un menú desplegable de todas las plantillas disponibles dentro de mi carpeta de tema. Ahora investigaré con las pistas que me diste. Pero si tienes algún tutorial en mente, no dudes en compartirlo aquí conmigo.
¡Gracias!
