Les custom post types pourquoi et comment ?

Introduction

Ce tutoriel explique la mise en œuvre des custom post types (type de post personnalisé) sous WordPress afin d’ajouter une nouvelle forme de contenu.

Nous étudierons la création d’un custom post type avec l’aide du plugin Custom Post Types UI et son application dans l’interface admin.

Puis, nous aborderons son traitement afin d’en afficher les informations côté site web par l’intermédiaire des templates de notre thème et de la feuille de style.

Tout le processus se déroulera avec un exemple concret.

Pourquoi ajouter des custom post types ou une nouvelle forme de contenu à WordPress ?

WordPress comprend nativement cinq types de contenu :

  • les articles,
  • les pages,
  • les fichiers attachés (les médias),
  • les menus de navigation,
  • les révisions.

Ces cinq types de contenu de base ne sont pas toujours suffisants et nous pouvons éprouver le besoin de distinguer différents contenus.

A titre d’exemple, nous pouvons évoquer :

  • un portfolio qui affiche les projets réalisés,
  • une bibliothèque ou un médiathéque pour la gestion de livres ou films,
  • la gestion de biens immobiliers…

Ce dernier exemple nous servira d’étude de cas.

Comment ajouter un nouveau type de contenu ?

Nous pouvons ajouter un type de contenu :

  • soit avec l’aide d’un plugin comme Custom Post Types UI
  • soit en générant le code (directement ou indirectement)

Nous étudierons d’abord le plugin puis nous verrons comment générer et intégrer le code.

En effet, le plugin n’est qu’une présentation ergonomique et plus conviviale des différents arguments de la fonction WordPress register_post_type.

Quelles fonctionnalités allons nous mettre en pratique ?

  • Une interface administrative pour gérer le contenu,
  • des templates (PHP) dans le thème pour visualiser notre nouveau contenu,
  • une feuille de style (CSS) dans le thème pour la mise en forme de contenu,
  • un javascript (JS) pour ajouter de l’interactivité.

La mise en place de ces fonctionnalités nous permettra de :

Dans notre étude de cas, la gestion de biens immobiliers, ces fonctionnalités se traduiront sur les points suivants :

  • les biens se comporteront comme un article de base,
  • mais ils pourront être classés, soit, comme maison, soit, comme appartement :
    • fait appel à la notion de taxonomie (à la manière des catégories)
    • avec une sélection unique du type de bien (choix exclusif avec des boutons radio).
  • Tous les biens immobiliers, en général, pourront être listés sur une page (archive),
  • mais aussi par type de bien – maison ou appartement – (taxonomy).
  • Un bien immobilier pourra être affiché avec tout son descriptif,
  • avec une navigation sur le même type de bien (maison précédent, maison suivante).
  • Un menu intégrera le Custom Post Types et les termes de la taxonomie.
  • Enfin, la page d’accueil de notre site intégrera les biens immobiliers.

Quelques précisions

WordPress utiliser le terme technique slug pour identifier notre custom post type (entre autres). Ce terme opère comme un identifiant et prend la forme d’une chaine alphanumérique.

Cet identifiant est très important car nous le retrouvons dans :

  • la définition des noms de templates spécifiques aux Custom Post Types ,
  • le nom des classes générées automatiquement par WordPress,
  • le nom de paramètres de certaines fonctions WordPress.

Signalons aussi que ce tutoriel n’abordera pas la question des champs personnalisés qui sera traité prochainement dans un autre tutoriel vidéo.

Les sources

Les templates du thème enfant

archive-immo.php
<?php
/**
 * The template for displaying immo CPT archive pages
 *
 * @link https://codex.wordpress.org/Template_Hierarchy
 *
 * @package WordPress
 * @subpackage Babel_Web_tutoriels
 * @since Babel Web tutoriels 1.0
 */

