single-{$post_type}-{slug}.php para tipos de entrada personalizados

2 feb 2012, 18:36:50
Vistas: 16K
Votos: 22

Mi parte favorita de la jerarquía de plantillas de WordPress es la capacidad de crear rápidamente archivos de plantilla para páginas mediante el slug, sin tener que editar la página en WordPress para seleccionar una plantilla.

Actualmente podemos hacer esto:

page-{slug}.php

Pero me gustaría poder hacer esto:

single-{post_type}-{slug}.php

Para que, por ejemplo, en un tipo de entrada llamado review, pueda hacer una plantilla para una entrada llamada "Mi Gran Reseña" en single-review-mi-gran-resena.php

¿Alguien ha implementado esto antes? single-{post_type}-{slug}.php

2
Comentarios

Nunca he usado una configuración así antes, pero si es demasiado complicado, ¿por qué no simplemente crear un archivo de plantilla y asociarlo a la reseña en cuestión?

Shane Shane
2 feb 2012 18:53:50

WP 3.4 busca automáticamente single-{post_type}-{slug}.php, así que actualizar a WP 3.4 es otra opción.

yitwail yitwail
29 jul 2012 20:56:33
Todas las respuestas a la pregunta 6
12
20

A) La Base en el Núcleo

Como puedes ver en el Codex en la explicación de la Jerarquía de Plantillas, single-{$post_type}.php ya está soportado.


B) Extendiendo la Jerarquía del Núcleo

Afortunadamente, ahora hay algunos filtros y ganchos dentro de /wp-includes/template-loader.php.

  • do_action('template_redirect');
  • apply_filters( 'template_include', $template )
  • Y: un filtro específico dentro de get_query_template( $type, ... ) llamado: "$type}_template"

B.1) Cómo funciona

  1. Dentro del archivo del cargador de plantillas, la plantilla se carga mediante una variable de consulta/condicional wp_query: is_*().
  2. El condicional luego activa (en el caso de una plantilla "single"): is_single() && $template = get_single_template()
  3. Esto a su vez activa get_query_template( $type, $templates ), donde $type es single
  4. Luego tenemos el filtro "{$type}_template"

C) La Solución

Como solo queremos extender la jerarquía con una plantilla que se cargue antes de la plantilla actual "single-{$object->post_type}.php", interceptaremos la jerarquía y agregaremos una nueva plantilla al principio del array de plantillas.

// Extender la jerarquía
function add_posttype_slug_template( $templates )
{

    $object = get_queried_object();

    // Nueva 
    $templates[] = "single-{$object->post_type}-{$object->post_name}.php";
    // Como en el núcleo
    $templates[] = "single-{$object->post_type}.php";
    $templates[] = "single.php";

    return locate_template( $templates );    
}
// Ahora agregamos el filtro al gancho apropiado
function intercept_template_hierarchy()
{
    add_filter( 'single_template', 'add_posttype_slug_template', 10, 1 );
}
add_action( 'template_redirect', 'intercept_template_hierarchy', 20 );

NOTA: (Si deseas usar algo diferente al slug predeterminado del objeto) Tendrás que ajustar $slug según la estructura de tus enlaces permanentes. Simplemente usa lo que necesites del objeto global (object) $post.

Tickets de Trac

Dado que el enfoque anterior actualmente no está soportado (solo puedes filtrar la ruta absoluta ubicada de esta manera), aquí hay una lista de tickets de trac:

2 feb 2012 19:25:45
Comentarios

Quiero probar esto, pero parece que falta algo en tu línea add_filter al final.

supertrue supertrue
2 feb 2012 22:45:04

@supertrue Buen ojo. :) Encontré otro ) faltante dentro del filtro. Corregido. Quizás quieras cambiar el guión por un guión bajo antes del slug dentro de la plantilla. Solo para que el sufijo destaque mejor al revisar las plantillas.

kaiser kaiser
3 feb 2012 00:01:12

Provoca este error en todo el sitio: Advertencia: array_unshift() [function.array-unshift]: El primer argumento debe ser un array en [línea que contiene array_unshift]

