Dans cet article, je vous propose une compilation de techniques visant à modifier complètement l’arborescence d’un site fonctionnant avec WordPress afin de la rendre plus conforme aux standards des applications web 1.

Remarque préliminaire

Avant de tenter de reproduire ce qui suit sur votre site de production, faites-vous la main sur un site de test aussi proche que possible du site de production. Si, comme moi, vous êtes chez o2switch, c’est facile, vous avez autant de sites que vous voulez #promo 🙂 )

Arborescence souhaitée

Pour illustrer la suite, nous utiliserons l’arborescence racine suivante – proche de celle préconisée par mon hébergeur o2switch (cela peut être différent chez vous, il suffira d’adapter en conséquence) :

  • /home/myuser/sites est le répertoire contenant tous les sites, dont le site WordPress à modifier. Ce dossier ne doit pas être visible sur internet, donc non publié par le serveur web.
  • /home/myuser/sites/myblog est le répertoire d’installation du site utilisant WordPress (probablement un blog 🙂 ). Ce dossier n’est pas non plus visible. Seule l’installation de WordPress l’est, par exemple par un sous-domaine que nous appellerons là-aussi pour illustrer myblog.mydomain. Ce sous-domaine publie donc le dossier /home/myuser/sites/myblog/wordpress.

Voici ensuite l’exemple d’arborescence classique WordPress que nous allons modifier :

/home/myuser/sites
|_myblog           : racine du site contenant wp-config.php et index.php
  |_/wordpress     : contenu visible sur Internet
    |_/wp-admin    : admin WordPress
    |_/wp-includes : moteur WordPress
    |_/wp-content  : themes, plugins, mu-plugins, upgrade, cache, uploads, languages

Et celle que nous voulons obtenir :

/home/myuser/sites
|_media/myblog : contenu du dossier uploads
|_myblog       : racine du site contenant les éléments privés
  |_/web       : contenu visible sur internet
  |_/web/app   : moteur wordpress (fichiers à la racine, wp-includes, wp-admin)
  |_/web/ext   : plugins, mu-plugins et themes
  |_/web/data  : données de travail telles que les caches, le dossier upgrade, les optimisations d'images, etc.
  |_/web/lang  : fichiers de traduction

Dans la suite, nous ferons des copies des fichiers et dossier à relocaliser afin d’éviter de rendre indisponible notre installation de WordPress pendant l’opération. A la fin, il faudra supprimer l’ancienne installation pour faire de la place. Il ne faut bien sûr pas faire de modification dans WordPress pendant l’opération, au risque de perdre des modifications.

Commençons par créer les dossiers web/, web/app/, web/ext/, web/data/ et web/lang/ dans /home/myuser/sites/myblog/.

Nous copions ensuite les fichiers à la racine de wordpress/ dans web/app/, ainsi que les dossiers wp-admin/ et wp-includes/

C’est tout pour le moment, les copies restantes seront faites au fur et à mesure.

Déplacer wp-content en dehors de l’arborescence de WordPress

Ici, nous allons déplacer le dossier wp-content en dehors du dossier d’installation de WordPress. L’emplacement cible sera le dossier web/data/ que nous avons créé un peu plus tôt. Nous allons copier le contenu de wp-content dans web/data/, à l’exception des dossiers plugins/, mu-plugins/, themes/, languages/ et uploads/ qui seront traités dans la suite. Comme indiqué précédemment, nous ferons du ménage seulement à la fin, afin que notre site continue à fonctionner pendant les modifications.

Dans wp-config.php, ajoutons les deux lignes suivantes 2 qui vont définir wp-content comme étant le dossier web/data/ :

define( 'WP_CONTENT_DIR', dirname(__FILE__) . '/data' );
define( 'WP_CONTENT_URL', $host . '/data' );

Déplacer les dossiers des plugins et mu-plugins

Nous allons maintenant déplacer les dossiers wp-content/plugins/ et wp-content/mu-plugins/ dans web/ext/plugins/ et web/ext/mu-plugins/. Copions d’abord ces deux dossiers dans leur emplacement cible.