get_header(); ?>

	<div id="primary" class="content-area">
		<main id="main" class="site-main" role="main">

		<?php if ( have_posts() ) : ?>

			<header class="page-header">
				<?php
					the_archive_title( '<h1 class="page-title">', '</h1>' );
					the_cpt_description( '<div class="cpt-description">', '</div>' );
				?>
			</header><!-- .page-header -->
            <div id="immo-grid" class="immo-grid">
                <ul class="immo-list">
                <?php
                // Start the Loop.
                while ( have_posts() ) : the_post();

                    get_template_part( 'template-parts/grid', 'immo' );

                // End the loop.
                endwhile;
                ?>
                </ul> <!-- .immo-list -->
            </div> <!-- .immo-grid -->
            <?php
			// Previous/next page navigation.
			the_posts_pagination( array(
				'prev_text'          => __( 'Previous page', 'twentysixteen' ),
				'next_text'          => __( 'Next page', 'twentysixteen' ),
				'before_page_number' => '<span class="meta-nav screen-reader-text">' . __( 'Page', 'twentysixteen' ) . ' </span>',
			) );

		// If no content, include the "No posts found" template.
		else :
			get_template_part( 'template-parts/content', 'none' );

		endif;
		?>

		</main><!-- .site-main -->
	</div><!-- .content-area -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

 

template-parts/grid-immo.php
<?php
/**
 * The template part for displaying immo content in grid form
 *
 * @package WordPress
 * @subpackage Babel_Web_Tutoriels
 * @since Babel Web Tutoriels 1.0
 */
?>

<figure id="post-<?php the_ID(); ?>" <?php post_class(); ?> href="<?php echo esc_url( get_permalink() ) ?>">
	<?php the_post_thumbnail("medium"); ?>
	<figcaption>
		<h2><?php 
			$term_list = wp_get_post_terms($post->ID, 'type-immo', array("fields" => "names"));
			echo $term_list[0];
		?></h2>
		<p class='description'><?php the_title(); ?></p>
	</figcaption>
</figure>

 

taxonomy-type-immo.php
<?php
/**
 * The template for displaying archive type immo pages
 *
 * @link https://codex.wordpress.org/Template_Hierarchy
 *
 * @package WordPress
 * @subpackage Babel_Web_Tutoriels
 * @since Babel Web Tutoriels 1.0
 */

get_header(); ?>

	<div id="primary" class="content-area">
		<main id="main" class="site-main" role="main">

		<?php if ( have_posts() ) : ?>

			<header class="page-header">
				<?php
					the_archive_title( '<h1 class="page-title">', '</h1>' );
					the_archive_description( '<div class="cpt-description">', '</div>' );
				?>
			</header><!-- .page-header -->

			<div id="immo-grid" class="immo-grid">
				<ul class="immo-list">
				<?php
				// Start the Loop.
				while ( have_posts() ) : the_post();

					get_template_part( 'template-parts/grid', 'immo' );

				// End the loop.
				endwhile;
				?>
				</ul> <!-- .immo-list -->
			</div> <!-- .immo-grid -->
			<?php
			// Previous/next page navigation.
			the_posts_pagination( array(
				'prev_text'          => __( 'Previous page', 'twentysixteen' ),
				'next_text'          => __( 'Next page', 'twentysixteen' ),
				'before_page_number' => '<span class="meta-nav screen-reader-text">' . __( 'Page', 'twentysixteen' ) . ' </span>',
			) );

		// If no content, include the "No posts found" template.
		else :
			get_template_part( 'template-parts/content', 'none' );

		endif;
		?>

		</main><!-- .site-main -->
	</div><!-- .content-area -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

 

single-immo.php
<?php
/**
 * The template for displaying all single custom posts immo
 *
 * @package WordPress
 * @subpackage Babel_Web_Tutoriels
 * @since Babel Web Tutoriels 1.0
 */

get_header(); ?>

