J’ai effectué une mission de quelques moi au sein d’un grand groupe de presse. Au sein de l’équipe, je me suis occupé de migrer l’ancien backoffice « Web » vers le nouveau : WordPress.

L’architecture technique de ce site est en composée de trois serveurs frontaux (devant s’occuper de l’affichage du site) et d’un serveur de base de données. En réalité, seuls deux des serveurs frontaux sont utilisés pour afficher le site, le troisième ayant été délaissé suite à une incompatibilité de l’application de réplication du cache.

Ainsi, à la fin de cette migration, les deux serveurs frontaux ont dû répondre à la fois au frontoffice du site, mais également au backoffice. Le fait d’avoir un troisième serveur, inexploité, a fait surgir une idée: déplacer cette charge du backoffice sur ce serveur. Les deux serveurs frontaux porteraient alors bien leur nom et l’autre aurait une réelle utilité.

Le principe

Sur le papier, cela reste cohérent et l’architecture n’est pas si complexe puisque les personnes se connectant sur le backoffice travaillent au sein des locaux de mon client: on n’ouvre pas le serveur vers l’extérieur, mais on redirige un sous-domaine (du style « admin.mondomaine.fr« ) vers ce serveur.

Il est même simple de régler certains détails comme le cache, puisqu’on peut toujours demander à ce serveur « back » de se connecter sur un des caches « front ».

Reste alors un problème : WordPress ne connaît qu’une URL pour son admin. C’est www. mondomaine.fr.fr/wp-admin et pas autre chose. Comme vous le savez, WordPress stocke les URL en dur dans la base, et certaines fonctions du coeur ont même la chaîne ‘wp-admin’ codée en dur ! Cela implique que WordPress redirige quiconque essaie d’accéder à son administration vers cette URL.

A première vue, c’est donc pratiquement mission impossible de déplacer l’administration sur un sous-domaine, surtout si on ne veut pas toucher au cœur…

Challenge accepted!

Le VHost

Je ne vais pas expliquer ici ce qu’est un VHost, mais pour ceux que ça intéresse, Wikipédia propose une définition claire et concise.

Une méthode de développement possible avec WordPress est d’installer un serveur HTTP local puis de créer un VHost afin de rediriger l’URL du site de production vers la version locale. Cela permet de ne pas s’embrouiller avec les URLs enregistrées dans la base : ce sont les mêmes.

Chez ce client, c’est un peu différent puisqu’il y a plusieurs environnements à gérer. Cela n’empêche pas la méthode des VHosts en local, puisque cela a l’avantage de permettre une configuration quasi-identique (les URLs se ressemblent, on peut définir des variables d’environnement pour le VHost, l’installation multisite de WordPress accepte le mode « sous-domaines », …).

Ainsi, voici un exemple de VHost pointant vers la partie administration d’une installation de WordPress.

<VirtualHost *:80>
        ServerName admin.mondomaine.fr
        ServerAdmin monmail@monmail.com
        DocumentRoot "C:/wamp/www/mondomaine.fr/wp-admin/"
        ErrorLog "logs/mondomaine.error.log"
        CustomLog "logs/mondomaine.access.log" common
        <Directory "C:/wamp/www/mondomaine.fr/wp-admin/">
            Options FollowSymLinks
            AllowOverride All
            Order deny,allow
            Deny from all
            Allow from all
    </Directory>
</VirtualHost>

Wp-login.php

Le fonctionnement de WordPress pour la phase de connexion est simple : on redirige l’utilisateur vers l’URL « <site_url>/wp-login.php« , où <site_url> est l’adresse du front. Cela nous pose problème puisqu’on veut également que cette phase de connexion soit prise en charge par notre administration.

Ceci nous impose donc de copier le fichier wp-login.php vers le dossier wp-admin/ où est située toute l’administration. Étant donné que c’est un fichier qui se rajoute, il a peu de chances de se faire écraser par une mise à jour de WordPress et c’est tant mieux.

Une fois ce fichier copié, il va falloir un petit peu le modifier. D’une part parce que le bootstrap n’est plus à jour au niveau de l’arborescence, et puis parce que les formulaires pointent toujours vers l’ancien fichier.

Du coup, on va effectuer les modifications suivantes :

  • ligne 12 :
require( dirname(__FILE__) . '/../wp-load.php' );
  • lignes 404, 451, 510 et 630 :

