WordPress traduction des slug

Slug et traduction pour les Custom Post Types WordPress

Si vous créez un type de contenu customisé (custom post type) pour votre site ou un plugin autonome, vous souhaiterez sûrement mettre en place un système convivial de traduction ou de modification de son slug permettant de créer les bases d’URL optimisées SEO pour ce dernier. Nous allons ici créer un plugin utilisant les API Options et Settings de WordPress, et permettant la mise à jour automatique des permaliens (percalines) de nos types de contenu personnalisés.

Vous l’aurez compris, l’idée clé est ici de produire grâce au slug WordPress, un contenu flexible. En effet, tout l’intérêt des custom post types est de pouvoir en proposer un affichage formaté, mais surtout de permettre leur catégorisation et un accès standardisé et sémantiquement pertinent, et pour cela, quoi de mieux qu’une URL bien propre ?

Nous allons donc nous aider de l’API d’options de WordPress de façon précoce pour prédéfinir notre slug, et l’API Settings pour créer notre page de personnalisation au sein d’un plugin volontairement basique. Nous encapsulerons nos fonctions dans une classe pour plus de clarté.

Pour information, un slug n’est pas une limace, mais un formatage du titre de page ou d’article visant à produire une URL dite user friendlyet je ne suis pas entomologiste, mais bien webmaster freelance ;-).

Création du plugin et enregistrement des hooks d’activation et désactivation

Pour définir nos options à l’activation du plugin (et les supprimer lors de sa désactivation) nous allons utiliser register_activation_hook() et register_deactivation_hook() et ciblant des méthodes statiques de notre classe, ce qui autorisera leur utilisation avant même son instanciation. Commençons donc par créer note plugin :


 #wp-content/plugins/jst_i18nable/jst_i18nable.php
<?php 
/**
 * Plugin Name: 18nable CPT 
 * Description: Provides a custom post type with translatable slug for your CPT can be anything 
 */ 
if( !defined( 'ABSPATH' ) ) exit; 
if( !class_exists( 'JST_Generic_CPT' ) ) { 
	class JST_Generic_CPT { 
		private $opts; 
		/** 
		 * Constructor 
		 */ public function __construct() { 
			/** 
			 * récupération/Assignation des options 
			 */ 
			$this->opts = get_option( 'jst_generic_cpt' );
		}
		/**
		 * Activation
		 */
		static public function activation()
		{
			add_option( 'jst_generic_cpt', array(
				'slug'			=> 'generic-cpt',
				'singular'		=> 'Genetric CPT',
				'plural'		=> 'Generic CPTs',
				'menu_title'	=> 'Generic CPT'
			) );
		}

		/**
		 * Deactivation
		 */
		static public function deactivation()
		{
			delete_option( 'jst_generic_cpt' );
		}
	}
	/**
	 * Création et suppression de l'option à l'activation/désactivation
	 */
	register_activation_hook( __FILE__, array( 'JST_Generic_CPT', 'activation' ) );
	register_deactivation_hook( __FILE__, array( 'JST_Generic_CPT	', 'deactivation' ) );
	new JST_Generic_CPT; 
}

La méthode add_option() est évidemment la première méthode de l’API options de WordPress. Nous enregistrons une option sous forme de tableau pour ne pas polluer inutilement la base de données, et plus précisément la table wp_options. Ce tableau sera sérialisé automatiquement à l’enregistrement des données. Dans le constructeur de notre class, nous assignons directement nos options à une propriété $opts pour nous permettre de les récupérer à tout moment.

Une page d’administration pour gérer le slug

Pour créer des pages d’administration dans WordPress, on peut utiliser le hook ‘admin-menu’. Cette page permettra à l’utilisateur de remplir les champs avec la valeur souhaitée. On peut bien sûr s’en servir pour proposer beaucoup d’autres options à gérer. Toujours dans le constructeur :


	/**
	 * Création page d'option
	 */
	add_action( 'admin_menu', array( $this, 'option_pages' ) );