<div id="primary" class="content-area">
	<main id="main" class="site-main" role="main">
		<?php
		// Start the loop.
		while ( have_posts() ) : the_post();

			// Include the single post content template.
			get_template_part( 'template-parts/content', 'single-immo' );

			// Previous/next post navigation.
			the_post_navigation( array(
				'next_text' => '<span class="meta-nav" aria-hidden="true">' . __( 'Next', 'twentysixteen' ) . '</span> ' .
					'<span class="screen-reader-text">' . __( 'Next post:', 'twentysixteen' ) . '</span> ' .
					'<span class="post-title">%title</span>',
				'prev_text' => '<span class="meta-nav" aria-hidden="true">' . __( 'Previous', 'twentysixteen' ) . '</span> ' .
					'<span class="screen-reader-text">' . __( 'Previous post:', 'twentysixteen' ) . '</span> ' .
					'<span class="post-title">%title</span>',
				'in_same_term'	=> true,
				'taxonomy'	=> 'type-immo'
			) );
			// End of the loop.
		endwhile;
		?>

	</main><!-- .site-main -->
</div><!-- .content-area -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

 

template-parts/content-single-immo.php
<?php
/**
 * The template part for displaying single custom posts immo
 *
 * @package WordPress
 * @subpackage Babel_Web_Tutoriels
 * @since Babel Web Tutoriels 1.0
 */
?>

<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
	<header class="entry-header">
		<?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
	</header><!-- .entry-header -->

	<?php twentysixteen_excerpt(); ?>

	<?php twentysixteen_post_thumbnail(); ?>

	<div class="entry-content">
		<?php
			the_content();
		?>
	</div><!-- .entry-content -->
</article><!-- #post-## -->

 

functions.php
<?php
/**
 * gestion du cpt UI immo
 */
add_action( 'init', 'cptui_register_my_cpts_immo' );
function cptui_register_my_cpts_immo() {
	$labels = array(
		"name" => __( 'Immobiliers', 'bwt' ),
		"singular_name" => __( 'Immobilier', 'bwt' ),
		"menu_name" => __( 'Immobilier', 'bwt' ),
		"all_items" => __( 'Immobiliers', 'bwt' ),
		"add_new" => __( 'Ajouter', 'bwt' ),
		"add_new_item" => __( 'Ajouter un bien immobilier', 'bwt' ),
		"edit_item" => __( 'Modifier', 'bwt' ),
		"new_item" => __( 'Nouveau bien immobilier', 'bwt' ),
		"view_item" => __( 'Voir un bien immobilier', 'bwt' ),
		"search_items" => __( 'Rechercher un bien immobilier', 'bwt' ),
		"not_found" => __( 'Aucun bien immobilier correspondant à votre demande', 'bwt' ),
		"not_found_in_trash" => __( 'Aucun bien immobilier dans la poubelle', 'bwt' ),
		"archives" => __( 'Archives des biens immobiliers', 'bwt' ),
		);

	$args = array(
		"label" => __( 'Immobiliers', 'bwt' ),
		"labels" => $labels,
		"description" => "Gestion des biens immobiliers",
		"public" => true,
		"show_ui" => true,
		"show_in_rest" => false,
		"rest_base" => "",
		"has_archive" => true,
		"show_in_menu" => true,
		"exclude_from_search" => false,
		"capability_type" => "post",
		"map_meta_cap" => true,
		"hierarchical" => false,
		"rewrite" => array( "slug" => "biens-immobiliers", "with_front" => true ),
		"query_var" => true,
		"menu_position" => 5,"menu_icon" => "dashicons-building",		
		"supports" => array( "title", "editor", "thumbnail", "excerpt", "custom-fields", "revisions" ),		
		"taxonomies" => array( "type-immo" ),		
	);
	register_post_type( "immo", $args );

// End of cptui_register_my_cpts_immo()
}

/**
 * gestion de la taxonomie type-immo
 */
