diff --git a/ecrire/inc_filtres.php3 b/ecrire/inc_filtres.php3
index 9a2018162831f448d42cfa9db38eb3bf56a114e8..e521db6e4ede6e9d841a1e3b0f6b4dd90b62143b 100644
--- a/ecrire/inc_filtres.php3
+++ b/ecrire/inc_filtres.php3
@@ -208,6 +208,19 @@ function vider_url($url) {
 	return $url;
 }
 
+//
+// Ajouter le &var_recherche=toto dans les boucles de recherche
+//
+function url_var_recherche($url) {
+	if ($GLOBALS['HTTP_GET_VARS']['recherche']
+	AND !ereg("var_recherche", $url)) {
+		$url .= strpos($url, '?') ? '&' : '?';
+		$url .= "var_recherche=".urlencode($GLOBALS['recherche']);
+	}
+	return $url;
+}
+
+
 // Extraire une date de n'importe quel champ (a completer...)
 function extraire_date($texte) {
 	// format = 2001-08
diff --git a/ecrire/inc_texte.php3 b/ecrire/inc_texte.php3
index 117eb81077abe294b1cdf2f6278686edf94d9fc6..579d9d46d3b917dcd80fefd8e1c59731709ef01b 100644
--- a/ecrire/inc_texte.php3
+++ b/ecrire/inc_texte.php3
@@ -478,6 +478,8 @@ function typo($letexte) {
 // de la regexp ci-dessous, et elle retourne le texte a inserer a la place
 // et le lien "brut" a usage eventuel de redirection...
 function extraire_lien ($regs) {
+	global $flag_ecrire;
+
 	$lien_texte = $regs[1];
 
 	$lien_url = trim($regs[3]);
@@ -485,13 +487,12 @@ function extraire_lien ($regs) {
 	$lien_interne = false;
 	if (ereg('^[[:space:]]*(art(icle)?|rub(rique)?|br(.ve)?|aut(eur)?|mot|site|doc(ument)?|im(age|g))?[[:space:]]*([[:digit:]]+)(#.*)?[[:space:]]*$', $lien_url, $match)) {
 		// Traitement des liens internes
-		if (@file_exists('inc-urls.php3')) {
+		if ($flag_ecrire)
+			include_ecrire('inc_urls.php3');
+		else if (@file_exists('inc-urls.php3'))
 			include_local('inc-urls.php3');
-		} elseif (@file_exists('inc-urls-dist.php3')) {
+		else if (@file_exists('inc-urls-dist.php3'))
 			include_local('inc-urls-dist.php3');
-		} else {
-			include_ecrire('inc_urls.php3');
-		}
 
 		$id_lien = $match[8];
 		$ancre = $match[9];
diff --git a/ecrire/inc_version.php3 b/ecrire/inc_version.php3
index 0c4201251c56d59930f24db9fb241483cd1abd86..ed8b94e415e560772ac4bb460b72726f312700ab 100644
--- a/ecrire/inc_version.php3
+++ b/ecrire/inc_version.php3
@@ -1099,7 +1099,14 @@ function spip_log($message, $logname='spip') {
 		@rename($logfile.'.2',$logfile.'.3');
 		@rename($logfile.'.1',$logfile.'.2');
 		@rename($logfile,$logfile.'.1');
+		#if (function_exists('logrotate'))
+		#	logrotate($logfile);
 	}
+
+	// recopier les spip_log mysql (ce sont uniquement des erreurs)
+	// dans le spip_log general
+	if ($logname == 'mysql')
+		spip_log($message);
 }
 
 //
diff --git a/inc-admin.php3 b/inc-admin.php3
index 7c5df9b46ee5a3675fff7d6852a2e0f037825104..7b84e58e5f1e7f42b362ca526ef53e0476460fda 100644
--- a/inc-admin.php3
+++ b/inc-admin.php3
@@ -265,7 +265,8 @@ function erreur_requete_boucle($query, $id_boucle, $type) {
 //
 function erreur_squelette($message, $fautif, $lieu) {
 	global $auteur_session, $debug_messages;
-
+	static $runs;
+	
 	// Drapeau pour interdire d'ecrire les fichiers dans le cache
 	# define('spip_erreur_fatale', 'erreur_squelette');
 	# En fait, a partir du moment ou l'erreur est dans le squelette,
@@ -289,6 +290,9 @@ function erreur_squelette($message, $fautif, $lieu) {
 		$debug_messages .= "<div style='position: fixed; top: 10px; left: 10px;
 		z-index: 10000; background-color: pink;'>$message</div>";
 	}
+
+	// Eviter les boucles infernales
+	if (++$runs > 4) die ($debug_messages);
 }
 
 ?>
diff --git a/inc-balises.php3 b/inc-balises.php3
new file mode 100644
index 0000000000000000000000000000000000000000..f7a0da647569ab154e3f78e2bd12a9b2adca69a5
--- /dev/null
+++ b/inc-balises.php3
@@ -0,0 +1,687 @@
+<?php
+
+//
+// Ce fichier regroupe la quasi totalite des definitions de #BALISES de spip
+// Pour chaque balise, il est possible de surcharger, dans mes_fonctions.php3,
+// la fonction balise_TOTO_dist par une fonction balise_TOTO() respectant la
+// meme API : recoit en entree plein de donnees, les modifie et les retourne.
+// Le format du bloc de donnees $p est un objet de la class ParamChamp,
+// definie dans inc-compilo-index.php3
+//
+
+## NB: les fonctions de forum sont definies dans inc-forum.php3
+
+
+// Ce fichier ne sera execute qu'une fois
+if (defined("_INC_BALISES")) return;
+define("_INC_BALISES", "1");
+
+
+function balise_NOM_SITE_SPIP_dist($p) {
+	$p->code = "lire_meta('nom_site')";
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_EMAIL_WEBMASTER_dist($p) {
+	$p->code = "lire_meta('email_webmaster')";
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_CHARSET_dist($p) {
+	$p->code = "lire_meta('charset')";
+	$p->type = 'php';
+	return $p;
+}
+
+
+function balise_LANG_LEFT_dist($p) {
+	$p->code = "lang_dir(\$spip_lang,'left','right')";
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_LANG_RIGHT_dist($p) {
+	$p->code = "lang_dir(\$spip_lang,'right','left')";
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_LANG_DIR_dist($p) {
+	$p->code = "lang_dir(\$spip_lang,'ltr','rtl')";
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_PUCE_dist($p) {
+	$p->code = "propre('- ')";
+	$p->type = 'php';
+	return $p;
+}
+
+
+// #DATE
+// Cette fonction sait aller chercher dans le contexte general
+// quand #DATE est en dehors des boucles
+// http://www.spip.net/fr_article1971.html
+function balise_DATE_dist ($p) {
+	$_date = champ_sql('date', $p);
+	$p->code = "$_date";
+	$p->process = 'vider_date(%s)';
+	$p->type = 'php';
+	return $p;
+}
+
+// #DATE_REDAC
+// http://www.spip.net/fr_article1971.html
+function balise_DATE_REDAC_dist ($p) {
+	$_date = champ_sql('date_redac', $p);
+	$p->code = "$_date";
+	$p->process = 'vider_date(%s)';
+	$p->type = 'php';
+	return $p;
+}
+
+// #DATE_MODIF
+// http://www.spip.net/fr_article1971.html
+function balise_DATE_MODIF_dist ($p) {
+	$_date = champ_sql('date_modif', $p);
+	$p->code = "$_date";
+	$p->process = 'vider_date(%s)';
+	$p->type = 'php';
+	return $p;
+}
+
+// #DATE_NOUVEAUTES
+// http://www.spip.net/fr_article1971.html
+function balise_DATE_NOUVEAUTES_dist($p) {
+	$p->code = "((lire_meta('quoi_de_neuf') == 'oui' AND lire_meta('majnouv')) ? normaliser_date(lire_meta('majnouv')) : \"'0000-00-00'\")";
+	$p->process = 'vider_date(%s)';
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_URL_SITE_SPIP_dist($p) {
+	$p->code = "lire_meta('adresse_site')";
+	$p->type = 'php';
+	return $p;
+}
+
+
+function balise_URL_ARTICLE_dist($p) {
+	$_type = $p->type_requete;
+
+	// Cas particulier des boucles (SYNDIC_ARTICLES)
+	if ($_type == 'syndic_articles') {
+		$p->code = champ_sql('url', $p);
+	}
+
+	// Cas general : chercher un id_article dans la pile
+	else {
+		$_id_article = champ_sql('id_article', $p);
+		$p->code = "generer_url_article($_id_article)";
+
+		if ($p->boucles[$p->id_boucle]->hash)
+			$p->code = "url_var_recherche(" . $p->code . ")";
+	}
+
+	$p->type = 'html';
+	return $p;
+}
+
+function balise_URL_RUBRIQUE_dist($p) {
+	$p->code = "generer_url_rubrique(" . 
+	champ_sql('id_rubrique',$p) . 
+	")" ;
+	if ($p->boucles[$p->id_boucle]->hash)
+	$p->code = "url_var_recherche(" . $p->code . ")";
+
+	$p->type = 'html';
+	return $p;
+}
+
+function balise_URL_BREVE_dist($p) {
+	$p->code = "generer_url_breve(" .
+	champ_sql('id_breve',$p) . 
+	")";
+	if ($p->boucles[$p->id_boucle]->hash)
+	$p->code = "url_var_recherche(" . $p->code . ")";
+
+	$p->type = 'html';
+	return $p;
+}
+
+function balise_URL_MOT_dist($p) {
+	$p->code = "generer_url_mot(" .
+	champ_sql('id_mot',$p) .
+	")";
+	$p->code = "url_var_recherche(" . $p->code . ")";
+
+	$p->type = 'html';
+	return $p;
+}
+
+function balise_URL_FORUM_dist($p) {
+	$p->code = "generer_url_forum(" .
+	champ_sql('id_forum',$p) .")";
+
+	$p->type = 'html';
+	return $p;
+}
+
+function balise_URL_DOCUMENT_dist($p) {
+	$p->code = "generer_url_document(" .
+	champ_sql('id_document',$p) . ")";
+
+	$p->type = 'html';
+	return $p;
+}
+
+function balise_URL_AUTEUR_dist($p) {
+	$p->code = "generer_url_auteur(" .
+	champ_sql('id_auteur',$p) .")";
+	if ($p->boucles[$p->id_boucle]->hash)
+	$p->code = "url_var_recherche(" . $p->code . ")";
+
+	$p->type = 'html';
+	return $p;
+}
+
+function balise_NOTES_dist($p) {
+	// Recuperer les notes
+	$p->code = '$GLOBALS["les_notes"]';
+	// Vider ensuite les globales des notes recuperees
+	// avec une formule qui renvoit toujours ""
+	$p->code .= '. ($GLOBALS["les_notes"] = $GLOBALS["compt_note"] = '
+	. '($GLOBALS["marqueur_notes"]++)?"":"")';
+	$p->type = 'html';
+	return $p;
+}
+
+function balise_RECHERCHE_dist($p) {
+	$p->code = 'htmlspecialchars($GLOBALS["recherche"])';
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_COMPTEUR_BOUCLE_dist($p) {
+	$p->code = '$compteur_boucle';
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_TOTAL_BOUCLE_dist($p) {
+	if ($p->id_mere === '') {
+		include_local("inc-admin.php3");
+		erreur_squelette(_L("Champ #TOTAL_BOUCLE hors boucle"), '', $p->id_boucle);
+	}
+	$p->code = "\$Numrows['$p->id_mere']";
+	$p->boucles[$p->id_mere]->numrows = true;
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_POINTS_dist($p) {
+	$n = 0;
+	$b = $p->id_boucle;
+	$p->code = '';
+	while ($b != '') {
+	if ($s = $p->boucles[$b]->param) {
+	  foreach($s as $v) {
+		if (strpos($v,'recherche') !== false) {
+		  $p->code = '$Pile[$SP' . (($n==0) ? "" : "-$n") .
+			'][points]';
+		  $b = '';
+		  break;
+		}
+	  }
+	}
+	$n++;
+	$b = $p->boucles[$b]->id_parent;
+	}
+	if (!$p->code) {
+		include_local("inc-admin.php3");
+		erreur_squelette(_L("Champ #POINTS hors d'une recherche"), '', $p->id_boucle);
+	}
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_POPULARITE_ABSOLUE_dist($p) {
+	$p->code = 'ceil(' .
+	champ_sql('popularite', $p) .
+	')';
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_POPULARITE_SITE_dist($p) {
+	$p->code = 'ceil(lire_meta(\'popularite_total\'))';
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_POPULARITE_MAX_dist($p) {
+	$p->code = 'ceil(lire_meta(\'popularite_max\'))';
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_EXPOSER_dist($p) {
+	global  $table_primary;
+	$on = 'on';
+	$off= '';
+	if ($p->fonctions) {
+	// Gerer la notation [(#EXPOSER|on,off)]
+	reset($p->fonctions);
+	list(, $onoff) = each($p->fonctions);
+	ereg("([^,]*)(,(.*))?", $onoff, $regs);
+	$on = addslashes($regs[1]);
+	$off = addslashes($regs[3]);
+	
+	// autres filtres
+	$filtres=Array();
+	while (list(, $nom) = each($p->fonctions))
+	  $filtres[] = $nom;
+	$p->fonctions = $filtres;
+	}
+
+	$type_boucle = $p->type_requete;
+	$primary_key = $table_primary[$type_boucle];
+
+	$p->code = '(calcul_exposer('
+	.champ_sql($primary_key, $p)
+	.', "'.$primary_key.'", $Pile[0]) ?'." '$on': '$off')";
+	$p->type = 'php';
+	return $p;
+}
+
+
+//
+// Inserer directement un document dans le squelette
+//
+function balise_EMBED_DOCUMENT_dist($p) {
+	$_id_document = champ_sql('id_document',$p);
+	$p->code = "embed_document($_id_document, '" .
+	texte_script($p->fonctions ? join($p->fonctions, "|") : "") .
+	"', false)";
+	unset ($p->fonctions);
+	$p->type = 'html';
+	return $p;
+}
+
+// Debut et fin de surlignage auto des mots de la recherche
+// on insere une balise Span avec une classe sans spec:
+// c'est transparent s'il n'y a pas de recherche,
+// sinon elles seront remplacees par les fontions de inc_surligne
+// flag_pcre est juste une flag signalant que preg_match est dispo.
+
+function balise_DEBUT_SURLIGNE_dist($p) {
+	global $flag_pcre;
+	$p->code = ($flag_pcre ? ('\'<span class="spip_surligneconditionnel">\'') : "''");
+	return $p;
+}
+function balise_FIN_SURLIGNE_dist($p) {
+	global $flag_pcre;
+	$p->code = ($flag_pcre ? ('\'</span class="spip_surligneconditionnel">\'') : "''");
+	return $p;
+}
+
+// Formulaire de changement de langue
+function balise_MENU_LANG_dist($p) {
+	$p->code = '"<"."?php
+include_ecrire(\"inc_lang.php3\");
+echo menu_langues(\"var_lang\", \$menu_lang);
+?".">"';
+	$p->type = 'php';
+	return $p;
+}
+
+// Formulaire de changement de langue / page de login
+function balise_MENU_LANG_ECRIRE_dist($p) {
+	$p->code = '"<"."?php
+include_ecrire(\"inc_lang.php3\");
+echo menu_langues(\"var_lang_ecrire\", \$menu_lang);
+?".">"';
+	$p->type = 'php';
+	return $p;
+}
+
+//
+// Formulaires de login
+//
+function balise_LOGIN_PRIVE_dist($p) {
+	$p->code = '"<"."?php include(\'inc-login.php3\'); login(\'\', \'prive\'); ?".">"'; 
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_LOGIN_PUBLIC_dist($p) {
+	if ($nom = $p->fonctions[0])
+	$lacible = "new Link('".$nom."')";
+	else
+	$lacible = '\$GLOBALS[\'clean_link\']';
+	$p->code = '"<"."?php include(\'inc-login.php3\'); login(' . $lacible . ', false); ?".">"';
+	$p->fonctions = array();
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_URL_LOGOUT_dist($p) {
+	if ($p->fonctions) {
+	$url = "&url=".$p->fonctions[0];
+	$p->fonctions = array();
+	} else {
+	$url = '&url=\'.urlencode(\$clean_link->getUrl()).\'';
+	}
+	$p->code = '"<"."?php if (\$GLOBALS[\'auteur_session\'][\'login\'])
+{ echo \'spip_cookie.php3?logout_public=\'.\$GLOBALS[\'auteur_session\'][\'login\'].\'' . $url . '\'; } ?".">"';
+	$p->type = 'php';
+	return $p;
+}
+
+function balise_INTRODUCTION_dist ($p) {
+	$_type = $p->type_requete;
+	$_texte = champ_sql('texte', $p);
+	$_chapo = champ_sql('chapo', $p);
+	$_descriptif = champ_sql('descriptif', $p);
+	$p->code = "calcul_introduction('$_type', $_texte, $_chapo, $_descriptif)";
+
+	$p->type = 'html';
+	return $p;
+}
+
+
+// #LANG
+// non documente ?
+function balise_LANG_dist ($p) {
+	$_lang = champ_sql('lang', $p);
+	$p->code = "($_lang ? $_lang : \$GLOBALS['spip_lang'])";
+	$p->type = 'php';
+	return $p;
+}
+
+
+// #LESAUTEURS
+// les auteurs d'un article (ou d'un article syndique)
+// http://www.spip.net/fr_article902.html
+// http://www.spip.net/fr_article911.html
+function balise_LESAUTEURS_dist ($p) {
+	// Cherche le champ 'lesauteurs' dans la pile
+	$_lesauteurs = champ_sql('lesauteurs', $p); 
+
+	// Si le champ n'existe pas (cas de spip_articles), on donne la
+	// construction speciale sql_auteurs(id_article) ;
+	// dans le cas contraire on prend le champ 'les_auteurs' (cas de
+	// spip_syndic_articles)
+	if ($_lesauteurs AND $_lesauteurs != '$Pile[0][lesauteurs]') {
+		$p->code = $_lesauteurs;
+	} else {
+		$_id_article = champ_sql('id_article', $p);
+
+		# On pourrait mieux faire qu'utiliser cette fonction assistante ?
+		$p->code = "sql_auteurs($_id_article)";
+	}
+
+	$p->type = 'html';
+	return $p;
+}
+
+
+// #PETITION
+// Champ testant la presence d'une petition
+// non documente ???
+function balise_PETITION_dist ($p) {
+	$_id_article = champ_sql('id_article', $p);
+	$p->code = 'sql_petitions($_id_article)';
+	$p->type = 'php';
+	return $p;
+}
+
+
+// #POPULARITE
+// http://www.spip.net/fr_article1846.html
+function balise_POPULARITE_dist ($p) {
+	$_popularite = champ_sql('popularite', $p);
+	$p->code = "ceil(min(100, 100 * $_popularite
+	/ max(1 , 0 + lire_meta('popularite_max'))))";
+	$p->type = 'php';
+	return $p;
+}
+
+
+//
+// Fonction commune aux balises #LOGO_XXXX
+// (les balises portant ce type de nom sont traitees en bloc ici)
+//
+function calcul_balise_logo ($p) {
+
+	// analyser la balise LOGO_xxx
+	eregi("^LOGO_(([A-Z]+)(_.*)?)", $p->nom_champ, $regs);
+	$type_logo = $regs[1];	// ARTICLE_RUBRIQUE
+	$type_objet = $regs[2];	// ARTICLE
+	$suite_logo = $regs[3];	// _RUBRIQUE
+	$_id_objet = champ_sql("id_$type_objet", $p);
+
+	// analyser les filtres
+	$flag_fichier = 'false';
+	$filtres = '';
+	if (is_array($p->fonctions)) {
+		foreach($p->fonctions as $nom) {
+			if (ereg('^(left|right|center|top|bottom)$', $nom))
+				$align = $nom;
+			else if ($nom == 'lien') {
+				$flag_lien_auto = 'oui';
+				$flag_stop = true;
+			}
+			else if ($nom == 'fichier') {
+				$flag_fichier = 'true';
+				$flag_stop = true;
+			}
+			// double || signifie "on passe aux filtres"
+			else if ($nom == '')
+				$flag_stop = true;
+			else if (!$flag_stop) {
+				$lien = $nom;
+				$flag_stop = true;
+			}
+			// apres un URL ou || ou |fichier ce sont
+			// des filtres (sauf left...lien...fichier)
+			else
+				$filtres[] = $nom;
+		}
+		// recuperer les autres filtres s'il y en a
+		$p->fonctions = $filtres;
+	}
+
+	//
+	// Preparer le code du lien
+	//
+	// 1. filtre |lien
+	if ($flag_lien_auto AND !$lien)
+		$code_lien = '($lien = generer_url_'.$type_objet.'('.$_id_objet.')) ? $lien : ""';
+	// 2. lien indique en clair (avec des balises : imprimer#ID_ARTICLE.html)
+	else if ($lien) {
+		$code_lien = "'".texte_script(trim($lien))."'";
+		while (ereg("^([^#]*)#([A-Za-z_]+)(.*)$", $code_lien, $match)) {
+			$c = calculer_champ(array(), $match[2], $p->id_boucle, $p->boucles, $p->id_mere);
+			$code_lien = str_replace('#'.$match[2], "'.".$c.".'", $code_lien);
+		}
+		// supprimer les '' disgracieux
+		$code_lien = ereg_replace("^''\.|\.''$", "", $code_lien);
+	}
+	if (!$code_lien)
+		$code_lien = "''";
+
+	switch ($suite_logo) {
+		case '_NORMAL':
+			$onoff = 'true, false';
+			break;
+		case '_SURVOL':
+			$onoff = 'false, true';
+			break;
+		case '':
+		default:
+			$onoff = 'true, true';
+			break;
+	}
+
+	// cas des documents
+	if ($type_objet == 'DOCUMENT')
+		$code_logo =
+			"array(integre_image($_id_objet,'','fichier_vignette'), '')";
+	else
+		$code_logo = "cherche_logo_objet('$type_objet',
+			$_id_objet, $onoff)";
+
+	// cas des logo #BREVE_RUBRIQUE et #ARTICLE_RUBRIQUE
+	if ($suite_logo == '_RUBRIQUE') {
+		$_id_rubrique = champ_sql("id_rubrique", $p);
+		$code_logo = "(\$logo = $code_logo) ? \$logo : ".
+		"cherche_logo_objet('RUBRIQUE', $_id_rubrique, $onoff)";
+	}
+
+	$p->code = "affiche_logos($code_logo, $code_lien, '$align', $flag_fichier)";
+
+	$p->type = 'php';
+	return $p;
+}
+
+// #EXTRA [(#EXTRA|isbn)]
+// Champs extra
+// Non documentes, en voie d'obsolescence, cf. ecrire/inc_extra.php3
+function balise_EXTRA_dist ($p) {
+	$_extra = champ_sql('extra', $p);
+	$p->code = $_extra;
+
+	// Gerer la notation [(#EXTRA|isbn)]
+	if ($p->fonctions) {
+		include_ecrire("inc_extra.php3");
+		list ($key, $champ_extra) = each($p->fonctions);	// le premier filtre
+		$type_extra = $p->type_requete;
+			// ci-dessus est sans doute un peu buggue : si on invoque #EXTRA
+			// depuis un sous-objet sans champ extra d'un objet a champ extra,
+			// on aura le type_extra du sous-objet (!)
+		if (extra_champ_valide($type_extra, $champ_extra)) {
+			unset($p->fonctions[$key]);
+			$p->code = "extra($p->code, '".addslashes($champ_extra)."')";
+
+			// Appliquer les filtres definis par le webmestre
+			$filtres = extra_filtres($type_extra, $champ_extra);
+			if ($filtres) foreach ($filtres as $f)
+				$p->code = "$f($p->code)";
+		}
+	}
+
+	$p->type = 'html';
+	return $p;
+}
+
+
+
+//
+// Traduction des champs "formulaire"
+//
+
+//
+// Note : les balises de gestion de forums (FORMULAIRE_FORUM et
+// PARAMETRES_FORUM) sont definies dans le fichier inc-forum.php3
+// qui centralise toute la gestion des forums
+//
+
+//
+// Formulaire de recherche
+//
+function balise_FORMULAIRE_RECHERCHE_dist($p) {
+	if ($p->fonctions) {
+		list(, $lien) = each($p->fonctions);	// le premier est un url
+		while (list(, $filtre) = each($p->fonctions))
+			$filtres[] = $filtre;	// les suivants sont des filtres
+		$p->fonctions = $filtres;
+	}
+	if (!$lien) $lien = 'recherche.php3';
+
+	$formulaire_recherche = "\"<form action='$lien' method='get' class='formrecherche'><input type='text' id='formulaire_recherche' size='20' class='formrecherche' name='recherche' value='\" . _T('info_rechercher') . \"' /></form>\"";
+
+	$p->code = "((lire_meta('activer_moteur') != 'oui') ? '' :
+	$formulaire_recherche)";
+
+	$p->type = 'html';
+	return $p;
+}
+
+
+//
+// Formulaire d'inscription comme redacteur (dans inc-formulaires.php3)
+//
+function balise_FORMULAIRE_INSCRIPTION_dist($p) {
+
+	$p->code = '(lire_meta("accepter_inscriptions") != "oui") ? "" :
+		("<"."?php include(\'inc-formulaires.php3\'); lang_select(\"$spip_lang\"); formulaire_inscription(\"redac\"); lang_dselect(); ?".">")';
+
+	$p->type = 'php';
+	return $p;
+}
+
+//
+// Formulaire ecrire auteur
+//
+function balise_FORMULAIRE_ECRIRE_AUTEUR_dist($p) {
+	$_id_auteur = champ_sql('id_auteur', $p);
+	$_mail_auteur = champ_sql('email', $p);
+
+	$p->code = '!email_valide('.$_mail_auteur.') ? "" :
+		("<'.'?php include(\'inc-formulaires.php3\');
+		lang_select(\'$spip_lang\');
+		formulaire_ecrire_auteur(".'.$_id_auteur.'.", \'".texte_script('.$_mail_auteur.')."\');
+		lang_dselect(); ?'.'>")';
+
+	$p->type = 'php';
+	return $p;
+}
+
+//
+// Formulaire signature de petition
+//
+function balise_FORMULAIRE_SIGNATURE_dist($p) {
+	$_id_article = champ_sql('id_article', $p);
+
+	$p->code = '!($petition = sql_petitions('.$_id_article.')) ? "" :
+		("<"."?php include(\'inc-formulaires.php3\');
+		lang_select(\'$spip_lang\');
+		echo formulaire_signature(".'.$_id_article.'.",
+			\'".texte_script(serialize($petition))."\');
+		lang_dselect(); ?".">")';
+
+	$p->type = 'php';
+	return $p;
+}
+
+// Formulaire d'inscription de site dans l'annuaire
+function balise_FORMULAIRE_SITE_dist($p) {
+	$_id_rubrique = champ_sql('id_rubrique', $p);
+
+	$p->code = '(lire_meta("proposer_sites") != 2) ? "":
+		"<"."?php include(\'inc-formulaires.php3\');
+		lang_select(\'".$GLOBALS[\'spip_lang\']."\');
+		formulaire_site(".'.$_id_rubrique.'.");
+		lang_dselect(); ?".">"';
+
+	$p->type = 'php';
+	return $p;
+}
+
+
+
+//
+// Boutons d'administration: 
+//
+function balise_FORMULAIRE_ADMIN_dist($p) {
+	$p->code = "'<!-- @@formulaire_admin@@45609871@@ -->'";
+	$p->type = "php";
+	return $p;
+}
+
+
+?>
diff --git a/inc-calcul_mysql3.php b/inc-calcul-outils.php3
similarity index 50%
rename from inc-calcul_mysql3.php
rename to inc-calcul-outils.php3
index ed83467e5fc5c0459ce8e7be31bc74ea3d93e682..1a3cf524483482776ff638879bad87a74ce42905 100644
--- a/inc-calcul_mysql3.php
+++ b/inc-calcul-outils.php3
@@ -1,63 +1,176 @@
 <?php
-    
-# Ce fichier concentre tous les appels SQL lors de l'execution d'un squelette.
-
-# Cette fonction est syste'matiquement appelee par les squelettes
-# pour constuire une requete SQL a` partir de la boucle SPIP originale.
-# Elle construit et exe'cute une reque^te SQL correspondant a` une balise Boucle
-# Elle notifie une erreur SQL dans le flux de sortie et termine le processus.
-# Sinon, retourne la ressource interrogeable par fetch_row ou fetch_array.
-# Elle peut etre re'de'finie pour s'interfacer avec d'autres serveurs SQL
-# Recoit en argument:
-# - le tableau des champs a` ramener
-# - le tableau des tables a` consulter
-# - le tableau des conditions a` remplir
-# - le crite`re de regroupement
-# - le crite`re de classement
-# - le crite`re de limite
-# - une sous-requete e'ventuelle (MySQL > 4.1)
-# - un compteur de sous-requete
-# - le nom de la table
-# - le nom de la boucle (pour le message d'erreur e'ventuel)
-
-## NB : le traitement des SQL de forums est defini dans inc-forum.php3
-
-function spip_abstract_select (
-	$select = array(), $from = array(), $where = '',
-	$groupby = '', $orderby = '', $limit = '',
-	$sousrequete = '', $cpt = '',
-	$table = '', $id = '') {
-
-	$DB = 'spip_';
-	$q = " FROM $DB" . join(", $DB", $from)
-	. (is_array($where) ? ' WHERE ' . join(' AND ', $where) : '')
-	. ($groupby ? " GROUP BY $groupby" : '')
-	. ($orderby ? "\nORDER BY $orderby" : '')
-	. ($limit ? "\nLIMIT $limit" : '');
-
-	if (!$sousrequete)
-		$q = " SELECT ". join(", ", $select) . $q;
-	else
-		$q = " SELECT S_" . join(", S_", $select)
-		. " FROM (" . join(", ", $select)
-		. ", COUNT(".$sousrequete.") AS compteur " . $q
-		.") AS S_$table WHERE compteur=" . $cpt;
-
-	//
-	// Erreur ? C'est du debug, ou une erreur du serveur
-	//
-	if (!($result = @spip_query($q))) {
-		include_local('inc-admin.php3');
-		echo erreur_requete_boucle($q, $id, $table);
+
+//
+// Des fonctions diverses utilisees lors du calcul d'une page ; ces fonctions
+// bien pratiques n'ont gu俊e de logique organisationnelle ; elles sont
+// appelees par certaines balises au moment du calcul des pages. (Peut-on
+// trouver un modele de donnees qui les associe physiquement au fichier
+// definissant leur balise ???
+//
+
+// ON TROUVERA EN QUEUE DE FICHIER LES FONCTIONS FAISANT DES APPELS SQL
+
+
+// Ce fichier ne sera execute qu'une fois
+if (defined("_INC_CALCUL_OUTILS")) return;
+define("_INC_CALCUL_OUTILS", "1");
+
+
+#
+# AFFREUX !!  Passer tout ca en CSS au plus vite !
+#
+tester_variable('espace_logos',3);
+// HSPACE=xxx VSPACE=xxx pour les logos (#LOGO_ARTICLE)
+tester_variable('espace_images',3);
+// HSPACE=xxx VSPACE=xxx pour les images integrees
+
+//
+// Retrouver le logo d'un objet (et son survol)
+//
+
+
+function cherche_image($id_objet, $type_objet) {
+	// cherche l'image liee a l'objet
+	$on = cherche_image_nommee($type_objet.'on'.$id_objet);
+
+	// cherche un survol
+	$off =(!$on ? '' :
+	cherche_image_nommee($type_objet.'off'.$id_objet));
+
+	if (!$on)
+		return false;
+
+	return array($on, $off);
+}
+
+function cherche_logo_objet ($type, $id_objet, $on = false, $off = false, $flag_fichier=false) {
+
+	# spip_log("cherche logo $type $id_objet $on $off $flag_fichier");
+	switch($type) {
+		case 'ARTICLE':
+			$logo = cherche_image($id_objet, 'art');
+			break;
+		case 'AUTEUR':
+			$logo = cherche_image($id_objet, 'aut');
+			break;
+		case 'BREVE':
+			$logo = cherche_image($id_objet, 'breve');
+			break;
+		case 'SITE':
+			$logo = cherche_image($id_objet, 'site');
+			break;
+		case 'MOT':
+			$logo = cherche_image($id_objet, 'mot');
+			break;
+		// recursivite
+		case 'RUBRIQUE':
+			if (!($logo = cherche_image ($id_objet, 'rub'))
+			AND $id_objet > 0)
+				$logo = cherche_logo_objet('RUBRIQUE',
+				sql_parent($id_objet), true, true);
+			break;
+		default:
+			spip_log("cherche_logo_objet: type '$type' inconnu");
+	}
+
+	// Quelles images sont demandees ?
+	if (!$on) unset($logo[0]);
+	if (!$off) unset($logo[1]);
+
+	if ($logo[0] OR $logo[1])
+		return $logo;
+}
+
+// Renvoie le code html pour afficher le logo, avec ou sans survol, avec ou sans lien, etc.
+function affiche_logos($logo, $lien, $align, $flag_fichier) {
+	global $num_survol;
+	global $espace_logos;
+
+	list($arton,$artoff) = $logo;
+
+	// Pour les documents comme pour les logos, le filtre |fichier donne
+	// le chemin du fichier apres 'IMG/' ;  peut-etre pas d'une purete
+	// remarquable, mais a conserver pour compatibilite ascendante.
+	// -> http://www.spip.net/fr_article901.html
+	if ($flag_fichier) {
+		$on = ereg_replace("^IMG/","",$arton);
+		$off = ereg_replace("^IMG/","",$artoff);
+		return $on ? $on : $off;
+	}
+
+	$num_survol++;
+	if ($arton) {
+		//$imgsize = @getimagesize("$arton");
+		//$taille_image = ereg_replace("\"","'",$imgsize[3]);
+		if ($align) $align="align='$align' ";
+
+		$milieu = "<img src='$arton' $align".
+			" name='image$num_survol' ".$taille_image." border='0' alt=''".
+			" hspace='$espace_logos' vspace='$espace_logos' class='spip_logos' />";
+
+		if ($artoff) {
+			if ($lien) {
+				$afflien = "<a href='$lien'";
+				$afflien2 = "a>";
+			}
+			else {
+				$afflien = "<div";
+				$afflien2 = "div>";
+			}
+			$milieu = "$afflien onMouseOver=\"image$num_survol.src=".
+				"'$artoff'\" onMouseOut=\"image$num_survol.src=".
+				"'$arton'\">$milieu</$afflien2";
+		}
+		else if ($lien) {
+			$milieu = "<a href='$lien'>$milieu</a>";
+		}
+	} else {
+		$milieu="";
 	}
+	return $milieu;
+}
 
-	#  spip_log(spip_num_rows($result));
-	return $result;
+
+
+//
+// fonction standard de calcul de la balise #INTRODUCTION
+// on peut la surcharger en definissant dans mes_fonctions.php3 :
+// function introduction($type,$texte,$descriptif) {...}
+//
+function calcul_introduction ($type, $texte, $chapo='', $descriptif='') {
+	if (function_exists("introduction"))
+		return introduction ($type, $texte, $chapo, $descriptif);
+
+	switch ($type) {
+		case 'articles':
+			if ($descriptif)
+				return propre($descriptif);
+			else if (substr($chapo, 0, 1) == '=')	// article virtuel
+				return '';
+			else
+				return PtoBR(propre(supprimer_tags(couper_intro($chapo."\n\n\n".$texte, 500))));
+			break;
+		case 'breves':
+			return PtoBR(propre(supprimer_tags(couper_intro($texte, 300))));
+			break;
+		case 'forums':
+			return PtoBR(propre(supprimer_tags(couper_intro($texte, 600))));
+			break;
+		case 'rubriques':
+			if ($descriptif)
+				return propre($descriptif);
+			else
+				return PtoBR(propre(supprimer_tags(couper_intro($texte, 600))));
+			break;
+	}
 }
 
 
-# toutes les fonctions avec requete SQL, necessaires aux squelettes.
+//
+// FONCTIONS FAISANT DES APPELS SQL
+//
 
+# NB : a l'exception des fonctions de forum regroupees dans inc-forum.
 
 function calcul_exposer ($id, $type, $reference) {
 	static $exposer;
@@ -233,4 +346,5 @@ function sql_rubrique_fond($contexte, $lang) {
 }
 
 
-?>
+
+?>
\ No newline at end of file
diff --git a/inc-calcul.php3 b/inc-calcul.php3
index 30404573909cc85294b7da59ca0c651b9aab34c6..d10df149a8f073b32ca3bfa133393e6ee2e4bcc4 100644
--- a/inc-calcul.php3
+++ b/inc-calcul.php3
@@ -15,8 +15,10 @@ include_ecrire("inc_filtres.php3");
 include_ecrire("inc_lang.php3");
 include_ecrire("inc_documents.php3");
 include_ecrire("inc_forum.php3");
-include_local("inc-calcul_mysql3.php");
-include_local("inc-calcul_html4.php");
+include_local("inc-calcul-outils.php3");
+
+#include_local("inc-calcul_html");	# anciens noms des fichiers
+#include_local("inc-calcul_mysql");
 
 
 // Ce fichier peut contenir une affectation de $dossier_squelettes  indiquant
@@ -66,7 +68,12 @@ function charger_squelette ($squelette) {
 		}
 
 		// sinon le compiler
-		include_local("inc-calcul-squel.php3");
+		if ($GLOBALS['tradition']) {
+			include_local("inc-calcul-squel.php3");
+		}
+		else {
+			include_local("inc-compilo.php3");
+		}
 		if (!lire_fichier ($sourcefile, $skel)) { 
 			// erreur webmaster : $fond ne correspond a rien
 			include_ecrire ("inc_presentation.php3");
@@ -152,6 +159,7 @@ function cherche_page ($cache, $contexte, $fond, $id_rubrique, $lang='')  {
 	// Calculer la page a partir du main() du skel compile
 	$page =  $fonc(array('cache' =>$cache),
 		array($contexte),
+		/* obsolete avec les doublons de inc-compilo */
 		array(
 			'articles' => '0',
 			'rubriques' => '0',
@@ -295,7 +303,7 @@ function calculer_page($chemin_cache, $elements, $delais, $inclusion=false) {
 	serialize($page['signal']))." -->\n";
 
 	// Enregistrer le fichier cache
-	if ($delais > 0)
+	if ($delais > 0 AND empty($GLOBALS['HTTP_POST_VARS']))
 		ecrire_fichier($chemin_cache, $signal.$page['texte']);
 
 	return $page;
@@ -303,66 +311,6 @@ function calculer_page($chemin_cache, $elements, $delais, $inclusion=false) {
 
 
 
-# Fonctions appelees par les squelettes (insertion dans le code trop lourde)
-
-tester_variable('espace_logos',3);  // HSPACE=xxx VSPACE=xxx pour les logos (#LOGO_ARTICLE)
-tester_variable('espace_images',3);  // HSPACE=xxx VSPACE=xxx pour les images integrees
-
-//
-// Retrouver le logo d'un objet (et son survol)
-//
-
-function cherche_image($id_objet, $type_objet) {
-	// cherche l'image liee a l'objet
-	$on = cherche_image_nommee($type_objet.'on'.$id_objet);
-
-	// cherche un survol
-	$off =(!$on ? '' :
-	cherche_image_nommee($type_objet.'off'.$id_objet));
-
-	if (!$on)
-		return false;
-
-	return array($on, $off);
-}
-
-function cherche_logo_objet ($type, $id_objet, $on = false, $off = false, $flag_fichier=false) {
-
-spip_log("cherche logo $type $id_objet $on $off $flag_fichier");
-	switch($type) {
-		case 'ARTICLE':
-			$logo = cherche_image($id_objet, 'art');
-			break;
-		case 'AUTEUR':
-			$logo = cherche_image($id_objet, 'aut');
-			break;
-		case 'BREVE':
-			$logo = cherche_image($id_objet, 'breve');
-			break;
-		case 'SITE':
-			$logo = cherche_image($id_objet, 'site');
-			break;
-		case 'MOT':
-			$logo = cherche_image($id_objet, 'mot');
-			break;
-		// recursivite
-		case 'RUBRIQUE':
-			if (!($logo = cherche_image ($id_objet, 'rub'))
-			AND $id_objet > 0)
-				$logo = cherche_logo_objet('RUBRIQUE',
-				sql_parent($id_objet), true, true);
-			break;
-		default:
-			spip_log("cherche_logo_objet: type '$type' inconnu");
-	}
-
-	// Quelles images sont demandees ?
-	if (!$on) unset($logo[0]);
-	if (!$off) unset($logo[1]);
-
-	if ($logo[0] OR $logo[1])
-		return $logo;
-}
 
 
 // Fonction appelee par le skel pour assembler les balises
@@ -383,4 +331,59 @@ function _f($push = false, $texte='') {
 			return $texte;
 }
 
+### A passer peut-etre dans inc_db_mysql
+// Cette fonction est systematiquement appelee par les squelettes
+// pour constuire une requete SQL de type "lecture" (SELECT) a partir
+// de chaque boucle.
+// Elle construit et exe'cute une reque^te SQL correspondant a` une balise
+// Boucle ; elle notifie une erreur SQL dans le flux de sortie et termine
+// le processus.
+// Sinon, retourne la ressource interrogeable par fetch_row ou fetch_array.
+// Elle peut etre re'de'finie pour s'interfacer avec d'autres serveurs SQL
+// Recoit en argument:
+// - le tableau des champs a` ramener
+// - le tableau des tables a` consulter
+// - le tableau des conditions a` remplir
+// - le crite`re de regroupement
+// - le crite`re de classement
+// - le crite`re de limite
+// - une sous-requete e'ventuelle (MySQL > 4.1)
+// - un compteur de sous-requete
+// - le nom de la table
+// - le nom de la boucle (pour le message d'erreur e'ventuel)
+
+
+function spip_abstract_select (
+	$select = array(), $from = array(), $where = '',
+	$groupby = '', $orderby = '', $limit = '',
+	$sousrequete = '', $cpt = '',
+	$table = '', $id = '') {
+
+	$DB = 'spip_';
+	$q = " FROM $DB" . join(", $DB", $from)
+	. (is_array($where) ? ' WHERE ' . join(' AND ', $where) : '')
+	. ($groupby ? " GROUP BY $groupby" : '')
+	. ($orderby ? "\nORDER BY $orderby" : '')
+	. ($limit ? "\nLIMIT $limit" : '');
+
+	if (!$sousrequete)
+		$q = " SELECT ". join(", ", $select) . $q;
+	else
+		$q = " SELECT S_" . join(", S_", $select)
+		. " FROM (" . join(", ", $select)
+		. ", COUNT(".$sousrequete.") AS compteur " . $q
+		.") AS S_$table WHERE compteur=" . $cpt;
+
+	//
+	// Erreur ? C'est du debug, ou une erreur du serveur
+	//
+	if (!($result = @spip_query($q))) {
+		include_local('inc-admin.php3');
+		echo erreur_requete_boucle($q, $id, $table);
+	}
+
+	#  spip_log(spip_num_rows($result));
+	return $result;
+}
+
 ?>
diff --git a/inc-calcul_html4.php b/inc-calcul_html4.php
deleted file mode 100644
index ee1db983c92fa150e20351de0c5417d65b38c8ca..0000000000000000000000000000000000000000
--- a/inc-calcul_html4.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-// Ce fichier ne sera execute qu'une fois
-if (defined("_CALCUL_HTML4")) return;
-define("_CALCUL_HTML4", "1");
-
-// Renvoie le code html pour afficher le logo, avec ou sans survol, avec ou sans lien, etc.
-function affiche_logos($logo, $lien, $align, $flag_fichier) {
-	global $num_survol;
-	global $espace_logos;
-
-	list($arton,$artoff) = $logo;
-
-	// Pour les documents comme pour les logos, le filtre |fichier donne
-	// le chemin du fichier apres 'IMG/' ;  peut-etre pas d'une purete
-	// remarquable, mais a conserver pour compatibilite ascendante.
-	// -> http://www.spip.net/fr_article901.html
-	if ($flag_fichier) {
-		$on = ereg_replace("^IMG/","",$arton);
-		$off = ereg_replace("^IMG/","",$artoff);
-		return $on ? $on : $off;
-	}
-
-	$num_survol++;
-	if ($arton) {
-		//$imgsize = @getimagesize("$arton");
-		//$taille_image = ereg_replace("\"","'",$imgsize[3]);
-		if ($align) $align="align='$align' ";
-
-		$milieu = "<img src='$arton' $align".
-			" name='image$num_survol' ".$taille_image." border='0' alt=''".
-			" hspace='$espace_logos' vspace='$espace_logos' class='spip_logos' />";
-
-		if ($artoff) {
-			if ($lien) {
-				$afflien = "<a href='$lien'";
-				$afflien2 = "a>";
-			}
-			else {
-				$afflien = "<div";
-				$afflien2 = "div>";
-			}
-			$milieu = "$afflien onMouseOver=\"image$num_survol.src=".
-				"'$artoff'\" onMouseOut=\"image$num_survol.src=".
-				"'$arton'\">$milieu</$afflien2";
-		}
-		else if ($lien) {
-			$milieu = "<a href='$lien'>$milieu</a>";
-		}
-	} else {
-		$milieu="";
-	}
-	return $milieu;
-}
-
-
-
-//
-// Ajouter le &var_recherche=toto dans les boucles de recherche
-//
-function url_var_recherche($url) {
-	if ($GLOBALS['HTTP_GET_VARS']['recherche'] && !ereg("var_recherche", $url)) {
-		$url .= strpos($url, '?') ? '&' : '?';
-		$url .= "var_recherche=".urlencode($GLOBALS['recherche']);
-	}
-	return $url;
-}
-
-//
-// fonction standard de calcul de la balise #INTRODUCTION
-// on peut la surcharger en definissant dans mes_fonctions.php3 :
-// function introduction($type,$texte,$descriptif) {...}
-//
-function calcul_introduction ($type, $texte, $chapo='', $descriptif='') {
-	if (function_exists("introduction"))
-		return introduction ($type, $texte, $chapo, $descriptif);
-
-	switch ($type) {
-		case 'articles':
-			if ($descriptif)
-				return propre($descriptif);
-			else if (substr($chapo, 0, 1) == '=')	// article virtuel
-				return '';
-			else
-				return PtoBR(propre(supprimer_tags(couper_intro($chapo."\n\n\n".$texte, 500))));
-			break;
-		case 'breves':
-			return PtoBR(propre(supprimer_tags(couper_intro($texte, 300))));
-			break;
-		case 'forums':
-			return PtoBR(propre(supprimer_tags(couper_intro($texte, 600))));
-			break;
-		case 'rubriques':
-			if ($descriptif)
-				return propre($descriptif);
-			else
-				return PtoBR(propre(supprimer_tags(couper_intro($texte, 600))));
-			break;
-	}
-}
-
-function calcul_form_rech($lien)
-{
-  return 
-    "<form action='$lien' method='get' class='formrecherche'><input type='text' id='formulaire_recherche' size='20' class='formrecherche' name='recherche' value='" . _T('info_rechercher') . "' /></form>";
-}
-
-?>
diff --git a/inc-compilo-debug.php3 b/inc-compilo-debug.php3
new file mode 100644
index 0000000000000000000000000000000000000000..d52631899131e64eff1bd0bca7bffea042445061
--- /dev/null
+++ b/inc-compilo-debug.php3
@@ -0,0 +1,85 @@
+<?php
+
+//
+// Outils pour debugguer le compilateur (pas inclus)
+//
+
+//
+// Ce fichier ne sera execute qu'une fois
+if (defined("_INC_COMPILO_DEBUG")) return;
+define("_INC_COMPILO_DEBUG", "1");
+
+//
+// Fonctions debug
+//
+
+function affval($val) {
+
+	echo "&ldquo;" . entites_html($val) . "&rdquo;";
+
+}
+
+function afftable($table) {
+
+	if (!$table) return;
+	reset($table);
+	echo "<UL>";
+	while (list($key, $val) = each($table)) {
+		echo "<LI>";
+		affobject($val);
+		echo "</LI>";
+	}
+	echo "</UL>\n";
+}
+
+
+function affobject($val)
+{
+  if (!is_object($val))
+    affval($val);
+  else
+    switch ($val->type) {
+		case 'boucle':
+			echo "<font color='red'><b>Boucle".$val->id_boucle."</b>";
+			echo "<br><i><small>".affval($val->requete)."</small></i></font>";
+			break;
+		case 'texte':
+			echo affval($val->texte);
+			break;
+		case 'include':
+			echo affval($val->fichier);
+			afftable($params);
+			break;
+    		case 'champ':
+			echo "<font color='blue'><i>#".$val->nom_champ;
+			if ($val->fonctions) echo " <small>(".join(',', $val->fonctions).")</small>";
+			echo "</i></font>";
+			echo "<ul><li>";
+			echo afftable($val->cond_avant);
+			echo "</li><li>";
+			echo afftable($val->cond_apres);
+			echo "</li></ul>";
+			break;
+    }
+}
+
+
+function affboucle($val) {
+	echo "<hr><ul>";
+	foreach(get_object_vars($val) as $k => $v)
+	  {
+	    echo "<li><b>$k : </b>";
+	    if (is_array($v)) 
+	      if (!$v) echo "<i>Tableau vide</i>"; else afftable($v); 
+	    elseif (is_object($v))
+	      echo afftable($v);
+	    else affval($v);
+	    echo  "</li>"; }
+	echo "</ul>\n";
+}
+
+function affboucles($boucles) {
+  while (list($key, $val) = each($boucles)) affboucle($val);
+}
+
+?>
diff --git a/inc-compilo-index.php3 b/inc-compilo-index.php3
new file mode 100644
index 0000000000000000000000000000000000000000..e7180f73f56da33a8a96e3e0e2bea1c66441693e
--- /dev/null
+++ b/inc-compilo-index.php3
@@ -0,0 +1,314 @@
+<?php
+
+// Definition des classes Boucle, Texte, Inclure, etc.,
+// et fonctions de recherche et de reservation
+// dans l'arborescence des boucles
+
+// Ce fichier ne sera execute qu'une fois
+if (defined("_INC_COMPILO_INDEX")) return;
+define("_INC_COMPILO_INDEX", "1");
+
+//
+// encodage d'une boucle SPIP en un objet PHP
+//
+class Boucle {
+	var $type = 'boucle';
+	var $id_boucle, $id_parent;
+	var $cond_avant, $milieu, $cond_apres, $cond_altern;
+	var $lang_select;
+	var $type_requete;
+	var $param;
+	var $separateur;
+	var $doublons;
+	var $partie, $total_parties,$mode_partie;
+	var $externe = ''; # appel a partir d'une autre boucle (recursion)
+	// champs pour la construction de la requete SQL
+	var $tout = false;
+	var $plat = false;
+	var $select;
+	var $from;
+	var $where;
+	var $limit;
+	var $group = '';
+	var $order = '';
+	var $date = 'date' ;
+	var $hash = false ;
+	var $lien = false;
+	var $sous_requete = false;
+	var $compte_requete = 1;
+	var $hierarchie = '';
+	// champs pour la construction du corps PHP
+	var $return;
+	var $numrows = false; 
+}
+
+class Texte {
+	var $type = 'texte';
+	var $texte;
+}
+
+class Inclure {
+	var $type = 'include';
+	var $fichier;
+	var $params;
+}
+
+class Champ {
+	var $type = 'champ';
+	var $nom_champ;
+	var $cond_avant, $cond_apres; // tableaux d'objets
+	var $fonctions;
+}
+
+//
+// Structure de donnees pour parler aux fonctions calcul_champ_TOTO
+//
+class ParamChamp {
+	var $fonctions;
+	var $nom_champ;
+	var $id_boucle;
+	var $boucles;
+	var $id_mere;
+	var $type_requete;
+	var $code;			// code du calcul
+	var $process;		// processeurs standards, exemple 'propre(%s)'
+	var $etoile;		// le champ a ete appele avec une etoile (booleen)
+	var $type;			// 'num'erique, 'h'=texte (html) ou 'p'=script (php) ?
+						// -> definira les pre et post-traitements obligatoires
+
+	function retour() {
+		// Annuler les traitements si le champ est etoile
+		if ($this->etoile) unset($this->process);
+
+		$code_filtre = applique_filtres(
+			$this->fonctions,
+			$this->code,
+			$this->id_boucle,
+			$this->boucles,
+			$this->id_mere,
+			$this->type,
+			$this->process
+		);
+		return $code_filtre;
+	}
+}
+
+
+
+// index_pile retourne la position dans la pile du champ SQL $nom_champ 
+// en prenant la boucle la plus proche du sommet de pile (indique par $idb).
+// Si on ne trouve rien, on considere que ca doit provenir du contexte 
+// (par l'URL ou l'include) qui a ete recopie dans Pile[0]
+// (un essai d'affinage a debouche sur un bug vicieux)
+// Si ca reference un champ SQL, on le memorise dans la structure $boucles
+// afin de construire un requete SQL minimale (plutot qu'un brutal 'SELECT *')
+
+include_ecrire('inc_serialbase.php3');
+
+function index_pile($idb, $nom_champ, &$boucles) {
+	global $exceptions_des_tables, $table_des_tables, $tables_principales;
+
+	// Recherche d'un champ dans un etage superieur
+	$i = 0;
+	if ($c=strpos($nom_champ, ':')) {
+		$idbs = substr($nom_champ, 0, $c);
+		$nom_champ = substr($nom_champ, $c+1);
+		while (($idb != $idbs) && $idb) {
+			$i++;
+			$idb = $boucles[$idb]->id_parent;
+		}
+	}
+
+	$c = strtolower($nom_champ);
+	// attention a la boucle nommee 0 ....
+	while ($idb!== '') {
+		#spip_log("Cherche: $nom_champ '$idb' '$c'");
+		$r = $boucles[$idb]->type_requete;
+		// indirection (pour les rares cas ou le nom de la table est /= du type)
+		$t = $table_des_tables[$r];
+		if (!$t)
+			$t = $r; // pour les tables non Spip
+		// $t est le nom PHP de cette table 
+		#spip_log("Go: idb='$idb' r='$r' c='$c' nom='$nom_champ'");
+		$desc = $tables_principales[$t];
+		if (!$desc) {
+			include_local("inc-admin.php3");
+			erreur_squelette(_L("Table SQL absente de \$tables_principales dans inc_serialbase"), $r, "'$idb'");
+		}
+		$excep = $exceptions_des_tables[$r][$c];
+		if ($excep) {
+			// entite SPIP alias d'un champ SQL
+			if (!is_array($excep)) {
+				$e = $excep;
+			} 
+			// entite SPIP alias d'un champ dans une autre table SQL 
+			else {
+				$t = $excep[0];
+				$e = $excep[1];
+			}
+		}
+		else {
+			// $e est le type SQL de l'entree (ici utile comme booleen)
+			// entite SPIP homonyme au champ SQL
+			if ($desc['field'][$c])
+				$e = $c;
+			else
+				$e = '';
+		}
+
+		#spip_log("Dans $idb ($t $e): $desc");
+
+		// On l'a trouve
+		if ($e) {
+			$boucles[$idb]->select[] = $t . "." . $e;
+			return '$Pile[$SP' . ($i ? "-$i" : "") . '][\'' . $e . '\']';
+		}
+
+		// Sinon on remonte d'un cran
+		$idb = $boucles[$idb]->id_parent;
+		$i++;
+	}
+
+	#spip_log("Pas vu $nom_champ dans les " . count($boucles) . " boucles");
+	// esperons qu'il y sera
+	return('$Pile[0][\''.$nom_champ.'\']');
+}
+
+// cette fonction sert d'API pour demander le champ '$champ' dans la pile
+function champ_sql($champ, $p) {
+	return index_pile($p->id_boucle, $champ, $p->boucles);
+}
+
+# calculer_champ genere le code PHP correspondant a la balise Spip $nom_champ
+# Retourne une EXPRESSION php 
+function calculer_champ($fonctions, $nom_champ, $id_boucle, &$boucles, $id_mere, $etoile = false) {
+	// Preparer les parametres
+	$p = new ParamChamp;
+	$p->fonctions = $fonctions;
+	$p->nom_champ = $nom_champ;
+	$p->id_boucle = $id_boucle;
+	$p->boucles = &$boucles;
+	$p->id_mere = $id_mere;
+	$p->type = 'html';
+	$p->process = '';
+	$p->type_requete = $boucles[$id_boucle]->type_requete;
+
+	// regarder s'il existe une fonction personnalisee balise_NOM()
+	$f = 'balise_' . $nom_champ;
+	if (function_exists($f))
+		$p = $f($p);
+
+	else {
+	// regarder s'il existe une fonction standard balise_NOM_dist()
+	$f = 'balise_' . $nom_champ . '_dist';
+	if (function_exists($f))
+		$p = $f($p);
+
+	else {
+	// S'agit-il d'un logo ? Une fonction speciale les traite tous
+	if (ereg('^LOGO_', $nom_champ))
+		$p = calcul_balise_logo($p);
+
+	else {
+	// On regarde ensuite s'il y a un champ SQL homonyme,
+	// et on definit le type et les traitements
+	$p->code = champ_sql($nom_champ, $p);
+	if (($p->code) && ($p->code != '$Pile[0][\''.$nom_champ.'\']')) {
+
+		// Par defaut basculer en numerique pour les #ID_xxx
+		if (substr($nom_champ,0,3) == 'ID_') $p->type = 'num';
+	}
+
+	else {
+	// si index_pile a ramene le choix par defaut, 
+	// ca doit plutot etre un champ SPIP non SQL,
+	// ou ni l'un ni l'autre => on le renvoie sous la forme brute '#TOTO'
+	$p->code = "'#$nom_champ'";
+	$p->type = 'php';	// pas de traitement
+	
+	}}}}
+	
+	// Aller chercher les processeurs standards definis dans inc-champ-squel
+	if (!$etoile)
+		$p->process = champs_traitements($nom_champ);
+
+	// Retourner l'expression php correspondant au champ + ses filtres
+	return $p->retour();
+}
+
+
+// Genere l'application d'une liste de filtres
+function applique_filtres ($fonctions, $code, $id_boucle, $boucles, $id_mere, $type ='html', $process='') {
+
+	// pretraitements standards
+	switch ($type) {
+		case 'num':
+			$code = "intval($code)";
+			break;
+		case 'php':
+			break;
+		case 'html':
+		default:
+			$code = "trim($code)";
+			break;
+	}
+
+	// traitements standards
+	if (strpos($process, '%s') !== false)
+		$code = str_replace('%s', $code, $process);
+
+	// Appliquer les filtres perso
+	if ($fonctions) {
+		foreach($fonctions as $fonc) {
+			if ($fonc) {
+				$arglist = '';
+				if (ereg('([^\{\}]*)\{(.+)\}$', $fonc, $regs)) {
+					$fonc = $regs[1];
+					$args = $regs[2];
+					while (ereg('([^,]+),?(.*)$', $args, $regs)) {
+						$args = $regs[2];
+						$arg = trim($regs[1]);
+						if ($arg) {
+							if ($arg[0] =='#')
+								$arg = calculer_champ(array(), substr($arg,1),
+									$id_boucle, $boucles, $id_mere);
+							else if ($arg[0] =='$')
+								$arg = '$Pile[0][\'' . substr($arg,1) . "']";
+							$arglist .= ','.$arg;
+						}
+					}
+				}
+				if (function_exists($fonc))
+					$code = "$fonc($code$arglist)";
+				else
+					$code = "'".texte_script(
+						_T('erreur_filtre', array('filtre' => $fonc))
+					)."'";
+			}
+		}
+	}
+
+	// post-traitement securite
+	if ($type == 'html')
+		$code = "interdire_scripts($code)";
+
+	return $code;
+}
+
+
+//
+// Reserve les champs necessaires a la comparaison avec le contexte donne par
+// la boucle parente ; attention en recursif il faut les reserver chez soi-meme
+// ET chez sa maman
+// 
+function calculer_argument_precedent($idb, $nom_champ, &$boucles) {
+
+	// recursif ?
+	if ($boucles[$idb]->externe)
+		index_pile ($idb, $nom_champ, $boucles); // reserver chez soi-meme
+
+	// reserver chez le parent et renvoyer l'habituel $Pile[$SP]['nom_champ']
+	return index_pile ($boucles[$idb]->id_parent, $nom_champ, $boucles);
+}
+
+?>
diff --git a/inc-calcul-squel.php3 b/inc-compilo.php3
similarity index 78%
rename from inc-calcul-squel.php3
rename to inc-compilo.php3
index e00279e6f1f2d51b173f490c70af0b9b6177e4ad..0fe72e3afb3d9577808feebe69a06c950ab8935b 100644
--- a/inc-calcul-squel.php3
+++ b/inc-compilo.php3
@@ -1,27 +1,140 @@
 <?php
 
+//
+// Fichier principal du compilateur de squelettes
+//
+
 // Ce fichier ne sera execute qu'une fois
-if (defined("_INC_CALCUL_SQUEL")) return;
-define("_INC_CALCUL_SQUEL", "1");
+if (defined("_INC_COMPILO")) return;
+define("_INC_COMPILO", "1");
+
+
+// Definition de la structure $p, et fonctions de recherche et de reservation
+// dans l'arborescence des boucles
+include_local("inc-compilo-index.php3");  # index ? structure ? pile ?
+#include_local("inc-bcl-squel.php3");	# (anciens noms des fichiers)
+#include_local("inc-index-squel.php3");
+
+// definition des balises
+include_local("inc-balises.php3");
+#include_local("inc-logo-squel.php3");
+#include_local("inc-vrac-squel.php3");
+#include_local("inc-form-squel.php3");
 
-// Fichier principal du compilateur de squelettes, incluant tous les autres.
+// definition des criteres
+include_local("inc-criteres.php3");
+#include_local("inc-arg-squel.php3");
 
-include_local("inc-bcl-squel.php3");
-include_local("inc-arg-squel.php3");
+
+// gestion des balises de forums
+include_local("inc-forum.php3");
+
+
+// a traiter (essentiellement, ce sont des definitions standard de spip:
+// inc-compilo-standard/spip/redac ?
 include_local("inc-reqsql-squel.php3");
 include_local("inc-champ-squel.php3");
-include_local("inc-logo-squel.php3");
-include_local("inc-form-squel.php3");
-include_local("inc-vrac-squel.php3");
-include_local("inc-index-squel.php3");
-include_local("inc-text-squel.php3");
-include_local("inc-debug.php3");
-include_local("inc-forum.php3");
 
-// Produit le corps PHP d'une boucle Spip,
+
+
+// outils pour debugguer le compilateur
+#include_local("inc-compilo-debug.php3"); # desactive
+
+
+//
+// Calculer un <INCLURE()>
+//
+function calculer_inclure($fichier, $params, $id_boucle, &$boucles) {
+	global $dossier_squelettes;
+
+	$criteres = '';
+	if ($params) {
+		foreach($params as $param) {
+			if (ereg("^([_0-9a-zA-Z]+)[[:space:]]*(=[[:space:]]*([^}]+))?$", $param, $args)) {
+				$var = $args[1];
+				$val = ereg_replace('^["\'](.*)["\']$', "\\1", trim($args[3]));
+				$val = addslashes(addslashes($val));
+
+				// Cas de la langue : passer $spip_lang
+				// et non table.lang (car depend de {lang_select})
+				if ($var =='lang') {
+					if ($val)
+						$l[] = "\'lang\' => \'$val\'";
+					else
+						$l[] = "\'lang\' => \''.\$GLOBALS[spip_lang].'\'";
+				}
+
+				// Cas normal {var=val}
+				else
+				if ($val)
+					$l[] = "\'$var\' => \'$val\'";
+				else
+					$l[] = "\'$var\' => \'' . addslashes(" . index_pile($id_boucle, $var, $boucles) . ") .'\'";
+		    }
+		$criteres = join(", ",$l);
+		}
+	}
+	return "\n'<".
+		"?php\n\t\$contexte_inclus = array($criteres);\n\t".
+		"\$fichier_inclus = \'$fichier\';\n" .
+		(($dossier_squelettes) ?
+		("
+			if (@file_exists(\'$dossier_squelettes/$fichier\')){
+				include(\'$dossier_squelettes/$fichier\');
+			} else {
+				include(\'$fichier\');
+			} " ) :
+		("\tinclude(\'$fichier\');")) .
+		"\n?'." . "'>'";
+}
+
+
+//
+// Traite une partie "texte" d'un squelette (c'est-a-dire tout element
+// qui ne contient ni balise, ni boucle, ni <INCLURE()> ; le transforme
+// en une EXPRESSION php (qui peut etre l'argument d'un Return ou la
+// partie droite d'une affectation). Ici sont analyses les elements
+// multilingues des squelettes : <:xxx:> et <multi>[fr]coucou</multi>
+//
+function calculer_texte($texte, $id_boucle, &$boucles, $id_mere) {
+	$code = "'".ereg_replace("([\\\\'])", "\\\\1", $texte)."'";
+
+	// bloc multi
+	if (eregi('<multi>', $texte)) {
+		$ouvre_multi = 'extraire_multi(';
+		$ferme_multi = ')';
+	} else {
+		$ouvre_multi = $ferme_multi = '';
+	}
+
+	// Reperer les balises de traduction <:toto:>
+	while (eregi("<:(([a-z0-9_]+):)?([a-z0-9_]+)(\|[^>]*)?:>", $code, $match)) {
+		//
+		// Traiter la balise de traduction multilingue
+		//
+		$chaine = strtolower($match[3]);
+		if (!($module = $match[2]))
+			// ordre standard des modules a explorer
+			$module = 'local/public/spip';
+		$c = applique_filtres(explode('|',
+			substr($match[4],1)),
+			"_T('$module:$chaine')",
+			$id_boucle, 
+			$boucles,
+			$id_mere,
+			'php');	// ne pas manger les espaces avec trim()
+		$code = str_replace($match[0], "'$ferme_multi.$c.$ouvre_multi'", $code);
+	}
+
+	return $ouvre_multi . $code . $ferme_multi;
+}
+
+
+//
+// calculer_boucle() produit le corps PHP d'une boucle Spip,
 // essentiellement une boucle while (ou une double en cas de hierarchie)
 // remplissant une variable $t0 retourne'e en valeur
-
+//
 function calculer_boucle($id_boucle, &$boucles) {
 	global $table_primary, $table_des_tables; 
 
@@ -92,13 +205,14 @@ function calculer_boucle($id_boucle, &$boucles) {
 	
 	if ($lang_select AND !$constant)
 		$debut .= '
-			if ($x = $Pile[$SP]["lang"]) $spip_lang = $x; // langue';
+			if ($x = $Pile[$SP]["lang"]) $spip_lang = $x; // lang_select';
 
 	$debut .= $invalide;
 
 	if ($boucle->doublons)
-		$debut .= "\n			\$doublons['$type_boucle'] .= ','. " .
-		index_pile($id_boucle, $primary_key, $boucles) . "; // doublons";
+		$debut .= "\n			\$doublons['".$boucle->doublons."'] .= ','. " .
+		index_pile($id_boucle, $primary_key, $boucles)
+		. "; // doublons";
 
 
 	//
@@ -205,6 +319,10 @@ function calculer_boucle($id_boucle, &$boucles) {
 }
 
 
+//
+// fonction traitant les criteres {1,n} (analyses dans inc-criteres)
+//
+## a deplacer dans inc-criteres ??
 function calculer_parties($partie, $mode_partie, $total_parties, $id_boucle) {
 
 	// Notes :
@@ -442,7 +560,7 @@ function calculer_squelette($squelette, $nom, $gram, $sourcefile) {
 
 	if ($boucles) foreach($boucles as $id => $boucle) { 
 		if ($boucle->type_requete != 'boucle') {
-			$res = calculer_params($id, $boucles);
+			calculer_criteres($id, $boucles);
 			$boucles[$id]->return = calculer_liste($boucle->milieu,
 				$nom,
 				$id,
@@ -505,7 +623,7 @@ $code
 //
 // Fonction principale du squelette $sourcefile
 //
-function $nom (\$Cache, \$Pile, \$doublons, \$Numrows='', \$SP=0) {
+function $nom (\$Cache, \$Pile, \$ignore_les_doublons_inc_calcul_php3, \$Numrows='', \$SP=0) {
 $corps
 \$t0 = $return;
 
diff --git a/inc-arg-squel.php3 b/inc-criteres.php3
similarity index 55%
rename from inc-arg-squel.php3
rename to inc-criteres.php3
index a4fc502e9536398b95a1d8569743d4a4031ee17a..867ddf785fe6c75bfe8f94094753885dd215a53f 100644
--- a/inc-arg-squel.php3
+++ b/inc-criteres.php3
@@ -1,25 +1,268 @@
 <?php
 
-# Traduction des arguments d'une boucle par affectation du tableau $boucles
-# retourne un tableau en cas d'erreur
+//
+// Definition des {criteres} d'une boucle
+//
+
+// Ce fichier ne sera execute qu'une fois
+if (defined("_INC_CRITERES")) return;
+define("_INC_CRITERES", "1");
+
+
+// {racine}
+// http://www.spip.net/@racine
+function critere_racine_dist($param, $not, &$boucle, $infos) {
+	global $table_des_tables;
+
+	if ($param != 'racine' OR $not)
+		return "erreur";
+
+	$boucle->where[] = $infos['id_table'].".id_parent='0'";
+
+}
+
+// {exclus}
+// http://www.spip.net/@exclus
+function critere_exclus_dist($param, $not, &$boucle, $infos) {
+
+	if ($param != 'exclus' OR $not)
+		return "erreur";
+
+	$boucle->where[] = $infos['id_field']."!='\"."
+	. calculer_argument_precedent($infos['idb'],
+		$infos['primary'], $infos['boucles']) . ".\"'";
+
+}
+
+// {doublons} ou {unique}
+// http://www.spip.net/@doublons
+function critere_doublons_dist($param, $not, &$boucle, $infos) {
+
+	if (!preg_match("/(doublons|unique)[[:space:]]*([a-z_0-9]*)/i",
+	$param, $match))
+		return "erreur";
 
-function calculer_params($idb, &$boucles) {
+	$boucle->doublons = $infos['type'].$match[2];
+	$boucle->where[] = '" .' .
+		"calcul_mysql_in('".$infos['id_field']."', "
+		.'"0".$doublons[\''.$boucle->doublons."'], 'NOT') . \"";
+}
+
+// {lang_select}
+// http://www.spip.net/@lang_select
+function critere_lang_select_dist($param, $not, &$boucle, $infos) {
+	if (preg_match('/lang_select(=(oui|non))?$/i', $param, $match)) {
+		if (!$lang_select = $match[3])
+			$lang_select = 'oui';
+		if ($not)
+			$lang_select = ($lang_select=='oui')?'non':'oui';
+		$boucle->lang_select = $lang_select;
+	}
+	else return "erreur";
+}
+
+// {debut_xxx}
+// http://www.spip.net/@debut_
+function critere_debut_dist($param, $not, &$boucle, $infos) {
+	if (ereg('^debut([-_a-zA-Z0-9]+),([0-9]*)$', $param, $match)) {
+		$debut_lim = "debut".$match[1];
+		$boucle->limit =
+			'intval($GLOBALS["'.$debut_lim.'"]).",'.$match[2] .'"' ;
+	}
+	else return "erreur";
+}
+
+// {recherche}
+// http://www.spip.net/@recherche
+function critere_recherche_dist($param, $not, &$boucle, $infos) {
+	$boucle->from[] = "index_".$infos['id_table']." AS rec";
+	$boucle->select[] = 'SUM(rec.points + 100*(" .' . 
+		'calcul_mysql_in("rec.hash",
+		calcul_branche($hash_recherche_strict),"") . "))
+		AS points';
+
+	// horrible hack du aux id_forum = spip_forum et id_article=spip_articleS
+	// en fait il faudrait la fonction inverse de table_objet()
+	$id = 'id_'.preg_replace('/s$/', '', $infos['id_table']);
+
+	// rec.id_article = articles.id_article
+	$boucle->where[] = "rec.".$id
+	. "=" . $infos['id_table'].'.'.$id;
+
+	// group by articles.id_article
+	$boucle->group = $infos['id_field'];
+
+	// et la recherche trouve
+	$boucle->where[] = '" .' . 'calcul_mysql_in("rec.hash",
+		calcul_branche($hash_recherche),"") . "';
+
+	// oui cette boucle est une boucle recherche, le noter dans la pile
+	// (certes, c'est un peu lourd comme ecriture)
+	$infos['boucles'][$infos['idb']]->hash = true;
+}
+
+// {inverse}
+// http://www.spip.net/@inverse
+function critere_inverse_dist($param, $not, &$boucle, $infos) {
+	// Classement par ordre inverse
+	if ($param == 'inverse' AND !$not) {
+		if ($boucle->order)
+			$boucle->order .= ".' DESC'";
+		else 
+			return _L("&nbsp: inversion d'un ordre inexistant");
+	} else
+		return 'erreur';
+}
+
+// {traduction}
+// http://www.spip.net/@traduction
+function critere_traduction_dist($param, $not, &$boucle, $infos) {
+	if ($param == 'traduction') {
+		$boucle->where[] = $infos['id_table'].".id_trad > 0";
+		$boucle->where[] = $infos['id_table'].".id_trad ='\"."
+		. calculer_argument_precedent($infos['idb'], 'id_trad',
+			$infos['boucles'])
+		. ".\"'";
+
+	} else
+		return 'erreur';
+}
+
+// {origine_traduction}
+// http://www.spip.net/@origine_traduction
+function critere_origine_traduction_dist($param, $not, &$boucle, $infos) {
+	if ($param == 'origine_traduction')
+		$boucle->where[] = $infos['id_table'].".id_trad = "
+		. $infos['id_field'];
+	else
+		return "erreur";
+}
+
+
+// {meme_parent}
+// http://www.spip.net/@meme_parent
+function critere_meme_parent_dist($param, $not, &$boucle, $infos) {
+	if ($param != 'meme_parent')
+		return "erreur";
+	else {
+		if ($infos['type'] == 'rubriques') {
+			$boucle->where[] = $infos['id_table'].".id_parent='\"."
+			. calculer_argument_precedent($infos['idb'], 'id_parent',
+			$infos['boucles'])
+			. ".\"'";
+		} else if ($infos['type'] == 'forums') {
+			$boucle->where[] = $infos['id_table'].".id_parent='\"."
+			. calculer_argument_precedent($infos['idb'], 'id_parent',
+			$infos['boucles'])
+			. ".\"'";
+			$boucle->where[] = $infos['id_table'].".id_parent > 0";
+			$boucle->plat = true;
+		} else
+			return _L("erreur {meme_parent} ne s'applique pas &agrave;
+			d'autres boucles que (FORUMS) ou (RUBRIQUES)");
+	}
+}
+
+// {branche ?}
+// http://www.spip.net/@branche
+function critere_branche_dist($param, $not, &$boucle, $infos) {
+	if (preg_match('/branche[[:space:]]*([?])?$/i', $param, $regs)) {
+		$c = "calcul_mysql_in('".$infos['id_table'].".id_rubrique',
+		calcul_branche(" . calculer_argument_precedent($infos['idb'],
+		'id_rubrique', $infos['boucles']) . "), '')";
+		if (!$regs[1])
+			$where = "\". $c .\"" ;
+		else
+			$where = "\".("
+			. calculer_argument_precedent($infos['idb'], 'id_rubrique',
+			$infos['boucles'])."? $c : 1).\"";
+
+		if ($not)
+			$boucle->where[] = "NOT($where)";
+		else
+			$boucle->where[] = "$where";
+	} else
+		return "erreur";
+}
+
+// Tri : {par xxxx}
+// http://www.spip.net/@par
+function critere_par_dist($param, $not, &$boucle, $infos) {
+	if ($not)
+		return "erreur";
+
+	preg_match('/par[[:space:]]*(.*)/ims', $param, $regs);
+	$tri = trim($regs[1]);
+
+	// par hasard
+	if ($tri == 'hasard') {
+		// on pourrait peut-etre passer a "RAND() AS alea" ?
+		$boucle->select[] = "MOD(".$infos['id_field']." * UNIX_TIMESTAMP(),
+		32767) & UNIX_TIMESTAMP() AS alea";
+		$boucle->order = "'alea'";
+	}
+
+	// par titre_mot
+	else if ($tri == 'titre_mot') {
+		$boucle->order= "'mots.titre'";
+	}
+
+	// par type_mot
+	else if ($tri == 'type_mot'){
+		$boucle->order= "'mots.type'";
+	}
+	// par points
+	else if ($tri == 'points'){
+		$boucle->order= "'points'";
+	}
+	// par num champ(, suite)
+	else if (ereg("^num[[:space:]]+([^,]*)(,.*)?",$tri, $match2)) {
+		$boucle->select[] = "0+".$infos['id_table'].".".$match2[1]." AS num";
+		$boucle->order = "'num".texte_script($match2[2])."'";
+	}
+	// par champ
+	else if (ereg("^[a-z0-9]+$", $tri)) {
+		if ($tri == 'date')
+			$tri = $GLOBALS['table_date'][$infos['type']];
+		$boucle->order = "'".$infos['id_table'].".".$tri."'";
+	}
+	// tris par critere bizarre
+	// (formule composee, virgules, etc).
+	else { 
+		$boucle->order = "'".texte_script($tri)."'";
+	}
+}
+
+
+
+function calculer_criteres ($idb, &$boucles) {
 	global $tables_relations, $table_primary, $table_des_tables, $table_date;
-	$boucle = &$boucles[$idb];
-	$type = $boucle->type_requete;
+	$boucle = &$boucles[$idb];				# nom de la boucle
+	$type = $boucle->type_requete;			# articles
 	$params = $boucle->param;
-	$id_table = $table_des_tables[$type];
-	$id_field = $id_table . "." . $table_primary[$type];
+	$id_table = $table_des_tables[$type];	# articles ->   'table'
+	$primary = $table_primary[$type];		# id_article -> 'id'
+	$id_field = $id_table . "." . $primary; # articles.id_article -> 'table_id'
+
+	// les infos complementaires a passer aux fonctions critere_xxx
+	$infos = array(
+		'type' => $type,			# (articles)
+		'id_table' => $id_table,	# 'table'
+		'id_field' => $id_field,	# 'table_id'
+		'primary' => $primary,		# 'id'
+		'boucles' => &$boucles,		# boucles
+		'idb' => $idb				# 'nom_boucle'
+	);
 
-	// Cas de la hierarchie : on cree des params supplementaires
-	// $hierarchie sera calculee par un ajout dans 
+	// Cas specifique pour la hierarchie : on cree des criteres supplementaires
+	// $hierarchie sera calculee par une fonction de inc-calcul-mysql
 	if ($type == 'hierarchie') {
 		$boucle->where[] = 'id_rubrique IN ($hierarchie)';
 		$boucle->select[] = 'FIND_IN_SET(id_rubrique, \'$hierarchie\')-1 AS rang';
-		if (!$boucle->order)
-			$boucle->order = 'rang';
+		$boucle->order = 'rang';
 
 		// Supprimer le parametre id_article/id_rubrique/id_syndic
+		// qui est superfetatoire (mais indique dans la doc)
 		$params2 = array();
 		foreach($params as $param)
 			if (!ereg('^id_(article|syndic|rubrique)$', $param))
@@ -31,26 +274,39 @@ function calculer_params($idb, &$boucles) {
 		.', false);';
 	}
 
-
+	//
+	// Traitement de la liste des criteres
+	//
 	if (is_array($params)) {
 		foreach($params as $param) {
-			if ($param == 'exclus') {
-			$boucle->where[] = "$id_field!='\"." .
-				calculer_argument_precedent($idb, $table_primary[$type], $boucles) .
-				".\"'";
-			}
-			else if ($param == 'unique' OR $param == 'doublons') {
-				$boucle->doublons = true;
-				$boucle->where[] = '" .' .
-				"calcul_mysql_in('$id_field', \$doublons['$type'], 'NOT') . \"";
-			}
-			else if (ereg('^(!)? *lang_select(=(oui|non))?$', $param, $match)) {
-				if (!$lang_select = $match[3])
-					$lang_select = 'oui';
-				if ($match[1])
-					$lang_select = ($lang_select=='oui')?'non':'oui';
-				$boucles[$idb]->lang_select = $lang_select;
+
+			// Analyse du critere
+			preg_match("/^([!]?)[[:space:]]*(debut|([a-z_]+))/ism",
+				$param, $match);
+			$critere = $match[2];
+			$not = ($match[1] == '!');
+
+			// synonymes ?
+			$synonymes = array('unique'=>'doublons');
+			if ($synonymes[$critere]) $critere = $synonymes[$critere];
+
+
+			// critere personnalise ?
+			$f = "critere_".$critere;
+			if (!function_exists($f))
+				$f .= '_dist';
+
+			// fonction critere standard ?
+			if (function_exists($f)) {
+				if ($erreur = $f($param, $not, $boucle, $infos)) {
+					include_local('inc-admin.php3');
+					erreur_squelette(_T('info_erreur_squelette'),
+					_L("&nbsp: erreur dans le critere {$param} : $erreur"),
+					$idb);
+				}
 			}
+			
+			# Criteres a passer en fonction critere_xxx_dist
 			else if (ereg('^([0-9]+)/([0-9]+)$', $param, $match)) {
 				$boucle->partie = $match[1];
 				$boucle->total_parties = $match[2];
@@ -70,71 +326,7 @@ function calculer_params($idb, &$boucles) {
 					(($match[1]=='n')?'-':'+').(($match[5]=='n')?'-':'+');
 				}
 			}
-			else if (ereg('^debut([-_a-zA-Z0-9]+),([0-9]*)$', $param, $match)) {
-				$debut_lim = "debut".$match[1];
-				$boucle->limit =
-					'intval($GLOBALS["'.$debut_lim.'"]).",'.$match[2] .'"' ;
-			}
-			else if ($param == 'recherche') {
-				$boucle->from[] = "index_$id_table AS rec";
-				$boucle->select[] = 'SUM(rec.points + 100*(" .' . 
-					'calcul_mysql_in("rec.hash",
-					calcul_branche($hash_recherche_strict),"") . "))
-					AS points';
-				# a cause des exceptions forum{s}? et syndic
-				# NB: utiliser table_objet() ?
-				if (!($r = $table_primary[$id_table]))
-				  $r = $table_primary[$type];
-				
-				$boucle->where[] = "rec.$r=$id_field";
-				$boucle->group = $id_field;
-				$boucle->where[] = '" .' . 'calcul_mysql_in("rec.hash",
-					calcul_branche($hash_recherche),"") . "';
-				$boucles[$idb]->hash = true;
-			}
-
-			// Classement par ordre inverse
-			else if ($param == 'inverse') {
-				if ($boucle->order) {
-					$boucle->order .= ' DESC';
-				} else {
-					include_local('inc-admin.php3');
-					erreur_squelette(_T('info_erreur_squelette'),
-					_L("&nbsp: inversion d'un ordre inexistant"), $idb);
-				}
-			}
 
-			// Gerer les traductions
-			else if ($param == 'traduction') {
-				$boucle->where[] = "$id_table.id_trad > 0
-				AND $id_table.id_trad ='\"." .
-				calculer_argument_precedent($idb, 'id_trad', $boucles) . ".\"'";
-			}
-			else if ($param == 'origine_traduction') {
-				$boucle->where[] = "$id_table.id_trad = $id_table.id_article";
-			}
-      
-			// Special rubriques
-			else if ($param == 'meme_parent') {
-				$boucle->where[] = "$id_table.id_parent='\"." .
-					calculer_argument_precedent($idb, 'id_parent', $boucles) . ".\"'";
-				if ($type == 'forums') {
-					$boucle->where[] = "$id_table.id_parent > 0";
-					$boucle->plat = true;
-				}
-			}
-			else if ($param == 'racine') {
-				$boucle->where[] = "$id_table.id_parent='0'";
-			}
-			else if (ereg("^branche *(\??)", $param, $regs)) {
-				$c = "calcul_mysql_in('$id_table.id_rubrique',
-				calcul_branche(" . calculer_argument_precedent($idb, 'id_rubrique',
-				$boucles) . "), '')";
-				if (!$regs[1])
-					$boucle->where[] = "\". $c .\"" ;
-				else
-					$boucle->where[] = "\".(".calculer_argument_precedent($idb, 'id_rubrique', $boucles)."? $c : 1).\"";
-			}
 			// Restriction de valeurs (implicite ou explicite)
 			else if (eregi('^([a-z_]+) *(\??)((!?)(<=?|>=?|==?|IN) *"?([^<>=!"]*))?"?$', $param, $match)) {
 				// Variable comparee
@@ -310,27 +502,6 @@ function calculer_params($idb, &$boucles) {
 				if ($col_table)
 					$col = "$col_table.$col";
 
-				/*
-				// Pas bon : les criteres sont des ET logiques
-				$vu = 0;
-				if (($op == '=') && (!$match[4]) && ($boucle->where)) {
-					// reperer un parametre repete - {id_mot=1}{id_mot=2}
-					//  pour cre'er une sous-requete
-					foreach ($boucle->where as $k => $v) {
-						if (ereg("^ *$col *(=|IN) *['\(](.*)['\)]",$v, $m)) {
-							$boucle->where[$k] = "$col IN ($m[2],$val)";
-							// esperons que c'est le meme !
-							$boucle->sous_requete = $col;
-							$boucle->compte_requete++;
-							$vu=1;
-							break;
-							}
-						}
-					}
-
-				if (!$vu) {
-				*/
-
 				if ($op) {
 					if ($match[4] == '!')
 						$where = "NOT ($col $op '$val')";
@@ -348,6 +519,24 @@ function calculer_params($idb, &$boucles) {
 
 			} // fin du if sur les restrictions de valeurs
 
+			// Special rubriques
+			else if ($param == 'meme_parent') {
+				$boucle->where[] = "$id_table.id_parent='\"." .
+					calculer_argument_precedent($idb, 'id_parent', $boucles) . ".\"'";
+				if ($type == 'forums') {
+					$boucle->where[] = "$id_table.id_parent > 0";
+					$boucle->plat = true;
+				}
+			}
+			else if (ereg("^branche *(\??)", $param, $regs)) {
+				$c = "calcul_mysql_in('$id_table.id_rubrique',
+				calcul_branche(" . calculer_argument_precedent($idb, 'id_rubrique',
+				$boucles) . "), '')";
+				if (!$regs[1])
+					$boucle->where[] = "\". $c .\"" ;
+				else
+					$boucle->where[] = "\".(".calculer_argument_precedent($idb, 'id_rubrique', $boucles)."? $c : 1).\"";
+			}
 			// Selection du classement
 			else if (ereg('^par[[:space:]]+([^}]*)$', $param, $match)) {
 				$tri = trim($match[1]);
@@ -431,19 +620,5 @@ function calculer_param_dynamique($val, &$boucles, $idb) {
 	}
 }
 
-//
-// Reserve les champs necessaires a la comparaison avec le contexte donne par
-// la boucle parente ; attention en recursif il faut les reserver chez soi-meme
-// ET chez sa maman
-// 
-function calculer_argument_precedent($idb, $nom_champ, &$boucles) {
-
-	// recursif ?
-	if ($boucles[$idb]->externe)
-		index_pile ($idb, $nom_champ, $boucles); // reserver chez soi-meme
-
-	// reserver chez le parent et renvoyer l'habituel $Pile[$SP]['nom_champ']
-	return index_pile ($boucles[$idb]->id_parent, $nom_champ, $boucles);
-}
 
 ?>
diff --git a/inc-form-squel.php3 b/inc-form-squel.php3
deleted file mode 100644
index 5614ffd5c75bcc4c7f5763ec75cceea70d4537fe..0000000000000000000000000000000000000000
--- a/inc-form-squel.php3
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-
-//
-// Traduction des champs "formulaire" et "parametres"
-//
-
-
-// Formulaire de recherche
-function balise_FORMULAIRE_RECHERCHE_dist($p) {
-	if ($p->fonctions) {
-		list(, $lien) = each($p->fonctions);	// le premier est un url
-		while (list(, $filtre) = each($p->fonctions))
-			$filtres[] = $filtre;	// les suivants sont des filtres
-		$p->fonctions = $filtres;
-	}
-	if (!$lien) $lien = 'recherche.php3';
-
-	$p->code = "((lire_meta('activer_moteur') != 'oui') ? '' : calcul_form_rech('$lien'))";
-
-	$p->type = 'html';
-	return $p;
-}
-
-
-// Formulaire d'inscription comme redacteur (dans inc-formulaires.php3)
-function balise_FORMULAIRE_INSCRIPTION_dist($p) {
-
-	$p->code = '(lire_meta("accepter_inscriptions") != "oui") ? "" :
-		("<"."?php include(\'inc-formulaires.php3\'); lang_select(\"$spip_lang\"); formulaire_inscription(\"redac\"); lang_dselect(); ?".">")';
-
-	$p->type = 'php';
-	return $p;
-}
-
-// Formulaire ecrire auteur
-function balise_FORMULAIRE_ECRIRE_AUTEUR_dist($p) {
-	$_id_auteur = champ_sql('id_auteur', $p);
-	$_mail_auteur = champ_sql('email', $p);
-
-	$p->code = '!email_valide('.$_mail_auteur.') ? "" :
-		("<'.'?php include(\'inc-formulaires.php3\');
-		lang_select(\'$spip_lang\');
-		formulaire_ecrire_auteur(".'.$_id_auteur.'.", \'".texte_script('.$_mail_auteur.')."\');
-		lang_dselect(); ?'.'>")';
-
-	$p->type = 'php';
-	return $p;
-}
-
-// Formulaire signature de petition
-function balise_FORMULAIRE_SIGNATURE_dist($p) {
-	$_id_article = champ_sql('id_article', $p);
-
-	$p->code = '!($petition = sql_petitions('.$_id_article.')) ? "" :
-		("<"."?php include(\'inc-formulaires.php3\');
-		lang_select(\'$spip_lang\');
-		echo formulaire_signature(".'.$_id_article.'.",
-			\'".texte_script(serialize($petition))."\');
-		lang_dselect(); ?".">")';
-
-	$p->type = 'php';
-	return $p;
-}
-
-// Formulaire d'inscription de site dans l'annuaire
-function balise_FORMULAIRE_SITE_dist($p) {
-	$_id_rubrique = champ_sql('id_rubrique', $p);
-
-	$p->code = '(lire_meta("proposer_sites") != 2) ? "":
-		"<"."?php include(\'inc-formulaires.php3\');
-		lang_select(\'".$GLOBALS[\'spip_lang\']."\');
-		formulaire_site(".'.$_id_rubrique.'.");
-		lang_dselect(); ?".">"';
-
-	$p->type = 'php';
-	return $p;
-}
-
-//
-// Formulaires de gestion de forums : les balises sont definies
-// dans le fichier inc-forum.php3 qui centralise toute la gestion des forums
-//
-// Formulaire de reponse a un forum
-#	function balise_FORMULAIRE_FORUM_dist($p) {}
-// Parametres de reponse a un forum
-#	function balise_PARAMETRES_FORUM_dist($p) {}
-include_local('inc-forum.php3');
-
-
-//
-// Boutons d'administration: 
-//
-function balise_FORMULAIRE_ADMIN_dist($p) {
-	$p->code = "'<!-- @@formulaire_admin@@45609871@@ -->'";
-	$p->type = "php";
-	return $p;
-}
-
-?>
diff --git a/inc-reqsql-squel.php3 b/inc-reqsql-squel.php3
index 8f33122d17171f7dff6687252e02c0097f7ca2b1..d013ad8f639dc2f437e371ed87a86b2cdcb7133c 100644
--- a/inc-reqsql-squel.php3
+++ b/inc-reqsql-squel.php3
@@ -134,7 +134,7 @@ function calculer_requete(&$boucle) {
 		"', $boucle->where) . '"')) .
 		"), # WHERE
 		'".addslashes($boucle->group)."', # GROUP
-		'".addslashes($boucle->order)."', # ORDER
+		" . ($boucle->order ? $boucle->order : "''") .", # ORDER
 		" . (strpos($boucle->limit, 'intval') === false ?
 			"'$boucle->limit'" :
 			$boucle->limit). ", # LIMIT