single-{$post_type}-{slug}.php per tipi di post personalizzati
La mia parte preferita della gerarchia dei template di WordPress è la possibilità di creare rapidamente file di template per le pagine utilizzando lo slug, senza dover modificare la pagina in WordPress per selezionare un template.
Attualmente possiamo fare questo:
page-{slug}.php
Ma mi piacerebbe poter fare questo:
single-{post_type}-{slug}.php
In modo che, ad esempio, in un tipo di post chiamato review
, potrei creare un template per un post chiamato "La mia grande recensione" in single-review-la-mia-grande-recensione.php
Qualcuno ha già implementato qualcosa del genere? single-{post_type}-{slug}.php
A) La Base nel Core
Come puoi vedere nel Codex nella spiegazione della Gerarchia dei Template, single-{$post_type}.php
è già supportato.
B) Estendere la Gerarchia del Core
Fortunatamente ora ci sono alcuni filtri e hook all'interno di /wp-includes/template-loader.php
.
do_action('template_redirect');
apply_filters( 'template_include', $template )
- E: un filtro specifico dentro
get_query_template( $type, ... )
chiamato:"$type}_template"
B.1) Come Funziona
- Dentro il file del template loader, il template viene caricato da una variabile di query/wp_query condizionale:
is_*()
. - La condizione poi attiva (nel caso di un template "single"):
is_single() && $template = get_single_template()
- Questo attiva poi
get_query_template( $type, $templates )
, dove$type
èsingle
- Poi abbiamo il filtro
"{$type}_template"
C) La Soluzione
Visto che vogliamo solo estendere la gerarchia con un template che viene caricato prima dell'attuale template "single-{$object->post_type}.php"
, intercepteremo la gerarchia e aggiungeremo un nuovo template all'inizio dell'array dei template.
// Estendi la gerarchia
function add_posttype_slug_template( $templates )
{
$object = get_queried_object();
// Nuovo
$templates[] = "single-{$object->post_type}-{$object->post_name}.php";
// Come nel core
$templates[] = "single-{$object->post_type}.php";
$templates[] = "single.php";
return locate_template( $templates );
}
// Ora aggiungiamo il filtro all'hook appropriato
function intercept_template_hierarchy()
{
add_filter( 'single_template', 'add_posttype_slug_template', 10, 1 );
}
add_action( 'template_redirect', 'intercept_template_hierarchy', 20 );
NOTA: (Se vuoi usare qualcosa di diverso dallo slug predefinito dell'oggetto) Dovrai modificare $slug
in base alla tua struttura dei permalink. Basta usare ciò che ti serve dal globale (object) $post
.
Ticket Trac
Poiché l'approccio sopra attualmente non è supportato (puoi solo filtrare il percorso assoluto in questo modo), ecco una lista di ticket trac:
- Estendere la gerarchia con post_type->slug
- Introdurre un filtro per
get_query_template()
- Filtrare l'intera gerarchia - ticket più promettente ⤎ segui questo ticket di @scribu come cc

Voglio provare questo, ma sembra che manchi qualcosa nella tua riga add_filter alla fine.

@supertrue Ottimo occhio. :) Ho trovato un altro )
mancante all'interno del filtro. Sistemato. Forse vorresti sostituire il trattino con un underscore prima dello slug all'interno del template. Solo per far risaltare meglio il suffisso quando si controllano i template.

Causa questo errore sul sito: Avviso: array_unshift() [function.array-unshift]: Il primo argomento dovrebbe essere un array nella [riga contenente array_unshift]

Ok, ma allora qualcos'altro sta intercettando i template core. La funzione funziona bene e $templates
è un array. Vedi le funzioni core in questo pastebin (senza data di scadenza). Assicurati di testare questo con un'installazione senza plugin e con il tema di default. Poi attivali uno dopo l'altro e verifica se l'errore si ripresenta.