Ensuite, pour le dossier plugins, nous ajoutons dans wp-config.php les deux lignes suivantes 2 qui vont indiquer à WordPress le nouvel emplacement des plugins :

define( 'WP_PLUGIN_DIR', dirname(<strong>FILE</strong>) . '/ext/plugins' );<br>
define( 'WP_PLUGIN_URL', $host . '/ext/plugins' );

Pour le dossier mu-plugin, c’est le même principe. Ajoutons ces deux lignes dans wp-config.php 2 :

define( 'WPMU_PLUGIN_DIR', dirname(__FILE__) . '/ext/mu-plugins' );
define( 'WPMU_PLUGIN_URL', $host . '/ext/mu-plugins' );

Déplacer le dossier des traductions

Déplaçons ensuite le dossier contenant les traductions, de wp-content/languages/ vers app/web/lang/. Pour cela, nous ajoutons encore une ligne au fichier wp-config.php :

define( 'WP_LANG_DIR', dirname(__FILE__) . '/lang' );

Comme à chaque étape, nous copions également le contenu de wp-content/languages/ dans app/web/lang/.

Déplacer le dossier des thèmes

Pour cette étape, nous n’avons malheureusement pas de constante WP_THEMES_DIR à notre disposition. L’opération va donc être un peu plus complexe. Pour nous en sortir, nous allons créer un mu-plugin en nous inspirant fortement de deux tickets de wordpress.org 3, 4.

Le premier ticket nous offre l’essentiel du code du mu-plugin alors que le second nous aide à le faire vraiment fonctionner en y ajoutant la variable globale $wp_theme_directories.

Commençons par créer le fichier wp-themes-dir.php dans le dossier web/ext/mu-plugins/ et ajoutons-y le code suivant qui va définir le dossier web/ext/themes/ comme destination des thèmes :

<?php
/**
 * Plugin Name: Change Themes folder
 * Description: A WordPress Plugin For Change themes folder
 * Author: Mehrshad Darzi
 * Version:     1.0.0
 */

// themes folder is located in public/ext folder.
global $wp_theme_directories;
$wp_theme_directories[] = $_SERVER['DOCUMENT_ROOT'].'/ext/themes';

register_theme_directory( ABSPATH . 'ext/themes' );

// ABSPATH points to app.
add_filter( 'theme_root', function () { return ABSPATH . '../ext/themes';  });
add_filter( 'theme_root_uri', function () { return home_url( '/ext/themes' ); }, 10, 1 );

N’oublions pas de copier le contenu du dossier existant wp-content/themes/ dans le nouveau web/ext/themes/.

Déplacer les fichiers médias dans une arborescence dédiée

La modification la plus délicate est d’isoler les fichiers médias dans un dossier distinct du site, idéalement dans un sous-domaine dédié. En effet, cela suppose de modifier directement la base de données. L’intérêt est de pouvoir le cas échéant déplacer ce sous-domaine dans un CDN afin d’accélérer le temps de chargement du site 5.

La mise-à-jour de la base de données de WordPress consiste à modifier les champs contenant des URL et des chemins pointant vers des images. Pour cela vous pouvez utiliser phpMyAdmin. Les manipulations détaillées avec ce dernier sont décrites dans l’excellent article cité en référence 5. Je ne le paraphraserai pas ici.

Au préalable, nous allons copier le contenu de wp-content/uploads/ dans /home/myuser/sites/media/myblog/. C’est l’emplacement des images dans la nouvelle arborescence.

Avant de tenter les mises à jour décrites dans la suite, il est bien entendu indispensable d’exporter la base de données WordPress pour la sauvegarder en cas d’erreur de manipulation. Nous pouvons utiliser pour cela la fonction d’export de phpMyAdmin.

Dans la suite, nous supposons que le préfixe des tables de la base de données WordPress est wp_et que nous déplaçons les fichiers médias vers l’emplacement physique /home/myuser/sites/medias/myblog/ et l’URL media.mydomain/myblog. Cela suppose bien sûr de créer le dossier et le sous-domaine correspondants.