supertrue supertrue
4 feb 2012 02:02:30

Ok, pero entonces algo más está interceptando las plantillas principales. La función funciona bien y $templates es un array. Consulta las funciones principales en este pastebin (sin fecha de expiración). Asegúrate de probar esto con una instalación sin plugins y con el tema por defecto. Luego activa uno tras otro y comprueba si el error sigue ocurriendo.

kaiser kaiser
4 feb 2012 02:47:49

Sí, he depurado esto y obtengo la ruta absoluta final de la primera plantilla encontrada como cadena. Tendré que hablar con algún desarrollador del núcleo sobre esto, antes de modificar la respuesta. Además: me equivoqué en algo: slug solo está disponible para términos y taxonomías. Deberías reemplazar $post->post_name con lo que se ajuste a tu estructura de enlaces permanentes. Actualmente no hay forma de hacer esto automáticamente para todos los casos recuperando y reemplazando la ruta dependiendo de tu estructura de enlaces permanentes y reglas de reescritura. Espera otra actualización.

kaiser kaiser
4 feb 2012 03:19:40

Te he dejado una versión funcional - aunque esto no es satisfactorio (copiar/pegar el array principal y luego volver a añadirlo dentro de una función personalizada).

kaiser kaiser
4 feb 2012 03:27:01

esto no pareció tener ningún efecto... ¿la parte final está correcta? No veo que se llame a intercept_template_hierarchy en ningún lado. Cambiar el último add_posttype_slug_template por intercept_template_hierarchy resultó en Fatal error: [] operator not supported for strings

supertrue supertrue
7 feb 2012 04:59:47

@supertrue Sí, esto es un problema. Simplemente no funciona. Ya estoy hablando con un desarrollador del núcleo para incluir esto en el ciclo de la versión 3.4. Vuelve en unos días y vuelve a publicar esta pregunta con un comentario aquí. Gracias. Nota al margen: El núcleo se comporta extremadamente estúpido en este punto, ya que el array de plantillas no se filtra, sino la ruta de la plantilla localizada en su lugar. Considero decir que esto es de hecho un bug.

kaiser kaiser
7 feb 2012 05:47:49

@supertrue La discusión ya comenzó. Espera una actualización en los próximos días. Si no es una actualización, entonces tal vez un ticket en trac :P

kaiser kaiser
7 feb 2012 12:54:09

@supertrue Dominik Schilling (también conocido como Ocean90) se agregó a algunos de los tickets. Espera progreso con la versión 3.4.

kaiser kaiser
10 feb 2012 17:19:06

@kaiser tu solución se ve muy bien pero no funciona ($templates no es un array en mi caso… Recibo un error fatal). ¿Hay alguna posibilidad de hacer que funcione?

gregmatys gregmatys
18 nov 2013 14:54:38

@gregmatys Por favor lee el último párrafo "Tickets de Trac".

kaiser kaiser
18 nov 2013 15:00:35
Mostrar los 7 comentarios restantes
4

Siguiendo la imagen de la Jerarquía de Plantillas, no veo tal opción.

Así que aquí está cómo lo haría:

Solución 1 (La mejor en mi opinión)

Crea un archivo de plantilla y asócialo a la reseña

 <?php
 /*
 Template Name: Mi Gran Reseña
 */
 ?>

Al añadir el archivo PHP de plantilla en tu directorio de tema, aparecerá como una opción de plantilla en la página de edición de tu entrada.

Solución 2

Esto probablemente se podría lograr usando el hook template_redirect.

En el archivo functions.php:

 function my_redirect()
 {
      global $post;

      if( get_post_type( $post ) == "my_cpt" && is_single() )
      {
           if( file_exists( get_template_directory() . '/single-my_cpt-' . $post->post_name . '.php' ) )
           {
                include( get_template_directory() . '/single-my_cpt-' . $post->post_name . '.php' );
                exit;
           }
      }
 }
 add_action( 'template_redirect', 'my_redirect' );

EDITADO

Añadida verificación con file_exists

2 feb 2012 19:06:21
Comentarios

¿Por qué usas exit; ahí?

