Utiliser l’API REST de WordPress avec PHP et URL Rewriting

Mathieu Chartier Programmation 0 commentaire

L'API REST de WordPress permet à tout utilisateur (webmaster) de s'interfacer avec les données publiques ou privées d'un site réalisé avec le CMS. Nous allons voir une manière (il en existe plusieurs) pour se connecter à l'API REST de WordPress avec PHP, tout en utilisant de l'URL Rewriting pour obtenir des URL jolies comme tout, dans un environnement externe à WordPress. Sous entendu, nous allons appeler les données d'un site WordPress pour des pages PHP indépendantes du CMS.

Non pas que je ne veuille fournir davantage d'informations sur ce qu'est l'API REST de WordPress, ou même sur des bases de PHP, l'idée est ici d'obtenir quelques exemples de codes clés en main pour avancer plus rapidement, et non de faire de la littérature (par ailleurs très bien réalisée sur d'autres blogs). Bien entendu, comme pour tout ce qui touche au développement, chacun aura loisir de personnaliser et réadapter les exemples de codes pour lui-même. Ici encore, j'ai fait le choix d'un code en PHP procédural qui pourra parler à tout le monde, tout en utilisant des fonctions pour rester coller proche de l'esprit "objet" de la POO (chaque fonction pouvant être vue comme un objet ou une méthode, il suffirait juste de réadapter un peu l'écriture).

Nous allons étudier plusieurs points fondamentaux pour s'amuser un minimum avec l'API :

  • Récupérer les données de l'API publique, quelles qu'elles soient (pour les données privées, il faut s'authentifier, c'est une étape supplémentaire que la documentaire de WordPress explique plutôt bien).
  • Utiliser des données d'articles : lister les articles et trouver les informations d'un article en particulier...
  • Utiliser des données de pages : listes les pages et récupérer les données pratiques (titre, date...).
  • Récupérer un menu WordPress : permet de récupérer automatiquement la liste des items d'un menu généré via WordPress.
  • Réécrire les URL pour respecter la structure du site indépendant, tout en conservant les slugs de WordPress.

