Fondamentaux

Le développement d’un site sous WordPress implique nécessairement deux choses :

La création d’un thème :

https://developer.wordpress.org/themes

L’ajout de fonctionnalités via des plugins.

https://developer.wordpress.org/plugins :

Le thème accueille les templates (les vues « php » ), les assets statiques (CSS, sprites) et le fichier functions.php dédié principalement à l’activation / désactivation de fonctionnalités du CMS.

Pour un aperçu des possibilités offertes par ce fichier, il est important de consulter chaque année le fichier « functions.php » du thème par défaut de WordPress.

Pour connaitre la liste des différents templates à intégrer à votre thème, rendez-vous sur la template hiérarchie à l’adresse ci-dessous :

https://developer.wordpress.org/themes/basics/template-hierarchy

Il existe deux grandes « familles » de templates. Les « archives » et les « singles »

Les archives affichent une liste de « posts » ou d’item.

Les singles affichent un seul post ou item.

Communément, en-tête et pied de page sont gérés dans des fichiers à part. (header.php et footer.php) et directement intégrés aux templates via les fonctions get_header() et get_footer().

header.php contiendra toujours la fonction wp_head() dans la balise html « head »
footer.php contiendra toujours la fonction wp_footer() avant la fermeture de la balise html.Ainsi, un thème minimaliste pourrait fonctionner seulement avec les fichiers suivants :

index.php
singular.php
header.php
footer.php
style.css
functions.php

Le fichier index.php s’occupera de gérer les pages « d’archives » (ie. catégorie, recherche, page d’accueil etc…) grâce à un recours à la « loop » WordPress.
https://codex.wordpress.org/The_Loop

<?php
get_header();

if ( have_posts() ) :

while ( have_posts() ) : the_post(); ?>

<article><?php the_title(); the_content(); ?></article>

<?php endwhile;

endif;

get_footer();

Le fichier singular.php s’occupera de gérer les pages « single » (article et page) :

<?php

get_header();

the_post(); ?>

<article><?php the_title(); the_content(); ?></article>

get_footer();

De base, les fonctionnalités « Widgets & Sidebar » ne sont pas disponibles dans votre thème. Cela fait partie des choses à activer à la demande via le fichier functions.php
https://codex.wordpress.org/Function_Reference/register_sidebar

Créer une sidebar :

// functions.php

add_action( 'widgets_init', 'theme_slug_widgets_init' );

function theme_slug_widgets_init() {

register_sidebar( array(

'name' => __( 'Main Sidebar', 'theme-slug' ),

'id' => 'sidebar-1',

'description' => __( 'Widgets in this area will be shown on all posts and pages.', 'theme-slug' ),

'before_widget' => '<li id="%1$s" class="widget %2$s">',

'after_widget'  => '</li>',

'before_title'  => '<h2 class="widgettitle">',

'after_title'   => '</h2>',

) );

}

La gestion des widgets est désormais accessibles en back-office. Et la sidebar peut-être appelée via la function get_sidebar() qui chargera le fichier sidebar.php de votre thème. (sur le même principe que header et footer).

Les « Hooks »

Que ce soit pour les plugins ou la création du thème lui-même, le développement pour WordPress s’appuie essentiellement sur les « hooks » qui permettent soit :

  • D’exécuter du code à un endroit précis de l’exécution du code principal, ce sont les actions.
  • De manipuler une variable avant de la renvoyer au code principal. ce sont les filtres.

Toutes les actions & filtres natifs n’étant pas encore documentés, il est important consulter le plus souvent possible le code source de WordPress.

Concernant les actions, il est important de retenir dans un premier temps que l’essentiel du code chargé par votre thème ( via le fichier functions.php) doit être exécuté après l’action after_setup_theme.

https://codex.wordpress.org/Plugin_API/Action_Reference/after_setup_theme

Dans l’exemple précédent, notez la création de la sidebar est déclenchée sur l’action widget_init.

Concernant le développement de plugin, c’est après l’action plugins_loaded.

https://codex.wordpress.org/Plugin_API/Action_Reference/plugins_loaded

Comprendre sur quelle action doit s’exécuter son code est primordiale. Un code déclenché trop tôt pourra par exemple déclencher une erreur fatale si l’on s’appuie sur des fonctions ou des classes non encore chargées. L’ordre de chargement est parfaitement décrit ici :