action="<?php echo esc_url( admin_url( 'wp-login.php', 'login_post' ) ); ?>"

On a ainsi un fichier wp-admin/wp-login.php qui charge correctement le cœur de WordPress et qui se rappelle lui-même lors d’un appel de formulaire… Enfin presque.

Effectivement, la fonction admin_url() retourne toujours « mondomaine.fr/wp-admin/« , ce n’est pas vraiment ce qui est voulu.
Et puis de toute façon, WordPress ne connaissant pas cette URL, il ne veut pas afficher notre belle page.

Encore un petit effort.

La configuration

A partir de cette étape, on va mettre en place la configuration nécessaire pour faire croire à WordPress que l’URL de son administration est bien notre sous-domaine.

Tout va se passer dans le fichier wp-config.php. Tout d’abord, il va falloir définir une nouvelle constante afin de stocker cette URL. Appelons-la par exemple ‘ADMIN_SITEURL’ :

define('ADMIN_SITEURL', 'http://admin.mondomaine.fr');

Ensuite, afin que la connexion se passe bien et que WordPress puisse retrouver les données grâce à ses cookies, il faut surcharger 2 constantes définies normalement par le coeur. La première définit le chemin d’accès à l’administration (sans le domaine), donc par défaut ‘/wp-admin‘. La deuxième définit le domaine sur lequel s’appliquent les cookies, soit « mondomaine.fr » par défaut.

define( 'ADMIN_COOKIE_PATH', FALSE );
define( 'COOKIE_DOMAIN', '.mondomaine.fr' );

Ici, on définit donc que les cookies sont partagés entre tous les sous-domaines de « mondomaine.fr » et que le cookie de l’admin sera disponible depuis la racine.

Enfin, toujours dans le fichier wp-config.php, afin d’éviter une petite erreur PHP qui survient après avoir fait tout ça dans le fichier wp-includes/vars.php (à la ligne 28), il convient d’ajouter le bout de code suivant :

if('http://' . $_SERVER['HTTP_HOST'] == ADMIN_SITEURL)
    $_SERVER['PHP_SELF'] = '/wp-admin' . $_SERVER['PHP_SELF'];

Le fait de corriger l’erreur permet également de bien définir la variable $pagenow, variable globale disponible partout dans l’administration, et sur laquelle se basent quelques contrôles et/ou fonctionnalités.

Les filtres

Nous voyons le bout du tunnel désormais ! Toute la préparation a été faite et même les cookies sont bien cuits, il ne nous reste plus qu’à modifier les valeurs que récupère WordPress grâce à quelques fonctions et quelques filtres. Ces filtres pourront être placés dans le thème (le fichier functions.php par exemple), ou dans un plugin dédié.

Première valeur : l’URL de l’administration, bien sûr ! La fonction admin_url() est là pour ça, et elle contient un filtre : « admin_url« .

function mondomaine_admin_url($admin_url, $path) {
        // Admin-ajax must be accessed from the front (otherwise the AJAX calls won't work because of cross-domain securities)
        // so don't overwrite this URL in case we found 'admin-ajax.php' in the needed URL
        if(is_admin()|| 0 !== strpos($path, 'admin-ajax.php')){
            $admin_url = ADMIN_SITEURL.'/';

            if ( !empty($path) && is_string($path) && strpos($path, '..') === false )
                $admin_url .= ltrim($path, '/');
        }

        return $admin_url;
    }

    add_filter('admin_url', 'mondomaine_admin_url', 10, 2);

On reprend ici le fonctionnement de la véritable fonction admin_url() du coeur, sauf que l’on prend comme URL de base notre sous-domaine.

Petite formalité en plus : on vérifie si l’URL demandée n’est pas celle du fichier « admin-ajax.php« . En effet, ce fichier sert d’agrégateur pour tous les appels AJAX faits au sein de WordPress, que ce soit depuis l’administration ou depuis le frontoffice. Ce qui veut dire que si l’on a installé un plugin qui effectue des appels AJAX depuis le frontoffice, il ira taper sur cette URL.

On pourrait le laisser avec le sous-domaine, sauf que pour des raisons de sécurité, il est interdit de faire des appels AJAX entre deux domaines différents : l’appel va donc échouer… Il serait possible de mettre en place des autorisations spécifiques pour laisser passer ces appels, mais ce n’est pas le but de notre solution ici : on laisse donc ce fichier accessible depuis le front à l’adresse habituelle (mondomaine.fr/wp-admin/admin-ajax.php).