Dans l'exemple que je vais prendre, la structure du site indépendant est à la racine, et un répertoire "wordpress" est à l'intérieur pour recueillir le CMS. Personne n'accèdera au dossier du WordPress, seul le site indépendant à la racine pourra l'utiliser et le back-office restera accessible (c'est mieux si on veut gérer les données ^^). Tenez bien compte de cette structure car elle influence toute la partie sur le .htaccess et la réécriture d'URL (même dans les fonctions PHP), donc il faudra réadapter un peu si vous souhaitez vraiment séparer le WordPress (mais ce n'est pas forcément recommandé).

Quel est l'intérêt d'utiliser l'API REST de WordPress dans un site indépendant ?

Vous vous demandez peut-être pourquoi il est intéressant d'utiliser l'API REST quand on a déjà un site WordPress sous la main ? Et bien il existe plusieurs raisons de faire des appels dans des sites web indépendants, et je vais en citer au moins deux parmi elles, qui me semblent suffisamment parlantes :

  • Offusquer la présence d'un WordPress dans un site : la sécurité est clairement améliorée face à une grande majorité de pirates (attention toutefois, l'idéal reste de bloquer certains accès à l'API pour aller au fond des choses). En effet, la lecture du code source ne permet plus du tout de repérer qu'un WordPress se cache derrière les sites, et donc entraîne les pirates dans l'erreur. Avec la petite astuce du .htaccess que je présenterais en fin d'article, cela rajoute même davantage de sécurité puisque le site public WordPress retourne une page 404, comme s'il n'existait pas (donc bon courage pour savoir qu'un WordPress tourne en arrière-plan... ^^).
  • Améliorer les performances : le fait d'outrepasser la lourdeur des appels de WordPress (qui n'est pas le pire en la matière malgré tout) permet d'économiser beaucoup d'énergie et d'améliorer les performances globales d'un site, en utilisant que la partie "données" du CMS.

Le seul inconvénient provient de la perte d'utilisation de certains types de plugins pour le front office. En effet, si vous utilisez des plugins d'accordion ou de sliders par exemple, il vous manquera certainement les CSS et JS correspondants, offrant un rendu inutilisable. L'astuce consiste alors à récupérer les fichiers utiles, mais cela ne facilite pas les choses. Souvent, c'est directement dans le site indépendant que l'on gère ces types de plugins JS/jQuery, etc.

Pour information, vous pouvez consulter l'API REST en tapant "/wp-json" à la fin de l'URL de base d'un site WordPress. Le résultat ressemble à une liste de données, dont celles qui nous importent le plus sont les routes, à savoir les chemins d'accès à chaque type de donnée disponibles (pages, posts, users, medias...).

API REST de WordPress en PHP

La fonction getMenuUrl()

Vous verrez de temps l'usage de la fonction getMenuUrl(), cette dernière est utile uniquement pour réécrire les URL avec les formes "page/..." et "article/..." directement, sans avoir à intégrer des URL absolues partout dans les pages (un des défauts de WordPress, mais pas simple à éviter dans les faits). Voici la fonction si nécessaire, qui fonctionne pour l'exemple de cet article.

function getMenuUrl(string $url, string $type = ""): string {
	// Nettoyage de l'URL WordPress
	$url = str_replace("wordpress/", "", $url);

	// S'il s'agit d'une page, l'URL doit changer
	if($type == "page") {
		$slug = substr($url, strrpos($url, '/') + 1);
		$url = str_replace($slug, "page/".$slug, $url);
	}

	// S'il s'agit d'un article, l'URL doit changer
	if($type == "post") {
		$slug = substr($url, strrpos($url, '/') + 1);
		$url = str_replace($slug, "article/".$slug, $url);		
	}

	return $url;
}

Comment se connecter à l'API REST de WordPress avec PHP ?

Il existe une multitude de manière de se connecter à l'API REST de WordPress avec PHP, soit avec cURL, soit avec file_get_contents() notamment. J'ai opté pour la seconde solution ici, en apportant des paramètres complémentaires utiles pour la structure du site en exemple.

Il s'agit d'une fonction get() prenant 1 paramètre obligatoire (le chemin vers la ressource de l'API REST) et 3 paramètres optionnels :

  • $apiPath correspond à la route de l'API à laquelle on souhaite accéder.
  • $params est un tableau (optionnel) de paramètres utiles pour l'API. Par exemple, nous l'utiliserons pour cibler une page ou une article par son slug, ou encore un auteur pour son ID, etc.
  • $directory permet d'indiquer le chemin vers le répertoire de wordpress, s'il est installé sur le même serveur (vider le paramètre sinon).
  • $prefixes permet de nettoyer les préfixes d'URL en vue de la réécriture d'URL. Dans notre exemple, nous avons un fichier pages.php et articles.php qui traitent des deux post types de WordPress, mais surtout, la réécriture d'URL redirige les pages vers des formes /page/SLUG_PAGE ou /article/SLUG_ARTICLE. Par conséquent, il faut nettoyer les préfixes "page/" et "article/" pour que les chemins d'appel vers l'API soient correctes.
// Fonction de connexion à l'API Rest de WordPress
function get(string $apiPath, array $params = array(), string $directory = "wordpress", array $prefixes = array('page', 'article')) {
	// Récupération du protocole et de la base d'URL utile (PATH)
	$protocole = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https://" : "http://");
	$baseUrl = substr($_SERVER['REQUEST_URI'], 0, strrpos($_SERVER['REQUEST_URI'], '/'))."/";

	// Nettoyage potentiel de préfixe dans les URL --> Utile pour l'URL rewriting parfois
	if(!empty($prefixes)) {
		foreach ($prefixes as $prefixe) {
			$baseUrl = str_replace($prefixe."/", "", $baseUrl); // Nettoyage pour l'URL rewriting
		}
	}

	// Arguments potentiels
	$args = '';
	if(!empty($params)) {
		$args = '?'.http_build_query($params);
	}

	// Constitution de l'URL pour l'API
	$apiUrl = $protocole.$_SERVER['HTTP_HOST'].$baseUrl.$directory.'/wp-json';
	$apiUrl = $apiUrl.$apiPath.$args;

	// Vérifie l'existence de l'URL
	set_error_handler(function($severity, $message, $file, $line) {
		throw new ErrorException($message, $severity, $severity, $file, $line);
	});
	try {
		// Envoi de la requête pour l'API Rest
		$json = file_get_contents($apiUrl);
		
		// Retourne le résultat JSON décodé
		$result = json_decode($json);
		return $result;
	}
	catch (Exception $e) {
		echo $e->getMessage();
	}
}

Comme vous le voyez, tout est prévu dans la fonction pour retourner une erreur si la route utilisée pour l'API est inaccessible (retourne une erreur grâce au try/catch), etc. Il ne reste donc plus qu'à exploiter la fonction... :-)

Comment récupérer les articles (posts) avec l'API REST de WordPress en PHP ?

Voici un exemple simple qui permet de récupérer la liste des articles de WordPress avec l'API REST et PHP. Le code HTML n'est valable qu'à titre d'exemple, vous pouvez faire ce que vous voulez.

<?php
// Récupère tous les articles publiés de la catégorie "non-classé" (ID 1), classés du plus récent au plus ancien
$posts = get('/wp/v2/posts/', array(
	'categories' => 1,
	'status' => 'publish',
	'orderby' => 'id',
	'order' => 'desc',
));
if(!empty($posts)) {
	foreach($posts as $post) {
?>
<div class="name"><a href="<?php echo getMenuUrl($post->link, "post"); ?>"><?php echo $post->title->rendered; ?></a></div>
<div class="date"><?php echo date("d/m/Y - H:i", strtotime($post->date)); ?></div>
<div class="excerpt"><?php echo $post->excerpt->rendered; ?></div>
<?php
	}
}
?>

Cet exemple va afficher les un sous les autres les articles de la catégorie "non-classé" de WordPress, avec le titre, la date et l'extrait de l'article. Pour voir d'autres types de données récupérables, l'idéal est de faire un var_dump($posts) ou print_r($posts) pour avoir la liste des appels possibles.

Contrairement à la base de données de WordPress, il ne faut pas appeler les données directement par le nom des colonnes habituelles. Par exemple, le titre se récupère dans la colonne "post_title" dans la BDD, mais avec l'API REST, il faut l'afficher avec $instance->title->rendered ($instance varie selon l'appel que vous avez effectué). Il faut donc être vigilant aux différentes possibilités, mais tout a été prévu. Ainsi, vous pouvez éviter d'utiliser le "guid" de WordPress (URL non réécrite) en le remplaçant par le "link", comme dans l'exemple ci-dessus, et ainsi profiter d'une URL propre, nettoyée dans l'exemple par la fonction getMenuUrl().

Ensuite, quand on clique sur un lien parmi la liste d'articles, l'idéal est d'afficher le contenu de l'article, etc. Dans notre exemple, nous allons récupérer les données grâce au slug de l'article (capté via $post->link et la réécriture d'URL proposée en fin d'article). Ainsi, dans le fichier articles.php de l'exemple, nous avons d'un côté la liste des articles, et dans un autre bloc le contenu d'un article donné, repris par un code comme celui-ci.

<?php
// Récupère l'article le plus récent par défaut
$lastPost = get('/wp/v2/posts/', array(
	'per_page' => 1
));

// Récupère le slug de l'article (grâce à l'URL rewriting)
$slug = '';
if(!empty($lastPost)) {
	$slug = (isset($_GET['slug']) && is_string($_GET['slug'])) ? $_GET['slug'] : $lastPost[0]->slug;
}

// Récupère les données d'un article cliqué
if(!empty($slug)) {
	// Récupère les données de l'article via le slug
	$post = get('/wp/v2/posts/', array(
		'slug' => $slug,
	));

	// Récupère les données de l'auteur (pour avoir son pseudo)
	if(!empty($post)) {
		$post= $post[0];
		$author = get('/wp/v2/users/'.$post->author);
	?>

	<h2 class="main-title">Article : <span><?php echo $post->title->rendered ?></span></h2>
	<div class="content-meta">
		<p class="meta-date">Date : <span><?php echo date("d/m/Y à H:i", strtotime($post->date)); ?></span></p>
		<p class="meta-author">Auteur : <span><?php echo $author->name; ?></span></p>
	</div>
	<div id="content">
		<div id="content-text"><?php echo $post->content->rendered; ?></div>
	</div>
<?php
	}
}
?>

Il ne s'agit que d'un exemple, mais il vous donne une idée de la manière de récupérer des données d'auteur d'un article, etc. Ici, il y a également la gestion du paramètre "slug" pour cibler un article par le slug récupéré via l'URL rewriting. Cela pourrait aussi fonctionner avec un ID si besoin, avec le paramètre "id" bien entendu.

Comment récupérer les pages avec PHP et l'API REST de WordPress ?

Pour récupérer le contenu des pages, c'est globalement le même fonctionnement que pour les articles, si ce n'est que la route change. Je ne vous remontre donc pas tout, mais voici comment récupérer les données d'une page captée par son slug, dans le même esprit que ce qui a été vu précédemment.

<?php
$slug = (isset($_GET['slug']) && is_string($_GET['slug'])) ? $_GET['slug'] : "";
if(!empty($slug)) {
	$page = get('/wp/v2/pages/', array(
		'slug' => $slug,
	));
	if(!empty($page)) {
		$page = $page[0];
		$author = get('/wp/v2/users/'.$page->author);
	?>
	<h2 class="main-title">Page : <span><?php echo $page->title->rendered; ?></span></h2>
	<div class="content-meta">
		<p class="meta-date">Date : <span><?php echo date("d/m/Y à H:i", strtotime($page->date)); ?></span></p>
		<p class="meta-author">Auteur : <span><?php echo $author->name; ?></span></p>
	</div>
	<div id="content">
		<div id="content-text"><?php echo $page->content->rendered; ?></div>
	</div>
<?php
	}
}
?>

Astuce : récupérer un menu avec l'API REST et un plugin WordPress

Il peut être intéressant de récupérer tout un menu complet avec l'API REST, mais WordPress ne l'a pas prévu nativement. Vous avez donc le choix de créer les routes (endpoints) vous-mêmes avec register_rest_routes() et des callbacks, ou en installant un plugin clé en main. Dans l'exemple, c'est l'extension WP REST API V2 Menus de Claudio La Barbera qui sert de base, et qui a ajouté plusieurs endpoints utiles.

Il est possible de récupérer tous les menus ou tous les emplacements de menu, mais l'usage le plus courant est de cibler un menu en particulier. Dans le cas, la route à utiliser dans la fonction get() est "/menus/v1/menus/SLUG_MENU".

Ci-dessous, le code permet de récupérer tous les items d'un menu appelé "menu" (comme c'est original ^^). Vous noterez qu'il est possible de récupérer la cible du lien, voire d'autres paramètres si vous en avez besoin.

<ul id="menu">
	<?php
	$menus = get('/menus/v1/menus/menu/');
	if(!empty($menus)) {
		foreach($menus->items as $menu) {
			$target = (!empty($menu->target)) ? ' target="'.$menu->target.'"' : '';
			echo '<li><a href="'.getMenuUrl($menu->url, $menu->object).'"'.$target.'>'.$menu->title.'</a></li>';
		}
	}
	?>
</ul>

Réécriture d'URL et sécurité

Nous n'avons pas fait le tour de toutes les possibilités, mais vous devez déjà avoir un aperçu suffisant pour vous lancer. Peut-être que j'écrirais d'autres articles plus poussés sur le sujet si les demandes affluent (et si le temps me le permet), mais rien n'est encore sûr. Avant de se quitter, voici comment gérer la réécriture d'URL pour sécuriser davantage le WordPress, avec une astuce en prime à la fin...

Dans un premier temps, nous allons traiter de la réécriture d'URL à visée esthétique. Ainsi, dans notre site "fait main", nous aurons des URL avec les slugs de WordPress, ce sera bien plus joli que des adresses avec des query strings comme ?id=12 ou ?page=25 en fin d'URL (sans parler des risques pour la sécurité quand rien n'est prévu pour en amont).

Dans un fichier .htaccess, placez le code suivant, qui permet de gérer l'affichage des pages, des articles et même des médias, comme je vais vous le montrer par la suite :

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^index\.php$ - [L]

# Redirection vers l'index en cas de dossier ou page inconnue
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} ^(page|article)/ [NC]
RewriteRule . /index.php [L]

# Réécriture des pages et articles
RewriteRule ^page/([a-zA-Z0-9_-]+)$ pages.php?slug=$1 [QSA,L]
RewriteRule ^article/([a-zA-Z0-9_-]+)$ articles.php?slug=$1 [QSA,L]

# Réécriture des médias
RewriteRule ^medias/(.*) wordpress/wp-content/uploads/$1 [L]

# Suppression des sections d'URL bloquantes
RewriteCond %{REQUEST_FILENAME} !/wordpress/ [NC]
RewriteRule (page|article)/(.*) $2 [L]
</IfModule>

En complement, la sécurité du WordPress utilisé en arrière-plan est nécessaire pour se protéger complètement. Nous avons observé ci-dessus que les médias sont également rediriger, cela présuppose que l'URL des médias de WordPress est modifiée à la volée lorsque l'on affiche les contenus dans le site. Pour ce faire, j'ai créé une fonction getCleanedContent() qui permet à la fois de supprimer le chemin des médias, mais aussi de nettoyer les classes de WordPress inutiles (et qui laissent des empreintes gênantes...). Sachez toutefois que l'astuce ne peut fonctionner que si vous ajouter tous les médias de WordPress dans le dossier upload, et non dans des sous-répertoires classés par date (il faut décocher le paramètre dans Réglages->Médias).

function getCleanedContent(string $str, string $directory = "wordpress"): string {
	// Modifie les chemins des médias
	$regex = '#'.$directory.'/wp-content/uploads/#i';
	$str = preg_replace($regex, "medias/", $str);

	// Supprime les classes inutiles de WordPress
	$regexClass = '# class="([^"]+)"#i';
	$str = preg_replace($regexClass, "", $str);

	return $str;
}

Il suffit d'ajouter cette fonction PHP lors de l'appel des contenus via l'API REST de WordPress, et ainsi les médias seront nettoyés, comme dans la capture suivante. Plus de classes ni de chemin vers le dossier "uploads" de WordPress, tout est géré discrètement par la réécriture d'URL.

API REST de WordPress en PHP

D'autres aspects peuvent être ajoutés pour sécuriser encore davantage le WordPress caché. L'idéal est de modifier le fichier .htaccess du WordPress en lui-même (différent de celui du site que nous avons créé précédemment) pour bloquer l'accès au front office du CMS. Voici le code à ajouter, qui permet de voir le dossier wp-admin, et donc d'administrer le site, sans que le reste ne soit accessible.

Souvent, les webmasters utilisent le drapeau [R=403] affichant un accès interdit, mais cela présuppose qu'il existe un WordPress. Je trouve plus malin d'utiliser une erreur 404 en retour pour sous-entendre qu'aucun WordPress n'existe. Il suffit d'ajouter ce code sous les lignes écrites par WordPress dans le fichier .htaccess à la racine de l'outil.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_FILENAME} !wp-admin/ [NC]
RewriteRule $ - [R=404,L]
</IfModule>

Conclusion sur l'API REST de WordPress avec PHP

Voilà, vous savez désormais comment utiliser les larges bases de l'API REST de WordPress avec PHP. Certes, il reste encore une multitude de possibilités, non présentées ici, mais le principe reste globalement toujours le même. Vous pouvez créer vos propres routes pour l'API, notamment si vous créez des custom post types par exemple, ou encore ajouter des surcouches d'authentification pour protéger les accès à l'API (si cela est utile car tout est masqué dans l'exemple, difficile de savoir qu'un WordPress est derrière...). Presque tout est possible, et cela rend l'intérêt très fort, mixant ainsi avec la souplesse d'un WordPress, utilisé en arrière-plan, et la souplesse d'un site "fait main", notamment pour booster le SEO en retirant le superflu.