La première requête de mise à jour de la base de données consiste à modifier les URL des images dans le contenu des articles (vous noterez que les accès se font en https, ce qui est une bonne pratique) :

UPDATE wp_posts SET post_content = REPLACE(post_content,'https://myblog.mydomain/wordpress/wp-content/uploads','https://media.mydomain/myblog')
UPDATE wpnt_posts SET guid = REPLACE(guid,'https://myblog.mydomain/wordpress/wp-content/uploads','https://media.mydomain/myblog')

Ensuite, ouvrons le menu « Rechercher » de la table wp_options et recherchons ‘option_name‘ ‘like %...%‘ ‘upload‘. Nous trouvons trois lignes. Modifions les deux lignes suivantes ainsi :

  • Dans ‘upload_path‘, nous remplaçons ‘wp-content/uploads‘ par ‘/home/myuser/sites/media.mydomain/myblog
  • Dans ‘upload_url_path‘ (qui devrait être vide), nous ajoutons ‘https://media.mydomain/myblog

Protéger les données sensibles

La dernière modification est de séparer ce qui a vraiment besoin d’être exposé sur internet (le moteur WordPress par exemple) du reste (les données sensibles telles que les codes d’accès à la base de données). Pour cela, nous utilisons le dossier web/ dans lequel nous placerons tout ce qui doit être visible d’Internet.

Les données sensibles de WordPress sont celles du fichier wp-config.php que nous plaçons en général au-dessus du dossier d’installation de WordPress, ce dernier étant capable d’aller le chercher. Pour rendre les choses plus claires, nous allons séparer les données vraiment sensibles contenues dans wp-config.php (les codes d’accès à la base de données notamment) de ce qui est de la simple mécanique WordPress. Pour cela nous créons un fichier config.php dans /home/myuser/sites/myblog qui sera appelé par le fichier /home/myuser/sites/myblog/web/wp-config.php 6.

Commençons par créer le nouveau fichier wp-config.php qui ne contient plus de données sensibles, dans le dossier web/. Notez que les lignes ajoutées précédemment pour déplacer les dossiers wp-content/, plugins/, mu-plugins/ et languages/ sont aussi présentes :

<?php
/**
 * This file automates all WordPress configuration intialisation,
 * based on config files stored outside the web folder.
 * There is nothing to change here, check <root_dir>/xxx-config.php files.
 *
 * @package WordPress
 */

ini_set( 'display_errors', 0 );

// ** Load parameters ** //
include( dirname( __FILE__ ) . '/../config.php' );

// ** Dynamically find host url depending on HTTPs usage. ** //
if ( isset( $_SERVER[ 'HTTP_HOST' ] ) ) {
	if ( !isset( $_SERVER[ 'HTTPS' ] ) ) {
		$_SERVER[ 'HTTPS' ] = '';
	}

	// Check for https or http.
	$protocol = ( !empty( $_SERVER[ 'HTTPS' ] ) && $_SERVER[ 'HTTPS' ] !== 'off' || $_SERVER[ 'SERVER_PORT' ] == 443 ) ? 'https' : 'http';
	$host = $protocol . '://' . $_SERVER[ 'HTTP_HOST' ];
} else {
	$host = 'http://' . SUB_DOMAIN;
}

// ** WordPress URL ** //
define( 'WP_SITEURL', $host . '/app' );
define( 'WP_HOME',    $host . '/' );

// ** Custom folders locations ** //
define( 'WP_CONTENT_DIR', dirname( __FILE__ ) . '/data' );
define( 'WP_CONTENT_URL', $host . '/data' );

define( 'WP_PLUGIN_DIR', dirname( __FILE__ ) . '/ext/plugins' );
define( 'WP_PLUGIN_URL', $host . '/ext/plugins' );

define( 'WPMU_PLUGIN_DIR', dirname( __FILE__ ) . '/ext/mu-plugins' );
define( 'WPMU_PLUGIN_URL', $host . '/ext/mu-plugins' );

