source: spip-zone/_plugins_/textwheel/inc/texte.php @ 70280

Last change on this file since 70280 was 70280, checked in by guy.cesaro@…, 7 years ago

report de [70263] et http://core.spip.org/projects/spip/repository/revisions/20200
les tableaux lisent mieux les nombres, merci a denisb et esj

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