https://codex.wordpress.org/Plugin_API/Action_Reference

Pour bien comprendre et prolonger l’exemple de la sidebar, vous constaterez que dans le chargement global de WordPress, widget_init intervenant après after_setup_theme, il est tout à fait possible de procéder de déclencher la création de sidebar sur widgit_init à l’intérieur même de l’action after_theme_setup.

Le concept est illustré par un thème codé sous forme de « class singleton » en page suivante.

<?php

//functions.php

class My_Theme {

const SIDEBARS = array(

'Main Sidebar'=>'main-sidebar',

'Footer Sidebar'=>'footer-sidebar'

);

private static $instance;
public static function get_instance() {

if ( null === self::$instance ) {

self::$instance = new self();

}

return self::$instance;

}

protected function __construct() {

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

}

public function init() {

add_action( 'widgets_init', array( $this, 'register_sidebar' ) );

}

public function register_sidebar() {

foreach (self::SIDEBARS as $name => $id) {

register_sidebar( array('name' => $name,'id' => $id ));

}

}

}

return My_Theme::get_instance();

Les Marqueurs conditionnels

Ils permettent de contextualiser son code et/ou ses fonctions au sein de son thème ou des plugins. Par exemple is_page() renverra true dans un contexte de page et false dans tous les autres contextes.

L’ensemble des marqueurs disponibles est à retrouver ici : https://codex.wordpress.org/fr:Marqueurs_conditionnels

L’ensemble de ces fonctions sont disponibles à partir du l’action « wp »

Que mettre dans les plugins et le thème

Le contenu projet est toujours à placer dans le dossier WP-CONTENT et c’est le seul dossier qui ne sera pas sujet à modifications lors d’une mise à jour de WordPress.
https://codex.wordpress.org/Updating_WordPress#Manual_Update
wp-content/ contient notamment les dossier themes qui contiendra notre thème projet, plugins et uploads. Il est également possible d’y ajouter un dossier mu-plugins qui permet d’ajouter des plugins « must-use » et qui seront chargés ainsi de manière automatique par WordPress et dépourvu de mécanisme d’activation / désactivation. C’est en général à cet endroit que l’on retrouvera la majeure partie de la « logique » projet et tout ce qui ne concerne pas directement la template hierarchy.