add_action( 'init', 'cptui_register_my_taxes_type_immo' );
function cptui_register_my_taxes_type_immo() {
	$labels = array(
		"name" => __( 'Types immobilier', 'bwt' ),
		"singular_name" => __( 'Type immobilier', 'bwt' ),
		"add_new_item" => __( 'Ajouter un nouveau type immobilier', 'bwt' ),
		);

	$args = array(
		"label" => __( 'Types immobilier', 'bwt' ),
		"labels" => $labels,
		"public" => true,
		"hierarchical" => true,
		"label" => "Types immobilier",
		"show_ui" => true,
		"query_var" => true,
		"rewrite" => array( 'slug' => 'biens-immobiliers-type', 'with_front' => true ),
		"show_admin_column" => true,
		"show_in_rest" => false,
		"rest_base" => "",
		"show_in_quick_edit" => true,
		'meta_box_cb' => 'type_immo_meta_box'
	);
	register_taxonomy( "type-immo", array( "immo" ), $args );

// End cptui_register_my_taxes_type_immo()
}


/**
 * Meta box pour les type-immo avec des boutons radio
 * http://wordpress.stackexchange.com/questions/139269/wordpress-taxonomy-radio-buttons
 */
function type_immo_meta_box($post, $meta_box_properties){
  $taxonomy = $meta_box_properties['args']['taxonomy'];
  $tax = get_taxonomy($taxonomy);
  $terms = get_terms($taxonomy, array('hide_empty' => 0));
  $name = 'tax_input[' . $taxonomy . ']';
  $postterms = get_the_terms( $post->ID, $taxonomy );
  $current = ($postterms ? array_pop($postterms) : false);
  $current = ($current ? $current->term_id : 0);
?>

<div id="taxonomy-<?php echo $taxonomy; ?>" class="categorydiv">
	<ul id="<?php echo $taxonomy; ?>-tabs" class="category-tabs">
		<li class="tabs"><a href="#<?php echo $taxonomy; ?>-all"><?php echo $tax->labels->all_items; ?></a></li>
	</ul>

	<div id="<?php echo $taxonomy; ?>-all" class="tabs-panel">
		<input name="tax_input[<?php echo $taxonomy; ?>][]" value="0" type="hidden">            
			<ul id="<?php echo $taxonomy; ?>checklist" data-wp-lists="list:symbol" class="categorychecklist form-no-clear">
<?php   
	foreach($terms as $term){
		$id = $taxonomy.'-'.$term->term_id;
?>
				<li id="<?php echo $id?>">
					<label class="selectit">
						<input value="<?php echo $term->term_id; ?>" name="tax_input[<?php echo $taxonomy; ?>][]" id="in-<?php echo $id; ?>"<?php if( $current === (int)$term->term_id ){?> checked="checked"<?php } ?> type="radio"> 
						<?php echo $term->name; ?>
					</label>
				</li>
<?php   
	}
?>
			</ul>
		</div>
	</div>
<?php
}



/**
 * Afficher la description du custom post type
 * @since babel web tutos 1.0
 */
function the_cpt_description ($before = "", $after = "") {
	global $post;
	$post_type = get_post_type_object( get_post_type($post) );
	echo $before.$post_type->description.$after;
}

/**
 * Ajouter les CPT biens immobiliers à la home page.
 * @since babel web tutos 1.0
 */
add_filter( 'pre_get_posts', 'bwt_get_posts' );
function bwt_get_posts( $query ) {
	if ( is_home() ) 
		//set_query_var('post_type', array( 'post', 'immo') );
		set_query_var('post_type', array( 'immo') );
}

/**
 * Displays the optional excerpt.
 * Wraps the excerpt in a div element.
 * Create your own twentysixteen_excerpt() function to override in a child theme.
 * @since Twenty Sixteen 1.0
 * @param string $class Optional. Class string of the div element. Defaults to 'entry-summary'.
 */
function twentysixteen_excerpt( $class = 'entry-summary' ) {
	$class = esc_attr( $class );

	if ( (has_excerpt() && !is_single()) || is_search() ) : ?>
		<div class="<?php echo $class; ?>">
			<?php the_excerpt(); ?>
		</div><!-- .<?php echo $class; ?> -->
	<?php endif;
}





/**
 * Enqueue scripts and styles.
 * @since babel web tutos 1.0
 */