Puis on définit notre méthode de classe ‘option_pages’ :


	/**
	 * Création des pages d'option
	 */
	public function option_pages()
	{
		add_options_page(
			'Generic CPT Settings',
			'GCPT Settings',
			'manage_options',
			__FILE__,
			array( $this, 'generic_cpt_options_page' )
		);
	}

Voici un rappel de la signature de add_option_page():


	add_option_page( $page_title, $menu_title, _$capabilities, $page_slug, $callback )

$capabilities est la permission requise que l’utilisateur devra avoir sur le site pour se voir présenter la page. $page_slug correspond à l’argument « page » de la query string correspondant à notre page d’administration. $callback est la méthode proposant l’affichage, il nous faut donc la définir immédiatement.

Affichage de la page de gestion des options : API Settings en action

L’API Settings de WordPress est faite pour simplifier l’affichage de formulaire de paramétrage des options. Elle gère la sécurisation du formulaire en générant pour nous les nonces, et elle permet de le partitionner afin d’offrir des sections et champs de formulaire documentés aux utilisateurs.

Nous nous contentons de définir les balises

et nous peuplons le formulaire avec deux méthodes d’affichage des sections et des champs : settings_fields( $database_options_group ) et do_settings_sections( $page ).

Ici $database_options_group renvoie au nom du groupe d’options en base de données et $page au slug de la page du formulaire. Nous récupérons ce slug avec la fonction PHP basename( __FILE__, ‘.php’ ), en amputant le nom de notre fichier de son extension :


	/**
	 * Generic CPT Option Page
	 */
	public function generic_cpt_options_page()
	{
?>
	<div class="wrap">
		<h1><?php _e( 'Generic CPT Options', 'jst' ) ?></h1>		
		<form action="options.php" method="post">
			<?php settings_fields( 'jst_generic_cpt' ); ?>
			<?php do_settings_sections( basename(__FILE__, '.php') ); ?>
			<input type="submit" class="button button-primary" value="<?php _e( "Save", "jst" ); ?>" />
		</form>
	</div>
<?php
	}

API Settings : enregistrement des paramètres

Cette API nous simplifie beaucoup la vie. Elle nous guide dans le processus d’assignation des options aux sections et champs de formulaire. Nous enregistrons le groupe d’options dans l’API avec la méthode register_settings()

Enregistrement des settings du formulaire

La signature proposée par le codex peut être un peu déroutante :

code>
	register_setting( $option_group, $option_name, $sanitize_callback )

Sachez qu’en général vous assignerez aux 2 premiers arguments le nom de l’option défini avant l’instanciation de la classe, en clair il devra correspondre à l’enregistrement de nos options en base de données.<br ?–> Pour appeler l’enregistrement des settings, on utilise le hook « admin_init » depuis le constructeur :


	/**
	 * Section et champs settings
	 */
	add_action( 'admin_init', array( $this, 'register_settings' ) );

Puis comme d’habitude nous créons la méthode de classe correspondante :


	/**
	 * Register Sections & Fields Settings
	 */
	public function register_settings()
	{
	 	register_setting(
			'jst_generic_cpt',
			'jst_generic_cpt',
			array( $this, 'sanitize_jst_generic_cpt' )
		);
	);

À ce stade, il nous faut impérativement construire notre fonction d’assainissement (sanitization dans le texte) des entrées utilisateur pour ces options. Cela consiste principalement à filtrer les données attendues correspondant à nos champs et à les renvoyer filtrées. On se contentera ici d’utiliser sanitize_text_field(), mais il faudrait quand même en profiter pour tout mettre en minuscule, remplacer les lettres accentuées et mettre des tirets à la place des éventuels espaces :


	/**
	 * Sanitize jST Generic CPT Options
	 */
	public function sanitize_jst_generic_cpt( $inputs )
	{
		$valid[ 'slug' ] = sanitize_text_field( $inputs[ 'slug' ] );
		return $inputs;
	}

Définition d’une section de formulaire et son champ associé