Sì, ho debugato e ottengo il percorso assoluto finale del primo template trovato come stringa. Dovrò parlare con qualche sviluppatore core riguardo a questo, prima di modificare la risposta. Inoltre: ho fatto confusione: slug
è disponibile solo per termini e tassonomie. Dovresti sostituire $post->post_name
con ciò che si adatta alla tua struttura dei permalink. Attualmente non c'è modo di farlo automaticamente per tutti i casi recuperando e sostituendo il percorso in base alla tua struttura dei permalink e alle regole di rewrite. Aspettati un altro aggiornamento.

Ti ho lasciato una versione funzionante - comunque questa non è soddisfacente (copiare/incollare l'array core e poi riaggiungerlo all'interno di una funzione personalizzata).

questo non sembra aver avuto alcun effetto... la parte finale è corretta? Non vedo che intercept_template_hierarchy
venga chiamato da nessuna parte. Cambiando l'ultimo add_posttype_slug_template
in intercept_template_hierarchy
ha restituito Fatal error: [] operator not supported for strings

@supertrue Sì, questo è un problema. Semplicemente non funziona. Sto già parlando con uno sviluppatore core per inserirlo nel ciclo della 3.4. Torna tra qualche giorno e ripubblica questa domanda con un commento qui. Grazie. Nota a margine: il core si comporta in modo estremamente stupido a questo punto, poiché l'array dei template non viene filtrato, ma viene filtrato invece il percorso del template localizzato. Considero il fatto che questo sia effettivamente un bug.

@supertrue La discussione è già iniziata. Aspettati un aggiornamento nei prossimi giorni. Se non sarà un aggiornamento, magari sarà un ticket su trac :P

@supertrue Dominik Schilling (alias Ocean90) si è aggiunto autonomamente ad alcuni ticket. Ci si aspetta progressi con la versione 3.4.

@kaiser la tua soluzione sembra davvero carina ma non funziona ($templates non è un array nel mio caso... ricevo un errore fatale). C'è qualche possibilità di farla funzionare?

Seguendo l'immagine della Gerarchia dei Template, non vedo un'opzione del genere.
Quindi ecco come procederei:
Soluzione 1 (La migliore secondo me)
Crea un file template e associalo alla recensione
<?php
/*
Template Name: La mia fantastica recensione
*/
?>
Aggiungendo il file template php nella directory del tuo tema, apparirà come opzione di template nella pagina di modifica del tuo articolo.
Soluzione 2
Questo potrebbe probabilmente essere ottenuto usando l'hook template_redirect
.
Nel file 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' );
MODIFICA
Aggiunto controllo file_exists

@kaiser Dev'essere stato in qualche tutorial che ho seguito all'epoca, se non è necessario lo rimuoverò.

@kaiser: Il exit()
è necessario per evitare il caricamento del template predefinito.

La risposta più votata (di 4 anni fa) non funziona più, ma il codex di WordPress fornisce la soluzione qui:
<?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 );
?>

Nel mio caso, ho dei custom post type Album e Traccia collegati da una tassonomia Album. Volevo poter utilizzare template Single diversi per i post Album e Traccia in base alla loro tassonomia Album.
Sulla base della risposta di Kaiser sopra, ho scritto questo codice. Funziona bene.
Nota: Non avevo bisogno dell'add_action().
// Aggiunge un'opzione di template aggiuntiva alla gerarchia dei template
add_filter( 'single_template', 'add_albumtrack_taxslug_template', 10, 1 );
function add_albumtrack_taxslug_template( $orig_template_path )
{
// a questo punto, $orig_template_path è un percorso assoluto localizzato al template single preferito.
$object = get_queried_object();
if ( ! (
// specifica un'altra opzione di template solo per i post type Album e Traccia.
in_array( $object->post_type, array( 'gregory-cpt-album','gregory-cpt-track' )) &&
// verifica che la tassonomia Album sia stata registrata.
taxonomy_exists( 'gregory-tax-album' ) &&
// ottiene il termine della tassonomia Album per il post corrente.
$album_tax = wp_get_object_terms( $object->ID, 'gregory-tax-album' )
))
return $orig_template_path;
// compone il nome del template
// presupposto: solo un termine di tassonomia Album per post. usiamo il primo oggetto nell'array.
$template = "single-{$object->post_type}-{$album_tax[0]->slug}.php";
$template = locate_template( $template );
return ( !empty( $template ) ? $template : $orig_template_path );
}
Ora posso creare template chiamati single-gregory-cpt-track-tax-serendipity.php e single-gregory-cpt-album-tax-serendipity.php e WP li utilizzerà automaticamente; 'tax-serendipity' è lo slug per il primo termine della tassonomia Album.
per riferimento, l'hook del filtro 'single_template' è dichiarato in:
/wp-includes/theme.php: get_query_template()
Grazie a Kaiser per il codice di esempio.
Saluti, Gregory

