Générer un sitemap : plan du site au format XML (Google map)

A la recherche d'un script pour créer un sitemap au format XML, je n'ai pas trouvé de solution satisfaisante. La raison essentielle est que les scripts en question ont une logique généralement orientée scan de répertoire, proposant alors de renseigner un tableau interdisant tel fichier, répertoire ou type de fichier.
Le problème se pose pour un site aux pages essentiellement dynamiques - soit la quasi totalité des sites que je produis - et devient critique dans le cas de réécriture d'URL à la volée (url rewriting).

Pourquoi un sitemap ?

Outre l'intérêt d'une indexation pertinente et "dirigée", il est également question d'économiser de la bande passante. A ce titre on peut presque parler d'acte civique :-)
Aussi, l'aide proposée aux webmasters, via la boîte à outils Google, est intéressante ; bien qu'il ne soit pas obligatoire de placer un sitemap pour l'utiliser.

Ma version des faits

N'attendre aucun scan récursif de répertoire ici, mais des exemples d'implémentation de tableaux. Ce script peut être appelé directement ou inclu dans un script existant, afin de modifier automatiquement le sitemap à la volée. Par exemple, inclure ce script dès qu'une entrée est modifiée au niveau du site en mode admin, en détectant une variable prédéfinies $_POST ou $_REQUEST caractéristique. Chacun trouvera la meilleure méthode pour déclencher la mise à jour.
Notez que j'ai choisi d'écrire les dates dans la forme la plus simple, à partir d'un timestamp.

Ce script peut être joint à la configuration automatique d'une syndication RSS.

Inclusion des fichiers utiles et définition des variables

Selon qu'on appelle le script directement (par un lien) ou qu'il soit inclut dans un autre script, afin d'automatiser la mise à jour, on aura besoin d'afficher le résultat, ce qui est contrôlé par $afficheresult. Ceci en testant l'existence d'une fonction personnelle (ici queryDB_open) avec function_exists.

<?
// mode muet par défaut
$afficheresult = false;
// si la fonction testée n'existe pas, c'est que le script est appelé directement
// donc inclusion des fichiers de config et affichage du résultat
if(!function_exists('queryDB_open')) {
	include('include/connexion.php');
	include('include/fonctions.php');
	// affichage du résultat
	$afficheresult = true;
}
// ouverture de la connexion (fonction perso) si absente
if(!isset($db)) $db = queryDB_open();
// nom du fichier sitemap
$sitemap = "sitemap.xml";
// URL absolue de la racine du site (NB. slash en fin de ligne)
$http = "http://ppan.club.fr/";
// email du webmaster (vide pour option)
$emailwebmaster = "";

Écriture des pages statiques

$tableau est construit sous la forme date=>(priority,lastmod,changefreq) et autorise 3 options de date. Le premier exemple $lastupdate est implémenté par la requête SQL, en relevant la date la plus récente d'une table, dont les rangées alimentent dynamiquement le contenu de la page index.php (news, édito, etc.). 0 indique d'extraire la date du fichier mapage.php et -1 permet de ne pas indiquer de mise à jour.
L'ensemble des paramètres sont optionnels, comme l'indique le protocole (ou encore le site officiel sitemaps.org). La variable $c totalise les pages indexées à travers l'ensemble des boucles.

// extraction de la dernière mise à jour dans 'matable'
// NB. MAX n'est pas pertinent pour un champ MySQL de type DATE
$sql = "SELECT MAX(ladate) FROM matable";
$res = @mysql_query($sql,$db);
$row = @mysql_fetch_row($res);
// formatage de la date (à partir d'un timestamp)
$lastupdate = date("Y-m-d",$row[0]);
// définition des pages statiques
// page=>(priority,lastmod,changefreq)
// priority : de 0.0 à 1.0 (0.5 par défaut)
// lastmod : date au format ISO 8601
// changefreq : always, hourly, daily, weekly, monthly, yearly, never
$tableau = array(
	'index.php'=>array('',$lastupdate,''),
	'mapage.php'=>array('1.0',0,'daily'),
	'mapage.php?flag=2'=>array('',-1,''),
	'accueil.html'=>array('0.3','2006-04-03','monthly'),
);
// affichage simplifié du résultat (liens pour vérification)
$affiche = "";
// écriture du fichier sitemap.xml
$ecrire = "";
// envoi d'un email au webmestre en cas d'erreur (et annulation de la màj)
$envoimail = "";
// nombre total des entrées écrites
$c = 0;
foreach($tableau as $page=>$google) {
	// extraction de la date du fichier si désiré
	if(!$google[1] && $google[1]!=-1) {
		$tmp = @stat($page);
		if($tmp[9]) $google[1] = date("Y-m-d", $tmp[9]);
	}
	$url = $http.$page;
	$ecrire .= "<url>\n";
	$ecrire .= "	<loc>$url</loc>\n";
	$affiche .= "<a href='$url' target='_blank'>$url</a><br>";
	if($google[0])
		$ecrire .= "	<priority>$google[0]</priority>\n";
	if($google[1]>0)
		$ecrire .= "	<lastmod>$google[1]</lastmod>\n";
	if($google[2])
		$ecrire .= "	<changefreq>$google[2]</changefreq>\n";
	$ecrire .= "</url>\n";
	$c++;
}
// $c vaut 0 : le tableau n'a pas été lu correctement
// indication de l'erreur et annulation d'écriture
if(!$c) $envoimail.=" tableau, ";

Exemple d'écriture de pages dynamiques simples (foliotage)