define( 'WP_LANG_DIR', dirname( __FILE__ ) . '/lang' );

// ** themes folder customization is handled by wp-themes-dir mu-plugins. ** //

/** Chemin absolu vers le dossier de WordPress. */
if ( !defined( 'ABSPATH' ) )
	define( 'ABSPATH', dirname( __FILE__ ) . '/' );

/** Réglage des variables de WordPress et de ses fichiers inclus. */
require_once( ABSPATH . 'wp-settings.php' );

Et le fichier config.php qui est à la racine de /home/myuser/sites/myblog donc en dehors de web/ :

<?php
/**
 * Configuration privée de WordPress.
 *
 * Ce fichier contient les réglages de configuration MySQL
 * et le niveau de debug pour les environnements de dev.
 * Ce fichier est appelé par web/wp-config.php.
 * Il n'est pas visible depuis le web car il contient des informations sensibles.
 *
 * @package WordPress
 */

// ** WordPress site sub-domain
define( 'SUB_DOMAIN', 'myblog.mydomain' );

// ** MySQL settings ** //
define( 'DB_NAME', 'mydb' );
define( 'DB_USER', 'mydb' );
define( 'DB_PASSWORD', 'mydb' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8' ); // You would probably not want to change this.
define( 'DB_COLLATE', '' );     // You should not change this except if you know what you are doing.

// ** Database table prefix ** //
$table_prefix  = 'wp_';

// ** Debug level ** //
define( 'WP_DEBUG', false );
if( WP_DEBUG ) {
	define( 'WP_DEBUG_LOG', true );
	define( 'WP_DEBUG_DISPLAY', true );
	@ini_set( 'display_errors', E_ALL );
}

// ** Clés uniques d’authentification et salage ** //
define('AUTH_KEY',         'put your unique phrase here');
define('SECURE_AUTH_KEY',  'put your unique phrase here');
define('LOGGED_IN_KEY',    'put your unique phrase here');
define('NONCE_KEY',        'put your unique phrase here');
define('AUTH_SALT',        'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT',   'put your unique phrase here');
define('NONCE_SALT',       'put your unique phrase here');

Une dernière chose à faire

Il y avait déjà un fichier index.php au même niveau que le fichier wp-config.php, à la racine du site. Il faut le copier dans web/ et le modifier pour qu’il aille chercher le fichier wp-blog-header.php dans app/ au lieu de wordpress/, par exemple comme ceci :

<?php
/**
 * Front to the WordPress application. This file doesn't do anything, but loads
 * wp-blog-header.php which does and tells WordPress to load the theme.
 *
 * @package WordPress
 */

/**
 * Tells WordPress to load the WordPress theme and output it.
 *
 * @var bool
 */
define('WP_USE_THEMES', true);

/** Loads the WordPress Environment and Template */
require( dirname( __FILE__ ) . '/app/wp-blog-header.php' );

Conclusion

Et voilà, notre arborescence WordPress est totalement revue ! Il ne reste plus qu’à supprimer l’ancienne installation contenue dans le dossier wordpress/ et les anciens wp-config.php et index.php … après quelques jours de fonctionnement sans problème de la nouvelle arborescence.

Références

  1. Bedrocks – Better WordPress project structure 
  2. How to change location of the plugins & WordPress themes folders – Charles Clarkson – WordPress StackExchange – 25 octobre 2013   
  3. Create wp_theme_directory_constants() function and dynamic WordPress Themes folder – Mehrshad Darzi – wordpress.org – 25 avril 2019 
  4. Register theme directory() not working for themes outside WP_CONTENT_DIR – T. Liebig – wordpress.org – 28 mai 2011 
  5. How to Move WordPress Uploads Path to Subdomain – Richie KS – Dezzain – 6 mai 2013  
  6. Managing Your WordPress Site with Git and Composer Part 4 – Gilbert Pellegrom – Delicious Brains – 6 octobre 2015