kaiser kaiser
2 feb 2012 20:26:36

@kaiser Debe haber estado en el tutorial que seguí en ese momento, si no es necesario lo eliminaré.

Shane Shane
2 feb 2012 21:48:04

@kaiser: El exit() es necesario para evitar que cargue la plantilla por defecto.

scribu scribu
3 feb 2012 02:30:35

La solución 1 solo funcionará para páginas, no para entradas.

IXN IXN
7 nov 2015 14:18:56
0

La respuesta principal (de hace 4 años) ya no funciona, pero el codex de WordPress tiene la solución aquí:

<?php
function add_posttype_slug_template( $single_template )
{
    $object = get_queried_object();
    $single_postType_postName_template = locate_template("single-{$object->post_type}-{$object->post_name}.php");
    if( file_exists( $single_postType_postName_template ) )
    {
        return $single_postType_postName_template;
    } else {
        return $single_template;
    }
}
add_filter( 'single_template', 'add_posttype_slug_template', 10, 1 );
?>
9 jun 2016 23:39:00
5

En mi caso, tengo tipos de contenido personalizados (custom post types) para Álbum y Pista vinculados por una taxonomía de Álbum. Quería poder usar diferentes plantillas Single para los posts de Álbum y Pista dependiendo de su taxonomía de Álbum.

Basándome en la respuesta de Kaiser anteriormente, escribí este código. Funciona bien.
Nota: No necesité el add_action().

// Añadir una opción de plantilla adicional a la jerarquía de plantillas
add_filter( 'single_template', 'add_albumtrack_taxslug_template', 10, 1 );
function add_albumtrack_taxslug_template( $orig_template_path )
{
    // en este punto, $orig_template_path es la ruta absoluta localizada a la plantilla single preferida

    $object = get_queried_object();

    if ( ! (
        // especificar otra opción de plantilla solo para los tipos de post Álbum y Pista
        in_array( $object->post_type, array( 'gregory-cpt-album','gregory-cpt-track' )) &&
        // verificar que la taxonomía Álbum ha sido registrada
        taxonomy_exists( 'gregory-tax-album' ) &&
        // obtener el término de taxonomía Álbum para el post actual
        $album_tax = wp_get_object_terms( $object->ID, 'gregory-tax-album' )
        ))
        return $orig_template_path;

    // ensamblar el nombre de la plantilla
    // suposición: solo un término de taxonomía Álbum por post. Usamos el primer objeto del array.
    $template = "single-{$object->post_type}-{$album_tax[0]->slug}.php";
    $template = locate_template( $template );
    return ( !empty( $template ) ? $template : $orig_template_path );
}

Ahora puedo crear plantillas llamadas single-gregory-cpt-track-tax-serendipity.php y single-gregory-cpt-album-tax-serendipity.php y WP las usará automáticamente; 'tax-serendipity' es el slug para el primer término de la taxonomía Álbum.

Para referencia, el hook de filtro 'single_template' se declara en:
/wp-includes/theme.php: get_query_template()

Gracias Kaiser por el código de ejemplo.

Saludos,
Gregory

18 abr 2012 09:46:11
Comentarios

Hola Greg - bienvenido a WPSE. Por favor publica solo respuestas como respuestas a las preguntas - no preguntas de seguimiento. Si tienes una pregunta que no está respondida por una respuesta existente y es demasiado extensa para un comentario, por favor abre otra pregunta :)

Stephen Harris Stephen Harris
18 abr 2012 18:07:19

la pregunta sobre string/array ha sido eliminada :-)

Gregory Gregory
18 abr 2012 19:04:13

"Gracias Kaiser por el código de ejemplo." - De nada.

kaiser kaiser
23 oct 2012 16:01:37

¿funciona para ti? primero que nada, '$template' no debería estar comentado en tu código.. y creo que en lugar de '$album_tax[0]->slug' debería ser '$object->post_name', ¿no es así?

gregmatys gregmatys
18 nov 2013 16:14:14

arreglé la línea de $template. gracias. ¿$object->post_name? no. eso devolvería el slug del post, pero necesito el slug del álbum al que está vinculado el post.