Revenons à notre méthode register_settings(). Nous y définissons maintenant une section pour notre page d’options ainsi qu’une fonction de rappel destinée à afficher un descriptif ou une aide contextuelle. Le premier argument est l’ID de la section et sera utilisé par la déclaration des champs qui lui seront associés. Les arguments suivant dans la signature sont le titre, le callback de description et le slug de la page :

Votre devis en 48 H chrono !

Demandez à être rappelé !

Nous préciserons ensemble votre projet
de vive voix 


		/**
		 * i18n Settings Section
		 */
		add_settings_section(
			'jst_generic_cpt_i18n',
			__( 'Genric CPT i18n', 'jst'),
			array( $this, 'jst_cpt_i18n_description'),
			basename( __FILE__, '.php' )
		);

Nous définirons plus tard une fonction simple affichant notre paragraphe d’aide pour la section. Ajoutons directement un champ « slug » pour « jst_generic_cpt_i18n ». La méthode add_settings_field() possède une signature assez proche de sa cousine servant à initialiser la section. Deux différences : le callback renvoie ici à la méthode de construction du champ (nous la voyons juste après), et le dernier argument se rapporte à l’ID de la section précédemment définie. Il existe en fait un cinquième argument exploitable servant à passer des données au callback mais nous ne l’utilisons pas ici :


		/**
		 * Slug Field
		 */
		add_settings_field(
			'jst_generic_cpt_slug',
			__( 'Slug', 'jst' ),
			array( $this, 'jst_cpt_slug' ),
			basename( __FILE__, '.php' ),
			'jst_generic_cpt_i18n'
		);
	}

Affichage d’une aide contextuelle pour la section du formulaire de gestion d’options

add_settings_section() définit un callback. La fonction associée sert à donner des conseils et avertissements à l’utilisateur :


	/**
	 * JST Generic CPT i18n Section
	 */
	public function jst_cpt_i18n_description()
	{
		echo '<p>'.__( 'More than just a translation, provide all menu items, names and slug for your CPT.' ).'</p>';
	}

Affichage du champ input associé à la section

L’idée est de récupérer les options stockées dans notre propriété de classe $opts et d’n extraire la valeur du champ à afficher. La définition des ces options à l’activation du plugin nous garantie que nous trouverons un tableau et notre fonction de sanitisation force la présence de l’index dans le tableau :


	/**
	 * Jst Generic CPT Slug Field
	 */
	public function jst_cpt_slug()
	{
		/**
		 * Récupération des options
		 */
		$opts = $this->opts;
		$slug = isset( $opts[ 'slug' ] ) ? esc_attr( $opts[ 'slug' ] ) : '';
		echo '<input name="jst_generic_cpt[slug]" type="text" value="'.$slug.'" />';
	}

Le détail à remarquer est l’utilisation de esc_attr() qui nous permet d’éviter les problèmes d’affichage des caractères « <« , « > » et « & ». Bon, nous voilà avec une belle page d’options, mais en l’état elle ne sert pas à grand-chose … Enregistrons un custom post type avec notre slug dynamique …

Enregistrement du custom post type avec traduction du slug

Je vous l’ai dit, l’avantage avec les classes, c’est que les propriétés sont accessibles partout. Nous nous servons du hook « init » dans le constructeur pour appeler la méthode d’enregistrement du type de contenu personnalisé. On va d’ailleurs élargir un peu la méthode pour la préparer à la réception d’éventuels types de contenus additionnels. Dans le constructeur :


	add_action( 'init', array( $this, 'register_post_types' ) );

