diff --git a/ecrire/action/editer_article.php b/ecrire/action/editer_article.php
index d2b8e086fd01f9e755514869f05e39149c91ecb0..510a82eabb08d33a91d0c56b796027477617193f 100644
--- a/ecrire/action/editer_article.php
+++ b/ecrire/action/editer_article.php
@@ -36,7 +36,7 @@ if (!defined('_ECRIRE_INC_VERSION')) {
  *     Identifiant de l'article. En absence utilise l'argument
  *     de l'action sécurisée.
  * @return array
- *     Liste (identifiant de l'article, Texte d'erreur éventuel)
+ *     Liste (identifiant de l'article, texte d'erreur éventuel)
  */
 function action_editer_article_dist($arg = null) {
 	include_spip('inc/autoriser');
diff --git a/ecrire/action/editer_auteur.php b/ecrire/action/editer_auteur.php
index 8b6983eaad510f92baa79aacf17f8c31b59d2c56..712ff15b17a5fb282a5017b0c4eb585278b402f6 100644
--- a/ecrire/action/editer_auteur.php
+++ b/ecrire/action/editer_auteur.php
@@ -33,7 +33,7 @@ if (!defined('_ECRIRE_INC_VERSION')) {
  *     Identifiant de l'auteur. En absence utilise l'argument
  *     de l'action sécurisée.
  * @return array
- *     Liste (identifiant de l'auteur, Texte d'erreur éventuel)
+ *     Liste (identifiant de l'auteur, texte d'erreur éventuel)
  */
 function action_editer_auteur_dist($arg = null) {
 
diff --git a/ecrire/action/editer_rubrique.php b/ecrire/action/editer_rubrique.php
index 3d08f717bbd4ca926459562f9d1959bc8e80150c..2718f33d5bec4fca1b8ce3690f00aa6bd4c81125 100644
--- a/ecrire/action/editer_rubrique.php
+++ b/ecrire/action/editer_rubrique.php
@@ -128,7 +128,7 @@ function rubrique_inserer($id_parent, $set = null) {
  * @return bool|string
  *     - false  : Aucune modification, aucun champ n'est à modifier
  *     - chaîne vide : Vide si tout s'est bien passé
- *     - chaîne : Texte d'un message d'erreur
+ *     - chaîne : texte d'un message d'erreur
  */
 function rubrique_modifier($id_rubrique, $set = null) {
 	include_spip('inc/autoriser');
@@ -230,7 +230,7 @@ function editer_rubrique_breves($id_rubrique, $id_parent, $c = []) {
  * @global array $GLOBALS ['visiteur_session']
  * @return string
  *     Chaîne vide : aucune erreur
- *     Chaîne : Texte du message d'erreur
+ *     Chaîne : texte du message d'erreur
  */
 function rubrique_instituer($id_rubrique, $c) {
 	// traitement de la rubrique parente
diff --git a/ecrire/balise/formulaire_.php b/ecrire/balise/formulaire_.php
index e98025a28494d412cf1fd8ae69c4c861d95f4922..8d06980b24016870c79f92a069e2f28e01ecfcd9 100644
--- a/ecrire/balise/formulaire_.php
+++ b/ecrire/balise/formulaire_.php
@@ -175,7 +175,7 @@ function balise_FORMULAIRE__dyn($form, ...$args) {
  * @param array $args
  *     Arguments envoyés à l'appel du formulaire
  * @return array|string
- *     array: Contexte d'environnement à envoyer au squelette
+ *     array: contexte d'environnement à envoyer au squelette
  *     string: Formulaire non applicable (message d’explication)
  **/
 function balise_FORMULAIRE__contexte($form, $args) {
diff --git a/ecrire/base/abstract_sql.php b/ecrire/base/abstract_sql.php
index 62a25d1d4fbc41f5c22f8895183357c7097bb6e4..c9af3639e1c8982fdc194a7ab3a45387cbe78bdd 100644
--- a/ecrire/base/abstract_sql.php
+++ b/ecrire/base/abstract_sql.php
@@ -199,11 +199,11 @@ function sql_set_charset($charset, $serveur = '', $option = true) {
  * @param array|string $where
  *     Conditions a remplir (Where)
  * @param array|string $groupby
- *     Critere de regroupement (Group by)
+ *     critere de regroupement (Group by)
  * @param array|string $orderby
  *     Tableau de classement (Order By)
  * @param string $limit
- *     Critere de limite (Limit)
+ *     critere de limite (Limit)
  * @param string|array $having
  *     Tableau ou chaine des des post-conditions à remplir (Having)
  * @param string $serveur
@@ -304,11 +304,11 @@ function sql_select(
  * @param array|string $where
  *    Conditions a remplir (Where)
  * @param array|string $groupby
- *    Critere de regroupement (Group by)
+ *    critere de regroupement (Group by)
  * @param array|string $orderby
  *    Tableau de classement (Order By)
  * @param string $limit
- *    Critere de limite (Limit)
+ *    critere de limite (Limit)
  * @param string|array $having
  *     Tableau ou chaine des des post-conditions à remplir (Having)
  * @param string $serveur
@@ -355,7 +355,7 @@ function sql_get_select(
  * @param array|string $where
  *    Conditions a remplir (Where)
  * @param array|string $groupby
- *    Critere de regroupement (Group by)
+ *    critere de regroupement (Group by)
  * @param string|array $having
  *     Tableau ou chaine des des post-conditions à remplir (Having)
  * @param string $serveur
@@ -696,7 +696,7 @@ function sql_free($res, $serveur = '', $option = true) {
  *
  * @return bool|string
  *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
- *     - Texte de la requête si demandé,
+ *     - texte de la requête si demandé,
  *     - False en cas d'erreur.
  **/
 function sql_insert($table, $noms, $valeurs, $desc = [], $serveur = '', $option = true) {
@@ -747,7 +747,7 @@ function sql_insert($table, $noms, $valeurs, $desc = [], $serveur = '', $option
  *
  * @return int|bool|string
  *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
- *     - Texte de la requête si demandé,
+ *     - texte de la requête si demandé,
  *     - False en cas d'erreur.
  **/
 function sql_insertq($table, $couples = [], $desc = [], $serveur = '', $option = true) {
@@ -791,9 +791,9 @@ function sql_insertq($table, $couples = [], $desc = [], $serveur = '', $option =
  *     - 'continue' : ne pas échouer en cas de serveur sql indisponible
  *
  * @return bool|string
- *     - True en cas de succès,
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - true en cas de succès,
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function sql_insertq_multi($table, $couples = [], $desc = [], $serveur = '', $option = true) {
 	$f = sql_serveur('insertq_multi', $serveur, $option === 'continue' or $option === false);
@@ -897,8 +897,8 @@ function sql_update($table, $exp, $where = '', $desc = [], $serveur = '', $optio
  *     - 'continue' : ne pas échouer en cas de serveur sql indisponible
  * @return bool|string
  *     - true si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function sql_updateq($table, $exp, $where = '', $desc = [], $serveur = '', $option = true) {
 	$f = sql_serveur('updateq', $serveur, $option === 'continue' or $option === false);
@@ -937,8 +937,8 @@ function sql_updateq($table, $exp, $where = '', $desc = [], $serveur = '', $opti
  *
  * @return bool|string
  *     - int : nombre de suppressions réalisées,
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function sql_delete($table, $where = '', $serveur = '', $option = true) {
 	$f = sql_serveur('delete', $serveur, $option === 'continue' or $option === false);
@@ -982,8 +982,8 @@ function sql_delete($table, $where = '', $serveur = '', $option = true) {
  *
  * @return bool|string
  *     - true si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function sql_replace($table, $couples, $desc = [], $serveur = '', $option = true) {
 	$f = sql_serveur('replace', $serveur, $option === 'continue' or $option === false);
@@ -1029,8 +1029,8 @@ function sql_replace($table, $couples, $desc = [], $serveur = '', $option = true
  *
  * @return bool|string
  *     - true si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function sql_replace_multi($table, $tab_couples, $desc = [], $serveur = '', $option = true) {
 	$f = sql_serveur('replace_multi', $serveur, $option === 'continue' or $option === false);
@@ -1065,9 +1065,9 @@ function sql_replace_multi($table, $tab_couples, $desc = [], $serveur = '', $opt
  *     - true : exécuter la requête
  *     - 'continue' : ne pas échouer en cas de serveur sql indisponible
  * @return bool|string
- *     - True en cas de succès,
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - true en cas de succès,
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function sql_drop_table($table, $exist = '', $serveur = '', $option = true) {
 	$f = sql_serveur('drop_table', $serveur, $option === 'continue' or $option === false);
@@ -1099,7 +1099,7 @@ function sql_drop_table($table, $exist = '', $serveur = '', $option = true) {
  *     - true : exécuter la requête
  *     - 'continue' : ne pas échouer en cas de serveur sql indisponible
  * @return bool|string
- *     - string Texte de la requête si demandé
+ *     - string texte de la requête si demandé
  *     - true si la requête a réussie, false sinon
  */
 function sql_drop_view($table, $exist = '', $serveur = '', $option = true) {
@@ -1257,10 +1257,9 @@ function sql_showtable($table, $table_spip = false, $serveur = '', $option = tru
  *     - true : exécuter la requête
  *     - 'continue' : ne pas échouer en cas de serveur sql indisponible
  * @return bool|string
- *     - True si la table existe,
- *     - False sinon,
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - true si la table existe,
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function sql_table_exists(string $table, bool $table_spip = true, $serveur = '', $option = true) {
 	$f = sql_serveur('table_exists', $serveur, $option === 'continue' or $option === false);
@@ -1432,7 +1431,7 @@ function sql_create_view($nom, $select_query, $serveur = '', $option = true) {
  *     - true : exécuter la requête
  *     - 'continue' : ne pas échouer en cas de serveur sql indisponible
  * @return string
- *     Texte de sélection pour la requête
+ *     texte de sélection pour la requête
  */
 function sql_multi($sel, $lang, $serveur = '', $option = true) {
 	$f = sql_serveur('multi', $serveur, $option === 'continue' or $option === false);
@@ -1486,7 +1485,7 @@ function sql_errno($serveur = '') {
  * Retourne une explication de requête (Explain) SQL
  *
  * @api
- * @param string $q Texte de la requête
+ * @param string $q texte de la requête
  * @param string $serveur Nom de la connexion
  * @param bool|string $option
  *     Peut avoir 3 valeurs :
@@ -1549,7 +1548,7 @@ function sql_optimize($table, $serveur = '', $option = true) {
  *     - true : exécuter la requête
  *     - 'continue' : ne pas échouer en cas de serveur sql indisponible
  * @return bool|string
- *     - string Texte de la requête si demandée,
+ *     - string texte de la requête si demandée,
  *     - true si la requête a réussie, false sinon
  */
 function sql_repair($table, $serveur = '', $option = true) {
@@ -1582,7 +1581,7 @@ function sql_repair($table, $serveur = '', $option = true) {
  *     - true : exécuter la requête
  *     - 'continue' : ne pas échouer en cas de serveur sql indisponible
  * @return array|resource|string|bool
- *     - string : Texte de la requête si on ne l'exécute pas
+ *     - string : texte de la requête si on ne l'exécute pas
  *     - ressource|bool : Si requête exécutée
  *     - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
  */
@@ -1623,11 +1622,11 @@ function sql_query($ins, $serveur = '', $option = true) {
  * @param array|string $where
  *    Conditions a remplir (Where)
  * @param array|string $groupby
- *    Critere de regroupement (Group by)
+ *    critere de regroupement (Group by)
  * @param array|string $orderby
  *    Tableau de classement (Order By)
  * @param string $limit
- *    Critere de limite (Limit)
+ *    critere de limite (Limit)
  * @param string|array $having
  *     Tableau ou chaine des des post-conditions à remplir (Having)
  * @param string $serveur
@@ -1691,11 +1690,11 @@ function sql_fetsel(
  * @param array|string $where
  *    Conditions a remplir (Where)
  * @param array|string $groupby
- *    Critere de regroupement (Group by)
+ *    critere de regroupement (Group by)
  * @param array|string $orderby
  *    Tableau de classement (Order By)
  * @param string $limit
- *    Critere de limite (Limit)
+ *    critere de limite (Limit)
  * @param string|array $having
  *     Tableau ou chaine des des post-conditions à remplir (Having)
  * @param string $serveur
@@ -2147,11 +2146,11 @@ function sql_in($champ, $valeurs, $not = '', $serveur = '', $option = true) {
  * @param array|string $where
  *     Conditions a remplir (Where)
  * @param array|string $groupby
- *     Critere de regroupement (Group by)
+ *     critere de regroupement (Group by)
  * @param array|string $orderby
  *     Tableau de classement (Order By)
  * @param string $limit
- *     Critere de limite (Limit)
+ *     critere de limite (Limit)
  * @param string|array $having
  *     Tableau ou chaine des des post-conditions à remplir (Having)
  * @param string $serveur
diff --git a/ecrire/base/connect_sql.php b/ecrire/base/connect_sql.php
index bb1cec3c82073c3f56375675983d85609e521440..e57e97457d9bb24536f10cb327136179c9d5ffd1 100644
--- a/ecrire/base/connect_sql.php
+++ b/ecrire/base/connect_sql.php
@@ -511,7 +511,7 @@ function query_reinjecte_textes($query, $textes) {
  * @deprecated 3.1 Pour compatibilité.
  * @see sql_query() ou l'API `sql_*`.
  *
- * @param string $query Texte de la requête
+ * @param string $query texte de la requête
  * @param string $serveur Nom du connecteur pour la base de données
  * @return bool|mixed
  *     - false si on ne peut pas exécuter la requête
diff --git a/ecrire/base/objets.php b/ecrire/base/objets.php
index 3be6dfd8bbe6656b675b49871b48c8f93afe153a..c5bdffcd706bb211ed142329949b9b38a28c0ee6 100644
--- a/ecrire/base/objets.php
+++ b/ecrire/base/objets.php
@@ -10,6 +10,8 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+
 /**
  * Fonctions relatives aux objets éditoriaux et SQL
  *
diff --git a/ecrire/inc/acces.php b/ecrire/inc/acces.php
index 54166c94935657be17d47bd6c9ccee3b924010db..767c67d1c867dbe0e5be4d0a27beeed1b5505d41 100644
--- a/ecrire/inc/acces.php
+++ b/ecrire/inc/acces.php
@@ -236,7 +236,7 @@ function generer_url_api_low_sec(string $script, string $format, string $fond, s
 
 
 /**
- * Inclure les arguments significatifs pour le hachage
+ * inclure les arguments significatifs pour le hachage
  *
  * Cas particulier du statut pour compatibilité ancien rss/suivi_revisions
  *
diff --git a/ecrire/inc/admin.php b/ecrire/inc/admin.php
index 53b70b8e0e2499a0be49e489bd01f3c8577d2576..63d4aeed44582caa4a873ee2b49cff2e3e1568ba 100644
--- a/ecrire/inc/admin.php
+++ b/ecrire/inc/admin.php
@@ -284,7 +284,7 @@ function fin_admin($action) {
  * @param string $suite
  *     Corps du formulaire
  * @param string $submit
- *     Texte du bouton de validation
+ *     texte du bouton de validation
  * @return string
  *     Code HTML du formulaire
  **/
diff --git a/ecrire/inc/bandeau.php b/ecrire/inc/bandeau.php
index b401b240c5ec68f0c0ff2194327c41caf776d77d..d20f727d1ea6962d3ccff71f26b571447b4f4fc5 100644
--- a/ecrire/inc/bandeau.php
+++ b/ecrire/inc/bandeau.php
@@ -31,10 +31,10 @@ include_spip('inc/boutons');
  * l'ajoute au contexte.
  *
  * @param null|array $contexte
- *     Contexte connu.
+ *     contexte connu.
  *     S'il n'est pas transmis, on prend `$_GET`
  * @return array
- *     Contexte
+ *     contexte
  **/
 function definir_barre_contexte($contexte = null) {
 	if (is_null($contexte)) {
diff --git a/ecrire/inc/charsets.php b/ecrire/inc/charsets.php
index 5ceb38298fc08fb090b7a81e4ae7965026d29bc1..876e034057f3e535c69ffc81fd6a4553fd0aea5f 100644
--- a/ecrire/inc/charsets.php
+++ b/ecrire/inc/charsets.php
@@ -168,7 +168,7 @@ function test_iconv(): bool {
  * @param string $charset_cible
  *     Charset de destination (unicode par défaut)
  * @return string|array
- *     Texte corrigé
+ *     texte corrigé
  **/
 function corriger_caracteres_windows($texte, $charset = 'AUTO', $charset_cible = 'unicode') {
 	static $trans;
@@ -249,11 +249,11 @@ function corriger_caracteres_windows($texte, $charset = 'AUTO', $charset_cible =
  * Transforme les é en {
  *
  * @param string $texte
- *     Texte à convertir
+ *     texte à convertir
  * @param bool $secure
  *     true pour *ne pas convertir* les caracteres malins < & etc.
  * @return string
- *     Texte converti
+ *     texte converti
  **/
 function html2unicode($texte, $secure = false) {
 	if (strpos($texte, '&') === false) {
@@ -285,9 +285,9 @@ function html2unicode($texte, $secure = false) {
  * Transforme ∠ en ∠ ainsi que toutes autres entités mathématiques
  *
  * @param string $texte
- *     Texte à convertir
+ *     texte à convertir
  * @return string
- *     Texte converti
+ *     texte converti
  **/
 function mathml2unicode($texte) {
 	static $trans;
@@ -313,12 +313,12 @@ function mathml2unicode($texte) {
  *     convertir les accents iso-8859-1
  *
  * @param string $texte
- *     Texte à convertir
+ *     texte à convertir
  * @param string $charset
  *     Charset actuel du texte
  *     Par défaut (AUTO), le charset est celui du site.
  * @return string
- *     Texte converti en unicode
+ *     texte converti en unicode
  **/
 function charset2unicode($texte, $charset = 'AUTO' /* $forcer: obsolete*/) {
 	static $trans;
@@ -400,12 +400,12 @@ function charset2unicode($texte, $charset = 'AUTO' /* $forcer: obsolete*/) {
  * ont ete encodees ainsi c'est a dessein
  *
  * @param string $texte
- *     Texte unicode à transformer
+ *     texte unicode à transformer
  * @param string $charset
  *     Charset à appliquer au texte
  *     Par défaut (AUTO), le charset sera celui du site.
  * @return string
- *     Texte transformé dans le charset souhaité
+ *     texte transformé dans le charset souhaité
  **/
 function unicode2charset($texte, $charset = 'AUTO') {
 	static $CHARSET_REVERSE = [];
@@ -454,12 +454,12 @@ function unicode2charset($texte, $charset = 'AUTO') {
  * Les caractères non resolus sont transformés en `&#123`;
  *
  * @param string $texte
- *     Texte unicode à importer
+ *     texte unicode à importer
  * @param string $charset
  *     Charset d'origine du texte
  *     Par défaut (AUTO), le charset d'origine est celui du site.
  * @return string
- *     Texte transformé dans le charset site
+ *     texte transformé dans le charset site
  **/
 function importer_charset($texte, $charset = 'AUTO') {
 	$s = null;
@@ -505,9 +505,9 @@ function importer_charset($texte, $charset = 'AUTO') {
  * Utilise la librairie mb si présente
  *
  * @param string $source
- *    Texte UTF-8 à transformer
+ *    texte UTF-8 à transformer
  * @return string
- *    Texte transformé en unicode
+ *    texte transformé en unicode
  **/
 function utf_8_to_unicode($source) {
 
@@ -609,9 +609,9 @@ function utf_8_to_unicode($source) {
  * => tout ca sera osolete quand on sera surs d'avoir mb_string
  *
  * @param string $source
- *    Texte UTF-8 à transformer
+ *    texte UTF-8 à transformer
  * @return string
- *    Texte transformé en unicode
+ *    texte transformé en unicode
  **/
 function utf_32_to_unicode($source) {
 
@@ -678,9 +678,9 @@ function caractere_utf_8($num) {
  * Convertit un texte unicode en utf-8
  *
  * @param string $texte
- *     Texte à convertir
+ *     texte à convertir
  * @return string
- *     Texte converti
+ *     texte converti
  **/
 function unicode_to_utf_8($texte) {
 
@@ -726,9 +726,9 @@ function unicode_to_utf_8($texte) {
  * Convertit les unicode Ĉ en javascript \u0108
  *
  * @param string $texte
- *     Texte à convertir
+ *     texte à convertir
  * @return string
- *     Texte converti
+ *     texte converti
  **/
 function unicode_to_javascript($texte) {
 	$vu = [];
@@ -746,9 +746,9 @@ function unicode_to_javascript($texte) {
  * Convertit les %uxxxx (envoyés par javascript) en &#yyy unicode
  *
  * @param string $texte
- *     Texte à convertir
+ *     texte à convertir
  * @return string
- *     Texte converti
+ *     texte converti
  **/
 function javascript_to_unicode($texte) {
 	while (preg_match(',%u([0-9A-F][0-9A-F][0-9A-F][0-9A-F]),', $texte, $regs)) {
@@ -762,9 +762,9 @@ function javascript_to_unicode($texte) {
  * Convertit les %E9 (envoyés par le browser) en chaîne du charset du site (binaire)
  *
  * @param string $texte
- *     Texte à convertir
+ *     texte à convertir
  * @return string
- *     Texte converti
+ *     texte converti
  **/
 function javascript_to_binary($texte) {
 	while (preg_match(',%([0-9A-F][0-9A-F]),', $texte, $regs)) {
@@ -883,7 +883,7 @@ function translitteration_chiffree($car): string {
  * Reconnaitre le BOM utf-8 (0xEFBBBF)
  *
  * @param string $texte
- *    Texte dont on vérifie la présence du BOM
+ *    texte dont on vérifie la présence du BOM
  * @return bool
  *    true s'il a un BOM
  **/
@@ -900,7 +900,7 @@ function bom_utf8($texte): bool {
  * @link http://w3.org/International/questions/qa-forms-utf-8.html
  *
  * @param string $string
- *     Texte dont on vérifie qu'il est de l'utf-8
+ *     texte dont on vérifie qu'il est de l'utf-8
  * @return bool
  *     true si c'est le cas
  **/
@@ -926,7 +926,7 @@ function is_utf8($string): bool {
  * Vérifie qu'une chaîne est en ascii valide
  *
  * @param string $string
- *     Texte dont on vérifie qu'il est de l'ascii
+ *     texte dont on vérifie qu'il est de l'ascii
  * @return bool
  *     true si c'est le cas
  **/
@@ -952,7 +952,7 @@ function is_ascii($string): bool {
  * @param string $headers
  *     Éventuels headers HTTP liés à cette page
  * @return string
- *     Texte transcodé dans le charset du site
+ *     texte transcodé dans le charset du site
  **/
 function transcoder_page($texte, $headers = ''): string {
 
diff --git a/ecrire/inc/filtres.php b/ecrire/inc/filtres.php
index 197adbd93ffd60ba09e662b424e7df7dc964f6b5..422f73fffb01c561e0c357cdcb45af9fb38cb6af 100644
--- a/ecrire/inc/filtres.php
+++ b/ecrire/inc/filtres.php
@@ -49,8 +49,8 @@ function charger_filtre($fonc, $default = 'filtre_identite_dist') {
 /**
  * Retourne le texte tel quel
  *
- * @param string $texte Texte
- * @return string Texte
+ * @param string $texte texte
+ * @return string texte
  **/
 function filtre_identite_dist($texte) {
  return $texte;
@@ -139,11 +139,11 @@ function chercher_filtre($fonc, $default = null) {
  * @uses appliquer_filtre_sinon()
  *
  * @param mixed $arg
- *     Texte (le plus souvent) sur lequel appliquer le filtre
+ *     texte (le plus souvent) sur lequel appliquer le filtre
  * @param string $filtre
  *     Nom du filtre à appliquer
  * @return string
- *     Texte traité par le filtre si le filtre existe,
+ *     texte traité par le filtre si le filtre existe,
  *     Chaîne vide sinon.
  **/
 function appliquer_filtre($arg, $filtre) {
@@ -165,12 +165,12 @@ function appliquer_filtre($arg, $filtre) {
  * @uses appliquer_filtre_sinon()
  *
  * @param mixed $arg
- *     Texte (le plus souvent) sur lequel appliquer le filtre
+ *     texte (le plus souvent) sur lequel appliquer le filtre
  * @param string $filtre
  *     Nom du filtre à appliquer
  * @return string
- *     Texte traité par le filtre si le filtre existe,
- *     Texte d'origine sinon
+ *     texte traité par le filtre si le filtre existe,
+ *     texte d'origine sinon
  **/
 function appliquer_si_filtre($arg, $filtre) {
 	$args = func_get_args();
@@ -502,7 +502,7 @@ function filtre_debug($val, $key = null) {
  *     - le second est le texte sur lequel on applique le filtre
  *     - les suivants sont les arguments du filtre image souhaité.
  * @return string
- *     Texte qui a reçu les filtres
+ *     texte qui a reçu les filtres
  **/
 function image_filtrer($args) {
 	$filtre = array_shift($args); # enlever $filtre
@@ -861,9 +861,9 @@ function entites_html($texte, $tout = false, $quote = true) {
  * @link https://www.spip.net/5513
  *
  * @param string $texte
- *     Texte à convertir
+ *     texte à convertir
  * @return string
- *     Texte converti
+ *     texte converti
  **/
 function filtrer_entites(?string $texte): string {
 	$texte ??= '';
@@ -950,9 +950,9 @@ function corriger_caracteres($texte) {
  * @link https://www.spip.net/4287
  *
  * @param string $texte
- *     Texte à transformer
+ *     texte à transformer
  * @return string
- *     Texte encodé pour XML
+ *     texte encodé pour XML
  */
 function texte_backend(string $texte): string {
 	if ($texte === '') {
@@ -1004,9 +1004,9 @@ function texte_backend(string $texte): string {
  * @filtre
  *
  * @param string $texte
- *     Texte à transformer
+ *     texte à transformer
  * @return string
- *     Texte encodé et quote pour XML
+ *     texte encodé et quote pour XML
  */
 function texte_backendq(string $texte): string {
 	return addslashes(texte_backend($texte));
@@ -1092,11 +1092,11 @@ function recuperer_numero(?string $texte): string {
  *     Ce filtre supprime aussi les signes inférieurs `<` rencontrés.
  *
  * @param string|array|null $texte
- *     Texte ou tableau de textes à échapper
+ *     texte ou tableau de textes à échapper
  * @param string $rempl
  *     Inutilisé.
  * @return string|array
- *     Texte ou tableau de textes converti
+ *     texte ou tableau de textes converti
  **/
 function supprimer_tags($texte, $rempl = '') {
 	if ($texte === null) {
@@ -1124,11 +1124,11 @@ function supprimer_tags($texte, $rempl = '') {
  *     ```
  *
  * @param string $texte
- *     Texte à échapper
+ *     texte à échapper
  * @param string $rempl
  *     Inutilisé.
  * @return string
- *     Texte converti
+ *     texte converti
  **/
 function echapper_tags($texte, $rempl = '') {
 	$texte = preg_replace('/<([^>]*)>/', "&lt;\\1&gt;", $texte);
@@ -1149,9 +1149,9 @@ function echapper_tags($texte, $rempl = '') {
  *     ```
  *
  * @param string $texte
- *     Texte à convertir
+ *     texte à convertir
  * @return string
- *     Texte converti
+ *     texte converti
  **/
 function textebrut($texte) {
 	$u = $GLOBALS['meta']['pcre_u'];
@@ -1176,9 +1176,9 @@ function textebrut($texte) {
  * @link https://www.spip.net/4297
  *
  * @param string $texte
- *     Texte avec des liens
+ *     texte avec des liens
  * @return string
- *     Texte avec liens ouvrants
+ *     texte avec liens ouvrants
  **/
 function liens_ouvrants($texte) {
 	if (
@@ -1236,9 +1236,9 @@ function liens_nofollow($texte) {
  *     ```
  *
  * @param string $texte
- *     Texte à transformer
+ *     texte à transformer
  * @return string
- *     Texte sans paraghaphes
+ *     texte sans paraghaphes
  **/
 function PtoBR($texte) {
 	$u = $GLOBALS['meta']['pcre_u'];
@@ -1267,8 +1267,8 @@ function PtoBR($texte) {
  * @deprecated 3.1
  * @see Utiliser le style CSS `word-wrap:break-word;`
  *
- * @param string $texte Texte
- * @return string Texte encadré du style CSS
+ * @param string $texte texte
+ * @return string texte encadré du style CSS
  */
 function lignes_longues($texte) {
 	if (!strlen(trim($texte))) {
@@ -1293,8 +1293,8 @@ function lignes_longues($texte) {
  *     [(#EXTENSION|majuscules)]
  *     ```
  *
- * @param string $texte Texte
- * @return string Texte en majuscule
+ * @param string $texte texte
+ * @return string texte en majuscule
  */
 function majuscules($texte) {
 	if (!strlen($texte)) {
@@ -1375,11 +1375,11 @@ function taille_en_octets($taille) {
  * @uses texte_backend()
  *
  * @param ?string $texte
- *     Texte à mettre en attribut
+ *     texte à mettre en attribut
  * @param bool $textebrut
  *     Passe le texte en texte brut (enlève les balises html) ?
  * @return string
- *     Texte prêt pour être utilisé en attribut HTML
+ *     texte prêt pour être utilisé en attribut HTML
  **/
 function attribut_html(?string $texte, $textebrut = true): string {
 	if ($texte === null) {
@@ -1914,15 +1914,15 @@ function alterner($i, ...$args) {
  * (dans ce cas l'option `$complet` n'est pas disponible)
  *
  * @param string|array $balise
- *     Texte ou liste de textes dont on veut extraire des balises
+ *     texte ou liste de textes dont on veut extraire des balises
  * @param string $attribut
  *     Nom de l'attribut désiré
  * @param bool $complet
- *     True pour retourner un tableau avec
+ *     true pour retourner un tableau avec
  *     - le texte de la balise
  *     - l'ensemble des résultats de la regexp ($r)
  * @return string|array|null
- *     - Texte de l'attribut retourné, ou tableau des textes d'attributs
+ *     - texte de l'attribut retourné, ou tableau des textes d'attributs
  *       (si 1er argument tableau)
  *     - Tableau complet (si 2e argument)
  *     - null lorsque l’attribut n’existe pas.
@@ -2347,8 +2347,8 @@ function email_valide($adresses) {
  * @filtre
  * @link https://www.spip.net/4134
  *
- * @param string $tags Texte
- * @return string Texte
+ * @param string $tags texte
+ * @return string texte
  **/
 function afficher_enclosures($tags) {
 	$s = [];
@@ -2383,7 +2383,7 @@ function afficher_enclosures($tags) {
  * @filtre
  * @link https://www.spip.net/4187
  *
- * @param string $tags Texte
+ * @param string $tags texte
  * @param string $rels Attribut `rel` à capturer (ou plusieurs séparés par des virgules)
  * @return string Liens trouvés
  **/
@@ -2446,7 +2446,7 @@ function enclosure2microformat($e) {
  * @filtre
  * @see enclosure2microformat() Pour l'inverse
  *
- * @param string $tags Texte HTML ayant des tag `<a>` avec microformat
+ * @param string $tags texte HTML ayant des tag `<a>` avec microformat
  * @return string Tags RSS `<enclosure>`.
  **/
 function microformat2enclosure($tags) {
@@ -2479,7 +2479,7 @@ function microformat2enclosure($tags) {
  *
  * @filtre
  *
- * @param string $tags Texte
+ * @param string $tags texte
  * @return string Tags RSS Atom `<dc:subject>`.
  **/
 function tags2dcsubject($tags) {
@@ -2513,7 +2513,7 @@ function tags2dcsubject($tags) {
  *     ce qui retournerait `<div> un <div> mot </div>` donc.
  *
  * @param string|array $texte
- *     Texte(s) dont on souhaite extraire une balise html
+ *     texte(s) dont on souhaite extraire une balise html
  * @param string $tag
  *     Nom de la balise html à extraire
  * @return void|string|array
@@ -2561,7 +2561,7 @@ function extraire_balise($texte, $tag = 'a') {
  *     tel que demander à extraire `div` dans un texte.
  *
  * @param string|array $texte
- *     Texte(s) dont on souhaite extraire une balise html
+ *     texte(s) dont on souhaite extraire une balise html
  * @param string $tag
  *     Nom de la balise html à extraire
  * @return array
@@ -3147,7 +3147,7 @@ function table_valeur($table, $cle, $defaut = '', $conserver_null = false) {
  * @link http://php.net/manual/fr/function.preg-match.php Pour des infos sur `preg_match()`
  *
  * @param ?string $texte
- *     Texte dans lequel chercher
+ *     texte dans lequel chercher
  * @param string|int $expression
  *     Expression régulière de recherche, sans le délimiteur
  * @param string $modif
@@ -3192,15 +3192,15 @@ function filtre_match_dist(?string $texte, $expression, $modif = 'UuimsS', $capt
  *     ```
  *
  * @param string $texte
- *     Texte
+ *     texte
  * @param string $expression
  *     Expression régulière
  * @param string $replace
- *     Texte de substitution des éléments trouvés
+ *     texte de substitution des éléments trouvés
  * @param string $modif
  *     Modificateurs pour l'expression régulière.
  * @return string
- *     Texte
+ *     texte
  **/
 function replace($texte, $expression, $replace = '', $modif = 'UimsS') {
 	$expression = str_replace('\/', '/', $expression);
@@ -4154,12 +4154,12 @@ function url_rss_forum($texte) {
  * @param string $url
  *   URL du lien
  * @param string $libelle
- *   Texte du lien
+ *   texte du lien
  * @param bool $on
  *   État exposé ou non (génère un lien). Si $on vaut true ou 1 ou ' ', l'etat expose est rendu par un <strong class='on'>...</strong>
  *   Si $on est de la forme span.active.truc par exemple, l'etat expose est rendu par <span class='active truc'>...</span> Seules les balises a, span et strong sont acceptees dans ce cas
  * @param string $class
- *   Classes CSS ajoutées au lien
+ *   classes CSS ajoutées au lien
  * @param string $title
  *   Title ajouté au lien
  * @param string $rel
@@ -4301,7 +4301,7 @@ function prepare_icone_base($type, $lien, $texte, $fond, $fonction = '', $class
 	$class_lien .= " $objet_type";
 	$class_bouton .= " $objet_type";
 
-	// Texte
+	// texte
 	$alt = attribut_html($texte);
 	$title = " title=\"$alt\""; // est-ce pertinent de doubler le alt par un title ?
 
@@ -4355,13 +4355,13 @@ function prepare_icone_base($type, $lien, $texte, $fond, $fonction = '', $class
  * @param string $lien
  *     URL du lien
  * @param string $texte
- *     Texte du lien
+ *     texte du lien
  * @param string $fond
  *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
  * @param string $fonction
  *     Fonction du lien (`edit`, `new`, `del`)
  * @param string $class
- *     Classe CSS, tel que `left`, `right` pour définir un alignement
+ *     classe CSS, tel que `left`, `right` pour définir un alignement
  * @param string $javascript
  *     Javascript ajouté sur le lien
  * @return string
@@ -4389,13 +4389,13 @@ function icone_base($lien, $texte, $fond, $fonction = '', $class = '', $javascri
  * @param string $lien
  *     URL du lien
  * @param string $texte
- *     Texte du lien
+ *     texte du lien
  * @param string $fond
  *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
  * @param string $fonction
  *     Fonction du lien (`edit`, `new`, `del`)
  * @param string $class
- *     Classe CSS à ajouter, tel que `left`, `right`, `center` pour définir un alignement.
+ *     classe CSS à ajouter, tel que `left`, `right`, `center` pour définir un alignement.
  *     Il peut y en avoir plusieurs : `left ajax`
  * @param string $javascript
  *     Javascript ajouté sur le lien
@@ -4435,13 +4435,13 @@ function filtre_icone_verticale_dist($lien, $texte, $fond, $fonction = '', $clas
  * @param string $lien
  *     URL du lien
  * @param string $texte
- *     Texte du lien
+ *     texte du lien
  * @param string $fond
  *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
  * @param string $fonction
  *     Fonction du lien (`edit`, `new`, `del`)
  * @param string $class
- *     Classe CSS à ajouter
+ *     classe CSS à ajouter
  * @param string $javascript
  *     Javascript ajouté sur le lien
  * @return string
@@ -4466,13 +4466,13 @@ function filtre_icone_horizontale_dist($lien, $texte, $fond, $fonction = '', $cl
  * @param string $lien
  *     URL de l'action
  * @param string $texte
- *     Texte du bouton
+ *     texte du bouton
  * @param string $fond
  *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
  * @param string $fonction
  *     Fonction du bouton (`edit`, `new`, `del`)
  * @param string $class
- *     Classes à ajouter au bouton, à l'exception de `ajax` qui est placé sur le formulaire.
+ *     classes à ajouter au bouton, à l'exception de `ajax` qui est placé sur le formulaire.
  * @param string $confirm
  *     Message de confirmation à ajouter en javascript sur le bouton
  * @return string
@@ -4495,15 +4495,15 @@ function filtre_bouton_action_horizontal_dist($lien, $texte, $fond, $fonction =
  * @param string $lien
  *     URL du lien
  * @param string $texte
- *     Texte du lien
+ *     texte du lien
  * @param string $fond
  *     Nom de l'image utilisée
  * @param string $align
- *     Classe CSS d'alignement (`left`, `right`, `center`)
+ *     classe CSS d'alignement (`left`, `right`, `center`)
  * @param string $fonction
  *     Fonction du lien (`edit`, `new`, `del`)
  * @param string $class
- *     Classe CSS à ajouter
+ *     classe CSS à ajouter
  * @param string $javascript
  *     Javascript ajouté sur le lien
  * @return string
@@ -4527,7 +4527,7 @@ function filtre_icone_dist($lien, $texte, $fond, $align = '', $fonction = '', $c
  *     [(#GET{truc}|explode{-})]
  *     ```
  *
- * @param string $a Texte
+ * @param string $a texte
  * @param string $b Séparateur
  * @return array Liste des éléments
  */
@@ -4550,7 +4550,7 @@ function filtre_explode_dist($a, $b) {
  *
  * @param array $a Tableau
  * @param string $b Séparateur
- * @return string Texte
+ * @return string texte
  */
 function filtre_implode_dist($a, $b) {
 	return is_array($a) ? implode($b, $a) : $a;
@@ -5018,7 +5018,7 @@ function objet_info($objet, $info) {
  * @param string $objet
  *     Objet
  * @return mixed|string
- *     Texte traduit du comptage, tel que '3 articles'
+ *     texte traduit du comptage, tel que '3 articles'
  */
 function objet_afficher_nb($nb, $objet) {
 	if (!$nb) {
@@ -5422,7 +5422,7 @@ function spip_affiche_mot_de_passe_masque(?string $passe, bool $afficher_partiel
  * en ne conservant que des caracteres alphanumeriques et un separateur
  *
  * @param string $texte
- *   Texte à transformer en nom machine
+ *   texte à transformer en nom machine
  * @param string $type
  *
  * @param array $options
diff --git a/ecrire/inc/filtres_dates.php b/ecrire/inc/filtres_dates.php
index d5c5f73b2a549e3c1f3f28b5a76cfdc239526fa6..a768f5a8677e89665539152df683637c7fe11df3 100644
--- a/ecrire/inc/filtres_dates.php
+++ b/ecrire/inc/filtres_dates.php
@@ -32,7 +32,7 @@ if (!defined('_ECRIRE_INC_VERSION')) {
  * @deprecated 4.2
  * @link https://www.spip.net/5516
  * @param string $texte
- *    Texte contenant une date tel que `2008-04`
+ *    texte contenant une date tel que `2008-04`
  * @return string
  *    Date au format SQL tel que `2008-04-01` sinon ''
  **/
@@ -1030,7 +1030,7 @@ function affdate_heure($numdate): string {
  *   - `jour` pour forcer l'affichage du nom du jour
  *   - `hcal` pour avoir un markup microformat abbr
  * @return string
- *     Texte de la date
+ *     texte de la date
  */
 function affdate_debut_fin($date_debut, $date_fin, $horaire = 'oui', $forme = ''): string {
 	$abbr = $jour = '';
diff --git a/ecrire/inc/filtres_mime.php b/ecrire/inc/filtres_mime.php
index f1d05bd05b52256ff17bb2ea72c53e043e92606b..5a343816ce7a591d9cbb1d5bda610bc49c20fe91 100644
--- a/ecrire/inc/filtres_mime.php
+++ b/ecrire/inc/filtres_mime.php
@@ -130,7 +130,7 @@ function filtre_text_dist($t) {
  *
  * @filtre
  * @param string $t
- *     Texte CSV
+ *     texte CSV
  * @return string
  *     Tableau (formaté en SPIP)
  **/
diff --git a/ecrire/inc/filtres_mini.php b/ecrire/inc/filtres_mini.php
index fdb6f958a3e4141b29fe09ddc37725f45f173189..6b5ee804accbcf226d8fd338f9fd95797f00caeb 100644
--- a/ecrire/inc/filtres_mini.php
+++ b/ecrire/inc/filtres_mini.php
@@ -126,7 +126,7 @@ function suivre_lien($url, $lien) {
  *
  * @param string $url URL
  * @param string $base URL de base de destination (par défaut ce sera l'URL de notre site)
- * @return string Texte ou URL (en absolus)
+ * @return string texte ou URL (en absolus)
  **/
 function url_absolue($url, $base = '') {
 	$url = trim((string) $url);
@@ -180,9 +180,9 @@ function protocole_verifier($url_absolue, $protocoles_autorises = ['http','https
  * @uses url_absolue()
  * @link https://www.spip.net/4126
  *
- * @param string $texte Texte
+ * @param string $texte texte
  * @param string $base URL de base de destination (par défaut ce sera l'URL de notre site)
- * @return string Texte avec des URLs absolues
+ * @return string texte avec des URLs absolues
  **/
 function liens_absolus($texte, $base = '') {
 	if (preg_match_all(',(<(a|link|image|img|script)\s[^<>]*(href|src)=[^<>]*>),imsS', $texte, $liens, PREG_SET_ORDER)) {
@@ -216,9 +216,9 @@ function liens_absolus($texte, $base = '') {
  * @link https://www.spip.net/4128
  * @global string $mode_abs_url Pour connaître le mode (url ou texte)
  *
- * @param string $texte Texte ou URL
+ * @param string $texte texte ou URL
  * @param string $base URL de base de destination (par défaut ce sera l'URL de notre site)
- * @return string Texte ou URL (en absolus)
+ * @return string texte ou URL (en absolus)
  **/
 function abs_url($texte, $base = '') {
 	if ($GLOBALS['mode_abs_url'] == 'url') {
diff --git a/ecrire/inc/install.php b/ecrire/inc/install.php
index 5e4eb05d56b2a28dd938efaede80250d179d2053..7e72034c545780c82a701d78bee800656e104818 100644
--- a/ecrire/inc/install.php
+++ b/ecrire/inc/install.php
@@ -72,7 +72,7 @@ function install_fichier_connexion($nom, $texte) {
  * @param string $ldap Type d'authentification (cas si 'ldap')
  * @param string $charset Charset de la connexion SQL
  * @return string
- *     Texte du fichier de connexion
+ *     texte du fichier de connexion
  *
  **/
 function install_connexion($adr, $port, $login, $pass, $base, $type, $pref, $ldap = '', $charset = '') {
@@ -244,7 +244,7 @@ function info_etape($titre, $complement = '') {
 /**
  * Retourne le code HTML d'un bouton `suivant>>` pour les phases d'installation
  *
- * @param string $code Texte du bouton
+ * @param string $code texte du bouton
  * @return string Code HTML du bouton
  **/
 function bouton_suivant($code = '') {
diff --git a/ecrire/inc/lister_objets.php b/ecrire/inc/lister_objets.php
index fafcd29f8f834ce97570d6ce8d060c2c2f5f862b..1381c3c1fcc65e37775f924b17d6ae8a5e732cbe 100644
--- a/ecrire/inc/lister_objets.php
+++ b/ecrire/inc/lister_objets.php
@@ -33,7 +33,7 @@ if (!defined('_ECRIRE_INC_VERSION')) {
  * @param string $vue
  *     Nom de l'objet
  * @param array $contexte
- *     Contexte du squelette
+ *     contexte du squelette
  * @param bool $force
  *     Si `true` le titre est affiché même s'il n'y a aucun élément dans la liste.
  * @return string
diff --git a/ecrire/inc/modifier.php b/ecrire/inc/modifier.php
index 12c14dbb8faafccde24c3a864ce82a461627eadf..c91ffa392f1d479a5d526aec77151988dafd6692 100644
--- a/ecrire/inc/modifier.php
+++ b/ecrire/inc/modifier.php
@@ -94,7 +94,7 @@ function collecter_requests($include_list, $exclude_list = [], $set = null, $tou
  * @return bool|string
  *     - false  : Aucune modification, aucun champ n'est à modifier
  *     - chaîne vide : Vide si tout s'est bien passé
- *     - chaîne : Texte d'un message d'erreur
+ *     - chaîne : texte d'un message d'erreur
  */
 function objet_modifier_champs($objet, $id_objet, $options, $c = null, $serveur = '') {
 	if (!$id_objet = intval($id_objet)) {
diff --git a/ecrire/inc/presentation.php b/ecrire/inc/presentation.php
index 02eb940ef221349621343450715869dba9b1a072..0191c16d2c4f7aeb1e639be2e0cbd9787dc067b2 100644
--- a/ecrire/inc/presentation.php
+++ b/ecrire/inc/presentation.php
@@ -224,7 +224,7 @@ function onglet($texte, $lien, $onglet_ref, $onglet, $icone = '') {
  *     ```
  *
  * @param string $texte
- *     Texte du lien
+ *     texte du lien
  * @param string $lien
  *     URL du lien
  * @param string $fond
@@ -256,7 +256,7 @@ function icone_verticale($texte, $lien, $fond, $fonction = '', $align = '', $jav
  * @see  filtre_icone_horizontale_dist() Pour l'usage en tant que filtre
  *
  * @param string $texte
- *     Texte du lien
+ *     texte du lien
  * @param string $lien
  *     URL du lien
  * @param string $fond
diff --git a/ecrire/inc/presentation_mini.php b/ecrire/inc/presentation_mini.php
index 080fd188b9d32731835f851332718261243f3315..7a6e80c383ffab640b25c132cbfb2abe2e94bb49 100644
--- a/ecrire/inc/presentation_mini.php
+++ b/ecrire/inc/presentation_mini.php
@@ -100,7 +100,7 @@ function debut_droite() {
  * @param string $exec
  *     Nom de la page exec en cours
  * @param array $contexte
- *     Contexte de la page
+ *     contexte de la page
  * @param array|null $auteur
  *     Session de l'auteur. Sera prise sur l'auteur connecté si non indiquée.
  * @return string
@@ -195,7 +195,7 @@ function html_tests_js() {
 /**
  * Retourne la liste des mises à jour de SPIP possibles
  *
- * @return string Texte présentant la liste des mises à jour existantes
+ * @return string texte présentant la liste des mises à jour existantes
  **/
 function info_maj_spip() {
 
diff --git a/ecrire/inc/rubriques.php b/ecrire/inc/rubriques.php
index 6b0efffb28484ad3a57271382786bec05a73338c..c763703aab5321ca0c36bae6c3a1d7b8dc2b287c 100644
--- a/ecrire/inc/rubriques.php
+++ b/ecrire/inc/rubriques.php
@@ -10,6 +10,8 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+
 /**
  * Fichier gérant l'actualisation et le suivi des rubriques, et de leurs branches
  *
diff --git a/ecrire/inc/texte.php b/ecrire/inc/texte.php
index 94d0fcda9725f737995f8213b000121a5aed6008..951d79cba9462dbf4a0eadcf560c95e0d1385308 100644
--- a/ecrire/inc/texte.php
+++ b/ecrire/inc/texte.php
@@ -212,7 +212,7 @@ function interdire_scripts($arg, $mode_filtre = null) {
  * @see  propre()
  *
  * @param string $letexte
- *     Texte d'origine
+ *     texte d'origine
  * @param bool|string $echapper
  *     Échapper ?
  * @param string|null $connect
@@ -220,7 +220,7 @@ function interdire_scripts($arg, $mode_filtre = null) {
  * @param array $env
  *     Environnement (pour les calculs de modèles)
  * @return string $t
- *     Texte transformé
+ *     texte transformé
  **/
 function typo($letexte, $echapper = true, $connect = null, $env = []) {
 	// Plus vite !
@@ -301,9 +301,9 @@ define('_TYPO_BALISE', ',</?[a-z!][^<>]*[' . preg_quote(_TYPO_PROTEGER) . '][^<>
  * @uses corriger_caracteres()
  * @uses corriger_caracteres()
  *
- * @param string $letexte Texte
+ * @param string $letexte texte
  * @param string $lang Langue
- * @return string Texte
+ * @return string texte
  */
 function corriger_typo($letexte, $lang = '') {
 
@@ -381,8 +381,8 @@ function paragrapher($letexte, $forcer = true) {
  *
  * Ne sert plus
  *
- * @param string $letexte Texte
- * @return string Texte
+ * @param string $letexte texte
+ * @return string texte
  **/
 function traiter_retours_chariots($letexte) {
 	$letexte = preg_replace(",\r\n?,S", "\n", $letexte);
@@ -406,13 +406,13 @@ function traiter_retours_chariots($letexte) {
  * @see  typo()
  *
  * @param string $t
- *     Texte avec des raccourcis SPIP
+ *     texte avec des raccourcis SPIP
  * @param string|null $connect
  *     Nom du connecteur à la bdd
  * @param array $env
  *     Environnement (pour les calculs de modèles)
  * @return string $t
- *     Texte transformé
+ *     texte transformé
  **/
 function propre($t, $connect = null, $env = []) {
 	// les appels directs a cette fonction depuis le php de l'espace
diff --git a/ecrire/inc/texte_mini.php b/ecrire/inc/texte_mini.php
index ebfffc6c85f666175c5d90ef7a6937902b9d8026..495e58b14bbfabff5e8b3a7c0e686ca2992499fa 100644
--- a/ecrire/inc/texte_mini.php
+++ b/ecrire/inc/texte_mini.php
@@ -371,13 +371,13 @@ function echappe_retour_modeles($letexte, $interdire_scripts = false) {
  * @link https://www.spip.net/4275
  *
  * @param string $texte
- *     Texte à couper
+ *     texte à couper
  * @param int $taille
  *     Taille de la coupe
  * @param string $suite
  *     Points de suite ajoutés.
  * @return string
- *     Texte coupé
+ *     texte coupé
  **/
 function couper($texte, $taille = 50, $suite = null) {
 	if (!($length = strlen($texte)) or $taille <= 0) {
@@ -648,9 +648,9 @@ function echapper_html_suspect($texte, $options = [], $connect = null, $env = []
  * @link https://www.spip.net/4310
  *
  * @param string $t
- *      Texte à sécuriser
+ *      texte à sécuriser
  * @return string
- *      Texte sécurisé
+ *      texte sécurisé
  **/
 function safehtml($t) {
 	static $safehtml;
@@ -738,11 +738,11 @@ function is_html_safe(string $texte): bool {
  *     gérer les autres modèles ?
  *
  * @param string $letexte
- *     Texte à nettoyer
+ *     texte à nettoyer
  * @param string|null $message
  *     Message de remplacement pour chaque image enlevée
  * @return string
- *     Texte sans les modèles d'image
+ *     texte sans les modèles d'image
  **/
 function supprime_img($letexte, $message = null) {
 	if ($message === null) {
diff --git a/ecrire/inc/urls.php b/ecrire/inc/urls.php
index 6f2cdd32fb1f6c58483adcd1b4562aa3fb3db56e..04f2fb6a632031b86151aa9d44861ac531f4d8fa 100644
--- a/ecrire/inc/urls.php
+++ b/ecrire/inc/urls.php
@@ -42,7 +42,7 @@ include_spip('base/objets');
  * @param string $fond
  *   Fond initial par défaut
  * @param array $contexte
- *   Contexte initial à prendre en compte
+ *   contexte initial à prendre en compte
  * @param bool $assembler
  *   `true` si l'URL correspond à l'URL principale de la page qu'on est en train d'assembler
  *   dans ce cas la fonction redirigera automatiquement si besoin
diff --git a/ecrire/inc/utils.php b/ecrire/inc/utils.php
index 80f8575710fc01a87a64a127b05b2d9800460f22..55fe7dea8c8fde9225c0c8926ddacc9fb78d3d60 100644
--- a/ecrire/inc/utils.php
+++ b/ecrire/inc/utils.php
@@ -399,7 +399,7 @@ function spip_log($message = null, $name = null) {
  * Enregistrement des journaux
  *
  * @uses inc_journal_dist()
- * @param string $phrase Texte du journal
+ * @param string $phrase texte du journal
  * @param array $opt Tableau d'options
  **/
 function journal($phrase, $opt = []) {
@@ -837,7 +837,7 @@ function test_plugin_actif($plugin) {
  *     - bool force : forcer un retour meme si la chaine n'a pas de traduction
  *     - bool sanitize : nettoyer le html suspect dans les arguments
  * @return string
- *     Texte
+ *     texte
  */
 function _T($texte, $args = [], $options = []) {
 	static $traduire = false;
@@ -902,14 +902,14 @@ function _T($texte, $args = [], $options = []) {
  *     ```
  *
  * @param string $text
- *     Texte
+ *     texte
  * @param array $args
  *     Couples (variable => valeur) à transformer dans le texte
  * @param array $options
  *     - string class : nom d'une classe a ajouter sur un span pour encapsuler la chaine
  *     - bool sanitize : nettoyer le html suspect dans les arguments
  * @return string
- *     Texte
+ *     texte
  */
 function _L($text, $args = [], $options = []) {
 	$f = $text;
@@ -1308,9 +1308,9 @@ function http_script($script, $src = '', $noscript = '') {
  *
  * @filtre
  * @param string $texte
- *     Texte à échapper
+ *     texte à échapper
  * @return string
- *     Texte échappé
+ *     texte échappé
  **/
 function texte_script(string $texte): string {
 	return str_replace('\'', '\\\'', str_replace('\\', '\\\\', $texte));
diff --git a/ecrire/inc/xml.php b/ecrire/inc/xml.php
index c16c7d84662b27d61dcd30d4f288fc8867f2df9a..3f96e66164eb8319520bcf26ccb8dd62d0c4d7b6 100644
--- a/ecrire/inc/xml.php
+++ b/ecrire/inc/xml.php
@@ -73,7 +73,7 @@ if (!defined('_SPIP_XML_TAG_SPLIT')) {
  * @see spip_xml_aplatit() pour l'inverse
  *
  * @param string $texte
- *     Texte XML
+ *     texte XML
  * @param bool $strict
  *     true pour râler si une balise n'est pas correctement fermée, false sinon.
  * @param bool $clean ?
diff --git a/ecrire/inc_version.php b/ecrire/inc_version.php
index d950d463136dd9937188ac7596b98d4a5ebe7434..a3eb7155be5e9cf49f038f89100a186b20810e3c 100644
--- a/ecrire/inc_version.php
+++ b/ecrire/inc_version.php
@@ -129,7 +129,7 @@ if (!defined('MODULES_IDIOMES')) {
 // *** Fin des define *** //
 
 
-// Inclure l'ecran de securite
+// inclure l'ecran de securite
 if (
 	!defined('_ECRAN_SECURITE')
 	and @file_exists($f = _ROOT_RACINE . _NOM_PERMANENTS_INACCESSIBLES . 'ecran_securite.php')
diff --git a/ecrire/iterateur/condition.php b/ecrire/iterateur/condition.php
index 7a81b3139fc4049b5e780de435e72fa211c9d508..86fc2dfe91ef0ba811d9722ce2c638faa8407ee3 100644
--- a/ecrire/iterateur/condition.php
+++ b/ecrire/iterateur/condition.php
@@ -10,6 +10,8 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+
 /**
  * Gestion de l'itérateur CONDITION
  *
diff --git a/ecrire/iterateur/data.php b/ecrire/iterateur/data.php
index 435f61e06e4aa2dce6ed1a9a8e8834d653a2150a..55455cc8945872d466351417b9fa5608fb0e4388 100644
--- a/ecrire/iterateur/data.php
+++ b/ecrire/iterateur/data.php
@@ -10,6 +10,8 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+
 /**
  * Gestion de l'itérateur DATA
  *
diff --git a/ecrire/iterateur/php.php b/ecrire/iterateur/php.php
index 91abdd65578ea2cc614474cdf37219f7897f4468..a86b65f9919a3b9c9ce96d52ccab2892914a50af 100644
--- a/ecrire/iterateur/php.php
+++ b/ecrire/iterateur/php.php
@@ -10,6 +10,8 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+
 /**
  * Gestion de l'itérateur PHP
  *
diff --git a/ecrire/iterateur/pour.php b/ecrire/iterateur/pour.php
index 33fa5794d02fab9f1429876ef204ff4704a7f0c6..36cb2a349b935e64ed1dc53fcd9efd19bb5775e3 100644
--- a/ecrire/iterateur/pour.php
+++ b/ecrire/iterateur/pour.php
@@ -10,6 +10,8 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+
 /**
  * Gestion de l'itérateur POUR
  *
diff --git a/ecrire/public/assembler.php b/ecrire/public/assembler.php
index 8c82f8b734e2213c286ccc7e773762b1667534c2..3ef7a6b718b91eee86a51136246592f0a230a12f 100644
--- a/ecrire/public/assembler.php
+++ b/ecrire/public/assembler.php
@@ -388,7 +388,7 @@ function inserer_balise_dynamique($contexte_exec, $contexte_compil) {
  *
  * @param string|array $texte
  * @param bool $echo Faut-il faire echo ou return
- * @param array $contexte_compil Contexte de la compilation
+ * @param array $contexte_compil contexte de la compilation
  * @return string
  */
 function inclure_balise_dynamique($texte, $echo = true, $contexte_compil = []) {
diff --git a/ecrire/public/balises.php b/ecrire/public/balises.php
index 617ef18920321a73e601ac2433426e5d4f452a6c..6e530fbb8aec28b03e13db1671aad017212f4ea5 100644
--- a/ecrire/public/balises.php
+++ b/ecrire/public/balises.php
@@ -10,6 +10,8 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Champ;
+
 /**
  * Ce fichier regroupe la quasi totalité des définitions de `#BALISES` de SPIP.
  *
@@ -552,7 +554,7 @@ function balise_TOTAL_BOUCLE_dist($p) {
  * Cette balise nécessite donc la présence de ce critère.
  *
  * @balise
- * @link https://www.spip.net/903 Boucles et balises de recherche
+ * @link https://www.spip.net/903 boucles et balises de recherche
  * @see critere_recherche_dist()
  *
  * @param Champ $p
@@ -717,9 +719,9 @@ function balise_EXPOSE_dist($p) {
  * @param Champ $p
  *     Pile au niveau de la balise
  * @param string $on
- *     Texte à afficher si l'élément est exposé (code à écrire tel que "'on'")
+ *     texte à afficher si l'élément est exposé (code à écrire tel que "'on'")
  * @param string $off
- *     Texte à afficher si l'élément n'est pas exposé (code à écrire tel que "''")
+ *     texte à afficher si l'élément n'est pas exposé (code à écrire tel que "''")
  * @return Champ
  *     Pile complétée par le code à générer
  **/
@@ -2540,7 +2542,7 @@ function balise_ACTION_FORMULAIRE($p) {
  * Cette balise s'utilise à la place des liens `action_auteur`, sous la forme
  * `#BOUTON_ACTION{libelle[,url[,class[,confirm[,title[,callback]]]]]}`
  *
- * - libelle  : Texte du bouton
+ * - libelle  : texte du bouton
  * - url      : URL d’action sécurisée
  * - class    : Classes à ajouter au bouton, à l'exception de `ajax` qui est placé sur le formulaire.
  *              Pour d'autres classes sur le formulaire, utiliser le filtre `ajouter_class`
diff --git a/ecrire/public/compiler.php b/ecrire/public/compiler.php
index 45b4c1c1d6fae74db48e26df5da846dc4d2704d1..b9ccdaa42a6d6812c3fde5b702959be32b509636 100644
--- a/ecrire/public/compiler.php
+++ b/ecrire/public/compiler.php
@@ -10,6 +10,11 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+use Spip\Core\Contexte;
+use Spip\Core\Inclure;
+use Spip\Core\Texte;
+
 /**
  * Fichier principal du compilateur de squelettes
  *
diff --git a/ecrire/public/composer.php b/ecrire/public/composer.php
index 4c0e7ca827ccc21794fc065e0f54e7fb25a21ef0..a3ada38a8297d2dbfd14d2c2887a91092b1df239 100644
--- a/ecrire/public/composer.php
+++ b/ecrire/public/composer.php
@@ -103,7 +103,7 @@ function public_composer_dist($squelette, $mime_type, $gram, $source, string $co
 			$nom = '';
 		}
 
-		// Contexte de compil inutile a present
+		// contexte de compil inutile a present
 		// (mais la derniere valeur de $boucle est utilisee ci-dessous)
 		$skel_code[$id] = $f;
 	}
diff --git a/ecrire/public/criteres.php b/ecrire/public/criteres.php
index f7a26f088c82a7f901c49a68070b49e70db67db4..4f051262eb11765e00e4342ec0a8129118faf550 100644
--- a/ecrire/public/criteres.php
+++ b/ecrire/public/criteres.php
@@ -10,6 +10,10 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+use Spip\Core\Critere;
+use Spip\Core\Texte;
+
 /**
  * Définition des {criteres} d'une boucle
  *
@@ -868,7 +872,7 @@ function calculer_critere_par_hasard($idb, &$boucles, $crit) {
  * @param array $boucles AST du squelette
  * @param Critere $crit Paramètres du critère dans cette boucle
  * @param array $tri Paramètre en cours du critère
- * @param string $champ Texte suivant l'expression ('titre' dans {par num titre})
+ * @param string $champ texte suivant l'expression ('titre' dans {par num titre})
  * @return string|array Clause pour le Order by (array si erreur)
  */
 function calculer_critere_par_expression_num($idb, &$boucles, $crit, $tri, $champ) {
@@ -906,7 +910,7 @@ function calculer_critere_par_expression_num($idb, &$boucles, $crit, $tri, $cham
  * @param array $boucles AST du squelette
  * @param Critere $crit Paramètres du critère dans cette boucle
  * @param array $tri Paramètre en cours du critère
- * @param string $champ Texte suivant l'expression ('titre' dans {par sinum titre})
+ * @param string $champ texte suivant l'expression ('titre' dans {par sinum titre})
  * @return string|array Clause pour le Order by (array si erreur)
  */
 function calculer_critere_par_expression_sinum($idb, &$boucles, $crit, $tri, $champ) {
@@ -954,7 +958,7 @@ function calculer_critere_par_expression_sinum($idb, &$boucles, $crit, $tri, $ch
  * @param array $boucles AST du squelette
  * @param Critere $crit Paramètres du critère dans cette boucle
  * @param array $tri Paramètre en cours du critère
- * @param string $champ Texte suivant l'expression ('titre' dans {par multi titre})
+ * @param string $champ texte suivant l'expression ('titre' dans {par multi titre})
  * @return string|array Clause pour le Order by (array si erreur)
  */
 function calculer_critere_par_expression_multi($idb, &$boucles, $crit, $tri, $champ) {
@@ -1855,7 +1859,7 @@ function critere_tri_dist($idb, &$boucles, $crit) {
 	$boucle->order[] = "tri_champ_order(\$tri,\$command['from'],\$senstri)";
 }
 
-# Criteres de comparaison
+# criteres de comparaison
 
 /**
  * Compile un critère non déclaré explicitement
diff --git a/ecrire/public/debusquer.php b/ecrire/public/debusquer.php
index a626ec1e57a353efc80cfee7199bc23a500c2d63..c15158983d3aa10fe5a6639c55965bd26834d8ef 100644
--- a/ecrire/public/debusquer.php
+++ b/ecrire/public/debusquer.php
@@ -1,5 +1,7 @@
 <?php
 
+use Spip\Core\Contexte;
+
 /***************************************************************************\
  *  SPIP, Système de publication pour l'internet                           *
  *                                                                         *
diff --git a/ecrire/public/decompiler.php b/ecrire/public/decompiler.php
index c956a201f8c11866b114ed35516bfd8687de8026..8f3d912eb7c9a1a1cbc3513c713dd4030627d13e 100644
--- a/ecrire/public/decompiler.php
+++ b/ecrire/public/decompiler.php
@@ -1,5 +1,7 @@
 <?php
 
+use Spip\Core\Texte;
+
 /***************************************************************************\
  *  SPIP, Système de publication pour l'internet                           *
  *                                                                         *
diff --git a/ecrire/public/fonctions.php b/ecrire/public/fonctions.php
index 8372bdcebfc013c26d1e97b0c36d0a2373a8730d..d224310198d20f62cf5711545e6f402e3520ee50 100644
--- a/ecrire/public/fonctions.php
+++ b/ecrire/public/fonctions.php
@@ -44,7 +44,7 @@ include_spip('inc/texte');
  * @param string $descriptif
  *     Descriptif de l'introduction
  * @param string $texte
- *     Texte à utiliser en absence de descriptif
+ *     texte à utiliser en absence de descriptif
  * @param string $longueur
  *     Longueur de l'introduction
  * @param string $connect
@@ -618,7 +618,7 @@ function formate_liste_critere_par_ordre_liste($valeurs, $serveur = '') {
  * @uses chercher_filtre()
  *
  * @param mixed $arg
- *     Texte (le plus souvent) sur lequel appliquer le filtre
+ *     texte (le plus souvent) sur lequel appliquer le filtre
  * @param string $filtre
  *     Nom du filtre à appliquer
  * @param array $args
@@ -626,7 +626,7 @@ function formate_liste_critere_par_ordre_liste($valeurs, $serveur = '') {
  * @param mixed $defaut
  *     Valeur par défaut à retourner en cas d'absence du filtre.
  * @return string
- *     Texte traité par le filtre si le filtre existe,
+ *     texte traité par le filtre si le filtre existe,
  *     Valeur $defaut sinon.
  **/
 function appliquer_filtre_sinon($arg, $filtre, $args, $defaut = '') {
diff --git a/ecrire/public/interfaces.php b/ecrire/public/interfaces.php
index bca42ee365c633243515ba84f1e6e73fe3cf2f61..136d013e8afbb46d580f36ed69d0d5a58e229a2f 100644
--- a/ecrire/public/interfaces.php
+++ b/ecrire/public/interfaces.php
@@ -21,783 +21,12 @@ if (!defined('_ECRIRE_INC_VERSION')) {
 }
 
 
-/**
- * Description d'un contexte de compilation
- *
- * Objet simple pour stocker le nom du fichier, la ligne, la boucle
- * permettant entre autre de localiser le lieu d'une erreur de compilation.
- * Cette structure est nécessaire au traitement d'erreur à l'exécution.
- *
- * Le champ code est inutilisé dans cette classe seule, mais harmonise
- * le traitement d'erreurs.
- *
- * @package SPIP\Core\Compilateur\AST
- */
-class Contexte {
-	/**
-	 * Description du squelette
-	 *
-	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
-	 *
-	 * Peut contenir les index :
-	 *
-	 * - nom : Nom du fichier de cache
-	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
-	 * - sourcefile : Chemin du squelette
-	 * - squelette : Code du squelette
-	 * - id_mere : Identifiant de la boucle parente
-	 * - documents : Pour embed et img dans les textes
-	 * - session : Pour un cache sessionné par auteur
-	 * - niv : Niveau de tabulation
-	 */
-	public array $descr = [];
-
-	/** Identifiant de la boucle */
-	public string $id_boucle = '';
-
-	/** Numéro de ligne dans le code source du squelette */
-	public int $ligne = 0;
-
-	/** Langue d'exécution */
-	public string $lang = '';
-
-	/** Résultat de la compilation: toujours une expression PHP */
-	public string $code = '';
-}
-
-
-/**
- * Description d'un texte
- *
- * @package SPIP\Core\Compilateur\AST
- **/
-class Texte {
-	/** Type de noeud */
-	public string $type = 'texte';
-
-	/** Le texte */
-	public string $texte;
-
-	/**
-	 * Contenu avant le texte.
-	 *
-	 * Vide ou apostrophe simple ou double si le texte en était entouré
-	 *
-	 * @var string|array
-	 */
-	public $avant = '';
-
-	/**
-	 * Contenu après le texte.
-	 *
-	 * Vide ou apostrophe simple ou double si le texte en était entouré
-	 *
-	 * @var string|array
-	 */
-	public $apres = '';
-
-	/** Numéro de ligne dans le code source du squelette */
-	public int $ligne = 0;
-}
-
-/**
- * Description d'une inclusion de squelette
- *
- * @package SPIP\Core\Compilateur\AST
- **/
-class Inclure {
-	/** Type de noeud */
-	public string $type = 'include';
-
-	/**
-	 * Nom d'un fichier inclu
-	 *
-	 * - Objet Texte si inclusion d'un autre squelette
-	 * - chaîne si inclusion d'un fichier PHP directement
-	 *
-	 * @var string|Texte
-	 */
-	public $texte;
-
-	/**
-	 * Inutilisé, propriété générique de l'AST
-	 *
-	 * @var string|array
-	 */
-	public $avant = '';
-
-	/**
-	 * Inutilisé, propriété générique de l'AST
-	 *
-	 * @var string|array
-	 */
-	public $apres = '';
-
-	/** Numéro de ligne dans le code source du squelette */
-	public int $ligne = 0;
-
-	/**
-	 * Valeurs des paramètres
-	 *
-	 * FIXME: type unique.
-	 * @var false|array
-	 *     - false: erreur de syntaxe
-	 */
-	public $param = [];
-
-	/** Source des filtres (compatibilité) (?) */
-	public array $fonctions = [];
-
-	/**
-	 * Description du squelette
-	 *
-	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
-	 *
-	 * Peut contenir les index :
-	 *
-	 * - nom : Nom du fichier de cache
-	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
-	 * - sourcefile : Chemin du squelette
-	 * - squelette : Code du squelette
-	 * - id_mere : Identifiant de la boucle parente
-	 * - documents : Pour embed et img dans les textes
-	 * - session : Pour un cache sessionné par auteur
-	 * - niv : Niveau de tabulation
-	 */
-	public array $descr = [];
-}
-
-
-/**
- * Description d'une boucle
- *
- * @package SPIP\Core\Compilateur\AST
- **/
-class Boucle {
-	/** Type de noeud */
-	public string $type = 'boucle';
-
-	/** Identifiant de la boucle */
-	public string $id_boucle;
-
-	/** Identifiant de la boucle parente */
-	public string $id_parent = '';
-
-	/** Un nom explicite qui peut être affecté manuellement à certaines boucles générées */
-	public string $nom = '';
-
-	/**
-	 * Partie avant toujours affichee
-	 *
-	 * @var string|array
-	 */
-	public $preaff = '';
-
-	/**
-	 * Partie optionnelle avant
-	 *
-	 * @var string|array
-	 */
-	public $avant = '';
-
-	/**
-	 * Pour chaque élément
-	 *
-	 * @var string|array
-	 */
-	public $milieu = '';
-
-	/**
-	 * Partie optionnelle après
-	 *
-	 * @var string|array
-	 */
-	public $apres = '';
-
-	/**
-	 * Partie alternative, si pas de résultat dans la boucle
-	 *
-	 * @var string|array
-	 */
-	public $altern = '';
-
-	/**
-	 * Partie apres toujours affichee
-	 *
-	 * @var string|array
-	 */
-	public $postaff = '';
-
-
-	/**
-	 * La boucle doit-elle sélectionner la langue ?
-	 *
-	 * Valeurs : '', 'oui', 'non'
-	 */
-	public string $lang_select = '';
-
-	/**
-	 * Alias de table d'application de la requête ou nom complet de la table SQL
-	 *
-	 * FIXME: un seul typage (string ?)
-	 *
-	 * @var string|false|null
-	 */
-	public $type_requete = null;
-
-	/**
-	 * La table est elle optionnelle ?
-	 *
-	 * Si oui, aucune erreur ne sera générée si la table demandée n'est pas présente
-	 */
-	public bool $table_optionnelle = false;
-
-	/**
-	 * Nom du fichier de connexion
-	 */
-	public string $sql_serveur = '';
-
-	/**
-	 * Paramètres de la boucle
-	 *
-	 * Description des paramètres passés à la boucle, qui servent ensuite
-	 * au calcul des critères
-	 *
-	 * FIXME: type unique.
-	 * @var false|array
-	 *     - false: erreur de syntaxe
-	 */
-	public $param = [];
-
-	/**
-	 * Critères de la boucle
-	 *
-	 * FIXME: type array unique.
-	 *
-	 * @var string|Critere[]
-	 * - string: phrasage (code brut). Il reste si erreur de critère
-	 * - array: analyse correcte des critères...
-	 */
-	public $criteres = [];
-
-	/**
-	 * Textes insérés entre 2 éléments de boucle (critère inter)
-	 *
-	 * @var string[]
-	 */
-	public array $separateur = [];
-
-	/**
-	 * Liste des jointures possibles avec cette table
-	 *
-	 * Les jointures par défaut de la table sont complétées en priorité
-	 * des jointures déclarées explicitement sur la boucle
-	 *
-	 * @see base_trouver_table_dist()
-	 */
-	public array $jointures = [];
-
-	/**
-	 * Jointures explicites avec cette table
-	 *
-	 * Ces jointures sont utilisées en priorité par rapport aux jointures
-	 * normales possibles pour retrouver les colonnes demandées extérieures
-	 * à la boucle.
-	 *
-	 * @var string|bool
-	 */
-	public $jointures_explicites = false;
-
-	/**
-	 * Nom de la variable PHP stockant le noms de doublons utilisés "$doublons_index"
-	 */
-	public string $doublons = '';
-
-	/**
-	 * Code PHP ajouté au début de chaque itération de boucle.
-	 *
-	 * Utilisé entre autre par les critères {pagination}, {n-a,b}, {a/b}...
-	 */
-	public string $partie = '';
-
-	/**
-	 * Nombre de divisions de la boucle, d'éléments à afficher,
-	 * ou de soustractions d'éléments à faire
-	 *
-	 * Dans les critères limitant le nombre d'éléments affichés
-	 * {a,b}, {a,n-b}, {a/b}, {pagination b}, b est affecté à total_parties.
-	 */
-	public string $total_parties = '';
-
-	/**
-	 * Code PHP ajouté avant l'itération de boucle.
-	 *
-	 * Utilisé entre autre par les critères {pagination}, {a,b}, {a/b}
-	 * pour initialiser les variables de début et de fin d'itération.
-	 */
-	public string $mode_partie = '';
-
-	/**
-	 * Identifiant d'une boucle qui appelle celle-ci de manière récursive
-	 *
-	 * Si une boucle est appelée de manière récursive quelque part par
-	 * une autre boucle comme <BOUCLE_rec(boucle_identifiant) />, cette
-	 * boucle (identifiant) reçoit dans cette propriété l'identifiant
-	 * de l'appelant (rec)
-	 */
-	public string $externe = '';
-
-	// champs pour la construction de la requete SQL
-
-	/**
-	 * Liste des champs à récupérer par la boucle
-	 *
-	 * Expression 'table.nom_champ' ou calculée 'nom_champ AS x'
-	 *
-	 * @var string[]
-	 */
-	public array $select = [];
-
-	/**
-	 * Liste des alias / tables SQL utilisées dans la boucle
-	 *
-	 * L'index est un identifiant (xx dans spip_xx assez souvent) qui servira
-	 * d'alias au nom de la table ; la valeur est le nom de la table SQL désirée.
-	 *
-	 * L'index 0 peut définir le type de sources de données de l'itérateur DATA
-	 *
-	 * @var string[]
-	 */
-	public array $from = [];
-
-	/**
-	 * Liste des alias / type de jointures utilisées dans la boucle
-	 *
-	 * L'index est le nom d'alias (comme pour la propriété $from), et la valeur
-	 * un type de jointure parmi 'INNER', 'LEFT', 'RIGHT', 'OUTER'.
-	 *
-	 * Lorsque le type n'est pas déclaré pour un alias, c'est 'INNER'
-	 * qui sera utilisé par défaut (créant donc un INNER JOIN).
-	 *
-	 * @var string[]
-	 */
-	public array $from_type = [];
-
-	/**
-	 * Liste des conditions WHERE de la boucle
-	 *
-	 * Permet de restreindre les éléments retournés par une boucle
-	 * en fonctions des conditions transmises dans ce tableau.
-	 *
-	 * Ce tableau peut avoir plusieurs niveaux de profondeur.
-	 *
-	 * Les éléments du premier niveau sont reliés par des AND, donc
-	 * chaque élément ajouté directement au where par
-	 * $boucle->where[] = array(...) ou $boucle->where[] = "'expression'"
-	 * est une condition AND en plus.
-	 *
-	 * Par contre, lorsqu'on indique un tableau, il peut décrire des relations
-	 * internes différentes. Soit $expr un tableau d'expressions quelconques de 3 valeurs :
-	 * $expr = array(operateur, val1, val2)
-	 *
-	 * Ces 3 valeurs sont des expressions PHP. L'index 0 désigne l'opérateur
-	 * à réaliser tel que :
-	 *
-	 * - "'='" , "'>='", "'<'", "'IN'", "'REGEXP'", "'LIKE'", ... :
-	 *    val1 et val2 sont des champs et valeurs à utiliser dans la comparaison
-	 *    suivant cet ordre : "val1 operateur val2".
-	 *    Exemple : $boucle->where[] = array("'='", "'articles.statut'", "'\"publie\"'");
-	 * - "'AND'", "'OR'", "'NOT'" :
-	 *    dans ce cas val1 et val2 sont également des expressions
-	 *    de comparaison complètes, et peuvent être eux-même des tableaux comme $expr
-	 *    Exemples :
-	 *    $boucle->where[] = array("'OR'", $expr1, $expr2);
-	 *    $boucle->where[] = array("'NOT'", $expr); // val2 n'existe pas avec NOT
-	 *
-	 * D'autres noms sont possibles pour l'opérateur (le nombre de valeurs diffère) :
-	 * - "'SELF'", "'SUBSELECT'" : indiquent des sous requêtes
-	 * - "'?'" : indique une condition à faire évaluer (val1 ? val2 : val3)
-	 */
-	public array $where = [];
-
-	public array $join = [];
-	public array $having = [];
-	public $limit = '';
-	public array $group = [];
-	public array $order = [];
-	public array $default_order = [];
-	public string $date = 'date';
-	public string $hash = '';
-	public $in = '';
-	public bool $sous_requete = false;
-
-	/**
-	 * Code PHP qui sera ajouté en tout début de la fonction de boucle
-	 *
-	 * Il sert à insérer le code calculant une hierarchie
-	 */
-	public string $hierarchie = '';
-
-	// champs pour la construction du corps PHP
-
-	/**
-	 * Description des sources de données de la boucle
-	 *
-	 * Description des données de la boucle issu de trouver_table
-	 * dans le cadre de l'itérateur SQL et contenant au moins l'index 'field'.
-	 *
-	 * @see base_trouver_table_dist()
-	 */
-	public array $show = [];
-
-	/**
-	 * Nom de la table SQL principale de la boucle, sans son préfixe
-	 */
-	public string $id_table = '';
-
-	/**
-	 * Nom de la clé primaire de la table SQL principale de la boucle
-	 */
-	public string $primary = '';
-
-	/**
-	 * Code PHP compilé de la boucle
-	 *
-	 * FIXME: un seul type (string ?)
-	 *
-	 * - false: boucle fautive ?
-	 *
-	 * @var string|false
-	 */
-	public $return = '';
-
-	public $numrows = false;
-	public $cptrows = false;
-
-	/**
-	 * Description du squelette
-	 *
-	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
-	 *
-	 * Peut contenir les index :
-	 *
-	 * - nom : Nom du fichier de cache
-	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
-	 * - sourcefile : Chemin du squelette
-	 * - squelette : Code du squelette
-	 * - id_mere : Identifiant de la boucle parente
-	 * - documents : Pour embed et img dans les textes
-	 * - session : Pour un cache sessionné par auteur
-	 * - niv : Niveau de tabulation
-	 */
-	public array $descr = [];
-
-	/** Numéro de ligne dans le code source du squelette */
-	public int $ligne = 0;
-
-
-	/**
-	 * table pour stocker les modificateurs de boucle tels que tout, plat ...,
-	 * utilisable par les plugins egalement
-	 *
-	 * @var array<string, mixed>
-	 */
-	public array $modificateur = [];
-
-	/**
-	 * Type d'itérateur utilisé pour cette boucle
-	 *
-	 * - 'SQL' dans le cadre d'une boucle sur une table SQL
-	 * - 'DATA' pour l'itérateur DATA, ...
-	 *
-	 * @var string
-	 */
-	public string $iterateur = ''; // type d'iterateur
-
-	/**
-	 * @var array $debug Textes qui seront insérés dans l’entête de boucle du mode debug
-	 */
-	public array $debug = [];
-
-	/**
-	 * Index de la boucle dont le champ présent dans cette boucle est originaire,
-	 * notamment si le champ a été trouve dans une boucle parente
-	 *
-	 * Tableau nom du champ => index de boucle
-	*/
-	public array $index_champ = [];
-
-	/** Résultat de la compilation (?) (sert au débusqueur) */
-	public string $code = '';
-
-	/** Source des filtres (compatibilité) (?) */
-	public array $fonctions = [];
-
-	// obsoletes, conserves provisoirement pour compatibilite
-	public $tout = false;
-	public $plat = false;
-	public $lien = false;
-}
-
-/**
- * Description d'un critère de boucle
- *
- * Sous-noeud de Boucle
- *
- * @package SPIP\Core\Compilateur\AST
- **/
-class Critere {
-	/** Type de noeud */
-	public string $type = 'critere';
-
-	/** Opérateur (>, <, >=, IN, ...) */
-	public ?string $op;
-
-	/** Présence d'une négation (truc !op valeur) */
-	public bool $not = false;
-
-	/** Présence d'une exclusion (!truc op valeur) */
-	public string $exclus = '';
-
-	/** Présence d'une condition dans le critère (truc ?) */
-	public bool $cond = false;
-
-	/**
-	 * Paramètres du critère
-	 * - $param[0] : élément avant l'opérateur
-	 * - $param[1..n] : éléments après l'opérateur
-	 *
-	 * FIXME: type unique.
-	 * @var false|array
-	 *     - false: erreur de syntaxe
-	 */
-	public $param = [];
-
-	/** Numéro de ligne dans le code source du squelette */
-	public int $ligne = 0;
-}
-
-/**
- * Description d'un champ (balise SPIP)
- *
- * @package SPIP\Core\Compilateur\AST
- **/
-class Champ {
-	/** Type de noeud */
-	public string $type = 'champ';
-
-	/** Nom du champ demandé. Exemple 'ID_ARTICLE' */
-	public ?string $nom_champ;
-
-	/** Identifiant de la boucle parente si explicité */
-	public ?string $nom_boucle = '';
-
-	/**
-	 * Partie optionnelle avant
-	 *
-	 * @var null|string|array
-	 */
-	public $avant;
-
-	/**
-	 * Partie optionnelle après
-	 *
-	 * @var null|string|array
-	 */
-	public $apres;
-
-	/**
-	 * Étoiles : annuler des automatismes
-	 *
-	 * - '*' annule les filtres automatiques
-	 * - '**' annule en plus les protections de scripts
-	 */
-	public ?string $etoile;
-
-	/**
-	 * Arguments et filtres explicites sur la balise
-	 *
-	 * - $param[0] contient les arguments de la balise
-	 * - $param[1..n] contient les filtres à appliquer à la balise
-	 *
-	 * FIXME: type unique.
-	 * @var false|array
-	 *     - false: erreur de syntaxe
-	 */
-	public $param = [];
-
-	/** Source des filtres (compatibilité) (?) */
-	public array $fonctions = [];
-
-	/**
-	 * Identifiant de la boucle
-	 *
-	 * @var string
-	 */
-	public $id_boucle = '';
-
-	/**
-	 * AST du squelette, liste de toutes les boucles
-	 *
-	 * @var Boucle[]
-	 */
-	public array $boucles;
-
-	/** Alias de table d'application de la requête ou nom complet de la table SQL */
-	public ?string $type_requete;
-
-	/** Résultat de la compilation: toujours une expression PHP */
-	public string $code = '';
-
-	/**
-	 * Interdire les scripts
-	 *
-	 * false si on est sûr de cette balise
-	 *
-	 * @see interdire_scripts()
-	 */
-	public bool $interdire_scripts = true;
-
-	/**
-	 * Description du squelette
-	 *
-	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
-	 *
-	 * Peut contenir les index :
-	 *
-	 * - nom : Nom du fichier de cache
-	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
-	 * - sourcefile : Chemin du squelette
-	 * - squelette : Code du squelette
-	 * - id_mere : Identifiant de la boucle parente
-	 * - documents : Pour embed et img dans les textes
-	 * - session : Pour un cache sessionné par auteur
-	 * - niv : Niveau de tabulation
-	 */
-	public array $descr = [];
-
-	/** Numéro de ligne dans le code source du squelette*/
-	public int $ligne = 0;
-
-	/** Drapeau pour reperer les balises calculées par une fonction explicite */
-	public bool $balise_calculee = false;
-}
-
-
-/**
- * Description d'une chaîne de langue
- **/
-class Idiome {
-	/** Type de noeud */
-	public string $type = 'idiome';
-
-	/** Clé de traduction demandée. Exemple 'item_oui' */
-	public string $nom_champ = '';
-
-	/** Module de langue où chercher la clé de traduction. Exemple 'medias' */
-	public string $module = '';
-
-	/** Arguments à passer à la chaîne */
-	public array $arg = [];
-
-	/**
-	 * Filtres à appliquer au résultat
-	 *
-	 * FIXME: type unique.
-	 * @var false|array
-	 *     - false: erreur de syntaxe
-	 */
-	public $param = [];
-
-	/** Source des filtres (compatibilité) (?) */
-	public array $fonctions = [];
-
-	/**
-	 * Inutilisé, propriété générique de l'AST
-	 *
-	 * @var string|array
-	 */
-	public $avant = '';
-
-	/**
-	 * Inutilisé, propriété générique de l'AST
-	 *
-	 * @var string|array
-	 */
-	public $apres = '';
-
-	/** Identifiant de la boucle */
-	public string $id_boucle = '';
-
-	/**
-	 * AST du squelette, liste de toutes les boucles
-	 *
-	 * @var Boucle[]
-	 */
-	public array $boucles;
-
-	/** Alias de table d'application de la requête ou nom complet de la table SQL */
-	public ?string $type_requete;
-
-	/** Résultat de la compilation: toujours une expression PHP */
-	public string $code = '';
-
-	/**
-	 * Interdire les scripts
-	 *
-	 * @see interdire_scripts()
-	 */
-	public bool $interdire_scripts = false;
-
-	/**
-	 * Description du squelette
-	 *
-	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
-	 *
-	 * Peut contenir les index :
-	 * - nom : Nom du fichier de cache
-	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
-	 * - sourcefile : Chemin du squelette
-	 * - squelette : Code du squelette
-	 * - id_mere : Identifiant de la boucle parente
-	 * - documents : Pour embed et img dans les textes
-	 * - session : Pour un cache sessionné par auteur
-	 * - niv : Niveau de tabulation
-	 */
-	public array $descr = [];
-
-	/** Numéro de ligne dans le code source du squelette */
-	public int $ligne = 0;
-}
-
-/**
- * Description d'un texte polyglotte (<multi>)
- *
- * @package SPIP\Core\Compilateur\AST
- **/
-class Polyglotte {
-	/** Type de noeud */
-	public string $type = 'polyglotte';
-
-	/**
-	 * Tableau des traductions possibles classées par langue
-	 *
-	 * Tableau code de langue => texte
-	 */
-	public array $traductions = [];
-
-	/** Numéro de ligne dans le code source du squelette */
-	public int $ligne = 0;
-}
-
-
 global $table_criteres_infixes;
 $table_criteres_infixes = ['<', '>', '<=', '>=', '==', '===', '!=', '!==', '<>', '?'];
 
 global $exception_des_connect;
 $exception_des_connect[] = ''; // ne pas transmettre le connect='' par les inclure
 
-
 /**
  * Déclarer les interfaces de la base pour le compilateur
  *
diff --git a/ecrire/public/jointures.php b/ecrire/public/jointures.php
index c3ca975326367f457aa820dc8ce44b16004ffd92..fc0b1506f8c27c2fa9b3486f5818389218b64b17 100644
--- a/ecrire/public/jointures.php
+++ b/ecrire/public/jointures.php
@@ -1,5 +1,7 @@
 <?php
 
+use Spip\Core\Boucle;
+
 /***************************************************************************\
  *  SPIP, Système de publication pour l'internet                           *
  *                                                                         *
diff --git a/ecrire/public/normaliser.php b/ecrire/public/normaliser.php
index 508ff94df2198d4626506364f571bf637667f053..39a6f3e219907316cae5748af85ac36544462119 100644
--- a/ecrire/public/normaliser.php
+++ b/ecrire/public/normaliser.php
@@ -1,5 +1,7 @@
 <?php
 
+use Spip\Core\Texte;
+
 /***************************************************************************\
  *  SPIP, Système de publication pour l'internet                           *
  *                                                                         *
diff --git a/ecrire/public/phraser_html.php b/ecrire/public/phraser_html.php
index 97a9ecc7136eecd674075ba9b8135f745ae7f37f..5c00ad6dc626f148a42c214d002c343014915dac 100644
--- a/ecrire/public/phraser_html.php
+++ b/ecrire/public/phraser_html.php
@@ -10,6 +10,14 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+use Spip\Core\Champ;
+use Spip\Core\Critere;
+use Spip\Core\Idiome;
+use Spip\Core\Inclure;
+use Spip\Core\Polyglotte;
+use Spip\Core\Texte;
+
 /**
  * Phraseur d'un squelette ayant une syntaxe SPIP/HTML
  *
diff --git a/ecrire/public/references.php b/ecrire/public/references.php
index a0abebfb6bb984d40fa5903e99b597f07f50351d..b57bc1d292d66654ee7201bbbc2765585c15efd9 100644
--- a/ecrire/public/references.php
+++ b/ecrire/public/references.php
@@ -10,6 +10,9 @@
  *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
+use Spip\Core\Boucle;
+use Spip\Core\Champ;
+
 /**
  * Fonctions de recherche et de reservation dans l'arborescence des boucles
  *
@@ -482,7 +485,7 @@ function calculer_champ($p) {
  * @return Champ
  *     Pile complétée par le code PHP pour l'exécution de la balise et de ses filtres
  **/
-function calculer_balise(string $nom, \Champ $p): \Champ {
+function calculer_balise(string $nom, Champ $p): Champ {
 
 	// S'agit-t-il d'une balise_XXXX[_dist]() ?
 	if ($f = charger_fonction($nom, 'balise', true)) {
@@ -684,7 +687,7 @@ function calculer_balise_dynamique($p, $nom, $l, $supp = []) {
  * @return array
  *     Liste des codes PHP d'éxecution des balises collectées
  **/
-function collecter_balise_dynamique(array $l, \Champ &$p, string $nom): array {
+function collecter_balise_dynamique(array $l, Champ &$p, string $nom): array {
 	$args = [];
 	foreach ($l as $c) {
 		if ($c === null) {
diff --git a/ecrire/public/sandbox.php b/ecrire/public/sandbox.php
index b1446f2d54e628adf92b1b9fd7533d85f3e7520b..1c87deb908196cc6213587bddcbaf5b11911ceea 100644
--- a/ecrire/public/sandbox.php
+++ b/ecrire/public/sandbox.php
@@ -34,11 +34,11 @@ if (!defined('_ECRIRE_INC_VERSION')) {
  * dans les squelettes
  *
  * @param string $texte
- *     Texte à composer
+ *     texte à composer
  * @param Champ $p
  *     Balise qui appelle ce texte
  * @return string
- *     Texte
+ *     texte
  */
 function sandbox_composer_texte($texte, &$p) {
 	$code = "'" . str_replace(['\\', "'"], ['\\\\', "\\'"], $texte) . "'";
diff --git a/ecrire/req/mysql.php b/ecrire/req/mysql.php
index d9d2bd09d17f2e8a0eb19df846bf9d2469c35de7..9b9cd07729b475683e6271732d94d5064faf7a25 100644
--- a/ecrire/req/mysql.php
+++ b/ecrire/req/mysql.php
@@ -203,7 +203,7 @@ function spip_mysql_get_charset($charset = [], $serveur = '', $requeter = true)
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return mysqli_result|bool|string|array
  *     - mysqli_result|bool : Si requête exécutée
- *     - string : Texte de la requête si on ne l'exécute pas
+ *     - string : texte de la requête si on ne l'exécute pas
  *     - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
  */
 function spip_mysql_query($query, $serveur = '', $requeter = true) {
@@ -291,7 +291,7 @@ function spip_mysql_query($query, $serveur = '', $requeter = true) {
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return array|bool|string
- *     - string : Texte de la requête si on ne l'exécute pas
+ *     - string : texte de la requête si on ne l'exécute pas
  *     - bool   : Si requête exécutée
  *     - array  : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
  */
@@ -323,7 +323,7 @@ function spip_mysql_optimize($table, $serveur = '', $requeter = true) {
 /**
  * Retourne une explication de requête (Explain) MySQL
  *
- * @param string $query Texte de la requête
+ * @param string $query texte de la requête
  * @param string $serveur Nom de la connexion
  * @param bool $requeter inutilisé
  * @return array           Tableau de l'explication
@@ -363,7 +363,7 @@ function spip_mysql_explain($query, $serveur = '', $requeter = true) {
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return array|bool|resource|string
- *     - string : Texte de la requête si on ne l'exécute pas
+ *     - string : texte de la requête si on ne l'exécute pas
  *     - ressource si requête exécutée, ressource pour fetch()
  *     - false si la requête exécutée a ratée
  *     - array  : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
@@ -410,8 +410,8 @@ function spip_mysql_select(
  *   0+x avec un champ x commencant par des chiffres est converti par MySQL
  *   en le nombre qui commence x. Pas portable malheureusement, on laisse pour le moment.
  *
- * @param string|array $orderby Texte du orderby à préparer
- * @return string Texte du orderby préparé
+ * @param string|array $orderby texte du orderby à préparer
+ * @return string texte du orderby préparé
  */
 function spip_mysql_order($orderby) {
 	return (is_array($orderby)) ? join(', ', $orderby) : $orderby;
@@ -426,7 +426,7 @@ function spip_mysql_order($orderby) {
  *
  * @param array|string $v
  *     Description des contraintes
- *     - string : Texte du where
+ *     - string : texte du where
  *     - sinon tableau : A et B peuvent être de type string ou array,
  *       OP et C sont de type string :
  *       - array(A) : A est le texte du where
@@ -468,7 +468,7 @@ function calculer_mysql_where($v) {
  * @param string $expression Mot clé de l'expression, tel que "WHERE" ou "ORDER BY"
  * @param array|string $v Données de l'expression
  * @param string $join Si les données sont un tableau, elles seront groupées par cette jointure
- * @return string            Texte de l'expression, une partie donc, du texte la requête.
+ * @return string            texte de l'expression, une partie donc, du texte la requête.
  */
 function calculer_mysql_expression($expression, $v, $join = 'AND') {
 	if (empty($v)) {
@@ -790,7 +790,7 @@ function spip_mysql_create_base($nom, $serveur = '', $requeter = true) {
  * @param string $nom
  *    Nom de la vue à creer
  * @param string $query_select
- *     Texte de la requête de sélection servant de base à la vue
+ *     texte de la requête de sélection servant de base à la vue
  * @param string $serveur
  *     Nom du connecteur
  * @param bool $requeter
@@ -825,7 +825,7 @@ function spip_mysql_create_view($nom, $query_select, $serveur = '', $requeter =
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return bool|string
- *     - string Texte de la requête si demandé
+ *     - string texte de la requête si demandé
  *     - true si la requête a réussie, false sinon
  */
 function spip_mysql_drop_table($table, $exist = '', $serveur = '', $requeter = true) {
@@ -844,7 +844,7 @@ function spip_mysql_drop_table($table, $exist = '', $serveur = '', $requeter = t
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return bool|string
- *     - string Texte de la requête si demandé
+ *     - string texte de la requête si demandé
  *     - true si la requête a réussie, false sinon
  */
 function spip_mysql_drop_view($view, $exist = '', $serveur = '', $requeter = true) {
@@ -881,7 +881,7 @@ function spip_mysql_showbase($match, $serveur = '', $requeter = true) {
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return bool|string
- *     - string Texte de la requête si demandée,
+ *     - string texte de la requête si demandée,
  *     - true si la requête a réussie, false sinon
  */
 function spip_mysql_repair($table, $serveur = '', $requeter = true) {
@@ -937,7 +937,7 @@ define('_MYSQL_RE_SHOW_TABLE', '/^[^(),]*\(((?:[^()]*\((?:[^()]*\([^()]*\))?[^()
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return array|string
  *     - chaîne vide si pas de description obtenue
- *     - string Texte de la requête si demandé
+ *     - string texte de la requête si demandé
  *     - array description de la table sinon
  */
 function spip_mysql_showtable($nom_table, $serveur = '', $requeter = true) {
@@ -1074,7 +1074,7 @@ function spip_mysql_seek($r, $row_number, $serveur = '', $requeter = true) {
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return int|string
- *     - String Texte de la requête si demandé
+ *     - string texte de la requête si demandé
  *     - int Nombre de lignes (0 si la requête n'a pas réussie)
  **/
 function spip_mysql_countsel(
@@ -1209,8 +1209,8 @@ function spip_mysql_free($r, $serveur = '', $requeter = true) {
  *     Exécuter la requête, sinon la retourner
  * @return bool|string|int|array
  *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur,
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur,
  *     - Tableau de description de la requête et du temps d'exécution, si var_profile activé
  **/
 function spip_mysql_insert($table, $champs, $valeurs, $desc = [], $serveur = '', $requeter = true) {
@@ -1286,8 +1286,8 @@ function spip_mysql_insert($table, $champs, $valeurs, $desc = [], $serveur = '',
  *     Exécuter la requête, sinon la retourner
  * @return bool|string|int|array
  *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur,
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur,
  *     - Tableau de description de la requête et du temps d'exécution, si var_profile activé
  **/
 function spip_mysql_insertq($table, $couples = [], $desc = [], $serveur = '', $requeter = true) {
@@ -1331,8 +1331,8 @@ function spip_mysql_insertq($table, $couples = [], $desc = [], $serveur = '', $r
  *     Exécuter la requête, sinon la retourner
  * @return int|bool|string
  *     - int|true identifiant du dernier élément inséré (si possible), ou true, si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function spip_mysql_insertq_multi($table, $tab_couples = [], $desc = [], $serveur = '', $requeter = true) {
 
@@ -1467,8 +1467,8 @@ function spip_mysql_updateq($table, $champs, $where = '', $desc = [], $serveur =
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return bool|string
  *     - int : nombre de suppressions réalisées,
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function spip_mysql_delete($table, $where = '', $serveur = '', $requeter = true) {
 	$res = spip_mysql_query(
@@ -1511,8 +1511,8 @@ function spip_mysql_delete($table, $where = '', $serveur = '', $requeter = true)
  *     Exécuter la requête, sinon la retourner
  * @return bool|string
  *     - true si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function spip_mysql_replace($table, $couples, $desc = [], $serveur = '', $requeter = true) {
 	return spip_mysql_query("REPLACE $table (" . join(',', array_keys($couples)) . ') VALUES (' . join(
@@ -1543,8 +1543,8 @@ function spip_mysql_replace($table, $couples, $desc = [], $serveur = '', $requet
  *     Exécuter la requête, sinon la retourner
  * @return bool|string
  *     - true si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function spip_mysql_replace_multi($table, $tab_couples, $desc = [], $serveur = '', $requeter = true) {
 	$cles = '(' . join(',', array_keys($tab_couples[0])) . ')';
@@ -1566,7 +1566,7 @@ function spip_mysql_replace_multi($table, $tab_couples, $desc = [], $serveur = '
  *
  * @param string $objet Colonne ayant le texte
  * @param string $lang Langue à extraire
- * @return string       Texte de sélection pour la requête
+ * @return string       texte de sélection pour la requête
  */
 function spip_mysql_multi($objet, $lang) {
 	$lengthlang = strlen("[$lang]");
@@ -1616,7 +1616,7 @@ function spip_mysql_hex($v) {
  * comme le fait `_q()` mais pour MySQL avec ses spécificités
  *
  * @param string|array|number $v
- *     Texte, nombre ou tableau à échapper
+ *     texte, nombre ou tableau à échapper
  * @param string $type
  *     Description du type attendu
  *    (par exemple description SQL de la colonne recevant la donnée)
@@ -1690,9 +1690,9 @@ function spip_mysql_in($val, $valeurs, $not = '', $serveur = '', $requeter = tru
 /**
  * Renvoie les bons echappements (mais pas sur les fonctions comme NOW())
  *
- * @param string|number $v Texte ou nombre à échapper
+ * @param string|number $v texte ou nombre à échapper
  * @param string $type Type de donnée attendue, description SQL de la colonne de destination
- * @return string|number     Texte ou nombre échappé
+ * @return string|number     texte ou nombre échappé
  */
 function spip_mysql_cite($v, $type) {
 	if (!$type) {
diff --git a/ecrire/req/sqlite_generique.php b/ecrire/req/sqlite_generique.php
index 3e285c08fb1a327618dbe499e8fd2473c387238d..fb05be28f2e122c5ab294c1f333e42f8e5427c45 100644
--- a/ecrire/req/sqlite_generique.php
+++ b/ecrire/req/sqlite_generique.php
@@ -445,7 +445,7 @@ function spip_sqlite_alter($query, $serveur = '', $requeter = true) {
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return array|null|resource|string
- *     - string Texte de la requête si demandée
+ *     - string texte de la requête si demandée
  *     - true si la requête réussie, false sinon.
  */
 function spip_sqlite_create(
@@ -517,7 +517,7 @@ function spip_sqlite_create_base($nom, $serveur = '', $option = true) {
  * @param string $nom
  *    Nom de la vue a creer
  * @param string $query_select
- *     Texte de la requête de sélection servant de base à la vue
+ *     texte de la requête de sélection servant de base à la vue
  * @param string $serveur
  *     Nom du connecteur
  * @param bool $requeter
@@ -667,7 +667,7 @@ function spip_sqlite_count($r, $serveur = '', $requeter = true) {
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return int|bool|string
- *     - String Texte de la requête si demandé
+ *     - string texte de la requête si demandé
  *     - int Nombre de lignes
  *     - false si la requête a échouée
  **/
@@ -708,8 +708,8 @@ function spip_sqlite_countsel(
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return bool|string
  *     - int : nombre de suppressions réalisées,
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function spip_sqlite_delete($table, $where = '', $serveur = '', $requeter = true) {
 	$res = spip_sqlite_query(
@@ -741,7 +741,7 @@ function spip_sqlite_delete($table, $where = '', $serveur = '', $requeter = true
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return bool|string
- *     - string Texte de la requête si demandé
+ *     - string texte de la requête si demandé
  *     - true si la requête a réussie, false sinon
  */
 function spip_sqlite_drop_table($table, $exist = '', $serveur = '', $requeter = true) {
@@ -765,7 +765,7 @@ function spip_sqlite_drop_table($table, $exist = '', $serveur = '', $requeter =
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return bool|string
- *     - string Texte de la requête si demandé
+ *     - string texte de la requête si demandé
  *     - true si la requête a réussie, false sinon
  */
 function spip_sqlite_drop_view($view, $exist = '', $serveur = '', $requeter = true) {
@@ -891,7 +891,7 @@ function spip_sqlite_errno($serveur = '') {
 /**
  * Retourne une explication de requête (Explain) SQLite
  *
- * @param string $query Texte de la requête
+ * @param string $query texte de la requête
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return array|string|bool
@@ -1068,8 +1068,8 @@ function spip_sqlite_in($val, $valeurs, $not = '', $serveur = '', $requeter = tr
  *     Exécuter la requête, sinon la retourner
  * @return bool|string|int|array
  *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur,
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur,
  *     - Tableau de description de la requête et du temps d'exécution, si var_profile activé
  **/
 function spip_sqlite_insert($table, $champs, $valeurs, $desc = [], $serveur = '', $requeter = true) {
@@ -1107,8 +1107,8 @@ function spip_sqlite_insert($table, $champs, $valeurs, $desc = [], $serveur = ''
  *     Exécuter la requête, sinon la retourner
  * @return bool|string|int|array
  *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur,
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur,
  *     - Tableau de description de la requête et du temps d'exécution, si var_profile activé
  **/
 function spip_sqlite_insertq($table, $couples = [], $desc = [], $serveur = '', $requeter = true) {
@@ -1152,9 +1152,9 @@ function spip_sqlite_insertq($table, $couples = [], $desc = [], $serveur = '', $
  * @param bool $requeter
  *     Exécuter la requête, sinon la retourner
  * @return bool|string
- *     - True en cas de succès,
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - true en cas de succès,
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function spip_sqlite_insertq_multi($table, $tab_couples = [], $desc = [], $serveur = '', $requeter = true) {
 	if (!$desc) {
@@ -1323,7 +1323,7 @@ function spip_sqlite_listdbs($serveur = '', $requeter = true) {
  *
  * @param string $objet Colonne ayant le texte
  * @param string $lang Langue à extraire
- * @return string       Texte de sélection pour la requête
+ * @return string       texte de sélection pour la requête
  */
 function spip_sqlite_multi($objet, $lang) {
 	$r = 'EXTRAIRE_MULTI(' . $objet . ", '" . $lang . "') AS multi";
@@ -1362,7 +1362,7 @@ function spip_sqlite_optimize($table, $serveur = '', $requeter = true) {
  * mais pour SQLite avec ses spécificités
  *
  * @param string|array|number $v
- *     Texte, nombre ou tableau à échapper
+ *     texte, nombre ou tableau à échapper
  * @param string $type
  *     Description du type attendu
  *    (par exemple description SQL de la colonne recevant la donnée)
@@ -1481,8 +1481,8 @@ function spip_sqlite_repair($table, $serveur = '', $requeter = true) {
  *     Exécuter la requête, sinon la retourner
  * @return bool|string
  *     - true si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function spip_sqlite_replace($table, $couples, $desc = [], $serveur = '', $requeter = true) {
 	if (!$desc) {
@@ -1528,8 +1528,8 @@ function spip_sqlite_replace($table, $couples, $desc = [], $serveur = '', $reque
  *     Exécuter la requête, sinon la retourner
  * @return bool|string
  *     - true si réussite
- *     - Texte de la requête si demandé,
- *     - False en cas d'erreur.
+ *     - texte de la requête si demandé,
+ *     - false en cas d'erreur.
  **/
 function spip_sqlite_replace_multi($table, $tab_couples, $desc = [], $serveur = '', $requeter = true) {
 
@@ -1560,7 +1560,7 @@ function spip_sqlite_replace_multi($table, $tab_couples, $desc = [], $serveur =
  * @param string $serveur Nom de la connexion
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return array|bool|resource|string
- *     - string : Texte de la requête si on ne l'exécute pas
+ *     - string : texte de la requête si on ne l'exécute pas
  *     - ressource si requête exécutée, ressource pour fetch()
  *     - false si la requête exécutée a ratée
  *     - array  : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
@@ -1747,7 +1747,7 @@ define('_SQLITE_RE_SHOW_TABLE', '/^[^(),]*\(((?:[^()]*\((?:[^()]*\([^()]*\))?[^(
  * @param bool $requeter Exécuter la requête, sinon la retourner
  * @return array|string
  *     - chaîne vide si pas de description obtenue
- *     - string Texte de la requête si demandé
+ *     - string texte de la requête si demandé
  *     - array description de la table sinon
  */
 function spip_sqlite_showtable($nom_table, $serveur = '', $requeter = true) {
@@ -2053,9 +2053,9 @@ function _sqlite_link($serveur = '') {
 /**
  * Renvoie les bons echappements (mais pas sur les fonctions comme NOW())
  *
- * @param string|number $v Texte ou nombre à échapper
+ * @param string|number $v texte ou nombre à échapper
  * @param string $type Type de donnée attendue, description SQL de la colonne de destination
- * @return string|number     Texte ou nombre échappé
+ * @return string|number     texte ou nombre échappé
  */
 function _sqlite_calculer_cite($v, $type) {
 	if ($type) {
@@ -2119,7 +2119,7 @@ function _sqlite_calculer_cite($v, $type) {
  * @param string $expression Mot clé de l'expression, tel que "WHERE" ou "ORDER BY"
  * @param array|string $v Données de l'expression
  * @param string $join Si les données sont un tableau, elles seront groupées par cette jointure
- * @return string            Texte de l'expression, une partie donc, du texte la requête.
+ * @return string            texte de l'expression, une partie donc, du texte la requête.
  */
 function _sqlite_calculer_expression($expression, $v, $join = 'AND') {
 	if (empty($v)) {
@@ -2148,8 +2148,8 @@ function _sqlite_calculer_expression($expression, $v, $join = 'AND') {
  * @note
  *   Pas besoin de conversion pour 0+x comme il faudrait pour mysql.
  *
- * @param string|array $orderby Texte du orderby à préparer
- * @return string Texte du orderby préparé
+ * @param string|array $orderby texte du orderby à préparer
+ * @return string texte du orderby préparé
  */
 function _sqlite_calculer_order($orderby) {
 	return (is_array($orderby)) ? join(', ', $orderby) : $orderby;
@@ -2194,7 +2194,7 @@ function _sqlite_calculer_select_as($args) {
  *
  * @param array|string $v
  *     Description des contraintes
- *     - string : Texte du where
+ *     - string : texte du where
  *     - sinon tableau : A et B peuvent être de type string ou array,
  *       OP et C sont de type string :
  *       - array(A) : A est le texte du where
@@ -2863,7 +2863,7 @@ class spip_sqlite {
  */
 
 class sqlite_requeteur {
-	/** @var string Texte de la requête */
+	/** @var string texte de la requête */
 	public $query = ''; // la requete
 	/** @var string Nom de la connexion */
 	public $serveur = '';
@@ -2987,7 +2987,7 @@ class sqlite_requeteur {
  * (fonction pour proteger les textes)
  */
 class sqlite_traducteur {
-	/** @var string $query Texte de la requête */
+	/** @var string $query texte de la requête */
 	public $query = '';
 	/** @var string $prefixe Préfixe des tables */
 	public $prefixe = '';
@@ -3160,7 +3160,7 @@ class sqlite_traducteur {
 	 * par `DATE ... strtotime`
 	 *
 	 * @param array $matches Captures
-	 * @return string Texte de date compris par SQLite
+	 * @return string texte de date compris par SQLite
 	 */
 	public function _remplacerDateParTime($matches) {
 		$op = strtoupper($matches[1] == 'ADD') ? '+' : '-';
@@ -3173,7 +3173,7 @@ class sqlite_traducteur {
 	 * par `CASE WHEN table=i THEN n ... ELSE 0 END`
 	 *
 	 * @param array $matches Captures
-	 * @return string Texte de liste ordonnée compris par SQLite
+	 * @return string texte de liste ordonnée compris par SQLite
 	 */
 	public function _remplacerFieldParCase($matches) {
 		$fields = substr($matches[0], 6, -1); // ne recuperer que l'interieur X de field(X)
diff --git a/ecrire/src/Core/Balise.php b/ecrire/src/Core/Balise.php
new file mode 100644
index 0000000000000000000000000000000000000000..0d2d832497d9ad564cfb9c4d105603ccc48c58e3
--- /dev/null
+++ b/ecrire/src/Core/Balise.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Spip\Core;
+
+/**
+ * Description d'une Balise.
+ */
+class Balise
+{
+	/** Type de noeud */
+	public string $type = 'balise';
+
+	/** Nom du champ demandé. Exemple 'ID_ARTICLE' */
+	public ?string $nom_champ;
+
+	/** Identifiant de la boucle parente si explicité */
+	public ?string $nom_boucle = '';
+
+	/**
+	 * Partie optionnelle avant
+	 *
+	 * @var null|string|array
+	 */
+	public $avant;
+
+	/**
+	 * Partie optionnelle après
+	 *
+	 * @var null|string|array
+	 */
+	public $apres;
+
+	/**
+	 * Étoiles : annuler des automatismes
+	 *
+	 * - '*' annule les filtres automatiques
+	 * - '**' annule en plus les protections de scripts
+	 */
+	public ?string $etoile;
+
+	/**
+	 * Arguments et filtres explicites sur la balise
+	 *
+	 * - $param[0] contient les arguments de la balise
+	 * - $param[1..n] contient les filtres à appliquer à la balise
+	 *
+	 * FIXME: type unique.
+	 * @var false|array
+	 *     - false: erreur de syntaxe
+	 */
+	public $param = [];
+
+	/** Source des filtres (compatibilité) (?) */
+	public array $fonctions = [];
+
+	/**
+	 * Identifiant de la boucle
+	 *
+	 * @var string
+	 */
+	public $id_boucle = '';
+
+	/**
+	 * AST du squelette, liste de toutes les boucles
+	 *
+	 * @var Boucle[]
+	 */
+	public array $boucles;
+
+	/** Alias de table d'application de la requête ou nom complet de la table SQL */
+	public ?string $type_requete;
+
+	/** Résultat de la compilation: toujours une expression PHP */
+	public string $code = '';
+
+	/**
+	 * Interdire les scripts
+	 *
+	 * false si on est sûr de cette balise
+	 *
+	 * @see interdire_scripts()
+	 */
+	public bool $interdire_scripts = true;
+
+	/**
+	 * Description du squelette
+	 *
+	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
+	 *
+	 * Peut contenir les index :
+	 *
+	 * - nom : Nom du fichier de cache
+	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
+	 * - sourcefile : Chemin du squelette
+	 * - squelette : Code du squelette
+	 * - id_mere : Identifiant de la boucle parente
+	 * - documents : Pour embed et img dans les textes
+	 * - session : Pour un cache sessionné par auteur
+	 * - niv : Niveau de tabulation
+	 */
+	public array $descr = [];
+
+	/** Numéro de ligne dans le code source du squelette*/
+	public int $ligne = 0;
+
+	/** Drapeau pour reperer les balises calculées par une fonction explicite */
+	public bool $balise_calculee = false;
+}
diff --git a/ecrire/src/Core/Boucle.php b/ecrire/src/Core/Boucle.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c08c5bb63399cb591d67d4daf4fe51b9de939b0
--- /dev/null
+++ b/ecrire/src/Core/Boucle.php
@@ -0,0 +1,373 @@
+<?php
+
+namespace Spip\Core;
+
+/**
+ * Description d'une boucle
+ */
+class Boucle
+{
+	/** Type de noeud */
+	public string $type = 'boucle';
+
+	/** Identifiant de la boucle */
+	public string $id_boucle;
+
+	/** Identifiant de la boucle parente */
+	public string $id_parent = '';
+
+	/** Un nom explicite qui peut être affecté manuellement à certaines boucles générées */
+	public string $nom = '';
+
+	/**
+	 * Partie avant toujours affichee
+	 *
+	 * @var string|array
+	 */
+	public $preaff = '';
+
+	/**
+	 * Partie optionnelle avant
+	 *
+	 * @var string|array
+	 */
+	public $avant = '';
+
+	/**
+	 * Pour chaque élément
+	 *
+	 * @var string|array
+	 */
+	public $milieu = '';
+
+	/**
+	 * Partie optionnelle après
+	 *
+	 * @var string|array
+	 */
+	public $apres = '';
+
+	/**
+	 * Partie alternative, si pas de résultat dans la boucle
+	 *
+	 * @var string|array
+	 */
+	public $altern = '';
+
+	/**
+	 * Partie apres toujours affichee
+	 *
+	 * @var string|array
+	 */
+	public $postaff = '';
+
+
+	/**
+	 * La boucle doit-elle sélectionner la langue ?
+	 *
+	 * Valeurs : '', 'oui', 'non'
+	 */
+	public string $lang_select = '';
+
+	/**
+	 * Alias de table d'application de la requête ou nom complet de la table SQL
+	 *
+	 * FIXME: un seul typage (string ?)
+	 *
+	 * @var string|false|null
+	 */
+	public $type_requete = null;
+
+	/**
+	 * La table est elle optionnelle ?
+	 *
+	 * Si oui, aucune erreur ne sera générée si la table demandée n'est pas présente
+	 */
+	public bool $table_optionnelle = false;
+
+	/**
+	 * Nom du fichier de connexion
+	 */
+	public string $sql_serveur = '';
+
+	/**
+	 * Paramètres de la boucle
+	 *
+	 * Description des paramètres passés à la boucle, qui servent ensuite
+	 * au calcul des critères
+	 *
+	 * FIXME: type unique.
+	 * @var false|array
+	 *     - false: erreur de syntaxe
+	 */
+	public $param = [];
+
+	/**
+	 * Critères de la boucle
+	 *
+	 * FIXME: type array unique.
+	 *
+	 * @var string|Critere[]
+	 * - string: phrasage (code brut). Il reste si erreur de critère
+	 * - array: analyse correcte des critères...
+	 */
+	public $criteres = [];
+
+	/**
+	 * Textes insérés entre 2 éléments de boucle (critère inter)
+	 *
+	 * @var string[]
+	 */
+	public array $separateur = [];
+
+	/**
+	 * Liste des jointures possibles avec cette table
+	 *
+	 * Les jointures par défaut de la table sont complétées en priorité
+	 * des jointures déclarées explicitement sur la boucle
+	 *
+	 * @see base_trouver_table_dist()
+	 */
+	public array $jointures = [];
+
+	/**
+	 * Jointures explicites avec cette table
+	 *
+	 * Ces jointures sont utilisées en priorité par rapport aux jointures
+	 * normales possibles pour retrouver les colonnes demandées extérieures
+	 * à la boucle.
+	 *
+	 * @var string|bool
+	 */
+	public $jointures_explicites = false;
+
+	/**
+	 * Nom de la variable PHP stockant le noms de doublons utilisés "$doublons_index"
+	 */
+	public string $doublons = '';
+
+	/**
+	 * Code PHP ajouté au début de chaque itération de boucle.
+	 *
+	 * Utilisé entre autre par les critères {pagination}, {n-a,b}, {a/b}...
+	 */
+	public string $partie = '';
+
+	/**
+	 * Nombre de divisions de la boucle, d'éléments à afficher,
+	 * ou de soustractions d'éléments à faire
+	 *
+	 * Dans les critères limitant le nombre d'éléments affichés
+	 * {a,b}, {a,n-b}, {a/b}, {pagination b}, b est affecté à total_parties.
+	 */
+	public string $total_parties = '';
+
+	/**
+	 * Code PHP ajouté avant l'itération de boucle.
+	 *
+	 * Utilisé entre autre par les critères {pagination}, {a,b}, {a/b}
+	 * pour initialiser les variables de début et de fin d'itération.
+	 */
+	public string $mode_partie = '';
+
+	/**
+	 * Identifiant d'une boucle qui appelle celle-ci de manière récursive
+	 *
+	 * Si une boucle est appelée de manière récursive quelque part par
+	 * une autre boucle comme <BOUCLE_rec(boucle_identifiant) />, cette
+	 * boucle (identifiant) reçoit dans cette propriété l'identifiant
+	 * de l'appelant (rec)
+	 */
+	public string $externe = '';
+
+	// champs pour la construction de la requete SQL
+
+	/**
+	 * Liste des champs à récupérer par la boucle
+	 *
+	 * Expression 'table.nom_champ' ou calculée 'nom_champ AS x'
+	 *
+	 * @var string[]
+	 */
+	public array $select = [];
+
+	/**
+	 * Liste des alias / tables SQL utilisées dans la boucle
+	 *
+	 * L'index est un identifiant (xx dans spip_xx assez souvent) qui servira
+	 * d'alias au nom de la table ; la valeur est le nom de la table SQL désirée.
+	 *
+	 * L'index 0 peut définir le type de sources de données de l'itérateur DATA
+	 *
+	 * @var string[]
+	 */
+	public array $from = [];
+
+	/**
+	 * Liste des alias / type de jointures utilisées dans la boucle
+	 *
+	 * L'index est le nom d'alias (comme pour la propriété $from), et la valeur
+	 * un type de jointure parmi 'INNER', 'LEFT', 'RIGHT', 'OUTER'.
+	 *
+	 * Lorsque le type n'est pas déclaré pour un alias, c'est 'INNER'
+	 * qui sera utilisé par défaut (créant donc un INNER JOIN).
+	 *
+	 * @var string[]
+	 */
+	public array $from_type = [];
+
+	/**
+	 * Liste des conditions WHERE de la boucle
+	 *
+	 * Permet de restreindre les éléments retournés par une boucle
+	 * en fonctions des conditions transmises dans ce tableau.
+	 *
+	 * Ce tableau peut avoir plusieurs niveaux de profondeur.
+	 *
+	 * Les éléments du premier niveau sont reliés par des AND, donc
+	 * chaque élément ajouté directement au where par
+	 * $boucle->where[] = array(...) ou $boucle->where[] = "'expression'"
+	 * est une condition AND en plus.
+	 *
+	 * Par contre, lorsqu'on indique un tableau, il peut décrire des relations
+	 * internes différentes. Soit $expr un tableau d'expressions quelconques de 3 valeurs :
+	 * $expr = array(operateur, val1, val2)
+	 *
+	 * Ces 3 valeurs sont des expressions PHP. L'index 0 désigne l'opérateur
+	 * à réaliser tel que :
+	 *
+	 * - "'='" , "'>='", "'<'", "'IN'", "'REGEXP'", "'LIKE'", ... :
+	 *    val1 et val2 sont des champs et valeurs à utiliser dans la comparaison
+	 *    suivant cet ordre : "val1 operateur val2".
+	 *    Exemple : $boucle->where[] = array("'='", "'articles.statut'", "'\"publie\"'");
+	 * - "'AND'", "'OR'", "'NOT'" :
+	 *    dans ce cas val1 et val2 sont également des expressions
+	 *    de comparaison complètes, et peuvent être eux-même des tableaux comme $expr
+	 *    Exemples :
+	 *    $boucle->where[] = array("'OR'", $expr1, $expr2);
+	 *    $boucle->where[] = array("'NOT'", $expr); // val2 n'existe pas avec NOT
+	 *
+	 * D'autres noms sont possibles pour l'opérateur (le nombre de valeurs diffère) :
+	 * - "'SELF'", "'SUBSELECT'" : indiquent des sous requêtes
+	 * - "'?'" : indique une condition à faire évaluer (val1 ? val2 : val3)
+	 */
+	public array $where = [];
+
+	public array $join = [];
+	public array $having = [];
+	public $limit = '';
+	public array $group = [];
+	public array $order = [];
+	public array $default_order = [];
+	public string $date = 'date';
+	public string $hash = '';
+	public $in = '';
+	public bool $sous_requete = false;
+
+	/**
+	 * Code PHP qui sera ajouté en tout début de la fonction de boucle
+	 *
+	 * Il sert à insérer le code calculant une hierarchie
+	 */
+	public string $hierarchie = '';
+
+	// champs pour la construction du corps PHP
+
+	/**
+	 * Description des sources de données de la boucle
+	 *
+	 * Description des données de la boucle issu de trouver_table
+	 * dans le cadre de l'itérateur SQL et contenant au moins l'index 'field'.
+	 *
+	 * @see base_trouver_table_dist()
+	 */
+	public array $show = [];
+
+	/**
+	 * Nom de la table SQL principale de la boucle, sans son préfixe
+	 */
+	public string $id_table = '';
+
+	/**
+	 * Nom de la clé primaire de la table SQL principale de la boucle
+	 */
+	public string $primary = '';
+
+	/**
+	 * Code PHP compilé de la boucle
+	 *
+	 * FIXME: un seul type (string ?)
+	 *
+	 * - false: boucle fautive ?
+	 *
+	 * @var string|false
+	 */
+	public $return = '';
+
+	public $numrows = false;
+	public $cptrows = false;
+
+	/**
+	 * Description du squelette
+	 *
+	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
+	 *
+	 * Peut contenir les index :
+	 *
+	 * - nom : Nom du fichier de cache
+	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
+	 * - sourcefile : Chemin du squelette
+	 * - squelette : Code du squelette
+	 * - id_mere : Identifiant de la boucle parente
+	 * - documents : Pour embed et img dans les textes
+	 * - session : Pour un cache sessionné par auteur
+	 * - niv : Niveau de tabulation
+	 */
+	public array $descr = [];
+
+	/** Numéro de ligne dans le code source du squelette */
+	public int $ligne = 0;
+
+
+	/**
+	 * table pour stocker les modificateurs de boucle tels que tout, plat ...,
+	 * utilisable par les plugins egalement
+	 *
+	 * @var array<string, mixed>
+	 */
+	public array $modificateur = [];
+
+	/**
+	 * Type d'itérateur utilisé pour cette boucle
+	 *
+	 * - 'SQL' dans le cadre d'une boucle sur une table SQL
+	 * - 'DATA' pour l'itérateur DATA, ...
+	 *
+	 * @var string
+	 */
+	public string $iterateur = ''; // type d'iterateur
+
+	/**
+	 * @var array $debug Textes qui seront insérés dans l’entête de boucle du mode debug
+	 */
+	public array $debug = [];
+
+	/**
+	 * Index de la boucle dont le champ présent dans cette boucle est originaire,
+	 * notamment si le champ a été trouve dans une boucle parente
+	 *
+	 * Tableau nom du champ => index de boucle
+	*/
+	public array $index_champ = [];
+
+	/** Résultat de la compilation (?) (sert au débusqueur) */
+	public string $code = '';
+
+	/** Source des filtres (compatibilité) (?) */
+	public array $fonctions = [];
+
+	// obsoletes, conserves provisoirement pour compatibilite
+	public $tout = false;
+	public $plat = false;
+	public $lien = false;
+}
diff --git a/ecrire/src/Core/Champ.php b/ecrire/src/Core/Champ.php
new file mode 100644
index 0000000000000000000000000000000000000000..5b20df21ff92e2442baad238259eebbfdabc5829
--- /dev/null
+++ b/ecrire/src/Core/Champ.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Spip\Core;
+
+/**
+ * Retro compatiblité un Champ, c'est une Balise.
+ */
+class Champ extends Balise
+{
+	/**
+	 * {@inheritDoc}
+	 */
+	public string $type = 'champ';
+}
diff --git a/ecrire/src/Core/Contexte.php b/ecrire/src/Core/Contexte.php
new file mode 100644
index 0000000000000000000000000000000000000000..985b228e67b9d6efda539f732e228753488d6ab2
--- /dev/null
+++ b/ecrire/src/Core/Contexte.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Spip\Core;
+
+/**
+ * Description d'un contexte de compilation
+ *
+ * Objet simple pour stocker le nom du fichier, la ligne, la boucle
+ * permettant entre autre de localiser le lieu d'une erreur de compilation.
+ * Cette structure est nécessaire au traitement d'erreur à l'exécution.
+ *
+ * Le champ code est inutilisé dans cette classe seule, mais harmonise
+ * le traitement d'erreurs.
+ */
+class Contexte
+{
+	/**
+	 * Description du squelette
+	 *
+	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
+	 *
+	 * Peut contenir les index :
+	 *
+	 * - nom : Nom du fichier de cache
+	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
+	 * - sourcefile : Chemin du squelette
+	 * - squelette : Code du squelette
+	 * - id_mere : Identifiant de la boucle parente
+	 * - documents : Pour embed et img dans les textes
+	 * - session : Pour un cache sessionné par auteur
+	 * - niv : Niveau de tabulation
+	 */
+	public array $descr = [];
+
+	/** Identifiant de la boucle */
+	public string $id_boucle = '';
+
+	/** Numéro de ligne dans le code source du squelette */
+	public int $ligne = 0;
+
+	/** Langue d'exécution */
+	public string $lang = '';
+
+	/** Résultat de la compilation: toujours une expression PHP */
+	public string $code = '';
+}
diff --git a/ecrire/src/Core/Critere.php b/ecrire/src/Core/Critere.php
new file mode 100644
index 0000000000000000000000000000000000000000..5c703ed119e29500587c6e01623c9cb52cd69310
--- /dev/null
+++ b/ecrire/src/Core/Critere.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Spip\Core;
+
+/**
+ * Description d'un critère de boucle.
+ *
+ * Sous-noeud de Boucle
+ **/
+class Critere {
+	/** Type de noeud */
+	public string $type = 'critere';
+
+	/** Opérateur (>, <, >=, IN, ...) */
+	public ?string $op;
+
+	/** Présence d'une négation (truc !op valeur) */
+	public bool $not = false;
+
+	/** Présence d'une exclusion (!truc op valeur) */
+	public string $exclus = '';
+
+	/** Présence d'une condition dans le critère (truc ?) */
+	public bool $cond = false;
+
+	/**
+	 * Paramètres du critère
+	 * - $param[0] : élément avant l'opérateur
+	 * - $param[1..n] : éléments après l'opérateur
+	 *
+	 * FIXME: type unique.
+	 * @var false|array
+	 *     - false: erreur de syntaxe
+	 */
+	public $param = [];
+
+	/** Numéro de ligne dans le code source du squelette */
+	public int $ligne = 0;
+}
diff --git a/ecrire/src/Core/Idiome.php b/ecrire/src/Core/Idiome.php
new file mode 100644
index 0000000000000000000000000000000000000000..c10d8c71a794f5c466acfc879c60d62a8c04cf10
--- /dev/null
+++ b/ecrire/src/Core/Idiome.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Spip\Core;
+
+/**
+ * Description d'une chaîne de langue
+ **/
+class Idiome
+{
+	/** Type de noeud */
+	public string $type = 'idiome';
+
+	/** Clé de traduction demandée. Exemple 'item_oui' */
+	public string $nom_champ = '';
+
+	/** Module de langue où chercher la clé de traduction. Exemple 'medias' */
+	public string $module = '';
+
+	/** Arguments à passer à la chaîne */
+	public array $arg = [];
+
+	/**
+	 * Filtres à appliquer au résultat
+	 *
+	 *
+	 * * FIXME: type unique.
+	 * @var false|array
+	 *     - false: erreur de syntaxe
+	 */
+	public $param = [];
+
+	/** Source des filtres (compatibilité) (?) */
+	public array $fonctions = [];
+
+	/**
+	 * Inutilisé, propriété générique de l'AST
+	 *
+	 * @var string|array
+	 */
+	public $avant = '';
+
+	/**
+	 * Inutilisé, propriété générique de l'AST
+	 *
+	 * @var string|array
+	 */
+	public $apres = '';
+
+	/** Identifiant de la boucle */
+	public string $id_boucle = '';
+
+	/**
+	 * AST du squelette, liste de toutes les boucles
+	 *
+	 * @var Boucle[]
+	 */
+	public array $boucles;
+
+	/** Alias de table d'application de la requête ou nom complet de la table SQL */
+	public ?string $type_requete;
+
+	/** Résultat de la compilation: toujours une expression PHP */
+	public string $code = '';
+
+	/**
+	 * Interdire les scripts
+	 *
+	 * @see interdire_scripts()
+	 */
+	public bool $interdire_scripts = false;
+
+	/**
+	 * Description du squelette
+	 *
+	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
+	 *
+	 * Peut contenir les index :
+	 * - nom : Nom du fichier de cache
+	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
+	 * - sourcefile : Chemin du squelette
+	 * - squelette : Code du squelette
+	 * - id_mere : Identifiant de la boucle parente
+	 * - documents : Pour embed et img dans les textes
+	 * - session : Pour un cache sessionné par auteur
+	 * - niv : Niveau de tabulation
+	 */
+	public array $descr = [];
+
+	/** Numéro de ligne dans le code source du squelette */
+	public int $ligne = 0;
+}
diff --git a/ecrire/src/Core/Inclure.php b/ecrire/src/Core/Inclure.php
new file mode 100644
index 0000000000000000000000000000000000000000..c28ca0168be486ac07a12a3dc74b022907c9a644
--- /dev/null
+++ b/ecrire/src/Core/Inclure.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Spip\Core;
+
+/**
+ * Description d'une inclusion de squelette.
+ **/
+class Inclure
+{
+	/** Type de noeud */
+	public string $type = 'include';
+
+	/**
+	 * Nom d'un fichier inclu
+	 *
+	 * - Objet Texte si inclusion d'un autre squelette
+	 * - chaîne si inclusion d'un fichier PHP directement
+	 *
+	 * @var string|Texte
+	 */
+	public $texte;
+
+	/**
+	 * Inutilisé, propriété générique de l'AST
+	 *
+	 * @var string|array
+	 */
+	public $avant = '';
+
+	/**
+	 * Inutilisé, propriété générique de l'AST
+	 *
+	 * @var string|array
+	 */
+	public $apres = '';
+
+	/** Numéro de ligne dans le code source du squelette */
+	public int $ligne = 0;
+
+	/**
+	 * Valeurs des paramètres
+	 *
+	 * FIXME: type unique.
+	 * @var false|array
+	 *     - false: erreur de syntaxe
+	 */
+	public $param = [];
+
+	/** Source des filtres (compatibilité) (?) */
+	public array $fonctions = [];
+
+	/**
+	 * Description du squelette
+	 *
+	 * Sert pour la gestion d'erreur et la production de code dependant du contexte
+	 *
+	 * Peut contenir les index :
+	 *
+	 * - nom : Nom du fichier de cache
+	 * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser)
+	 * - sourcefile : Chemin du squelette
+	 * - squelette : Code du squelette
+	 * - id_mere : Identifiant de la boucle parente
+	 * - documents : Pour embed et img dans les textes
+	 * - session : Pour un cache sessionné par auteur
+	 * - niv : Niveau de tabulation
+	 */
+	public array $descr = [];
+}
diff --git a/ecrire/src/Core/Polyglotte.php b/ecrire/src/Core/Polyglotte.php
new file mode 100644
index 0000000000000000000000000000000000000000..0b0880393b97fc11246bb2614804bc4cf32f9b64
--- /dev/null
+++ b/ecrire/src/Core/Polyglotte.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Spip\Core;
+
+/**
+ * Description d'un texte polyglotte.
+ *
+ * a.k.a. <multi>
+ **/
+class Polyglotte
+{
+	/** Type de noeud */
+	public string $type = 'polyglotte';
+
+	/**
+	 * Tableau des traductions possibles classées par langue
+	 *
+	 * Tableau code de langue => texte
+	 */
+	public array $traductions = [];
+
+	/** Numéro de ligne dans le code source du squelette */
+	public int $ligne = 0;
+}
diff --git a/ecrire/src/Core/Tests/BaliseTest.php b/ecrire/src/Core/Tests/BaliseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0abb301a7068821275fae38111112e86ccb54721
--- /dev/null
+++ b/ecrire/src/Core/Tests/BaliseTest.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Spip\Core\Tests;
+
+class BaliseTest
+{
+
+}
diff --git a/ecrire/src/Core/Texte.php b/ecrire/src/Core/Texte.php
new file mode 100644
index 0000000000000000000000000000000000000000..3eb1bad8180d4cd49365ef751f3ab3ccb2b4fdf9
--- /dev/null
+++ b/ecrire/src/Core/Texte.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Spip\Core;
+
+/**
+ * Description d'un texte.
+ **/
+class Texte
+{
+	/** Type de noeud */
+	public string $type = 'texte';
+
+	/** Le texte */
+	public string $texte;
+
+	/**
+	 * Contenu avant le texte.
+	 *
+	 * Vide ou apostrophe simple ou double si le texte en était entouré
+	 *
+	 * @var string|array
+	 */
+	public $avant = '';
+
+	/**
+	 * Contenu après le texte.
+	 *
+	 * Vide ou apostrophe simple ou double si le texte en était entouré
+	 *
+	 * @var string|array
+	 */
+	public $apres = '';
+
+	/** Numéro de ligne dans le code source du squelette */
+	public int $ligne = 0;
+}