Deuxième valeur : l’URL de la page de connexion. Sur le même modèle que la fonction admin_url()login_url() peut être filtrée.

function mondomaine_login_url($login_url_wp, $redirect) {
        $login_url = admin_url('wp-login.php', 'login');

        if ( !empty($redirect) )
            $login_url = add_query_arg('redirect_to', urlencode($redirect), $login_url);

        if ( FALSE !== strpos($login_url_wp, 'reauth') )
            $login_url = add_query_arg('reauth', '1', $login_url);

        return $login_url;
    }

    add_filter('login_url', 'mondomaine_login_url', 10, 2);

Toujours de la même manière, on reprend le fonctionnement de la fonction du coeur de WordPress, mais en indiquant que notre fichier « wp-login.php » se trouve dans l’administration.

Troisième valeur : l’URL de déconnexion. On commence à être habitués : il existe un filtre « logout_url« .

function mondomaine_logout_url($logout_url_wp, $redirect) {
        $args = array( 'action' => 'logout' );
        if ( !empty($redirect) ) {
            $args['redirect_to'] = urlencode( $redirect );
        }

        $logout_url = add_query_arg($args, admin_url('wp-login.php', 'login'));
        $logout_url = wp_nonce_url( $logout_url, 'log-out' );

        return $logout_url;
    }

    add_filter('logout_url', 'mondomaine_logout_url', 10, 2);

Encore une fois, on ne change que l’URL du fichier « wp-login.php » par rapport à la fonction du coeur.

La délivrance

Ca y est ?! Et bien oui. L’administration est accessible depuis l’URL « admin.mondomaine.fr« , on peut s’y connecter, y naviguer et tout ça sans avoir perdu les fonctionnalités de base de WordPress.

Pourtant, je ne suis pas sûr à 100% que tous les cas de figure soient pris en compte ici. Je n’ai eu besoin que de ces filtres, mais peut-être y en a-t-il d’autres à ajouter pour compléter la configuration.
De plus, si certains plugins que vous avez installés n’utilisent pas les fonctions dédiées comme admin_url(), il y aura sûrement quelques effets de bord…

Dernier point, et pas des moindres : Internet Explorer.
Lors de la mise en place de mon POC (Proof Of Concept), j’ai été confronté à un bug assez perturbant concernant IE. Lorsque l’on se connectait à l’administration, puis qu’on voulait afficher le frontoffice du site, la page plantait au premier appel de « document.write(‘</style>’);« , et seulement cet appel (les autres « document.write » passaient).

Après plusieurs recherches du côté des cookies ou de l’objet javascript document géré par IE, je suis arrivé à déterminer qu’une portion de code pouvait être retirée pour que ce bug n’apparaisse plus.
Cette portion de code est un ajout de style fait par WordPress directement dans le <head> de la page, nécessaire pour ne pas imprimer la barre d’administration avec le reste de la page :

<style type="text/css" media="print">#wpadminbar { display:none; }</style>

Ce code est ajouté par la fonction wp_admin_bar_header(), appelée lors d’une action WordPress : « wp_head« . Il suffisait donc de supprimer l’appel de cette fonction sur ce hook pour ne plus avoir le bug.

function mondomaine_remove_admin_bar_header() {
        remove_action('wp_head', 'wp_admin_bar_header');
    }

    add_action('init', 'mondomaine_remove_admin_bar_header', 11);

Afin de ne pas perdre la fonctionnalité qui consiste à cacher la barre d’admin lors d’une impression, il convient tout de même de remettre ce style quelque part, par exemple au sein d’une feuille de style dédiée au print déjà incluse sur le site.

Pour aller plus loin

Je suis preneur de commentaires ou retours d’expérience sur ce système de déplacement de l’administration : est-ce que vous trouvez cela utile ? Allez-vous vous servir de cette astuce ? Avez-vous déjà essayé mais abandonné puisqu’aucune documentation n’existe ? Avez-vous des remarques, des questions sur la mise en place ?
Bref, qu’en pensez-vous ?

Twitter de l’agence : @GLOBALISms

Le site de l’agence : WP Chrono.