Et pour communiquer entre les modèles (par exemple des custom post type https://codex.wordpress.org/Post_Types) et les vues on privilégiera la mise en place de hook personnalisés :

<?php

// mu-plugins/mon-post-type.php

add_action(‘after_post_content’,’display_share_button’);

function display_share_button(){

echo ‘sharebutton’;

}

// montheme/single-mon-post-type.php

the_content();

do_action(‘after_post_content’);

Pour bien comprendre l’utilisation de « hook » personnalisés, la lecture du code source de WooCommerce est très instructive. Notamment le dossier templates. A noter que les hooks sont tout aussi indispensables lorsqu’il s’agit d’interagir avec un plugin tiers. Et là encore, le plus simple sera de coder ses interactions dans le répertoire mu-plugins.

Rappel concernant les Custom Post Type (CPT)

Le recours au CPT est indispensable pour pouvoir gérer des contenus qui ne seraient pas compatibles avec les « types » natifs que sont les articles et pages.

Pour rappel, les pages sont à privilégier utiliser pour des contenus froids et potentiellement hiérarchiques : une page à propos, un formulaire de contact, la présentation d’une activité, une arborescence de services etc..

Les articles sont à privilégier pour des contenus chauds non hiérarchiques mais potentiellement thématisés grâce aux « taxonomies » étiquettes et catégories.

Grâce aux template de page, désormais disponible également pour les articles (et les CTP), il est possible de charger des fichiers PHP différents en fonction de choix de l’utilisateur (ou du développeur) https://developer.wordpress.org/themes/template-files-section/page-templates/, la décision de créer un custom post type ne doit donc pas être guidé uniquement par le besoin de disposer d’un template / look différent.

Par exemple, il ne serait pas utile de créer un custom post type pour quelques offres d’emploi sur un site corporate disposant seulement de quelques offres d’emploi. En revanche, il serait tout à fait justifier pour une entreprise dont l’activité principale serait le recrutement. Dans le premier cas, une catégorie d’article « recrutement » serait amplement suffisante,  dans le second cas, le custom post type permettrait par exemple d’ajouter des « taxonomy » comme le type de contrat, le secteur d’activité etc…

Organisation possible DU REPERTOIRE MU-PLUGINS

/post-type/loader.php

/post-type/includes/class.register.post.type.php

/post-type/includes/class.admin.post.type.php

/post-type/includes/class.front.post.type.php

/forms/gravityform.hook.php

/acf/acf.create.fields.php

/loader.php

etc…

https://codex.wordpress.org/Must_Use_Plugins

Problématiques de sécurité et solutions

Tout d’abord WordPress n’est pas plus vulnérable qu’un autre CMS mais il est beaucoup plus utilisés donc plus visé. Afin de se prémunir des risques les plus évidents il convient toutefois d’installer :

  • Une solution pour limiter le bruteforce sur le login (par exemple le module inclus dans jetpack) https://jetpack.com/support/security-features/
  • un htaccess dans le répertoire uploads pour éviter l’execution de code dans ce dossier généralement ouvert en écriture.
Options -Indexes
<Files *.php>
Deny from all
</Files>
  • Disposer d’une solution d’alerting et / ou de diagnostic sur les mises à jour disponibles type « wordfence, sucuri, secupress »
  • Ne pas avoir d’utilisateur admin ni de préfixe de table par défaut.
  • Adapter le wp-config.php pour empêcher les modifications de code via le back-office.
define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', true );

le fichier wp-config.php peut largement être customizé pour toutes les installations de WordPress, pour bien le connaitre il est important de lire cet article : https://codex.wordpress.org/fr:Modifier_wp-config.php

Performance et solutions

Sur un bon serveur il est tout à fait possible de faire tourner WordPress sans aucune solution de cache pour peu que le développement ne soit pas trop complexe. Si des problèmes apparaissent c’est généralement lié à MySQL ( j’exclue PHP si la version installée est la version 7…).

Il convient alors de diagnostiquer :

https://fr.wordpress.org/plugins/query-monitor/

Puis d’adapter son code soit en essayant autre chose par exemple via du MySQL plus bas niveau (mais toujours avec l’object $wpdb) soit en faisant usage des transients, par exemple pour stocker un résultat de requête ou un appel à une API distante. https://codex.wordpress.org/Transients_API

Pour ce qui concerne l’optimisation des assets je recommande généralement les deux plugins suivants :

https://fr.wordpress.org/plugins/autoptimize/ pour la minification (HTML/CSS/JS)

https://wordpress.org/plugins/tiny-compress-images/ pour la compression des images.

Enfin, si les performances ne sont toujours pas à la hauteurs, des solutions de cache « statique » type « comet cache » ou « wp rocket » sont à envisager. Il faut simplement dans ce cas-là bien veiller à comprendre et maitriser les mécanisme de purge…

Import / Export de données

Plusieurs options possibles : plugins ou script personnalisés.

Bon à savoir, il tout à fait possible de créer un « daemon » php tout en chargeant le core de WordPress pour exécuter des imports/exports en ligne de commande ou via des cron server :

<?php

// daemon.php

if(php_sapi_name() !== "cli")

die();

if ( !defined('ABSPATH') ) {

/** Set up WordPress environment */

require_once( dirname( __FILE__ ) . '/wp-load.php' );

}

global $wpdb;

…

à partir de là, tout se fera avec l’objet WP_QUERY pour de l’export et wp_insert_post pour de la création.

https://codex.wordpress.org/Class_Reference/WP_Query

https://developer.wordpress.org/reference/functions/wp_insert_post/

(bon à savoir, il existe un package « sublime » pour WordPress)

Côté plugin, pour réaliser du sur-mesure je recommande le plugin d’import intégré à WooCommerce qui va créer un nouvel « importeur » disponible dans le back-office dans le section « import ».  woocommerce/includes/admin/importers/class-wc-rate-importer.php

Multisites & Multilingue

Le multisite permet d’installer sur un même serveur plusieurs sites WordPress qui vont partager la même base de données via des tables différentes.

https://codex.wordpress.org/fr:Cr%C3%A9er_un_r%C3%A9seau

Nativement, la solution multi-site permet d’installer de nouveaux sites via des répertoires ou des sous-domaines. Toutefois, il existe un plugin pour faire du multisite, multi-domaine.

https://wordpress.org/plugins/wordpress-mu-domain-mapping/

Le multi-site est particulièrement intéressant dans :

1 – La mise en place d’un réseau de type « franchisé. ».

Chaque site dispose de sa propre url, de ses propres contenus et de ses propres utilisateurs mais partage une « identité commune », généralement transmise via un thème principale unique qui peut toutefois être personnalisé via :

  • Des thèmes enfants.
  • Des options de sites. (get_option pour une option « locale » vs get_site_option() pour une option globale du réseau ).

https://codex.wordpress.org/Function_Reference/get_site_option

2 – La mise en place d’un réseau de sites par « territoire » pour de la localisation (plutôt que pour de la traduction)

Chaque site dispose d’un suffixe de « langue » (/fr,/en/es) et peut être installé dans sa langue originale et disposé d’un contenu très différent des autres sites. (catalogue produit différents, legislation différentes etc…)

En multisite, il faut noter que l’administrateur principale devient « super-administrateur » à la création du réseau. Il devient donc administrateur de tous les autres sites crées et à venir. Tous les autres utilisateurs peuvent être assignés à un ou plusieurs sites.

Concernant les plugins, ils peuvent être activés « globalement » pour être disponibles partout. Ou activable site par site.  Généralement, la  plupart des plugins fonctionnent  sans problème en multisite mais nécessite des réglages dans chacun des sites. En développement spécifique pour du mutlisite, il convient de recourir à la fonction add_site_option() pour disposer de réglages commun à tout le réseau.

Pour ne pas avoir à gérer le cas particulier du www.mondomaine.com, c’est généralement une bonne idée d’installer le WordPress initial à la racine de mondomaine.com/

A contrario, la création d’un WordPress multilingue, n’implique pas la création de nouveaux site mais la création de traduction de contenus existants. Cette fonctionnalité ne fera probablement jamais partie du core WordPress et nécessite donc l’utilisation d’un plugin. Aujourd’hui, Polylang semble être la solution à retenir. Notamment dans sa version pro qui propose du support et des extensions, par exemple pour WooCommerce.

https://polylang.pro

En s’appuyant sur les tables natives de WordPress et via la création de taxonomie pour l’ajout de nouvelles langues, la solution Polylang est facile à mettre en place et performantes. Elle s’intègre parfaitement à ACF et la plupart des plugins les plus populaires, notamment grâce à une compatibilité avec le formal wpml-config.xml

Les contenus tel que les posts, catégorie et menus seront à traduire directement depuis le back-office.

Les chaines de caractères présentent dans la table « options » seront à traduire depuis le menu « string translation » (si présente dans wpml-config.xml et / ou enregistrées via https://polylang.pro/doc/function-reference/#pll_register_string

Les chaines « en dure » des thèmes et plugins appelées via get_text devront être traduit via poedit dans des fichiers po/mo ou directement dans le back-office à l’aide de loco translate. https://fr.wordpress.org/plugins/loco-translate/

Attention, loco translate ne sera pas en mesure de parcourir des sous-dossiers dans mu-plugins/

Lors de la création de contenu « automatique », import ou autre, il faudra utiliser la fonction « pll_set_post_language » pour configurer la langue d’un article tout juste créé. Puis « pll_save_post_translations »  pour associer les différentes traductions disponibles pour cette article.

Référentiel polylang : https://polylang.wordpress.com/documentation/documentation-for-developers/functions-reference/

Compte utilisateur

Lorsqu’un utilisateur est connecté à WordPress, il est possible d’accéder aux données de l’utilisateur grâce à la fonction wp_get_current_user() qui renvoie un objet « utilisateur » permettant d’accéder à ses propriétés.  Il est possible de tester si nous sommes en présence d’un utilisateur via la fonction is_user_logged_in().  A noter que toutes les informations concernant l’utilisateur sont disponibles après l’action « init » 

Chaque rôle utilisateur ayant des capacités différentes, il est souvent utile de recourir à la fonction current_user_can(‘capacité’) pour tester le niveau d’autorisation d’un utilisateur connecté. Par exemple, current_user_can(‘manage_options’) renverra « true » uniquement pour un administrateur.

Pour permettre à vos visiteurs de vous connecter sans passer par le back-office, la fonction wp_login_form permet d’afficher un formulaire de connexion tout en paramétrant notamment l’url de redirection après connexion.