620079e8f37738fb4f8afd62e3b0868224313077
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2013 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
13 if (!defined('_ECRIRE_INC_VERSION')) return;
15 include_spip('inc/texte_mini');
16 include_spip('inc/lien');
18 include_spip('inc/textwheel');
21 defined('_AUTOBR')||
define('_AUTOBR', "<br class='autobr' />");
22 define('_AUTOBR_IGNORER', _AUTOBR?
"<!-- ig br -->":"");
24 // Avec cette surcharge, cette globale n'est plus définie, et du coup ça plante dans les plugins qui font un foreach dessus comme ZPIP
25 $GLOBALS['spip_raccourcis_typo'] = array();
26 if (!isset($GLOBALS['toujours_paragrapher']))
27 $GLOBALS['toujours_paragrapher'] = true;
29 // class_spip : savoir si on veut class="spip" sur p i strong & li
30 // class_spip_plus : class="spip" sur les ul ol h3 hr quote table...
31 // la difference c'est que des css specifiques existent pour les seconds
33 if (!isset($GLOBALS['class_spip']))
34 $GLOBALS['class_spip'] = '';
35 if (!isset($GLOBALS['class_spip_plus']))
36 $GLOBALS['class_spip_plus'] = ' class="spip"';
40 * echapper les < script ...
45 function echappe_js($t) {
49 $wheel = new TextWheel(
50 SPIPTextWheelRuleset
::loader($GLOBALS['spip_wheels']['echappe_js'])
53 return $wheel->text($t);
57 * paragrapher seulement
60 * @param null $toujours_paragrapher
63 function paragrapher($t, $toujours_paragrapher = null) {
64 static $wheel = array();
65 if (is_null($toujours_paragrapher))
66 $toujours_paragrapher = $GLOBALS['toujours_paragrapher'];
68 if (!isset($wheel[$toujours_paragrapher])) {
69 $ruleset = SPIPTextWheelRuleset
::loader($GLOBALS['spip_wheels']['paragrapher']);
70 if (!$toujours_paragrapher
71 AND $rule=$ruleset->getRule('toujours-paragrapher')) {
72 $rule->disabled
= true;
73 $ruleset->addRules(array('toujours-paragrapher'=>$rule));
75 $wheel[$toujours_paragrapher] = new TextWheel($ruleset);
78 return $wheel[$toujours_paragrapher]->text($t);
83 * Securite : empecher l'execution de code PHP, en le transformant en joli code
84 * dans l'espace prive, cette fonction est aussi appelee par propre et typo
85 * si elles sont appelees en direct
86 * il ne faut pas desactiver globalement la fonction dans l'espace prive car elle protege
87 * aussi les balises des squelettes qui ne passent pas forcement par propre ou typo apres
89 * http://doc.spip.org/@interdire_scripts
94 function interdire_scripts($arg) {
95 // on memorise le resultat sur les arguments non triviaux
96 static $dejavu = array();
97 static $wheel = array();
99 // Attention, si ce n'est pas une chaine, laisser intact
100 if (!$arg OR !is_string($arg) OR !strstr($arg, '<')) return $arg;
101 if (isset($dejavu[$GLOBALS['filtrer_javascript']][$arg])) return $dejavu[$GLOBALS['filtrer_javascript']][$arg];
103 if (!isset($wheel[$GLOBALS['filtrer_javascript']])){
104 $ruleset = SPIPTextWheelRuleset
::loader(
105 $GLOBALS['spip_wheels']['interdire_scripts']
107 // Pour le js, trois modes : parano (-1), prive (0), ok (1)
108 // desactiver la regle echappe-js si besoin
109 if ($GLOBALS['filtrer_javascript']==1
110 OR ($GLOBALS['filtrer_javascript']==0 AND !test_espace_prive()))
111 $ruleset->addRules (array('securite-js'=>array('disabled'=>true)));
112 $wheel[$GLOBALS['filtrer_javascript']] = new TextWheel($ruleset);
115 $t = $wheel[$GLOBALS['filtrer_javascript']]->text($arg);
117 // Reinserer les echappements des modeles
118 if (defined('_PROTEGE_JS_MODELES'))
119 $t = echappe_retour($t,"javascript"._PROTEGE_JS_MODELES
);
120 if (defined('_PROTEGE_PHP_MODELES'))
121 $t = echappe_retour($t,"php"._PROTEGE_PHP_MODELES
);
123 return $dejavu[$GLOBALS['filtrer_javascript']][$arg] = $t;
128 * Typographie generale
129 * avec protection prealable des balises HTML et SPIP
131 * http://doc.spip.org/@typo
133 * @param string $letexte
134 * @param bool $echapper
135 * @param null $connect
139 function typo($letexte, $echapper=true, $connect=null, $env=array()) {
141 if (!$letexte) return $letexte;
143 // les appels directs a cette fonction depuis le php de l'espace
144 // prive etant historiquement ecrit sans argment $connect
145 // on utilise la presence de celui-ci pour distinguer les cas
146 // ou il faut passer interdire_script explicitement
147 // les appels dans les squelettes (de l'espace prive) fournissant un $connect
148 // ne seront pas perturbes
149 $interdire_script = false;
150 if (is_null($connect)){
152 $interdire_script = true;
155 $echapper = ($echapper?
'TYPO':false);
156 // Echapper les codes <html> etc
158 $letexte = echappe_html($letexte, $echapper);
161 // Installer les modeles, notamment images et documents ;
163 // NOTE : propre() ne passe pas par ici mais directement par corriger_typo
166 $letexte = traiter_modeles($mem = $letexte, false, $echapper ?
$echapper : '', $connect, null, $env);
167 if (!$echapper AND $letexte != $mem) $echapper = '';
170 $letexte = corriger_typo($letexte);
171 $letexte = echapper_faux_tags($letexte);
173 // reintegrer les echappements
174 if ($echapper!==false)
175 $letexte = echappe_retour($letexte, $echapper);
177 // Dans les appels directs hors squelette, securiser ici aussi
178 if ($interdire_script)
179 $letexte = interdire_scripts($letexte);
184 // Correcteur typographique
186 define('_TYPO_PROTEGER', "!':;?~%-");
187 define('_TYPO_PROTECTEUR', "\x1\x2\x3\x4\x5\x6\x7\x8");
189 define('_TYPO_BALISE', ",</?[a-z!][^<>]*[".preg_quote(_TYPO_PROTEGER
)."][^<>]*>,imsS");
192 * http://doc.spip.org/@corriger_typo
195 * @param string $lang
198 function corriger_typo($t, $lang='') {
199 static $typographie = array();
203 $t = pipeline('pre_typo', $t);
205 // Caracteres de controle "illegaux"
206 $t = corriger_caracteres($t);
208 // Proteger les caracteres typographiques a l'interieur des tags html
209 if (preg_match_all(_TYPO_BALISE
, $t, $regs, PREG_SET_ORDER
)) {
210 foreach ($regs as $reg) {
212 // hack: on transforme les caracteres a proteger en les remplacant
213 // par des caracteres "illegaux". (cf corriger_caracteres())
214 $insert = strtr($insert, _TYPO_PROTEGER
, _TYPO_PROTECTEUR
);
215 $t = str_replace($reg[0], $insert, $t);
219 // trouver les blocs multi et les traiter a part
220 $t = extraire_multi($e = $t, $lang, true);
223 // Charger & appliquer les fonctions de typographie
224 $idxl = "$lang:" . (isset($GLOBALS['lang_objet'])?
$GLOBALS['lang_objet']: $GLOBALS['spip_lang']);
225 if (!isset($typographie[$idxl]))
226 $typographie[$idxl] = charger_fonction(lang_typo($lang), 'typographie');
227 $t = $typographie[$idxl]($t);
229 // Les citations en une autre langue, s'il y a lieu
230 if (!$e) $t = echappe_retour($t, 'multi');
232 // Retablir les caracteres proteges
233 $t = strtr($t, _TYPO_PROTECTEUR
, _TYPO_PROTEGER
);
236 $t = pipeline('post_typo', $t);
238 # un message pour abs_url - on est passe en mode texte
239 $GLOBALS['mode_abs_url'] = 'texte';
249 define('_RACCOURCI_TH_SPAN', '\s*(:?{{[^{}]+}}\s*)?|<');
252 * http://doc.spip.org/@traiter_tableau
257 function traiter_tableau($bloc) {
258 // id "unique" pour les id du tableau
259 $tabid = substr(md5($bloc),0,4);
261 // Decouper le tableau en lignes
262 preg_match_all(',([|].*)[|]\n,UmsS', $bloc, $regs, PREG_PATTERN_ORDER
);
264 $debut_table = $summary = '';
268 // Traiter chaque ligne
269 $reg_line1 = ',^(\|(' . _RACCOURCI_TH_SPAN
. '))+$,sS';
270 $reg_line_all = ',^(' . _RACCOURCI_TH_SPAN
. ')$,sS';
272 foreach ($regs[1] as $ligne) {
275 // Gestion de la premiere ligne :
277 // - <caption> et summary dans la premiere ligne :
278 // || caption | summary || (|summary est optionnel)
279 if (preg_match(',^\|\|([^|]*)(\|(.*))?$,sS', rtrim($ligne,'|'), $cap)) {
281 if ($caption = trim($cap[1]))
282 $debut_table .= "<caption>".$caption."</caption>\n";
283 $summary = ' summary="'.entites_html(trim($cap[3])).'"';
285 // - <thead> sous la forme |{{titre}}|{{titre}}|
286 // Attention thead oblige a avoir tbody
287 else if (preg_match($reg_line1, $ligne, $thead)) {
288 preg_match_all('/\|([^|]*)/S', $ligne, $cols);
289 $ligne='';$cols= $cols[1];
291 for($c=count($cols)-1; $c>=0; $c--) {
297 $attr= " colspan='$colspan'";
300 // inutile de garder le strong qui n'a servi que de marqueur
301 $cols[$c] = str_replace(array('{','}'), '', $cols[$c]);
302 $ligne= "<th id='id{$tabid}_c$c'$attr>$cols[$c]</th>$ligne";
303 $hc[$c] = "id{$tabid}_c$c"; // pour mettre dans les headers des td
307 $debut_table .= "<thead><tr class='row_first'>".
308 $ligne."</tr></thead>\n";
313 // Sinon ligne normale
315 // Gerer les listes a puce dans les cellules
316 // on declenche simplement sur \n- car il y a les
317 // -* -# -? -! (qui produisent des - !)
318 if (strpos($ligne,"\n-")!==false)
319 $ligne = traiter_listes($ligne);
321 // tout mettre dans un tableau 2d
322 preg_match_all('/\|([^|]*)/S', $ligne, $cols);
324 // Pas de paragraphes dans les cellules
325 foreach ($cols[1] as &$col) {
326 if (strlen($col = trim($col))) {
327 $col = preg_replace("/\n{2,}/S", "<br /> <br />", $col);
329 $col = str_replace("\n", _AUTOBR
."\n", $col);
333 // assembler le tableau
338 // maintenant qu'on a toutes les cellules
339 // on prepare une liste de rowspan par defaut, a partir
340 // du nombre de colonnes dans la premiere ligne.
341 // Reperer egalement les colonnes numeriques pour les cadrer a droite
342 $rowspans = $numeric = array();
343 $n = count($lignes[0]);
345 // distinguer les colonnes numeriques a point ou a virgule,
346 // pour les alignements eventuels sur "," ou "."
347 $numeric_class = array('.'=>'point',','=>'virgule');
348 for($i=0;$i<$n;$i++
) {
350 for ($j=0;$j<$k;$j++
) {
351 $rowspans[$j][$i] = 1;
352 if ($align AND preg_match('/^[+-]?(?:\s|\d)*([.,]?)\d*$/', trim($lignes[$j][$i]), $r)){
359 $numeric[$i] = $align ?
(" class='numeric ".$numeric_class[$align]."'") : '';
361 for ($j=0;$j<$k;$j++
) {
362 if (preg_match($reg_line_all, $lignes[$j][0])) {
363 $hl[$j] = "id{$tabid}_l$j"; // pour mettre dans les headers des td
369 $hl = array(); // toute la colonne ou rien
371 // et on parcourt le tableau a l'envers pour ramasser les
372 // colspan et rowspan en passant
375 for($l=count($lignes)-1; $l>=0; $l--) {
380 for($c=count($cols)-1; $c>=0; $c--) {
382 $cell = trim($cols[$c]);
386 } elseif($cell=='^') {
387 $rowspans[$l-1][$c]+
=$rowspans[$l][$c];
391 $attr .= " colspan='$colspan'";
394 if(($x=$rowspans[$l][$c])>1) {
395 $attr.= " rowspan='$x'";
397 $b = ($c==0 AND isset($hl[$l]))?
'th':'td';
398 $h = (isset($hc[$c])?
$hc[$c]:'').' '.(($b=='td' AND isset($hl[$l]))?
$hl[$l]:'');
400 $attr.=" headers='$h'";
401 // inutile de garder le strong qui n'a servi que de marqueur
403 $attr.=" id='".$hl[$l]."'";
404 $cols[$c] = str_replace(array('{','}'), '', $cols[$c]);
406 $ligne= "\n<$b".$attr.'>'.$cols[$c]."</$b>".$ligne;
411 $class = alterner($l+
1, 'odd', 'even');
412 $html = "<tr class='row_$class $class'>$ligne</tr>\n$html";
414 return "\n\n<table".$GLOBALS['class_spip_plus'].$summary.">\n"
424 * Traitement des listes
425 * on utilise la wheel correspondante
427 * http://doc.spip.org/@traiter_listes
432 function traiter_listes ($t) {
433 static $wheel = null;
436 $wheel = new TextWheel(
437 SPIPTextWheelRuleset
::loader($GLOBALS['spip_wheels']['listes'])
440 return $wheel->text($t);
444 // Ces deux constantes permettent de proteger certains caracteres
445 // en les remplacanat par des caracteres "illegaux". (cf corriger_caracteres)
447 define('_RACCOURCI_PROTEGER', "{}_-");
448 define('_RACCOURCI_PROTECTEUR', "\x1\x2\x3\x4");
450 define('_RACCOURCI_BALISE', ",</?[a-z!][^<>]*[".preg_quote(_RACCOURCI_PROTEGER
)."][^<>]*>,imsS");
453 * mais d'abord, une callback de reconfiguration des raccourcis
454 * a partir de globales (est-ce old-style ? on conserve quand meme
455 * par souci de compat ascendante)
459 function personnaliser_raccourcis(&$ruleset){
460 if (isset($GLOBALS['debut_intertitre']) AND $rule=$ruleset->getRule('intertitres')){
461 $rule->replace
[0] = preg_replace(',<[^>]*>,Uims',$GLOBALS['debut_intertitre'],$rule->replace
[0]);
462 $rule->replace
[1] = preg_replace(',<[^>]*>,Uims',$GLOBALS['fin_intertitre'],$rule->replace
[1]);
463 $ruleset->addRules(array('intertitres'=>$rule));
465 if (isset($GLOBALS['debut_gras']) AND $rule=$ruleset->getRule('gras')){
466 $rule->replace
[0] = preg_replace(',<[^>]*>,Uims',$GLOBALS['debut_gras'],$rule->replace
[0]);
467 $rule->replace
[1] = preg_replace(',<[^>]*>,Uims',$GLOBALS['fin_gras'],$rule->replace
[1]);
468 $ruleset->addRules(array('gras'=>$rule));
470 if (isset($GLOBALS['debut_italique']) AND $rule=$ruleset->getRule('italiques')){
471 $rule->replace
[0] = preg_replace(',<[^>]*>,Uims',$GLOBALS['debut_italique'],$rule->replace
[0]);
472 $rule->replace
[1] = preg_replace(',<[^>]*>,Uims',$GLOBALS['fin_italique'],$rule->replace
[1]);
473 $ruleset->addRules(array('italiques'=>$rule));
475 if (isset($GLOBALS['ligne_horizontale']) AND $rule=$ruleset->getRule('ligne-horizontale')){
476 $rule->replace
= preg_replace(',<[^>]*>,Uims',$GLOBALS['ligne_horizontale'],$rule->replace
);
477 $ruleset->addRules(array('ligne-horizontale'=>$rule));
479 if (isset($GLOBALS['toujours_paragrapher']) AND !$GLOBALS['toujours_paragrapher']
480 AND $rule=$ruleset->getRule('toujours-paragrapher')) {
481 $rule->disabled
= true;
482 $ruleset->addRules(array('toujours-paragrapher'=>$rule));
487 * Nettoie un texte, traite les raccourcis autre qu'URL, la typo, etc.
489 * http://doc.spip.org/@traiter_raccourcis
492 * @param bool $show_autobr
495 function traiter_raccourcis($t, $show_autobr = false) {
496 static $wheel, $notes;
497 static $img_br_auto,$img_br_manuel,$img_br_no;
499 // hack1: respecter le tag ignore br
501 AND strncmp($t, _AUTOBR_IGNORER
, strlen(_AUTOBR_IGNORER
))==0) {
502 $ignorer_autobr = true;
503 $t = substr($t, strlen(_AUTOBR_IGNORER
));
505 $ignorer_autobr = false;
507 // Appeler les fonctions de pre_traitement
508 $t = pipeline('pre_propre', $t);
510 if (!isset($wheel)) {
511 $ruleset = SPIPTextWheelRuleset
::loader(
512 $GLOBALS['spip_wheels']['raccourcis'],'personnaliser_raccourcis'
514 $wheel = new TextWheel($ruleset);
516 if (_request('var_mode') == 'wheel'
517 AND autoriser('debug')) {
518 $f = $wheel->compile();
519 echo "<pre>\n".htmlspecialchars($f)."</pre>\n";
522 $notes = charger_fonction('notes', 'inc');
525 // Gerer les notes (ne passe pas dans le pipeline)
526 list($t, $mes_notes) = $notes($t);
528 $t = $wheel->text($t);
530 // Appeler les fonctions de post-traitement
531 $t = pipeline('post_propre', $t);
534 $notes($mes_notes,'traiter',$ignorer_autobr);
536 // hack2: wrap des autobr dans l'espace prive, pour affichage css
537 // car en css on ne sait pas styler l'element BR
538 if ($ignorer_autobr AND _AUTOBR
) {
539 if (is_null($img_br_no))
540 $img_br_no = ($show_autobr?
http_img_pack("br-no-10.png",_T("tw:retour_ligne_ignore"),"class='br-no'",_T("tw:retour_ligne_ignore")):"");
541 $t = str_replace(_AUTOBR
, $img_br_no, $t);
543 if ($show_autobr AND _AUTOBR
) {
544 if (is_null($img_br_manuel))
545 $img_br_manuel = http_img_pack("br-manuel-10.png",_T("tw:retour_ligne_manuel"),"class='br-manuel'",_T("tw:retour_ligne_manuel"));
546 if (is_null($img_br_auto))
547 $img_br_auto = http_img_pack("br-auto-10.png",_T("tw:retour_ligne_auto"),"class='br-auto'",_T("tw:retour_ligne_auto"));
548 if (false !== strpos(strtolower($t), '<br')) {
549 $t = preg_replace("/<br\b.*>/UiS", "$img_br_manuel\\0", $t);
550 $t = str_replace($img_br_manuel._AUTOBR
, $img_br_auto._AUTOBR
, $t);
559 * Filtre a appliquer aux champs du type #TEXTE*
560 * http://doc.spip.org/@propre
563 * @param string $connect
567 function propre($t, $connect=null, $env=array()) {
568 // les appels directs a cette fonction depuis le php de l'espace
569 // prive etant historiquement ecrits sans argment $connect
570 // on utilise la presence de celui-ci pour distinguer les cas
571 // ou il faut passer interdire_script explicitement
572 // les appels dans les squelettes (de l'espace prive) fournissant un $connect
573 // ne seront pas perturbes
574 $interdire_script = false;
575 if (is_null($connect) AND test_espace_prive()){
577 $interdire_script = true;
580 if (!$t) return strval($t);
582 $t = echappe_html($t);
583 $t = expanser_liens($t,$connect, $env);
585 $t = traiter_raccourcis($t, (isset($env['wysiwyg']) AND $env['wysiwyg'])?
true:false);
586 $t = echappe_retour_modeles($t, $interdire_script);