Afin de respecter la pagination originale du site la variable $nlignes, fixant le nombre d'entrées par page, est ici implémentée via l'inclusion en amont du fichier de configuration.

// page produisant plusieurs entrées foliotées (paginées)
// ex. http://monsite.com/unepage.php?pagesuivante=2
$page = "unepage.php";
// recherche de la dernière màj pour $page
$sql = "SELECT MAX(ladate) FROM matable";
$res = @mysql_query($sql,$db);
$row = @mysql_fetch_row($res);
$ladate = date("Y-m-d", $row[0]);
// extraction du nombre d'entrées
// ou encore : SELECT COUNT(*) FROM matable
$sql = "SELECT id FROM matable";
$res = @mysql_query($sql,$db);
$nbr = @mysql_num_rows($res);
// nombre de pages
$nbrpage = @ceil($nbr/$nlignes);
$pagesuivante=0;
for($i=0; $i<$nbrpage; $i++) {
	$variable = ""; // la première page n'a pas besoin de paramètre
	if($pagesuivante>0) $variable = "?pagesuivante=$pagesuivante";
	$url = $http.$page.$variable;
	$ecrire .= "<url>\n";
	$ecrire .= "	<loc>$url</loc>\n";
	$ecrire .= "	<lastmod>$ladate</lastmod>\n";
	$ecrire .= "</url>\n";
	$affiche .= "<a href='$url' target='_blank'>$url</a><br>";
	$c++;
	$pagesuivante+=$nlignes;
}
if(!$i) $envoimail.=" $page, ";

Exemple d'écriture de pages dynamiques (entrées uniques)

Dans cet exemple on trouve le paramètre flag en vue de stipuler l'écriture de l'esperluette & soit impérativement &amp;

// ex. http://monsite.com/repertoire/lapage.php?article=2&flag=2
$sql = "SELECT id,ladate FROM matable WHERE foo IS NOT NULL AND bar!=''";
$res = @mysql_query($sql,$db);
$start=0;
while($row = @mysql_fetch_assoc($res)) {
	$url = $http."repertoire/lapage.php?article=$row[id]&amp;flag=2";
	$ladate = date("Y-m-d", $row['ladate']);
	$ecrire .= "<url>\n";
	$ecrire .= "	<loc>$url</loc>\n";
	$ecrire .= "	<lastmod>$ladate</lastmod>\n";
	$ecrire .= "</url>\n";
	$affiche .= "<a href='$url' target='_blank'>$url</a><br>";
	$c++;
	$start++;
}
if(!$start) $envoimail.=" lapage.php, ";

Exemple d'écriture d'URL dynamiques réécrites (url rewriting)

Cet exemple justifie à lui seul la gestion ad hoc d'un sitemap. Toujours grâce à l'inclusion du fichier de fonctions propriétaires, on écrit les pages de façon coordonnées au site. Ici avec la fonction perso affichage_url qui écrit les URLs à la volée (en conjonction avec le mod rewrite du fichier .htaccess).

// ex. http://monsite.com/le-titre-de-la-page_5.php
$sql = "SELECT id,titre,ladate FROM matable WHERE foo=1";
$res = @mysql_query($sql,$db);
$start=0;
while($row=@mysql_fetch_assoc($res)) {
	// appel de la fonction (perso) de réécriture
	$url = $http.affichage_url($row['titre'],$row['id']);
	$ladate = date("Y-m-d", $row['ladate']);
	$ecrire .= "<url>\n";
	$ecrire .= "	<loc>$url</loc>\n";
	$ecrire .= "	<lastmod>$ladate</lastmod>\n";
	$ecrire .= "</url>\n";
	$affiche .= "<a href='$url' target='_blank'>$url</a><br>";
	$c++;
	$start++;
}
if(!$start) $envoimail.=" affichage_url, ";

Ecriture du fichier sitemap.xml

// en l'absence d'erreur
if(!strlen($envoimail)) {
	// ajout de l'en-tête XML et vérification des séquences
	$ecrire = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.google.com/schemas/sitemap/0.84\">\n".$ecrire."</urlset>\n";
	// ouverture du fichier en écriture
	if(!$fichier = @fopen($sitemap, 'w')) $envoimail.=" fopen, ";
	// écriture
	if(!strlen($envoimail) && !@fwrite($fichier,$ecrire)) $envoimail.=" fwrite, ";
	// fermeture du fichier
	if(!strlen($envoimail) && !@fclose($fichier)) $envoimail.=" fclose, ";
}

Affichage ou envoi du mail

Dans l'ordre : structure XML du fichier, puis liste des pages sous forme de liens et enfin erreurs éventuelles, afin de vérifier le bon déroulement du scipt. En mode muet, seulement sur erreur, le résumé est envoyé par email.

// affichage du résultat
if($afficheresult) {
	echo "<p>Résultat pour $c pages : <a href='".$http.$sitemap."' target='_blank'>$sitemap</a></p>\n";
	// affichage des erreurs
	if(strlen($envoimail))
		echo "<p><b>Erreurs :</b>$envoimail</p>\n";
	echo "<p>".str_replace("\n","\n<br>",htmlentities($ecrire))."</p>\n<p>$affiche</p>\n";
// envoi du mail au webmestre (mode muet)
} elseif(strlen($envoimail) && strlen($emailwebmaster))
	@mail($emailwebmaster,"Erreur sitemap $http","Erreur(s): $envoimail\n$http$sitemap\n\n$ecrire","From: $emailwebmaster");
?>

http://dev.ppan.net [Haut de page] [Document mis à jour le 29.03.2008]