Gregory Gregory
19 nov 2013 01:21:10
0

Usar Plantillas de Página

Otro enfoque para la escalabilidad sería duplicar la funcionalidad del menú desplegable de plantillas de página en el tipo de entrada page para tu tipo de entrada personalizado.

Código Reutilizable

La duplicación en el código no es una buena práctica. Con el tiempo puede causar un grave aumento en la base de código que luego dificulta mucho su gestión por parte del desarrollador. En lugar de crear una plantilla para cada slug individual, lo más probable es que necesites una plantilla de uno-a-muchos que pueda reutilizarse en lugar de una relación uno-a-uno entre entrada y plantilla.

El Código

# Define tu cadena de tipo de entrada personalizado
define('MY_CUSTOM_POST_TYPE', 'my-cpt');

/**
 * Registrar el meta box
 */
add_action('add_meta_boxes', 'page_templates_dropdown_metabox');
function page_templates_dropdown_metabox(){
    add_meta_box(
        MY_CUSTOM_POST_TYPE.'-page-template',
        __('Template', 'rainbow'),
        'render_page_template_dropdown_metabox',
        MY_CUSTOM_POST_TYPE,
        'side', #Prefiero la ubicación debajo del meta box de acciones de entrada
        'low'
    );
}

/**
 * Renderizar tu meta box - Este código es similar al que se muestra en el tipo de entrada page
 * @return void
 */
function render_page_template_dropdown_metabox(){
    global $post;
    $template = get_post_meta($post->ID, '_wp_page_template', true);
    echo "
        <label class='screen-reader-text' for='page_template'>Plantilla de Página</label>
            <select name='_wp_page_template' id='page_template'>
            <option value='default'>Plantilla por Defecto</option>";
            page_template_dropdown($template);
    echo "</select>";
}

/**
 * Guardar la plantilla de página
 * @return void
 */
function save_page_template($post_id){

    # Saltar los auto guardados
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
        return;
    elseif ( defined( 'DOING_AJAX' ) && DOING_AJAX )
        return;
    elseif ( defined( 'DOING_CRON' ) && DOING_CRON )
        return;

    # Solo actualizar el meta de plantilla de página si estamos en nuestro tipo de entrada específico
    elseif(MY_CUSTOM_POST_TYPE === $_POST['post_type'])
        update_post_meta($post_id, '_wp_page_template', esc_attr($_POST['_wp_page_template']));
}
add_action('save_post', 'save_page_template');


/**
 * Establecer la plantilla de página
 * @param string $template La plantilla determinada por WordPress
 * @return string $template Ruta completa a la plantilla predefinida o personalizada
 */
function set_page_template($template){
    global $post;
    if(MY_CUSTOM_POST_TYPE === $post->post_type){
        $custom_template = get_post_meta($post->ID, '_wp_page_template', true);
        if($custom_template)
            #como nuestro menú solo da el nombre base, usa la función locate_template() para encontrar fácilmente la ruta completa
            return locate_template($custom_template);
    }
    return $template;
}
add_filter('single_template', 'set_page_template');

Esta es una respuesta un poco tardía, pero pensé que sería valiosa ya que nadie en la web ha documentado este enfoque que yo sepa. Espero que esto ayude a alguien.

13 oct 2012 09:29:41
0

Actualización para el código de Brian, descubrí que cuando no se utilizaba el cuadro desplegable, la opción de plantilla "default" se guardaba en wp_page_template, lo que hacía que intentara encontrar una plantilla llamada default. Este cambio simplemente verifica si la opción es "default" al guardar y elimina el meta del post en su lugar (útil si cambiaste la opción de plantilla de vuelta a default)

elseif(MY_CUSTOM_POST_TYPE === $_POST['post_type']) {

if ( esc_attr($_POST['_wp_page_template']) === "default" ) :
    delete_post_meta($post_id, '_wp_page_template');
else :
    update_post_meta($post_id, '_wp_page_template', esc_attr($_POST['_wp_page_template']));
endif;
}
29 may 2014 21:20:33