Newer
Older
/***************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2008 *
* Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
* Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
\***************************************************************************/
if (!defined("_ECRIRE_INC_VERSION")) return;
//
// Gerer les variables de personnalisation, qui peuvent provenir
// des fichiers d'appel, en verifiant qu'elles n'ont pas ete passees
// par le visiteur (sinon, pas de cache)
//
// http://doc.spip.org/@tester_variable
function tester_variable($var, $val){
if (!isset($GLOBALS[$var]))
return $GLOBALS[$var] = $val;
if (
isset($_REQUEST[$var])
AND $GLOBALS[$var] == $_REQUEST[$var]
)
die ("tester_variable: $var interdite");
return $GLOBALS[$var];
// Init des globales reglant la typo de propre
// voir aussi traiter_raccourci_glossaire et traiter_raccourci_notes
// Retourne aussi un double tableau raccourci / texte-clair les utilisant.
global $class_spip, $class_spip_plus;
// class_spip : savoir si on veut class="spip" sur p i strong & li
// class_spip_plus : class="spip" sur les ul ol h3 hr quote table...
// la difference c'est que des css specifiques existent pour les seconds
tester_variable('class_spip', ''); /*' class="spip"'*/
tester_variable('class_spip_plus', ' class="spip"');
tester_variable('toujours_paragrapher', true);
return array(array(
/* 0 */ "/\n(----+|____+)/S",
/* 1 */ "/\n-- */S",
/* 2 */ "/\n- */S", /* DOIT rester a cette position */
/* 3 */ "/\n_ +/S",
/* 4 */ "/(^|[^{])[{][{][{]/S",
/* 5 */ "/[}][}][}]($|[^}])/S",
/* 6 */ "/(( *)\n){2,}(<br\s*\/?".">)?/S",
/* 7 */ "/[{][{]/S",
/* 8 */ "/[}][}]/S",
/* 9 */ "/[{]/S",
/* 10 */ "/[}]/S",
/* 11 */ "/(?:<br\s*\/?".">){2,}/S",
/* 12 */ "/<p>\n*(?:<br\s*\/?".">\n*)*/S",
/* 13 */ "/<quote>/S",
/* 14 */ "/<\/quote>/S",
/* 15 */ "/<\/?intro>/S"
),
array(
/* 0 */ "\n\n" . tester_variable('ligne_horizontale', "\n<hr$class_spip_plus />\n") . "\n\n",
/* 1 */ "\n<br />— ",
/* 3 */ "\n<br />",
/* 4 */ "\$1\n\n" . tester_variable('debut_intertitre', "\n<h3$class_spip_plus>"),
/* 5 */ tester_variable('fin_intertitre', "</h3>\n") ."\n\n\$1",
/* 6 */ "<p>",
/* 7 */ tester_variable('debut_gras', "<strong$class_spip>"),
/* 8 */ tester_variable('fin_gras', '</strong>'),
/* 9 */ tester_variable('debut_italique', "<i$class_spip>"),
/* 10 */ tester_variable('fin_italique', '</i>'),
/* 11 */ "<p>",
/* 12 */ "<p>",
/* 13 */ "<blockquote$class_spip_plus><p>",
/* 14 */ "</blockquote><p>",
/* 15 */ ""
)
);
}
// On initialise la puce pour eviter find_in_path() a chaque rencontre de \n-
// Mais attention elle depend de la direction et de X_fonctions.php, ainsi que
// de l'espace choisi (public/prive)
// http://doc.spip.org/@definir_puce
function definir_puce() {
// Attention au sens, qui n'est pas defini de la meme facon dans
// l'espace prive (spip_lang est la langue de l'interface, lang_dir
// celle du texte) et public (spip_lang est la langue du texte)
$dir = _DIR_RESTREINT ? lang_dir() : lang_dir($GLOBALS['spip_lang']);
$p = 'puce' . (test_espace_prive() ? '_prive' : '');
if ($dir == 'rtl') $p .= '_rtl';
tester_variable($p, 'AUTO');
if ($GLOBALS[$p] == 'AUTO') {
$img = find_in_path($p.'.gif');
list(,,,$size) = @getimagesize($img);
$GLOBALS[$p] = '<img src="'.$img.'" '
.$size.' alt="-" />';
}
return $GLOBALS[$p];
// Diverses fonctions essentielles
// XHTML - Preserver les balises-bloc : on liste ici tous les elements
// dont on souhaite qu'ils provoquent un saut de paragraphe
define('_BALISES_BLOCS',
.'t(able|[rdh]|body|foot|extarea)|'
.'form|object|center|marquee|address|'
.'d[ltd]|script|noscript|map|button|fieldset');
// Ne pas afficher le chapo si article virtuel
// http://doc.spip.org/@nettoyer_chapo
return (substr($chapo,0,1) == "=") ? '' : $chapo;
//
// Echapper les les elements perilleux en les passant en base64
//
// Creer un bloc base64 correspondant a $rempl ; au besoin en marquant
// une $source differente ; le script detecte automagiquement si ce qu'on
// echappe est un div ou un span
// http://doc.spip.org/@code_echappement
function code_echappement($rempl, $source='', $no_transform=false) {
if (!strlen($rempl)) return '';
// Tester si on echappe en span ou en div
$mode = preg_match(',</?('._BALISES_BLOCS.')[>[:space:]],iS', $rempl) ?
'div' : 'span';
$return = '';
// Decouper en morceaux, base64 a des probleme selon la taille de la pile
$taille = 30000;
for($i = 0; $i < strlen($rempl); $i += $taille) {
// Convertir en base64
$base64 = base64_encode(substr($rempl, $i, $taille));
$return .= inserer_attribut("<$mode class=\"base64$source\">",
'title', $base64) ."</$mode>";
}
Fil
a validé
return $return
. ((!$no_transform AND $mode == 'div')
? "\n\n"
: ''
);
;
Fil
a validé
// Echapper les <html>...</ html>
Christian Lefebvre
a validé
// http://doc.spip.org/@traiter_echap_html_dist
Fil
a validé
function traiter_echap_html_dist($regs) {
return $regs[3];
}
// Echapper les <code>...</ code>
Christian Lefebvre
a validé
// http://doc.spip.org/@traiter_echap_code_dist
Fil
a validé
function traiter_echap_code_dist($regs) {
$echap = htmlspecialchars($regs[3]); // il ne faut pas passer dans entites_html, ne pas transformer les &#xxx; du code !
Fil
a validé
// ne pas mettre le <div...> s'il n'y a qu'une ligne
if (is_int(strpos($echap,"\n"))) {
// supprimer les sauts de ligne debut/fin
// (mais pas les espaces => ascii art).
$echap = preg_replace("/^[\n\r]+|[\n\r]+$/s", "", $echap);
$echap = nl2br($echap);
$echap = "<div style='text-align: left;' "
Fil
a validé
. "class='spip_code' dir='ltr'><code>"
.$echap."</code></div>";
} else {
Fil
a validé
$echap = "<code class='spip_code' "
."dir='ltr'>".$echap."</code>";
Fil
a validé
$echap = str_replace("\t",
" ", $echap);
$echap = str_replace(" ", " ", $echap);
return $echap;
}
// Echapper les <cadre>...</ cadre> aka <frame>...</ frame>
Christian Lefebvre
a validé
// http://doc.spip.org/@traiter_echap_cadre_dist
Fil
a validé
function traiter_echap_cadre_dist($regs) {
Fil
a validé
$echap = trim(entites_html($regs[3]));
$n = substr_count($echap, "\n") + 1;
$echap = "\n<textarea readonly='readonly' cols='40' rows='$n' class='spip_cadre' dir='ltr'>$echap</textarea>";
return generer_form_ecrire('', $echap, " method='get'");
Fil
a validé
}
Christian Lefebvre
a validé
// http://doc.spip.org/@traiter_echap_frame_dist
Fil
a validé
function traiter_echap_frame_dist($regs) {
return traiter_echap_cadre_dist($regs);
}
Christian Lefebvre
a validé
// http://doc.spip.org/@traiter_echap_script_dist
Fil
a validé
function traiter_echap_script_dist($regs) {
// rendre joli (et inactif) si c'est un script language=php
if (preg_match(',<script\b[^>]+php,ims',
$regs[0]))
return highlight_string($regs[0],true);
// Cas normal : le script passe tel quel
Fil
a validé
return $regs[0];
}
// - pour $source voir commentaire infra (echappe_retour)
// - pour $no_transform voir le filtre post_autobr dans inc_filtres.php3
// http://doc.spip.org/@echappe_html
function echappe_html($letexte, $source='', $no_transform=false,
if (!is_string($letexte) or !strlen($letexte))
return $letexte;
if (!$preg) $preg = ',<(html|code|cadre|frame|script)'
.'(\s[^>]*)?'
.'>(.*)</\1>,UimsS';
$letexte, $matches, PREG_SET_ORDER))
foreach ($matches as $regs) {
// echappements tels quels ?
if ($no_transform) {
$echap = $regs[0];
// sinon les traiter selon le cas
Fil
a validé
else if (function_exists($f = 'traiter_echap_'.strtolower($regs[1])))
$echap = $f($regs);
else if (function_exists($f = $f.'_dist'))
$echap = $f($regs);
$letexte = str_replace($regs[0],
code_echappement($echap, $source, $no_transform),
if ($no_transform)
return $letexte;
// Gestion du TeX
if (strpos($letexte, "<math>") !== false) {
$letexte = traiter_math($letexte, $source);
// Echapper le php pour faire joli (ici, c'est pas pour la securite)
if (preg_match_all(
',<[?].*($|[?]>),UisS',
$letexte, $matches, PREG_SET_ORDER))
foreach ($matches as $regs) {
$letexte = str_replace($regs[0],
code_echappement(highlight_string($regs[0],true), $source),
$letexte);
}
// Traitement final des echappements
// Rq: $source sert a faire des echappements "a soi" qui ne sont pas nettoyes
// par propre() : exemple dans ecrire/inc_articles_ortho.php, $source='ORTHO'
// ou encore dans typo()
// http://doc.spip.org/@echappe_retour
function echappe_retour($letexte, $source='', $filtre = "") {
Fil
a validé
if (strpos($letexte,"base64$source")) {
# spip_log(htmlspecialchars($letexte)); ## pour les curieux
if (preg_match_all(
',<(span|div) class=[\'"]base64'.$source.'[\'"]\s.*>\s*</\1>,UmsS',
$letexte, $regs, PREG_SET_ORDER)) {
foreach ($regs as $reg) {
Fil
a validé
$rempl = base64_decode(extraire_attribut($reg[0], 'title'));
if ($filtre) $rempl = $filtre($rempl);
$letexte = str_replace($reg[0], $rempl, $letexte);
}
}
}
return $letexte;
esj
a validé
}
// Reinserer le javascript de confiance (venant des modeles)
function echappe_retour_modeles($letexte)
{
$letexte = echappe_retour($letexte);
// Dans l'espace prive, securiser ici
if (!_DIR_RESTREINT)
$letexte = interdire_scripts($letexte);
return trim($letexte);
}
// http://doc.spip.org/@nettoyer_raccourcis_typo
function nettoyer_raccourcis_typo($texte, $connect=''){
$texte = pipeline('nettoyer_raccourcis_typo',$texte);
// remplacer les liens
if (preg_match_all(',[[]([^][]*)->(>?)([^][]*)[]],S', $texte, $regs, PREG_SET_ORDER))
foreach ($regs as $reg) {
list ($titre,,)= traiter_raccourci_lien_atts($reg[1]);
$titre = calculer_url($reg[3], $titre, 'titre', $connect);
$titre = corriger_typo(supprimer_tags($titre));
$texte = str_replace($reg[0], $titre, $texte);
}
// supprimer les notes
$texte = preg_replace(",[[][[]([^]]|[]][^]])*[]][]],UimsS","",$texte);
// supprimer les codes typos
$texte = str_replace(array('}','{'), '', $texte);
// supprimer les tableaux
$texte = preg_replace(",(^|\r)\|.*\|\r,s", "\r", $texte);
return $texte;
}
// http://doc.spip.org/@couper
function couper($texte, $taille=50, $suite = ' (...)') {
if (!strlen($texte) OR $taille <= 0) return '';
$offset = 400 + 2*$taille;
if ( $offset<strlen($texte)
&& ($p_tag_ouvrant = strpos($texte,'<',$offset))!==NULL){
$p_tag_fermant = strpos($texte,'>',$offset);
if ($p_tag_fermant<$p_tag_ouvrant)
$offset += $p_tag_fermant; // prolonger la coupe jusqu'au tag fermant suivant eventuel
}
$texte = substr($texte, 0, $offset); /* eviter de travailler sur 10ko pour extraire 150 caracteres */
// on utilise les \r pour passer entre les gouttes
Fil
a validé
$texte = str_replace("\r\n", "\n", $texte);
$texte = str_replace("\r", "\n", $texte);
// sauts de ligne et paragraphes
$texte = preg_replace("/<(p|br)( [^>]*)?".">/", "\r", $texte);
Fil
a validé
// supprimer les traits, lignes etc
$texte = preg_replace("/(^|\r|\n)(-[-#\*]*|_ )/", "\r", $texte);
Fil
a validé
// supprimer les tags
$texte = supprimer_tags($texte);
$texte = trim(str_replace("\n"," ", $texte));
$texte .= "\n"; // marquer la fin
// travailler en accents charset
$texte = unicode2charset(html2unicode($texte, /* secure */ true));
$texte = nettoyer_raccourcis_typo($texte);
// corriger la longueur de coupe
// en fonction de la presence de caracteres utf
if ($GLOBALS['meta']['charset']=='utf-8'){
$long = charset2unicode($texte);
$long = spip_substr($long, 0, max($taille,1));
$nbcharutf = preg_match_all("/(&#[0-9]{3,5};)/S",$long,$matches);
$taille += $nbcharutf;
}
// couper au mot precedent
$court = preg_replace("/([^\s][\s]+)[^\s]*\n?$/", "\\1", $long);
$points = $suite;
// trop court ? ne pas faire de (...)
$points = '';
$texte = preg_replace("/([^\s][\s]+)[^\s]*\n?$/", "\\1", $long);
// encore trop court ? couper au caractere
$texte = $long;
} else
$texte = $court;
if (strpos($texte, "\n")) // la fin est encore la : c'est qu'on n'a pas de texte de suite
$points = '';
// remettre les paragraphes
$texte = preg_replace("/\r+/", "\n\n", $texte);
$texte = preg_replace('/&#?[a-z0-9]*$/S', '', $texte);
// Les elements de propre()
// Securite : empecher l'execution de code PHP ou javascript ou autre malice
// http://doc.spip.org/@interdire_scripts
function interdire_scripts($source) {
$source = preg_replace(",<(\%|\?|/?[[:space:]]*(script|base)),imsS", "<\\1", $source);
return $source;
*/
// afficher joliment les <script>
Christian Lefebvre
a validé
// http://doc.spip.org/@echappe_js
function echappe_js($t,$class='') {
if (preg_match_all(',<script.*?($|</script.),isS', $t, $r, PREG_SET_ORDER))
foreach ($r as $regs)
$t = str_replace($regs[0],
"<code$class>".nl2br(htmlspecialchars($regs[0])).'</code>',
$t);
return $t;
}
function protege_js_modeles($t) {
if (isset($GLOBALS['visiteur_session'])){
if (preg_match_all(',<script.*?($|</script.),isS', $t, $r, PREG_SET_ORDER)){
if (!defined('_PROTEGE_JS_MODELES')){
include_spip('inc/acces');
define('_PROTEGE_JS_MODELES',creer_uniqid());
}
foreach ($r as $regs)
$t = str_replace($regs[0],code_echappement($regs[0],'javascript'._PROTEGE_JS_MODELES),$t);
}
if (preg_match_all(',<\?php.*?($|\?'.'>),isS', $t, $r, PREG_SET_ORDER)){
if (!defined('_PROTEGE_PHP_MODELES')){
include_spip('inc/acces');
define('_PROTEGE_PHP_MODELES',creer_uniqid());
}
foreach ($r as $regs)
$t = str_replace($regs[0],code_echappement($regs[0],'php'._PROTEGE_PHP_MODELES),$t);
}
}
return $t;
}
// Securite : empecher l'execution de code PHP, en le transformant en joli code
// l'espace prive est securise globalement par un appel explicite un interdire_script
// on desactive l'appel des squelettes tant que la protection globale est la
// a terme (tout l'espace prive en skel) il faudra mettre $protege_espace_prive = true
// http://doc.spip.org/@interdire_scripts
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
function interdire_scripts($t, $protege_espace_prive = false) {
if (_DIR_RESTREINT || $protege_espace_prive) {
// rien ?
if (!$t OR !strstr($t, '<')) return $t;
// echapper les tags asp/php
$t = str_replace('<'.'%', '<%', $t);
// echapper le php
$t = str_replace('<'.'?', '<?', $t);
// echapper le < script language=php >
$t = preg_replace(',<(script\b[^>]+\blanguage\b[^\w>]+php\b),UimsS', '<\1', $t);
// Pour le js, trois modes : parano (-1), prive (0), ok (1)
switch($GLOBALS['filtrer_javascript']) {
case 0:
if (!_DIR_RESTREINT)
$t = echappe_js($t,' style="color:red"');
break;
case -1:
$t = echappe_js($t);
break;
}
// pas de <base href /> svp !
$t = preg_replace(',<(base\s),iS', '<\1', $t);
// Reinserer les echappements des modeles
if (defined('_PROTEGE_JS_MODELES'))
$t = echappe_retour($t,"javascript"._PROTEGE_JS_MODELES);
if (defined('_PROTEGE_PHP_MODELES'))
$t = echappe_retour($t,"php"._PROTEGE_PHP_MODELES);
return $t;
}
// Securite : utiliser SafeHTML s'il est present dans ecrire/safehtml/
// http://doc.spip.org/@safehtml
function safehtml($t) {
static $safehtml;
# attention safehtml nettoie deux ou trois caracteres de plus. A voir
if (strpos($t,'<')===false)
return str_replace("\x00", '', $t);
$t = interdire_scripts($t); // jolifier le php
$t = echappe_js($t);
if (!isset($safehtml))
$safehtml = charger_fonction('safehtml', 'inc');
if ($safehtml)
$t = $safehtml($t);
return interdire_scripts($t); // interdire le php (2 precautions)
Antoine Pitrou
a validé
// Typographie generale
// avec protection prealable des balises HTML et SPIP
function typo($letexte, $echapper=true, $connect='') {
// Plus vite !
if (!$letexte) return $letexte;
if ($echapper)
$letexte = echappe_html($letexte, 'TYPO');
//
// Installer les modeles, notamment images et documents ;
//
// NOTE : propre() l'a deja fait
// sauf pour les textes renvoyes par calculer_url()
$letexte = traiter_modeles($mem = $letexte, false, $echapper ? 'TYPO' : '', $connect);
if ($letexte != $mem) $echapper = true;
unset($mem);
$letexte = corriger_typo($letexte);
// reintegrer les echappements
if ($echapper)
$letexte = echappe_retour($letexte, 'TYPO');
// Dans l'espace prive, securiser ici
// quand tout l'espace prive sera en skel, supprimer cette ligne et retablir
// $protege_espace_prive = true dans interdire_script
if (!_DIR_RESTREINT)
$letexte = interdire_scripts($letexte,true);
return $letexte;
}
// Correcteur typographique
function corriger_typo($letexte) {
// Plus vite !
if (!$letexte) return $letexte;
// Caracteres de controle "illegaux"
$letexte = corriger_caracteres($letexte);
// Charger & appliquer la fonction de typographie
if ($typographie = charger_fonction(lang_typo(), 'typographie')) {
// Proteger les caracteres typographiques a l'interieur des tags html
$protege = "!':;?~%-";
$illegal = "\x1\x2\x3\x4\x5\x6\x7\x8";
if (preg_match_all(",</?[a-z!][^<>]*[".preg_quote($protege)."][^<>]*>,imsS",
$letexte, $regs, PREG_SET_ORDER)) {
foreach ($regs as $reg) {
$insert = $reg[0];
// hack: on transforme les caracteres a proteger en les remplacant
// par des caracteres "illegaux". (cf corriger_caracteres())
$insert = strtr($insert, $protege, $illegal);
$letexte = str_replace($reg[0], $insert, $letexte);
}
$letexte = $typographie($letexte);
// Retablir les caracteres proteges
$letexte = strtr($letexte, $illegal, $protege);
}
# un message pour abs_url - on est passe en mode texte
$GLOBALS['mode_abs_url'] = 'texte';
// analyse des raccourcis issus de [TITRE->RACCOURCInnn] et connexes
define('_RACCOURCI_URL', ',^\s*(\w*?)\s*(\d+)(\?(.*?))?(#([^\s]*))?\s*$,S');
function typer_raccourci ($lien) {
if (!preg_match(_RACCOURCI_URL, $lien, $match)) return array();
$f = $match[1];
// valeur par defaut et alias historiques
if (!$f) $f = 'article';
else if ($f == 'art') $f = 'article';
else if ($f == 'br') $f = 'breve';
else if ($f == 'rub') $f = 'rubrique';
else if ($f == 'aut') $f = 'auteur';
else if ($f == 'doc' OR $f == 'im' OR $f == 'img' OR $f == 'image' OR $f == 'emb')
$f = 'document';
else if (preg_match(',^br..?ve$,S', $f)) $f = 'breve'; # accents :(
$match[0] = $f;
$match[2] = entites_html($match[2]);
return $match;
}
// Cherche un lien du type [->raccourci 123]
// associe a une fonction generer_url_raccourci()
//
// Valeur retournee selon le parametre $pour:
// 'tout' : tableau [U,C,T,L] (vise <a href="U" class='C' hreflang='L'>T</a>)
// 'titre': seulement T ci-dessus (i.e. le TITRE ci-dessus ou dans table SQL)
// 'url': seulement U (i.e. generer_url_RACCOURCI)
// http://doc.spip.org/@calculer_url
function calculer_url ($ref, $texte='', $pour='url', $connect='') {
include_spip('base/abstract_sql');
if ($match = typer_raccourci($ref)) {
@list($f,,$id,,$args,,$ancre) = $match;
$lien = charger_fonction('lien', 'inc', true);
if ($lien) {
$r = $lien($f,$id,$args,$ancre,$texte,$pour,$connect);
if ($r)
return ($pour=='tout') ? $r :
(($pour=='url') ? $r[0] :$r[2]);
spip_log("raccourci indefini $f");
if (preg_match(",^\s*(http:?/?/?|mailto:?)\s*$,iS", $ref))
return ($pour != 'tout') ? '' : array('','','','');
$lien = entites_html(trim($ref));
// Liens explicites
if (!$texte) {
$texte = str_replace('"', '', $lien);
if (strlen($texte)>40)
$texte = substr($texte,0,35).'...';
$texte = "<html>$texte</html>";
$class = "spip_url spip_out";
} else $class = "spip_out";
if ($pour == 'titre') return $texte;
// petites corrections d'URL
if (preg_match(",^www\.[^@]+$,S",$lien))
$lien = "http://".$lien;
else if (strpos($lien, "@") && email_valide($lien))
$lien = "mailto:".$lien;
if (preg_match(",^\s*mailto:,",$lien))
$class = "spip_mailto";
// class spip_ancre sur les ancres pures (internes a la page)
if (substr($lien,0,1) == '#')
$class = 'spip_ancre';
return ($pour == 'url') ? $lien : array($lien, $class, $texte, '');
// http://doc.spip.org/@traiter_tableau
function traiter_tableau($bloc) {
// Decouper le tableau en lignes
preg_match_all(',([|].*)[|]\n,UmsS', $bloc, $regs, PREG_PATTERN_ORDER);
$debut_table = $summary = '';
$l = 0;
$numeric = true;
// Traiter chaque ligne
foreach ($regs[1] as $ligne) {
$l ++;
// Gestion de la premiere ligne :
if ($l == 1) {
// - <caption> et summary dans la premiere ligne :
// || caption | summary || (|summary est optionnel)
if (preg_match(',^\|\|([^|]*)(\|(.*))?$,sS', $ligne, $cap)) {
if ($caption = trim($cap[1]))
$debut_table .= "<caption>".$caption."</caption>\n";
$summary = ' summary="'.entites_html(trim($cap[3])).'"';
}
// - <thead> sous la forme |{{titre}}|{{titre}}|
// Attention thead oblige a avoir tbody
else if (preg_match(',^(\|([[:space:]]*(:?{{[^}]+}}[[:space:]]*)?|<))+$,sS',
preg_match_all("/\|([^|]*)/S", $ligne, $cols);
$ligne='';$cols= $cols[1];
$colspan=1;
for($c=count($cols)-1; $c>=0; $c--) {
$attr='';
if($cols[$c]=='<') {
$colspan++;
} else {
if($colspan>1) {
$attr= " colspan='$colspan'";
$colspan=1;
}
$ligne= "<th scope='col'$attr>$cols[$c]</th>$ligne";
}
}
$debut_table .= "<thead><tr class='row_first'>".
$ligne."</tr></thead>\n";
$l = 0;
}
}
// Sinon ligne normale
if ($l) {
// Gerer les listes a puce dans les cellules
if (strpos($ligne,"\n-*")!==false OR strpos($ligne,"\n-#")!==false)
$ligne = traiter_listes($ligne);
$ligne = preg_replace("/\n{2,}/", "<br />\n", $ligne);
// tout mettre dans un tableau 2d
preg_match_all("/\|([^|]*)/S", $ligne, $cols);
$lignes[]= $cols[1];
}
}
// maintenant qu'on a toutes les cellules
// on prepare une liste de rowspan par defaut, a partir
// du nombre de colonnes dans la premiere ligne
$rowspans = array();
for ($i=0; $i<count($lignes[0]); $i++)
$rowspans[] = 1;
// et on parcourt le tableau a l'envers pour ramasser les
// colspan et rowspan en passant
for($l=count($lignes)-1; $l>=0; $l--) {
$cols= $lignes[$l];
$colspan=1;
$ligne='';
for($c=count($cols)-1; $c>=0; $c--) {
$attr='';
if($cols[$c]=='<') {
$colspan++;
} elseif($cols[$c]=='^') {
$rowspans[$c]++;
} else {
if($colspan>1) {
$attr.= " colspan='$colspan'";
$colspan=1;
}
if($rowspans[$c]>1) {
$attr.= " rowspan='$rowspans[$c]'";
$rowspans[$c]=1;
}
$ligne= "\n<td".$attr.'>'.$cols[$c].'</td>'.$ligne;
$numeric &= (preg_match('/[{<]/',$cols[$c][0]) || is_numeric($cols[$c]));
// ligne complete
$class = 'row_'.alterner($l+1, 'even', 'odd');
$html = "<tr class=\"$class\">" . $ligne . "</tr>\n".$html;
if ($numeric)
$html = str_replace("\n<td", "\n<td style='text-align: right'", $html);
return "\n\n<table".$GLOBALS['class_spip_plus'].$summary.">\n"
. "</table>\n\n";
// Traitement des listes (merci a Michael Parienti)
// http://doc.spip.org/@traiter_listes
function traiter_listes ($texte) {
global $class_spip, $class_spip_plus;
$parags = preg_split(",\n[[:space:]]*\n,S", $texte);
// chaque paragraphe est traite a part
while (list(,$para) = each($parags)) {
$niveau = 0;
$lignes = explode("\n-", "\n" . $para);
// ne pas toucher a la premiere ligne
list(,$debut) = each($lignes);
$texte .= $debut;
// chaque item a sa profondeur = nb d'etoiles
while (list(,$item) = each($lignes)) {
preg_match(",^([*]*|[#]*)([^*#].*)$,sS", $item, $regs);
$profond = strlen($regs[1]);
if ($profond > 0) {
// changement de type de liste au meme niveau : il faut
// descendre un niveau plus bas, fermer ce niveau, et
// remonter
$nouv_type = (substr($item,0,1) == '*') ? 'ul' : 'ol';
$change_type = ($type AND ($type <> $nouv_type) AND ($profond == $niveau)) ? 1 : 0;
$type = $nouv_type;
// d'abord traiter les descentes
while ($niveau > $profond - $change_type) {
$ajout .= $pile_li[$niveau];
$ajout .= $pile_type[$niveau];
// puis les identites (y compris en fin de descente)
if ($niveau == $profond && !$change_type) {
}
// puis les montees (y compris apres une descente un cran trop bas)
while ($niveau < $profond) {
if ($niveau == 0) $ajout .= "\n\n";
$ajout .= "<$type$class_spip_plus>";
$pile_type[$niveau] = "</$type>";
$pile_li[$profond] = "</li>";
Fil
a validé
$ajout = "\n-"; // puce normale ou <hr>
Fil
a validé
$texte .= $ajout . $regs[2];
}
// retour sur terre
while ($niveau > 0) {
$ajout .= $pile_li[$niveau];
$ajout .= $pile_type[$niveau];
$niveau --;
}
$texte .= $ajout;
// paragraphe
$texte .= "\n\n";
}
// sucrer les deux derniers \n
return substr($texte, 0, -2);
}
// fonction en cas de texte extrait d'un serveur distant:
// on ne sait pas (encore) rapatrier les documents joints
esj
a validé
// Sert aussi a nettoyer un texte qu'on veut mettre dans un <a> etc.
// TODO: gerer les modeles ?
// http://doc.spip.org/@supprime_img
esj
a validé
function supprime_img($letexte, $message=NULL) {
if ($message===NULL) $message = '(' . _T('img_indisponible') . ')';
return preg_replace(',<(img|doc|emb)([0-9]+)(\|([^>]*))?'.'\s*/?'.'>,i',
esj
a validé
$message, $letexte);
// traite les modeles (dans la fonction typo), en remplacant
// le raccourci <modeleN|parametres> par la page calculee a
// partir du squelette modeles/modele.html
// Le nom du modele doit faire au moins trois caracteres (evite <h2>)
// Si $doublons==true, on repere les documents sans calculer les modeles
// mais on renvoie les params (pour l'indexation par le moteur de recherche)
// http://doc.spip.org/@traiter_modeles
define('_RACCOURCI_MODELE',
'(<([a-z_-]{3,})' # <modele
.'\s*([0-9]*)\s*' # id
.'([|](?:<[^<>]*>|[^>])*?)?' # |arguments (y compris des tags <...>)
.'\s*/?'.'>)' # fin du modele >
.'\s*(<\/a>)?' # eventuel </a>
);
define('_RACCOURCI_MODELE_DEBUT', '@^' . _RACCOURCI_MODELE .'@is');
function traiter_modeles($texte, $doublons=false, $echap='', $connect='') {
// detecter les modeles (rapide)
Fil
a validé
if (preg_match_all('/<[a-z_-]{3,}\s*[0-9|]+/iS',
$texte, $matches, PREG_SET_ORDER)) {
include_spip('public/assembler');
foreach ($matches as $match) {
// Recuperer l'appel complet (y compris un eventuel lien)
$a = strpos($texte,$match[0]);
preg_match(_RACCOURCI_MODELE_DEBUT, substr($texte, $a), $regs);
$regs[]=""; // s'assurer qu'il y a toujours un 5e arg, eventuellement vide
list(,$mod, $type, $id, $params, $fin) = $regs;
if ($fin AND preg_match(
',<a\s[^<>]*>\s*$,i', substr($texte, 0, $a), $r)) {
$lien = array(
extraire_attribut($r[0],'href'),
extraire_attribut($r[0],'class')
);
$n = strlen($r[0]);
$a -= $n;
$cherche = $n + strlen($regs[0]);
} else {
$lien = false;
$cherche = strlen($mod);
// calculer le modele
# hack articles_edit, breves_edit, indexation
if ($doublons)
$texte .= preg_replace(',[|][^|=]*,s',' ',$params);
$modele = inclure_modele($type, $id, $params, $lien, $connect);
// le remplacer dans le texte
if ($modele !== false) {
$modele = protege_js_modeles($modele);
$rempl = code_echappement($modele, $echap);
. substr($texte, $a+$cherche);
// hack pour tout l'espace prive
if (((!_DIR_RESTREINT) OR ($doublons)) AND ($id) AND (in_array($type,array('doc','emb','img'))))
$GLOBALS['doublons_documents_inclus'][] = $id;
}
}
return $texte;
}
//
// Une fonction pour fermer les paragraphes ; on essaie de preserver
// des paragraphes indiques a la main dans le texte
// (par ex: on ne modifie pas un <p align='center'>)
//
// deuxieme argument : forcer les <p> meme pour un seul paragraphe
//
// http://doc.spip.org/@paragrapher
function paragrapher($letexte, $forcer=true) {
$letexte = trim($letexte);
if (!strlen($letexte))
return '';
if ($forcer OR (
strstr($letexte,'<') AND preg_match(',<p\b,iS',$letexte)
)) {
// Ajouter un espace aux <p> et un "STOP P"
// transformer aussi les </p> existants en <p>, nettoyes ensuite
$letexte = preg_replace(',</?p(\s([^>]*))?'.'>,iS', '<STOP P><p \2>',
'<p>'.$letexte.'<STOP P>');
// Fermer les paragraphes (y compris sur "STOP P")
$letexte = preg_replace(
',(<p\s.*)(</?(STOP P|'._BALISES_BLOCS.')[>[:space:]]),UimsS',
"\n\\1</p>\n\\2", $letexte);
// Supprimer les marqueurs "STOP P"
$letexte = str_replace('<STOP P>', '', $letexte);
// Reduire les blancs dans les <p>
renato
a validé
// Do not delete multibyte utf character just before </p> having last byte equal to whitespace
$u = ($GLOBALS['meta']['charset']=='utf-8' && test_pcre_unicode()) ? 'u':'S';
$letexte = preg_replace(
renato
a validé
',(<p(>|\s[^>]*)>)\s*|\s*(</p[>[:space:]]),'.$u.'i', '\1\3',
$letexte);
// Supprimer les <p xx></p> vides
$letexte = preg_replace(',<p\s[^>]*></p>\s*,iS', '',
// Renommer les paragraphes normaux
$letexte);
}
return $letexte;
}
//
// Raccourcis ancre [#ancre<-]
//
define('_RACCOURCI_ANCRE', "|\[#?([^][]*)<-\]|S");
function traiter_raccourci_ancre($letexte)
{
if (preg_match_all(_RACCOURCI_ANCRE, $letexte, $m, PREG_SET_ORDER))
foreach ($m as $regs)
$letexte = str_replace($regs[0],
'<a name="'.entites_html($regs[1]).'"></a>', $letexte);
return $letexte;
}
//
// Raccourcis automatiques [?SPIP] vers un glossaire
//
define('_RACCOURCI_GLOSSAIRE', "|\[\?+\s*([^][<>]+)\]|S");
function traiter_raccourci_glossaire($letexte)
{
static $glosateur = NULL, $subst = NULL;
if ($glosateur === NULL) {
$glosateur = tester_variable('url_glossaire_externe',
"http://@lang@.wikipedia.org/wiki/");
$subst = strpos($glosateur,"%s") !== false;
}
if (empty($glosateur)
OR !preg_match_all(_RACCOURCI_GLOSSAIRE, $letexte, $m, PREG_SET_ORDER))
return $letexte;
$glosateur = str_replace("@lang@", $GLOBALS['spip_lang'], $glosateur);
foreach ($m as $regs) {
list($tout, $terme) = $regs;
// Eviter les cas particulier genre "[?!?]"
if (preg_match(',^(.*\w\S*)\s*$,', $terme, $r)) {
$terme = $r[1];
$_terme = preg_replace(',\s+,', '_', $terme);
// faire sauter l'eventuelle partie "|bulle d'aide" du lien
// cf. http://fr.wikipedia.org/wiki/Wikip%C3%A9dia:Conventions_sur_les_titres
$_terme = preg_replace(',[|].*,', '', $_terme);
if ($subst)
$url = str_replace("%s", rawurlencode($_terme), $glosateur);
else $url = $glosateur. $_terme;
list($terme, $bulle, $hlang) = traiter_raccourci_lien_atts($terme);
$url = traiter_raccourci_lien_lang($url, 'spip_glossaire', $terme, $hlang, '', $bulle);
$letexte = str_replace($tout, $url, $letexte);
}
}
return $letexte;
}
//
// Raccourcis liens [xxx->url]
// Note : complique car c'est ici qu'on applique typo(),
// et en plus on veut pouvoir les passer en pipeline
//
// Regexp des raccouris, aussi utilisee pour la fusion de sauvegarde Spip
// Laisser passer des paires de crochets pour la balise multi
// mais refuser plus d'imbrications ou de mauvaises imbrications
// sinon les crochets ne peuvent plus servir qu'a ce type de raccourci
define('_RACCOURCI_LIEN', ",\[([^][]*?([[]\w*[]][^][]*)*)->(>?)([^]]*)\],msS");
function expanser_liens($letexte, $connect='')
{
$inserts = array();
if (preg_match_all(_RACCOURCI_LIEN, $letexte, $matches, PREG_SET_ORDER)) {
$i = 0;
foreach ($matches as $regs) {
$n = count($regs);
list($texte, $bulle, $hlang) = traiter_raccourci_lien_atts($regs[1]);
list ($lien, $class, $texte, $lang) =
calculer_url($regs[$n-1], $texte, 'tout', $connect);
$inserts[++$i] = traiter_raccourci_lien_lang($lien, $class, $texte, $hlang, $lang, $bulle, $connect);
$letexte = str_replace($regs[0], "@@SPIP_ECHAPPE_LIEN_$i@@",
$letexte);
}
}
$letexte = corriger_typo(traiter_modeles($letexte, false, false, $connect));
foreach ($inserts as $i => $insert) {
$letexte = str_replace("@@SPIP_ECHAPPE_LIEN_$i@@", $insert, $letexte);
}
return $letexte;
}
/*
// Inserer un lien a partir du preg_match du raccourci [xx->url]
// Le preg-match a change, cette fonction est inutilisable
// $regs:
// 0=>tout le raccourci
// 1=>texte (ou texte|hreflang ou texte|bulle ou texte|bulle{hreflang})
// 2=>double fleche (historiquement, liens ouvrants)
// 3=>url
Christian Lefebvre
a validé
// http://doc.spip.org/@traiter_raccourci_lien
function traiter_raccourci_lien($regs, $connect='') {
list(,$texte, ,$url) = $regs;
list($texte, $bulle, $hlang) = traiter_raccourci_lien_atts($texte);
list ($lien, $class, $texte, $lang) =
calculer_url($url, $texte, 'tout', $connect);
return traiter_raccourci_lien_lang($lien, $class, $texte, $hlang, $lang, $bulle, $connect);
*/
function traiter_raccourci_lien_lang($lien, $class, $texte, $hlang, $lang, $bulle, $connect='')
// Si l'objet n'est pas de la langue courante, on ajoute hreflang
if (!$hlang AND $lang!=$GLOBALS['spip_lang'])
$hlang = $lang;
$lang = ($hlang ? ' hreflang="'.$hlang.'"' : '') . $bulle;
# ceci s'execute heureusement avant les tableaux et leur "|".
# Attention, le texte initial est deja echappe mais pas forcement
# celui retourne par calculer_url.
# Penser au cas [<imgXX|right>->URL], qui exige typo('<a>...</a>')
return typo('<a href="'.$lien
. ($class ? '" class="'.$class : '')
. '"'.$lang.'>'
. $texte.'</a>', true, $connect);
}
// Repere dans la partie texte d'un raccourci [texte->...]
// la langue et la bulle eventuelles
function traiter_raccourci_lien_atts($texte) {
$bulle = $hlang = '';
// title et hreflang donnes par le raccourci ?
if (preg_match(',^(.*?)([|]([^<>]*?))?([{]([a-z_]+)[}])?$,', $texte, $m)) {
// |infobulle ?
$bulle = ' title="'.texte_backend($m[3]).'"';
// si c'est un code de langue connu, on met un hreflang
if (traduire_nom_langue($m[5]) <> $m[5]) {
$hlang = $m[5];
}
// sinon c'est un italique
else {
$m[1] .= $m[4];
}
// S'il n'y a pas de hreflang sous la forme {}, ce qui suit le |
// est peut-etre une langue
} else if (preg_match(',^[a-z_]+$,', $m[3])) {
// si c'est un code de langue connu, on met un hreflang
// mais on laisse le title (c'est arbitraire tout ca...)
if (traduire_nom_langue($m[3]) <> $m[3]) {
$hlang = $m[3];
}
}
}
$texte = $m[1];
return array($texte, $bulle, $hlang);
}
// Fonction pour les champs chapo commencant par =, redirection qui peut etre:
// 1. un raccourci Spip habituel (premier If) [texte->TYPEnnn]
// 2. un ultra raccourci TYPEnnn voire nnn (article) (deuxieme If)
// 3. une URL std
// renvoie une tableau structure comme ci-dessus mais sans calcul d'URL
// (cf fusion de sauvegardes)
define('_RACCOURCI_CHAPO', ',^(\W*)(\W*)(\w*\d+([?#].*)?)$,');
function chapo_redirige($chapo, $url=false)
{
if (!preg_match(_RACCOURCI_LIEN, $chapo, $m))
if (!preg_match(_RACCOURCI_CHAPO, $chapo, $m))
return $chapo;
return !$url ? $m[3] : calculer_url($m[3]);
}
function chapo_redirigetil($chapo) { return $chapo && $chapo[0] == '=';}
function traiter_poesie($letexte)
{
if (preg_match_all(",<(poesie|poetry)>(.*)<\/(poesie|poetry)>,UimsS",
$letexte, $regs, PREG_SET_ORDER)) {
foreach ($regs as $reg) {
$lecode = preg_replace(",\r\n?,S", "\n", $reg[2]);
$lecode = preg_replace("/\n[\s]*\n/", "\n \n",$lecode);
$lecode = "<blockquote class=\"spip_poesie\">\n<div>".preg_replace("/\n+/", "</div>\n<div>", trim($lecode))."</div>\n</blockquote>\n\n";
$letexte = str_replace($reg[0], $lecode, $letexte);
}
}
return $letexte;
}
// callback pour la fonction traiter_raccourci_liens()
function autoliens_callback($r) {
Fil
a validé
if (strlen($l = $r[1])) {
if (preg_match(',^(http:/*),S', $l, $m))
$l = substr($l, strlen($m[1]));
if (preg_match(
'/^(?:[^\W_]((?:[^\W_]|-){0,61}[^\W_])?\.)+[a-zA-Z]{2,6}\b/S', $l)) {
$l = inserer_attribut(expanser_liens('[->http://'.$l.']'),
'rel', 'nofollow');
// si le texte ne contanait pas le 'http:' on le supprime aussi
Fil
a validé
if (!$m)
$l = str_replace('>http://', '>', $l);
return $l;
}
}
return $r[0];
}
// extraire les liens ecrits en mode texte brut
function traiter_raccourci_liens($texte) {
return preg_replace_callback(
',\[[^\[\]]*->.*?\]|<[^<>]*>|((http:|www\.)[^"\'\s\[\]]+),S',
'autoliens_callback', $texte);
return $texte;
}
// Harmonise les retours chariots et mange les paragraphes html
function traiter_retours_chariots($letexte) {
$letexte = preg_replace(",\r\n?,S", "\n", $letexte);
$letexte = preg_replace(",<p[>[:space:]],iS", "\n\n\\0", $letexte);
$letexte = preg_replace(",</p[>[:space:]],iS", "\\0\n\n", $letexte);
return $letexte;
}
// Nettoie un texte, traite les raccourcis autre qu'URL, la typo, etc.
// http://doc.spip.org/@traiter_raccourcis
function traiter_raccourcis($letexte) {
// Appeler les fonctions de pre_traitement
$letexte = pipeline('pre_propre', $letexte);
// Gerer les notes (ne passe pas dans le pipeline)
list($letexte, $mes_notes) = traite_raccourci_notes($letexte);
// A present on introduit des attributs class_spip*
// Init de leur valeur et connexes au premier appel
// Tableaux
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
// ne pas oublier les tableaux au debut ou a la fin du texte
$letexte = preg_replace(",^\n?[|],S", "\n\n|", $letexte);
$letexte = preg_replace(",\n\n+[|],S", "\n\n\n\n|", $letexte);
$letexte = preg_replace(",[|](\n\n+|\n?$),S", "|\n\n\n\n", $letexte);
if (preg_match_all(',[^|](\n[|].*[|]\n)[^|],UmsS', $letexte,
$regs, PREG_SET_ORDER))
foreach ($regs as $tab) {
$letexte = str_replace($tab[1], traiter_tableau($tab[1]), $letexte);
}
$letexte = "\n".trim($letexte);
// les listes
if (strpos($letexte,"\n-*")!==false OR strpos($letexte,"\n-#")!==false)
$letexte = traiter_listes($letexte);
// Proteger les caracteres actifs a l'interieur des tags html
$protege = "{}_-";
$illegal = "\x1\x2\x3\x4";
if (preg_match_all(",</?[a-z!][^<>]*[".preg_quote($protege)."][^<>]*>,imsS",
$letexte, $regs, PREG_SET_ORDER)) {
foreach ($regs as $reg) {
$insert = $reg[0];
// hack: on transforme les caracteres a proteger en les remplacant
// par des caracteres "illegaux". (cf corriger_caracteres())
$insert = strtr($insert, $protege, $illegal);
$letexte = str_replace($reg[0], $insert, $letexte);
}
}
// autres raccourcis
$letexte = preg_replace($remplace[0], $remplace[1], $letexte);
$letexte = preg_replace('@^\n<br />@S', '', $letexte);
// Retablir les caracteres proteges
$letexte = strtr($letexte, $illegal, $protege);
// Fermer les paragraphes ; mais ne pas en creer si un seul
$letexte = paragrapher($letexte, $GLOBALS['toujours_paragrapher']);
// Appeler les fonctions de post-traitement
$letexte = pipeline('post_propre', $letexte);
if ($mes_notes) traiter_les_notes($mes_notes);
return $letexte;
}
//
// Notes de bas de page
//
function traite_raccourci_notes($letexte)
{
global $compt_note, $marqueur_notes, $les_notes;
global $ouvre_ref, $ferme_ref, $ouvre_note, $ferme_note; #static ok
static $notes_vues = NULL;
if ($notes_vues === NULL) {
$ouvre_ref = tester_variable('ouvre_ref', ' [');
$ferme_ref = tester_variable('ferme_ref', ']');
$ouvre_note = tester_variable('ouvre_note', '[');
$ferme_note = tester_variable('ferme_note', '] ');
$les_notes = tester_variable('les_notes', '');
$compt_note = tester_variable('compt_note', 0);
$notes_vues === array();
}
$mes_notes = '';
$regexp = ', *\[\[(.*?)\]\],msS';
if (preg_match_all($regexp, $letexte, $matches, PREG_SET_ORDER))
foreach ($matches as $regs) {
if (preg_match(",^<([^>'\"]*)>,", ltrim($note_texte), $r)
AND strpos($note_texte, '</' . $r[1] .'>') === false) {
$num_note = $r[1];
$note_texte = substr_replace(ltrim($note_texte), '', 0, strlen($r[0]));
if ($marqueur_notes) // quand il y a plusieurs series
// de notes sur une meme page
$mn = $marqueur_notes.'-';
$ancre = $mn.rawurlencode($num_note);
// ne mettre qu'une ancre par appel de note (XHTML)
if (!$notes_vues[$ancre]++)
$name_id = " name=\"nh$ancre\" id=\"nh$ancre\"";
else
$name_id = "";
$lien = "<a href=\"#nb$ancre\"$name_id class=\"spip_note\" rel=\"footnote\">";
// creer le popup 'title' sur l'appel de note
if ($title = supprimer_tags(propre($note_texte))) {
$title = couper($title,80);
$lien = inserer_attribut($lien, 'title', $title);
$insert = "$ouvre_ref$lien$num_note</a>$ferme_ref";
// on l'echappe
$insert = code_echappement($insert);
$appel = "$ouvre_note<a href=\"#nh$ancre\" name=\"nb$ancre\" class=\"spip_note\" title=\"" . _T('info_notes') . " $ancre\" rev=\"footnote\">$num_note</a>$ferme_note";
// l'ajouter "tel quel" (echappe) dans les notes
if ($mes_notes)
$mes_notes .= "\n\n";
$mes_notes .= code_echappement($appel) . $note_texte;
}
// dans le texte, mettre l'appel de note a la place de la note
$pos = strpos($letexte, $note_source);
$letexte = substr($letexte, 0, $pos) . $insert
. substr($letexte, $pos + strlen($note_source));
Fil
a validé
}
return array($letexte, $mes_notes);
esj
a validé
}
// http://doc.spip.org/@traiter_les_notes
function traiter_les_notes($mes_notes) {
$mes_notes = propre('<p>'.$mes_notes);
if ($GLOBALS['class_spip'])
$mes_notes = str_replace('<p class="spip">', '<p class="spip_note">', $mes_notes);
$GLOBALS['les_notes'] .= $mes_notes;
esj
a validé
}
// Filtre a appliquer aux champs du type #TEXTE*
// http://doc.spip.org/@propre
function propre($t, $connect='') {
return !$t ? '' :
echappe_retour_modeles(
traiter_raccourcis(
expanser_liens(echappe_html($t),$connect)));