Nous y sommes, notre méthode de classe register_post_types() définit un tableau associatif de custom post types. Les indices correspondent à l’identifiant du contenu, les valeurs à un tableau d’options définissant les noms singuliers et pluriels, les éléments supportés par les posts, une icône et le slug si important pour la définition de nos URL. L’important est ici l’option « slug » du tableau d’option associé au type de contenu (dont on récupère la valeur depuis notre propriété de classe $opts) et son utilisation lors de l’enregistrement de celui-ci. On utilisera à cette fin l’option (l’index) « rewrite » du tableau d’arguments passé en deuxième paramètre de register_post_type. Les éléments libellés correspondants au menu d’administration de WordPress,on peut se contenter d’utiliser la fonction de traduction classique __() avec sprintf() : Enfin, dès l’enregistrement des custom post types dans la boucle foreach, nous appelons flush_rewrite_rules() pour éviter à l’utilisateur d’avoir à resauvegarder manuellement ses permaliens via le menu du même nom de l’interface :


	/**
	  * Register Generic CPT & others
	  */
	 public function register_post_types()
	{
		$types = array(
			'generic-cpt' => array(
				'menu_title'		=> 'GCPT',
				'singular'		=> 'Generic Custom Post',
				'plural'		=> 'Generic Custom Posts',
				'supports'		=> array( 'thumbnails', 'title', 'editor', 'excerpt', 'post-format' ),
				'icon'			=> 'dashicons-groups',
				'slug'			=> $this->opts[ 'slug' ]
			)
			/**
			 * Les autres éventuels CPT à enregistrer
			 */
		);
		$counter  = 0;
		foreach( $types as $type=>$args )
		{
			$labels = array(
				'name'			=> $args[ 'menu_title' ],
				'singular_name'		=> $args[ 'singular' ],
				'add_new'		=> sprintf( __( 'Add new %s', 'jst' ), $args[ 'singular' ] ),
				'add_new_item'		=> sprintf( __( 'Add new %s', 'jst' ), $args[ 'singular' ] ),
				'edit_item'		=> sprintf( __( 'Edit %', 'jst' ), $args[ 'singular' ] ),
				'all_items'		=> sprintf( __( 'All %s', 'jst' ), $args[ 'plural' ] ),
				'view_item'		=> sprintf( __( 'View %', 'jst' ), $args[ 'singular' ] ),
				'search_items'		=> sprintf( __( 'Search %s', 'jst' ), $args[ 'plural' ] ),
				'no_items_found'	=> sprintf( __( 'No %s found', 'jst' ), $args[ 'plural' ] ),
				'no_items_found_in_trash' => sprintf( __( 'No %s found in trash', 'jst' ), $args[ 'plural' ] ),
				'menu_name'		=> $args[ 'menu_title' ]
			);
			register_post_type( $type, array(
				'labels'		=> $labels,
				'public'		=> true,
				'publicly_queryable'	=> true,
				'has_archive'		=> true,
				'supports'		=> $args[ 'supports' ],
				'capability_type'	=> 'post',
				'rewrite'		=> array( 'with_front' => false, 'slug' => $args[ 'slug' ] ),
				'menu_icon'		=> $args[ 'icon' ],
				'menu_position'		=> 20 + $counter
			) );
			$counter ++;
		}
		flush_rewrite_rules();
	}

Nombres d’options présentées ici ne sont que des piqûres de rappel. Notons toutefois que bien que le codex nous dise explicitement qu’il vaut mieux ne pas appeler flush_rewrite_rules() depuis le hook « init », réaliser ce flush en fin de boucle est le seul moyen d’accéder directement à la page du post type créé antérieurement à une modification de slug. Tentez de supprimer cette ligne et vous aurez droit à une erreur 404.

Flush des règles de réécriture à l’activation et la désactivation du plugin

Enfin nous garantissons une bonne intégration du mécanisme en appelant à nouveau le flush depuis les callbacks d’activation et de désactivation :


	/**
	 * Activation
	 */
	static public function activation()
	{
		// ...
		flush_rewrite_rules();
	}

	/**
	 * Deactivation
	 */
	static public function deactivation()
	{
		// ...
		flush_rewrite_rules();
	}

Traduction des slugs WordPress : des custom post types flexibles

Vous voilà donc en présence d’un plugin 100% fonctionnel vous autorisant une création de contenus à la fois personnalisés et génériques. Cool non ? Pensez donc à proposer une fonctionnalité de traduction des slugs aux utilisateurs de vos custom post types, vous leur donnerez une dose de flexibilité et la capacité d’offrir des URL pertinentes aux moteurs de recherche.

Vous souhaitez vous documenter ? lisez les pages du codex :