function babelweb_scripts() {
	wp_enqueue_script( 'grid', get_stylesheet_directory_uri() . '/js/grid.js', array( 'jquery' ), '20160426', false );
}
add_action( 'wp_enqueue_scripts', 'babelweb_scripts' );
?>

Le CSS

css/cpt.css
/*
 * Single CPT - immo
 */
body.single-immo #main article,
body.single-immo .site-content {
	position: relative;
	background-color: #80dba4;
}

body.single-immo #main .entry-header {
	width:80%;
}

body.single-immo #main .entry-content {
	position: static;
	width:100%;
	padding:3%;
	text-align:justify;
}

body.single-immo .post-navigation {
	background-color: #a231af;
	padding:0 20px;
	border:0;
}

body.single-immo .post-navigation a:hover .post-title {
	color: #fff;
}

body.single-immo .post-navigation .meta-nav {
    color: #fff;
}

body.single-immo aside.sidebar {
	position: absolute;
	top: 0;
	margin-left:72%;
}

body.single-immo aside .widget {
    border-top: 2px solid #3eaf31;
	margin-bottom:2em;
}

body.single-immo aside .widget:first-child {
    border-top:0;
}

body.single-immo .site-footer {
	position:static;
	background-color: black;
}

/*
 * Archive CPT immo
 */
.post-type-archive-immo .site,
.post-type-archive-immo .site-content,
.post-type-archive-immo .content-area,
.post-type-archive-immo .site-main .page-header,
.immo-list {
	padding:0;
	margin:0;
}

.post-type-archive-immo .content-area {
    clear: both;
    float: none;
	padding:2%;
	background-color: #9380db;
	min-height: 770px;
}

.post-type-archive-immo .site-inner {
	max-width:100%;
	position:static;
}

.immo-grid {
	margin-top:2%;
}

body.post-type-archive-immo .site-content {
    background-color: #80dba4;
    position: relative;
}

body.post-type-archive-immo aside.sidebar {
    margin-left: 72%;
	position:absolute;
	top:0;
}

body.post-type-archive-immo .site-main .page-header,
body.post-type-archive-immo aside .widget:first-child {
    border-top: 0 none;
}

body.post-type-archive-immo aside .widget {
    border-top: 2px solid #3eaf31;
    margin-bottom: 2em;
}

body.post-type-archive-immo aside .widget h2 {
    color: #fff;
}

body.post-type-archive-immo .site-footer {
	position:static;
	background-color: black;
}

.immo-grid figure {
	font-family: Montserrat,"Helvetica Neue",sans-serif;
    background: #3085a3 none repeat scroll 0 0;
    cursor: pointer;
    display: inline-block;
    margin: 0 5px;
    overflow: hidden;
    position: relative;
    text-align: center;
}


.immo-grid figure img {
    max-width: 100%;
    min-height: 100%;
    opacity: 1;
    position: relative;
}

.immo-grid figure img {
    backface-visibility: hidden;
    transition: opacity 1s ease 0s, transform 1s ease 0s;
    -webkit-transition-property: opacity, -webkit-transform;
    -webkit-transition-duration: 1s, 1s;
    -webkit-transition-delay: 0s, 0s;
    -webkit-transition-timing-function: ease, ease;
}

.immo-grid figure figcaption::before, 
.immo-grid figure figcaption::after {
    pointer-events: none;
}

.immo-grid figure figcaption {
    position: absolute;
    width: 100%;
}

.immo-grid figure figcaption {
    backface-visibility: hidden;
    font-size: 0.5em;
    text-transform: uppercase;
    background: #fff none repeat scroll 0 0;
    bottom: 0;
    color: #3c4a50;
    padding: 1em;
    transform: translate3d(0px, 100%, 0px);
    -webkit-transform: translate3d(0px, 100%, 0px);
    transition: transform 0.35s ease 0s;
    -webkit-transition-property: -webkit-transform;
    -webkit-transition-duration: 0.35s;
    -webkit-transition-delay: 0s;
    -webkit-transition-timing-function: ease;
}

