[SPIP] ~2.1.12 -->2.1.25
[velocampus/web/www.git] / www / ecrire / inc / texte.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2014 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
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 \***************************************************************************/
12
13 if (!defined('_ECRIRE_INC_VERSION')) return;
14
15 include_spip('inc/filtres');
16 include_spip('inc/lang');
17 include_spip('inc/lien');
18
19 // init du tableau principal des raccourcis
20
21 global $spip_raccourcis_typo, $class_spip_plus, $debut_intertitre, $fin_intertitre, $debut_gras, $fin_gras, $debut_italique, $fin_italique;
22
23 $spip_raccourcis_typo = array(
24 array(
25 /* 4 */ "/(^|[^{])[{][{][{]/S",
26 /* 5 */ "/[}][}][}]($|[^}])/S",
27 /* 6 */ "/(( *)\n){2,}(<br\s*\/?".">)?/S",
28 /* 7 */ "/[{][{]/S",
29 /* 8 */ "/[}][}]/S",
30 /* 9 */ "/[{]/S",
31 /* 10 */ "/[}]/S",
32 /* 11 */ "/(?:<br\s*\/?".">){2,}/S",
33 /* 12 */ "/<p>\n*(?:<br\s*\/?".">\n*)*/S",
34 /* 13 */ "/<quote>/S",
35 /* 14 */ "/<\/quote>/S",
36 /* 15 */ "/<\/?intro>/S"
37 ),
38 array(
39 /* 4 */ "\$1\n\n" . $debut_intertitre,
40 /* 5 */ $fin_intertitre ."\n\n\$1",
41 /* 6 */ "<p>",
42 /* 7 */ $debut_gras,
43 /* 8 */ $fin_gras,
44 /* 9 */ $debut_italique,
45 /* 10 */ $fin_italique,
46 /* 11 */ "<p>",
47 /* 12 */ "<p>",
48 /* 13 */ "<blockquote$class_spip_plus><p>",
49 /* 14 */ "</blockquote><p>",
50 /* 15 */ ""
51 )
52 );
53
54 // Raccourcis dependant du sens de la langue
55
56 function definir_raccourcis_alineas()
57 {
58 global $ligne_horizontale;
59 static $alineas = array();
60 $x = _DIR_RESTREINT ? lang_dir() : lang_dir($GLOBALS['spip_lang']);
61 if (!isset($alineas[$x])) {
62
63 $alineas[$x] = array(
64 array(
65 /* 0 */ "/\n(----+|____+)/S",
66 /* 1 */ "/\n-- */S",
67 /* 2 */ "/\n- */S", /* DOIT rester a cette position */
68 /* 3 */ "/\n_ +/S"
69 ),
70 array(
71 /* 0 */ "\n\n" . $ligne_horizontale . "\n\n",
72 /* 1 */ "\n<br />&mdash;&nbsp;",
73 /* 2 */ "\n<br />".definir_puce()."&nbsp;",
74 /* 3 */ "\n<br />"
75 )
76 );
77 }
78 return $alineas[$x];
79 }
80
81 // On initialise la puce pour eviter find_in_path() a chaque rencontre de \n-
82 // Mais attention elle depend de la direction et de X_fonctions.php, ainsi que
83 // de l'espace choisi (public/prive)
84 // http://doc.spip.org/@definir_puce
85 function definir_puce() {
86
87 // Attention au sens, qui n'est pas defini de la meme facon dans
88 // l'espace prive (spip_lang est la langue de l'interface, lang_dir
89 // celle du texte) et public (spip_lang est la langue du texte)
90 $dir = _DIR_RESTREINT ? lang_dir() : lang_dir($GLOBALS['spip_lang']);
91
92 $p = 'puce' . (test_espace_prive() ? '_prive' : '');
93 if ($dir == 'rtl') $p .= '_rtl';
94
95 if (!isset($GLOBALS[$p])) {
96 $img = find_in_path($p.'.gif');
97 list(,,,$size) = @getimagesize($img);
98 $GLOBALS[$p] = '<img src="'.$img.'" '.$size.' class="puce" alt="-" />';
99 }
100 return $GLOBALS[$p];
101 }
102
103 // XHTML - Preserver les balises-bloc : on liste ici tous les elements
104 // dont on souhaite qu'ils provoquent un saut de paragraphe
105 if (!defined('_BALISES_BLOCS')) define('_BALISES_BLOCS',
106 'div|pre|ul|ol|li|blockquote|h[1-6r]|'
107 .'t(able|[rdh]|body|foot|extarea)|'
108 .'form|object|center|marquee|address|'
109 .'d[ltd]|script|noscript|map|button|fieldset|style');
110
111 //
112 // Echapper les elements perilleux en les passant en base64
113 //
114
115 // Creer un bloc base64 correspondant a $rempl ; au besoin en marquant
116 // une $source differente ; le script detecte automagiquement si ce qu'on
117 // echappe est un div ou un span
118 // http://doc.spip.org/@code_echappement
119 function code_echappement($rempl, $source='', $no_transform=false) {
120 if (!strlen($rempl)) return '';
121
122 // Tester si on echappe en span ou en div
123 $mode = preg_match(',</?('._BALISES_BLOCS.')[>[:space:]],iS', $rempl) ?
124 'div' : 'span';
125 $return = '';
126
127 // Decouper en morceaux, base64 a des probleme selon la taille de la pile
128 $taille = 30000;
129 for($i = 0; $i < strlen($rempl); $i += $taille) {
130 // Convertir en base64 et cacher dans un attribut
131 // utiliser les " pour eviter le re-encodage de ' et &#8217
132 $base64 = base64_encode(substr($rempl, $i, $taille));
133 $return .= "<$mode class=\"base64$source\" title=\"$base64\"></$mode>";
134 }
135
136 return $return
137 . ((!$no_transform AND $mode == 'div')
138 ? "\n\n"
139 : ''
140 );
141 ;
142 }
143
144 // Echapper les <html>...</ html>
145 // http://doc.spip.org/@traiter_echap_html_dist
146 function traiter_echap_html_dist($regs) {
147 return $regs[3];
148 }
149
150 // Echapper les <code>...</ code>
151 // http://doc.spip.org/@traiter_echap_code_dist
152 function traiter_echap_code_dist($regs) {
153 list(,,$att,$corps) = $regs;
154 $echap = htmlspecialchars($corps); // il ne faut pas passer dans entites_html, ne pas transformer les &#xxx; du code !
155
156 // ne pas mettre le <div...> s'il n'y a qu'une ligne
157 if (is_int(strpos($echap,"\n"))) {
158 // supprimer les sauts de ligne debut/fin
159 // (mais pas les espaces => ascii art).
160 $echap = preg_replace("/^[\n\r]+|[\n\r]+$/s", "", $echap);
161 $echap = nl2br($echap);
162 $echap = "<div style='text-align: left;' "
163 . "class='spip_code' dir='ltr'><code$att>"
164 .$echap."</code></div>";
165 } else {
166 $echap = "<code$att class='spip_code' dir='ltr'>".$echap."</code>";
167 }
168
169 $echap = str_replace("\t", "&nbsp; &nbsp; &nbsp; &nbsp; ", $echap);
170 $echap = str_replace(" ", " &nbsp;", $echap);
171 return $echap;
172 }
173
174 // Echapper les <cadre>...</ cadre> aka <frame>...</ frame>
175 // http://doc.spip.org/@traiter_echap_cadre_dist
176 function traiter_echap_cadre_dist($regs) {
177 $echap = trim(entites_html($regs[3]));
178 // compter les lignes un peu plus finement qu'avec les \n
179 $lignes = explode("\n",trim($echap));
180 $n = 0;
181 foreach($lignes as $l)
182 $n+=floor(strlen($l)/60)+1;
183 $n = max($n,2);
184 $echap = "\n<textarea readonly='readonly' cols='40' rows='$n' class='spip_cadre' dir='ltr'>$echap</textarea>";
185 return generer_form_ecrire('', $echap, " method='get'");
186 }
187 // http://doc.spip.org/@traiter_echap_frame_dist
188 function traiter_echap_frame_dist($regs) {
189 return traiter_echap_cadre_dist($regs);
190 }
191
192 // http://doc.spip.org/@traiter_echap_script_dist
193 function traiter_echap_script_dist($regs) {
194 // rendre joli (et inactif) si c'est un script language=php
195 if (preg_match(',<script\b[^>]+php,ims', $regs[0]))
196 return highlight_string($regs[0],true);
197
198 // Cas normal : le script passe tel quel
199 return $regs[0];
200 }
201
202 define('_PROTEGE_BLOCS', ',<(html|code|cadre|frame|script)(\s[^>]*)?>(.*)</\1>,UimsS');
203
204 // - pour $source voir commentaire infra (echappe_retour)
205 // - pour $no_transform voir le filtre post_autobr dans inc/filtres
206 // http://doc.spip.org/@echappe_html
207 function echappe_html($letexte, $source='', $no_transform=false,
208 $preg='') {
209 if (!is_string($letexte) or !strlen($letexte))
210 return $letexte;
211
212 if (($preg OR strpos($letexte,"<")!==false)
213 AND preg_match_all($preg ? $preg : _PROTEGE_BLOCS, $letexte, $matches, PREG_SET_ORDER))
214 foreach ($matches as $regs) {
215 // echappements tels quels ?
216 if ($no_transform) {
217 $echap = $regs[0];
218 }
219
220 // sinon les traiter selon le cas
221 else if (function_exists($f = 'traiter_echap_'.strtolower($regs[1])))
222 $echap = $f($regs);
223 else if (function_exists($f = $f.'_dist'))
224 $echap = $f($regs);
225
226 $letexte = str_replace($regs[0],
227 code_echappement($echap, $source, $no_transform),
228 $letexte);
229 }
230
231 if ($no_transform)
232 return $letexte;
233
234 // Gestion du TeX
235 if (strpos($letexte, "<math>") !== false) {
236 include_spip('inc/math');
237 $letexte = traiter_math($letexte, $source);
238 }
239
240 // Echapper le php pour faire joli (ici, c'est pas pour la securite)
241 if (strpos($letexte,"<"."?")!==false AND preg_match_all(',<[?].*($|[?]>),UisS',
242 $letexte, $matches, PREG_SET_ORDER))
243 foreach ($matches as $regs) {
244 $letexte = str_replace($regs[0],
245 code_echappement(highlight_string($regs[0],true), $source),
246 $letexte);
247 }
248
249 return $letexte;
250 }
251
252 //
253 // Traitement final des echappements
254 // Rq: $source sert a faire des echappements "a soi" qui ne sont pas nettoyes
255 // par propre() : exemple dans multi et dans typo()
256 // http://doc.spip.org/@echappe_retour
257 function echappe_retour($letexte, $source='', $filtre = "") {
258 if (strpos($letexte,"base64$source")) {
259 # spip_log(htmlspecialchars($letexte)); ## pour les curieux
260 if (strpos($letexte,"<")!==false AND
261 preg_match_all(',<(span|div)\sclass=[\'"]base64'.$source.'[\'"]\s(.*)>\s*</\1>,UmsS',
262 $letexte, $regs, PREG_SET_ORDER)) {
263 foreach ($regs as $reg) {
264 $rempl = base64_decode(extraire_attribut($reg[0], 'title'));
265 // recherche d'attributs supplementaires
266 $at = array();
267 foreach(array('lang', 'dir') as $attr) {
268 if ($a = extraire_attribut($reg[0], $attr))
269 $at[$attr] = $a;
270 }
271 if ($at) {
272 $rempl = '<'.$reg[1].'>'.$rempl.'</'.$reg[1].'>';
273 foreach($at as $attr => $a)
274 $rempl = inserer_attribut($rempl, $attr, $a);
275 }
276 if ($filtre) $rempl = $filtre($rempl);
277 $letexte = str_replace($reg[0], $rempl, $letexte);
278 }
279 }
280 }
281 return $letexte;
282 }
283
284 // Reinserer le javascript de confiance (venant des modeles)
285
286 // http://doc.spip.org/@echappe_retour_modeles
287 function echappe_retour_modeles($letexte, $interdire_scripts=false)
288 {
289 $letexte = echappe_retour($letexte);
290
291 // Dans les appels directs hors squelette, securiser aussi ici
292 if ($interdire_scripts)
293 $letexte = interdire_scripts($letexte,true);
294
295 return trim($letexte);
296 }
297
298
299 // http://doc.spip.org/@couper
300 function couper($texte, $taille=50, $suite = '&nbsp;(...)') {
301 if (!($length=strlen($texte)) OR $taille <= 0) return '';
302 $offset = 400 + 2*$taille;
303 while ($offset<$length
304 AND strlen(preg_replace(",<[^>]+>,Uims","",substr($texte,0,$offset)))<$taille)
305 $offset = 2*$offset;
306 if ( $offset<$length
307 && ($p_tag_ouvrant = strpos($texte,'<',$offset))!==NULL){
308 $p_tag_fermant = strpos($texte,'>',$offset);
309 if ($p_tag_fermant && ($p_tag_fermant<$p_tag_ouvrant))
310 $offset = $p_tag_fermant+1; // prolonger la coupe jusqu'au tag fermant suivant eventuel
311 }
312 $texte = substr($texte, 0, $offset); /* eviter de travailler sur 10ko pour extraire 150 caracteres */
313
314 // on utilise les \r pour passer entre les gouttes
315 $texte = str_replace("\r\n", "\n", $texte);
316 $texte = str_replace("\r", "\n", $texte);
317
318 // sauts de ligne et paragraphes
319 $texte = preg_replace("/\n\n+/", "\r", $texte);
320 $texte = preg_replace("/<(p|br)( [^>]*)?".">/", "\r", $texte);
321
322 // supprimer les traits, lignes etc
323 $texte = preg_replace("/(^|\r|\n)(-[-#\*]*|_ )/", "\r", $texte);
324
325 // supprimer les tags
326 $texte = supprimer_tags($texte);
327 $texte = trim(str_replace("\n"," ", $texte));
328 $texte .= "\n"; // marquer la fin
329
330 // travailler en accents charset
331 $texte = unicode2charset(html2unicode($texte, /* secure */ true));
332 $texte = nettoyer_raccourcis_typo($texte);
333
334 // corriger la longueur de coupe
335 // en fonction de la presence de caracteres utf
336 if ($GLOBALS['meta']['charset']=='utf-8'){
337 $long = charset2unicode($texte);
338 $long = spip_substr($long, 0, max($taille,1));
339 $nbcharutf = preg_match_all('/(&#[0-9]{3,5};)/S', $long, $matches);
340 $taille += $nbcharutf;
341 }
342
343
344 // couper au mot precedent
345 $long = spip_substr($texte, 0, max($taille-4,1));
346 $u = $GLOBALS['meta']['pcre_u'];
347 $court = preg_replace("/([^\s][\s]+)[^\s]*\n?$/".$u, "\\1", $long);
348 $points = $suite;
349
350 // trop court ? ne pas faire de (...)
351 if (spip_strlen($court) < max(0.75 * $taille,2)) {
352 $points = '';
353 $long = spip_substr($texte, 0, $taille);
354 $texte = preg_replace("/([^\s][\s]+)[^\s]*\n?$/".$u, "\\1", $long);
355 // encore trop court ? couper au caractere
356 if (spip_strlen($texte) < 0.75 * $taille)
357 $texte = $long;
358 } else
359 $texte = $court;
360
361 if (strpos($texte, "\n")) // la fin est encore la : c'est qu'on n'a pas de texte de suite
362 $points = '';
363
364 // remettre les paragraphes
365 $texte = preg_replace("/\r+/", "\n\n", $texte);
366
367 // supprimer l'eventuelle entite finale mal coupee
368 $texte = preg_replace('/&#?[a-z0-9]*$/S', '', $texte);
369
370 return quote_amp(trim($texte)).$points;
371 }
372
373 //
374 // Les elements de propre()
375 //
376
377 // afficher joliment les <script>
378 // http://doc.spip.org/@echappe_js
379 function echappe_js($t,$class=' class="echappe-js"') {
380 if (preg_match_all(',<script.*?($|</script.),isS', $t, $r, PREG_SET_ORDER))
381 foreach ($r as $regs)
382 $t = str_replace($regs[0],
383 "<code$class>".nl2br(htmlspecialchars($regs[0])).'</code>',
384 $t);
385 return $t;
386 }
387 // http://doc.spip.org/@protege_js_modeles
388 function protege_js_modeles($t) {
389 if (isset($GLOBALS['visiteur_session'])){
390 if (preg_match_all(',<script.*?($|</script.),isS', $t, $r, PREG_SET_ORDER)){
391 if (!defined('_PROTEGE_JS_MODELES')){
392 include_spip('inc/acces');
393 define('_PROTEGE_JS_MODELES',creer_uniqid());
394 }
395 foreach ($r as $regs)
396 $t = str_replace($regs[0],code_echappement($regs[0],'javascript'._PROTEGE_JS_MODELES),$t);
397 }
398 if (preg_match_all(',<\?php.*?($|\?'.'>),isS', $t, $r, PREG_SET_ORDER)){
399 if (!defined('_PROTEGE_PHP_MODELES')){
400 include_spip('inc/acces');
401 define('_PROTEGE_PHP_MODELES',creer_uniqid());
402 }
403 foreach ($r as $regs)
404 $t = str_replace($regs[0],code_echappement($regs[0],'php'._PROTEGE_PHP_MODELES),$t);
405 }
406 }
407 return $t;
408 }
409
410 // Securite : empecher l'execution de code PHP, en le transformant en joli code
411 // dans l'espace prive, cette fonction est aussi appelee par propre et typo
412 // si elles sont appelees en direct
413 // il ne faut pas desactiver globalement la fonction dans l'espace prive car elle protege
414 // aussi les balises des squelettes qui ne passent pas forcement par propre ou typo apres
415 // http://doc.spip.org/@interdire_scripts
416 function interdire_scripts($arg) {
417 // on memorise le resultat sur les arguments non triviaux
418 static $dejavu = array();
419
420 // Attention, si ce n'est pas une chaine, laisser intact
421 if (!$arg OR !is_string($arg) OR !strstr($arg, '<')) return $arg;
422
423 if (isset($dejavu[$GLOBALS['filtrer_javascript']][$arg])) return $dejavu[$GLOBALS['filtrer_javascript']][$arg];
424
425 // echapper les tags asp/php
426 $t = str_replace('<'.'%', '&lt;%', $arg);
427
428 // echapper le php
429 $t = str_replace('<'.'?', '&lt;?', $t);
430
431 // echapper le < script language=php >
432 $t = preg_replace(',<(script\b[^>]+\blanguage\b[^\w>]+php\b),UimsS', '&lt;\1', $t);
433
434 // Pour le js, trois modes : parano (-1), prive (0), ok (1)
435 switch($GLOBALS['filtrer_javascript']) {
436 case 0:
437 if (!_DIR_RESTREINT)
438 $t = echappe_js($t);
439 break;
440 case -1:
441 $t = echappe_js($t);
442 break;
443 }
444
445 // pas de <base href /> svp !
446 $t = preg_replace(',<(base\b),iS', '&lt;\1', $t);
447
448 // Reinserer les echappements des modeles
449 if (defined('_PROTEGE_JS_MODELES'))
450 $t = echappe_retour($t,"javascript"._PROTEGE_JS_MODELES);
451 if (defined('_PROTEGE_PHP_MODELES'))
452 $t = echappe_retour($t,"php"._PROTEGE_PHP_MODELES);
453
454 return $dejavu[$GLOBALS['filtrer_javascript']][$arg] = $t;
455 }
456
457 // Securite : utiliser SafeHTML s'il est present dans ecrire/safehtml/
458 // http://doc.spip.org/@safehtml
459 function safehtml($t) {
460 static $safehtml;
461
462 # attention safehtml nettoie deux ou trois caracteres de plus. A voir
463 if (strpos($t,'<')===false)
464 return str_replace("\x00", '', $t);
465
466 $t = interdire_scripts($t); // jolifier le php
467 $t = echappe_js($t);
468
469 if (!isset($safehtml))
470 $safehtml = charger_fonction('safehtml', 'inc', true);
471 if ($safehtml)
472 $t = $safehtml($t);
473
474 return interdire_scripts($t); // interdire le php (2 precautions)
475 }
476
477 // Typographie generale
478 // avec protection prealable des balises HTML et SPIP
479
480 // http://doc.spip.org/@typo
481 function typo($letexte, $echapper=true, $connect=null) {
482 // Plus vite !
483 if (!$letexte) return $letexte;
484
485 // les appels directs a cette fonction depuis le php de l'espace
486 // prive etant historiquement ecrit sans argment $connect
487 // on utilise la presence de celui-ci pour distinguer les cas
488 // ou il faut passer interdire_script explicitement
489 // les appels dans les squelettes (de l'espace prive) fournissant un $connect
490 // ne seront pas perturbes
491 $interdire_script = false;
492 if (is_null($connect)){
493 $connect = '';
494 $interdire_script = true;
495 }
496
497 // Echapper les codes <html> etc
498 if ($echapper)
499 $letexte = echappe_html($letexte, 'TYPO');
500
501 //
502 // Installer les modeles, notamment images et documents ;
503 //
504 // NOTE : propre() ne passe pas par ici mais directement par corriger_typo
505 // cf. inc/lien
506 $letexte = traiter_modeles($mem = $letexte, false, $echapper ? 'TYPO' : '', $connect);
507 if ($letexte != $mem) $echapper = true;
508 unset($mem);
509
510 $letexte = corriger_typo($letexte);
511
512 // reintegrer les echappements
513 if ($echapper)
514 $letexte = echappe_retour($letexte, 'TYPO');
515
516 // Dans les appels directs hors squelette, securiser ici aussi
517 if ($interdire_script)
518 $letexte = interdire_scripts($letexte);
519
520 return $letexte;
521 }
522
523 // Correcteur typographique
524
525 define('_TYPO_PROTEGER', "!':;?~%-");
526 define('_TYPO_PROTECTEUR', "\x1\x2\x3\x4\x5\x6\x7\x8");
527
528 define('_TYPO_BALISE', ",</?[a-z!][^<>]*[".preg_quote(_TYPO_PROTEGER)."][^<>]*>,imsS");
529
530 // http://doc.spip.org/@corriger_typo
531 function corriger_typo($letexte, $lang='') {
532 // Plus vite !
533 if (!$letexte) return $letexte;
534
535 $letexte = pipeline('pre_typo', $letexte);
536
537 // Caracteres de controle "illegaux"
538 $letexte = corriger_caracteres($letexte);
539
540 // Proteger les caracteres typographiques a l'interieur des tags html
541 if (preg_match_all(_TYPO_BALISE, $letexte, $regs, PREG_SET_ORDER)) {
542 foreach ($regs as $reg) {
543 $insert = $reg[0];
544 // hack: on transforme les caracteres a proteger en les remplacant
545 // par des caracteres "illegaux". (cf corriger_caracteres())
546 $insert = strtr($insert, _TYPO_PROTEGER, _TYPO_PROTECTEUR);
547 $letexte = str_replace($reg[0], $insert, $letexte);
548 }
549 }
550
551 // trouver les blocs multi et les traiter a part
552 $letexte = extraire_multi($e = $letexte, $lang, true);
553 $e = ($e === $letexte);
554
555 // Charger & appliquer les fonctions de typographie
556 $typographie = charger_fonction(lang_typo($lang), 'typographie');
557 $letexte = $typographie($letexte);
558
559 // Les citations en une autre langue, s'il y a lieu
560 if (!$e) $letexte = echappe_retour($letexte, 'multi');
561
562 // Retablir les caracteres proteges
563 $letexte = strtr($letexte, _TYPO_PROTECTEUR, _TYPO_PROTEGER);
564
565 // pipeline
566 $letexte = pipeline('post_typo', $letexte);
567
568 # un message pour abs_url - on est passe en mode texte
569 $GLOBALS['mode_abs_url'] = 'texte';
570
571 return $letexte;
572 }
573
574
575 //
576 // Tableaux
577 //
578
579 define('_RACCOURCI_CAPTION', ',^\|\|([^|]*)(\|(.*))?$,sS');
580 define('_RACCOURCI_TH_SPAN', '\s*(?:{{[^{}]+}}\s*)?|<');
581 define('_RACCOURCI_THEAD', true);
582
583 // http://doc.spip.org/@traiter_tableau
584 function traiter_tableau($bloc) {
585
586 // Decouper le tableau en lignes
587 preg_match_all(',([|].*)[|]\n,UmsS', $bloc, $regs, PREG_PATTERN_ORDER);
588 $lignes = array();
589 $debut_table = $summary = '';
590 $l = 0;
591 $numeric = true;
592
593 // Traiter chaque ligne
594 $reg_line1 = ',^(\|(' . _RACCOURCI_TH_SPAN . '))+$,sS';
595 $reg_line_all = ',^' . _RACCOURCI_TH_SPAN . '$,sS';
596 $num_cols = 0;
597 foreach ($regs[1] as $ligne) {
598 $l ++;
599
600 // Gestion de la premiere ligne :
601 if (($l == 1) AND preg_match(_RACCOURCI_CAPTION, rtrim($ligne,'|'), $cap)) {
602 // - <caption> et summary dans la premiere ligne :
603 // || caption | summary || (|summary est optionnel)
604 $l = 0;
605 if ($caption = trim($cap[1]))
606 $debut_table .= "<caption>".$caption."</caption>\n";
607 $summary = ' summary="'.entites_html(trim($cap[3])).'"';
608 } else {
609 // - <th> sous la forme |{{titre}}|{{titre}}|
610 if (preg_match($reg_line1, $ligne)) {
611 preg_match_all('/\|([^|]*)/S', $ligne, $cols);
612 $ligne='';$cols= $cols[1];
613 $colspan=1;
614 $num_cols = count($cols);
615 for($c=$num_cols-1; $c>=0; $c--) {
616 $attr='';
617 if($cols[$c]=='<') {
618 $colspan++;
619 } else {
620 if ($colspan>1) {
621 $attr= " colspan='$colspan'";
622 $colspan=1;
623 }
624 // inutile de garder le strong qui n'a servi que de marqueur
625 $cols[$c] = str_replace(array('{','}'), '', $cols[$c]);
626 $ligne= "<th scope='col'$attr>$cols[$c]</th>$ligne";
627 }
628 }
629 $lignes[] = $ligne;
630 } else {
631 // Sinon ligne normale
632 // Gerer les listes a puce dans les cellules
633 if (strpos($ligne,"\n-*")!==false OR strpos($ligne,"\n-#")!==false)
634 $ligne = traiter_listes($ligne);
635
636 // Pas de paragraphes dans les cellules
637 $ligne = preg_replace("/\n{2,}/", "<br /><br />\n", $ligne);
638
639 // tout mettre dans un tableau 2d
640 preg_match_all('/\|([^|]*)/S', $ligne, $cols);
641 $lignes[]= $cols[1];
642 }
643 }
644 }
645
646 // maintenant qu'on a toutes les cellules
647 // on prepare une liste de rowspan par defaut, a partir
648 // du nombre de colonnes dans la premiere ligne.
649 // Reperer egalement les colonnes numeriques pour les cadrer a droite
650 $rowspans = $numeric = array();
651 $n = $num_cols ? $num_cols : count($lignes[0]);
652 $k = count($lignes);
653 // distinguer les colonnes numeriques a point ou a virgule,
654 // pour les alignements eventuels sur "," ou "."
655 $numeric_class = array('.'=>'point',','=>'virgule');
656 for($i=0;$i<$n;$i++) {
657 $align = true;
658 for ($j=0;$j<$k;$j++) $rowspans[$j][$i] = 1;
659 for ($j=0;$j<$k;$j++) {
660 if (!is_array($lignes[$j])) continue; // cas du th
661 $cell = trim($lignes[$j][$i]);
662 if (preg_match($reg_line_all, $cell)) {
663 if (!preg_match('/^[+-]?(?:\s|\d)*([.,]?)\d*$/', $cell, $r))
664 { $align = ''; break;}
665 else if ($r[1]) $align = $r[1];
666 }
667 }
668 $numeric[$i] = !$align ? '' : (" class='numeric ".$numeric_class[$align]."'");
669 }
670
671 // et on parcourt le tableau a l'envers pour ramasser les
672 // colspan et rowspan en passant
673 $html = '';
674
675 for($l=count($lignes)-1; $l>=0; $l--) {
676 $cols= $lignes[$l];
677 if (!is_array($cols)) {
678 $class = 'first';
679 $ligne = $cols;
680 } else {
681 $ligne='';
682 $colspan=1;
683 $class = alterner($l+1, 'even', 'odd');
684 for($c=count($cols)-1; $c>=0; $c--) {
685 $attr= $numeric[$c];
686 $cell = trim($cols[$c]);
687 if($cell=='<') {
688 $colspan++;
689
690 } elseif($cell=='^') {
691 $rowspans[$l-1][$c]+=$rowspans[$l][$c];
692
693 } else {
694 if($colspan>1) {
695 $attr .= " colspan='$colspan'";
696 $colspan=1;
697 }
698 if(($x=$rowspans[$l][$c])>1) {
699 $attr.= " rowspan='$x'";
700 }
701 $ligne= "\n<td".$attr.'>'.$cols[$c].'</td>'.$ligne;
702 }
703 }
704 }
705 $html = "<tr class='row_$class'>$ligne</tr>\n$html";
706 }
707 if (_RACCOURCI_THEAD
708 AND preg_match("@^(<tr class='row_first'.*?</tr>)(.*)$@s", $html, $m))
709 $html = "<thead>$m[1]</thead>\n<tbody>$m[2]</tbody>\n";
710
711 return "\n\n<table".$GLOBALS['class_spip_plus'].$summary.">\n"
712 . $debut_table
713 . $html
714 . "</table>\n\n";
715 }
716
717
718 //
719 // Traitement des listes (merci a Michael Parienti)
720 //
721 // http://doc.spip.org/@traiter_listes
722 function traiter_listes ($texte) {
723 global $class_spip, $class_spip_plus;
724 $parags = preg_split(",\n[[:space:]]*\n,S", $texte);
725 $texte ='';
726
727 // chaque paragraphe est traite a part
728 while (list(,$para) = each($parags)) {
729 $niveau = 0;
730 $pile_li = $pile_type = array();
731 $lignes = explode("\n-", "\n" . $para);
732
733 // ne pas toucher a la premiere ligne
734 list(,$debut) = each($lignes);
735 $texte .= $debut;
736
737 // chaque item a sa profondeur = nb d'etoiles
738 $type ='';
739 while (list(,$item) = each($lignes)) {
740 preg_match(",^([*]*|[#]*)([^*#].*)$,sS", $item, $regs);
741 $profond = strlen($regs[1]);
742
743 if ($profond > 0) {
744 $ajout='';
745
746 // changement de type de liste au meme niveau : il faut
747 // descendre un niveau plus bas, fermer ce niveau, et
748 // remonter
749 $nouv_type = (substr($item,0,1) == '*') ? 'ul' : 'ol';
750 $change_type = ($type AND ($type <> $nouv_type) AND ($profond == $niveau)) ? 1 : 0;
751 $type = $nouv_type;
752
753 // d'abord traiter les descentes
754 while ($niveau > $profond - $change_type) {
755 $ajout .= $pile_li[$niveau];
756 $ajout .= $pile_type[$niveau];
757 if (!$change_type)
758 unset ($pile_li[$niveau]);
759 $niveau --;
760 }
761
762 // puis les identites (y compris en fin de descente)
763 if ($niveau == $profond && !$change_type) {
764 $ajout .= $pile_li[$niveau];
765 }
766
767 // puis les montees (y compris apres une descente un cran trop bas)
768 while ($niveau < $profond) {
769 if ($niveau == 0) $ajout .= "\n\n";
770 elseif (!isset($pile_li[$niveau])) {
771 $ajout .= "<li$class_spip>";
772 $pile_li[$niveau] = "</li>";
773 }
774 $niveau ++;
775 $ajout .= "<$type$class_spip_plus>";
776 $pile_type[$niveau] = "</$type>";
777 }
778
779 $ajout .= "<li$class_spip>";
780 $pile_li[$profond] = "</li>";
781 }
782 else {
783 $ajout = "\n-"; // puce normale ou <hr>
784 }
785
786 $texte .= $ajout . $regs[2];
787 }
788
789 // retour sur terre
790 $ajout = '';
791 while ($niveau > 0) {
792 $ajout .= $pile_li[$niveau];
793 $ajout .= $pile_type[$niveau];
794 $niveau --;
795 }
796 $texte .= $ajout;
797
798 // paragraphe
799 $texte .= "\n\n";
800 }
801
802 // sucrer les deux derniers \n
803 return substr($texte, 0, -2);
804 }
805
806
807 // fonction en cas de texte extrait d'un serveur distant:
808 // on ne sait pas (encore) rapatrier les documents joints
809 // Sert aussi a nettoyer un texte qu'on veut mettre dans un <a> etc.
810 // TODO: gerer les modeles ?
811 // http://doc.spip.org/@supprime_img
812 function supprime_img($letexte, $message=NULL) {
813 if ($message===NULL) $message = '(' . _T('img_indisponible') . ')';
814 return preg_replace(',<(img|doc|emb)([0-9]+)(\|([^>]*))?'.'\s*/?'.'>,i',
815 $message, $letexte);
816 }
817
818 //
819 // Une fonction pour fermer les paragraphes ; on essaie de preserver
820 // des paragraphes indiques a la main dans le texte
821 // (par ex: on ne modifie pas un <p align='center'>)
822 //
823 // deuxieme argument : forcer les <p> meme pour un seul paragraphe
824 //
825 // http://doc.spip.org/@paragrapher
826 function paragrapher($letexte, $forcer=true) {
827 global $class_spip;
828
829 $letexte = trim($letexte);
830 if (!strlen($letexte))
831 return '';
832
833 if ($forcer OR (
834 strstr($letexte,'<') AND preg_match(',<p\b,iS',$letexte)
835 )) {
836
837 // Ajouter un espace aux <p> et un "STOP P"
838 // transformer aussi les </p> existants en <p>, nettoyes ensuite
839 $letexte = preg_replace(',</?p\b\s?(.*?)>,iS', '<STOP P><p \1>',
840 '<p>'.$letexte.'<STOP P>');
841
842 // Fermer les paragraphes (y compris sur "STOP P")
843 $letexte = preg_replace(',(<p\s.*)(</?(STOP P|'._BALISES_BLOCS.')[>[:space:]]),UimsS',
844 "\n\\1</p>\n\\2", $letexte);
845
846 // Supprimer les marqueurs "STOP P"
847 $letexte = str_replace('<STOP P>', '', $letexte);
848
849 // Reduire les blancs dans les <p>
850 $u = @$GLOBALS['meta']['pcre_u'];
851 $letexte = preg_replace(',(<p\b.*>)\s*,UiS'.$u, '\1',$letexte);
852 $letexte = preg_replace(',\s*(</p\b.*>),UiS'.$u, '\1',$letexte);
853
854 // Supprimer les <p xx></p> vides
855 $letexte = preg_replace(',<p\b[^<>]*></p>\s*,iS'.$u, '',
856 $letexte);
857
858 // Renommer les paragraphes normaux
859 $letexte = str_replace('<p >', "<p$class_spip>",
860 $letexte);
861
862 }
863
864 return $letexte;
865 }
866
867 // http://doc.spip.org/@traiter_poesie
868 function traiter_poesie($letexte)
869 {
870 if (preg_match_all(",<(poesie|poetry)>(.*)<\/(poesie|poetry)>,UimsS",
871 $letexte, $regs, PREG_SET_ORDER)) {
872 $u = "/\n[\s]*\n/S" . $GLOBALS['meta']['pcre_u'];
873 foreach ($regs as $reg) {
874 $lecode = preg_replace(",\r\n?,S", "\n", $reg[2]);
875 $lecode = preg_replace($u, "\n&nbsp;\n",$lecode);
876 $lecode = "<blockquote class=\"spip_poesie\">\n<div>"
877 .preg_replace("/\n+/", "</div>\n<div>", trim($lecode))
878 ."</div>\n</blockquote>\n\n";
879 $letexte = str_replace($reg[0], $lecode, $letexte);
880 }
881 }
882 return $letexte;
883 }
884
885 // Harmonise les retours chariots et mange les paragraphes html
886 // http://doc.spip.org/@traiter_retours_chariots
887 function traiter_retours_chariots($letexte) {
888 $letexte = preg_replace(",\r\n?,S", "\n", $letexte);
889 $letexte = preg_replace(",<p[>[:space:]],iS", "\n\n\\0", $letexte);
890 $letexte = preg_replace(",</p[>[:space:]],iS", "\\0\n\n", $letexte);
891 return $letexte;
892 }
893
894 // Ces deux constantes permettent de proteger certains caracteres
895 // en les remplacanat par des caracteres "illegaux". (cf corriger_caracteres)
896
897 define('_RACCOURCI_PROTEGER', "{}_-");
898 define('_RACCOURCI_PROTECTEUR', "\x1\x2\x3\x4");
899
900 define('_RACCOURCI_BALISE', ",</?[a-z!][^<>]*[".preg_quote(_RACCOURCI_PROTEGER)."][^<>]*>,imsS");
901
902 // Nettoie un texte, traite les raccourcis autre qu'URL, la typo, etc.
903 // http://doc.spip.org/@traiter_raccourcis
904 function traiter_raccourcis($letexte) {
905
906 // Appeler les fonctions de pre_traitement
907 $letexte = pipeline('pre_propre', $letexte);
908
909 // Gerer les notes (ne passe pas dans le pipeline)
910 $notes = charger_fonction('notes', 'inc');
911 list($letexte, $mes_notes) = $notes($letexte);
912
913 //
914 // Tableaux
915 //
916
917 // ne pas oublier les tableaux au debut ou a la fin du texte
918 $letexte = preg_replace(",^\n?[|],S", "\n\n|", $letexte);
919 $letexte = preg_replace(",\n\n+[|],S", "\n\n\n\n|", $letexte);
920 $letexte = preg_replace(",[|](\n\n+|\n?$),S", "|\n\n\n\n", $letexte);
921
922 if (preg_match_all(',[^|](\n[|].*[|]\n)[^|],UmsS', $letexte,
923 $regs, PREG_SET_ORDER))
924 foreach ($regs as $t) {
925 $letexte = str_replace($t[1], traiter_tableau($t[1]), $letexte);
926 }
927
928 $letexte = "\n".trim($letexte);
929
930 // les listes
931 if (strpos($letexte,"\n-*")!==false OR strpos($letexte,"\n-#")!==false)
932 $letexte = traiter_listes($letexte);
933
934 // Proteger les caracteres actifs a l'interieur des tags html
935
936 if (preg_match_all(_RACCOURCI_BALISE, $letexte, $regs, PREG_SET_ORDER)) {
937 foreach ($regs as $reg) {
938 $insert = strtr($reg[0], _RACCOURCI_PROTEGER, _RACCOURCI_PROTECTEUR);
939 $letexte = str_replace($reg[0], $insert, $letexte);
940 }
941 }
942
943 // Traitement des alineas
944 list($a,$b) = definir_raccourcis_alineas();
945 $letexte = preg_replace($a, $b, $letexte);
946 // Introduction des attributs class_spip* et autres raccourcis
947 list($a,$b) = $GLOBALS['spip_raccourcis_typo'];
948 $letexte = preg_replace($a, $b, $letexte);
949 $letexte = preg_replace('@^\n<br />@S', '', $letexte);
950
951 // Retablir les caracteres proteges
952 $letexte = strtr($letexte, _RACCOURCI_PROTECTEUR, _RACCOURCI_PROTEGER);
953
954 // Fermer les paragraphes ; mais ne pas forcement en creer si un seul
955 $letexte = paragrapher($letexte, $GLOBALS['toujours_paragrapher']);
956
957 // Appeler les fonctions de post-traitement
958 $letexte = pipeline('post_propre', $letexte);
959
960 if ($mes_notes) $notes($mes_notes);
961
962 return $letexte;
963 }
964
965
966
967 // Filtre a appliquer aux champs du type #TEXTE*
968 // http://doc.spip.org/@propre
969 function propre($t, $connect=null) {
970 // les appels directs a cette fonction depuis le php de l'espace
971 // prive etant historiquement ecrits sans argment $connect
972 // on utilise la presence de celui-ci pour distinguer les cas
973 // ou il faut passer interdire_script explicitement
974 // les appels dans les squelettes (de l'espace prive) fournissant un $connect
975 // ne seront pas perturbes
976 $interdire_script = false;
977 if (is_null($connect)){
978 $connect = '';
979 $interdire_script = true;
980 }
981
982 return !$t ? strval($t) :
983 echappe_retour_modeles(
984 traiter_raccourcis(
985 expanser_liens(echappe_html($t),$connect)),$interdire_script);
986 }
987 ?>