Ciao Greg - benvenuto su WPSE. Per favore, pubblica solo risposte alle domande - non domande di follow-up. Se hai una domanda che non è stata risposta da una risposta esistente ed è troppo lunga per un commento, per favore apri un'altra domanda :)

funziona per te? prima di tutto, '$template' non dovrebbe essere commentato nel tuo codice.. e penso che invece di '$album_tax[0]->slug' ci dovrebbe essere '$object->post_name', non è vero?

Utilizzare i Template di Pagina
Un altro approccio per la scalabilità sarebbe duplicare la funzionalità del menu a discesa dei template di pagina sul tipo di post page
per il tuo custom post type.
Codice Riusabile
La duplicazione nel codice non è una buona pratica. Nel tempo può causare un grave gonfiamento della codebase rendendola poi molto difficile da gestire per uno sviluppatore. Invece di creare un template per ogni singolo slug, molto probabilmente avrai bisogno di un template uno-a-molti che possa essere riutilizzato invece di un rapporto uno-a-uno tra post e template.
Il Codice
# Definisci la stringa del tuo custom post type
define('MY_CUSTOM_POST_TYPE', 'my-cpt');
/**
* Registra il 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', #Preferisco il posizionamento sotto il meta box delle azioni del post
'low'
);
}
/**
* Renderizza il tuo metabox - Questo codice è simile a quello renderizzato sul tipo di post 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'>Template Pagina</label>
<select name='_wp_page_template' id='page_template'>
<option value='default'>Template Predefinito</option>";
page_template_dropdown($template);
echo "</select>";
}
/**
* Salva il template della pagina
* @return void
*/
function save_page_template($post_id){
# Salta i salvataggi automatici
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return;
elseif ( defined( 'DOING_AJAX' ) && DOING_AJAX )
return;
elseif ( defined( 'DOING_CRON' ) && DOING_CRON )
return;
# Aggiorna il meta del page template solo se siamo sul nostro specifico post type
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');
/**
* Imposta il template della pagina
* @param string $template Il template determinato dal cervello di WordPress
* @return string $template Percorso completo al template di pagina predefinito o personalizzato
*/
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)
#poiché il nostro menu a discesa restituisce solo il nome base, usa la funzione locate_template() per trovare facilmente il percorso completo
return locate_template($custom_template);
}
return $template;
}
add_filter('single_template', 'set_page_template');
Questa è una risposta un po' tardiva, ma ho pensato che potesse essere utile dato che nessuno sul web ha documentato questo approccio per quanto ne so. Spero che questo possa aiutare qualcuno.

Aggiornamento per il codice di Brian, ho scoperto che quando la casella a discesa non veniva utilizzata l'opzione template "default" veniva salvata in wp_page_template causando il tentativo di trovare un template chiamato default. Questa modifica verifica semplicemente la presenza dell'opzione "default" durante il salvataggio e invece cancella il post meta (utile se hai cambiato l'opzione del template tornando 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;
}