.immo-grid figure figcaption h2{
    display: inline-block;
	font-weight: 800;
    transform: translate3d(0px, 200%, 0px);
    -webkit-transform: translate3d(0px, 200%, 0px);
    transition: transform 0.35s ease 0s;
    -webkit-transition-property: -webkit-transform;
    -webkit-transition-duration: 0.35s;
    -webkit-transition-delay: 0s;
    -webkit-transition-timing-function: ease;
}

.immo-grid figure h2, 
.immo-grid figure p {
    margin: 0;
}

.immo-grid figure p {
    bottom: 10em;
    color: #fff;
    font-size: 1.8em;
	text-transform:uppercase;
    letter-spacing: 1px;
	font-weight:700;
    opacity: 0;
    position: absolute;
    transition: opacity 0.35s ease 0s;
    -webkit-transition-property: opacity;
    -webkit-transition-duration: 0.35s;
    -webkit-transition-delay: 0s;
    -webkit-transition-timing-function: ease;
}

/******* [transition effect] ********/
/*******    roll over mode   ********/
/*
 */
.immo-grid figure:hover img {
	opacity: 0.4;
	transform: scale3d(1.1,1.1,1);
	-webkit-transform: scale3d(1.1,1.1,1);
}

.immo-grid figure:hover figcaption,
.immo-grid figure:hover h2{
	transform: translate3d(0,0,0);
	-webkit-transform: translate3d(0,0,0);
}

.immo-grid figure:hover h2 {
	transition-delay: 0.05s;
	-webkit-transition-delay: 0.05s;
}

.immo-grid figure:hover p.description {
	opacity: 1;
}


/*
 * Taxonomy type-immo
 */
.tax-type-immo .site,
.tax-type-immo .site-content,
.tax-type-immo .content-area,
.tax-type-immo .site-main .page-header,
.immo-list {
	padding:0;
	margin:0;
}

.tax-type-immo .content-area {
    clear: both;
    float: none;
	padding:2%;
	background-color: #9380db;
	min-height: 770px;
}

.tax-type-immo .site-inner {
	max-width:100%;
	position:static;
}

body.tax-type-immo .site-content {
    background-color: #80dba4;
    position: relative;
}

body.tax-type-immo aside.sidebar {
    margin-left: 72%;
	position:absolute;
	top:0;
}

body.tax-type-immo .site-main .page-header,
body.tax-type-immo aside .widget:first-child {
    border-top: 0 none;
}

body.tax-type-immo aside .widget {
    border-top: 2px solid #3eaf31;
    margin-bottom: 2em;
}

body.tax-type-immo aside .widget h2 {
    color: #fff;
}

body.tax-type-immo .site-footer {
	position:static;
	background-color: black;
}

Le JS

js/grid.js
jQuery(document).ready(function($) {
	$("#immo-grid").on("click", "figure", function() {
		window.location.href = $(this).attr("href");
	});
});

Le SQL

Les types de post dans wp_posts
SELECT distinct post_type, count(*)
FROM bwt_posts
GROUP BY 1

custom post type

Les taxonomies et leurs termes
SELECT `object_id` as "id", post_title as "titre", post_type as "type de post", taxonomy as "taxonomie", terms.name as "terme"
FROM `bwt_term_relationships` as rel, `bwt_terms` as terms, `bwt_term_taxonomy` as tax, bwt_posts
WHERE 
rel.term_taxonomy_id = tax.term_taxonomy_id AND
tax.term_id = terms.term_id AND
object_id = ID

custom post types tax terms

Les traductions

Pour rappel, il ne s’agit pas de la traduction française officielle du plugin Custom Post Types UI mais de ma version officieuse.

Télécharger Custom Post Type UI fr_FR.

Quelques références

Fonctions WordPress

Filtres WordPress

Dashicons de WordPress

Plugins WordPress

Hiérarchie de templates

Hiérarchie de templates WordPress

Générateur de code pour les custom post types

Tutoriels sur WordPress