source: spip-zone/_core_/plugins/textwheel/inc/texte.php @ 48152

Last change on this file since 48152 was 48152, checked in by cedric@…, 10 years ago

retablir un point d'entree paragrapher() mais qui ignore le second argument $toujours_paragrapher obsolète

File size: 17.3 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/texte_mini');
16include_spip('inc/lien');
17
18include_spip('inc/textwheel');
19
20// 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
21$GLOBALS['spip_raccourcis_typo'] = array();
22
23// class_spip : savoir si on veut class="spip" sur p i strong & li
24// class_spip_plus : class="spip" sur les ul ol h3 hr quote table...
25// la difference c'est que des css specifiques existent pour les seconds
26//
27if (!isset($GLOBALS['class_spip']))
28        $GLOBALS['class_spip'] = '';
29if (!isset($GLOBALS['class_spip_plus']))
30        $GLOBALS['class_spip_plus'] = ' class="spip"';
31
32
33//
34// echapper les < script ...
35//
36function echappe_js($t) {
37        static $wheel = null;
38
39        if (!isset($wheel))
40                $wheel = new TextWheel(
41                        SPIPTextWheelRuleset::loader($GLOBALS['spip_wheels']['echappe_js'])
42                );
43
44        return $wheel->text($t);
45}
46
47//
48// paragagrapher seulement
49//
50function paragrapher($t) {
51        static $wheel = null;
52
53        if (!isset($wheel))
54                $wheel = new TextWheel(
55                        SPIPTextWheelRuleset::loader($GLOBALS['spip_wheels']['paragrapher'])
56                );
57
58        return $wheel->text($t);
59}
60
61
62// Securite : empecher l'execution de code PHP, en le transformant en joli code
63// dans l'espace prive, cette fonction est aussi appelee par propre et typo
64// si elles sont appelees en direct
65// il ne faut pas desactiver globalement la fonction dans l'espace prive car elle protege
66// aussi les balises des squelettes qui ne passent pas forcement par propre ou typo apres
67// http://doc.spip.org/@interdire_scripts
68function interdire_scripts($arg) {
69        // on memorise le resultat sur les arguments non triviaux
70        static $dejavu = array();
71        static $wheel = null;
72
73        // Attention, si ce n'est pas une chaine, laisser intact
74        if (!$arg OR !is_string($arg) OR !strstr($arg, '<')) return $arg; 
75
76        if (isset($dejavu[$GLOBALS['filtrer_javascript']][$arg])) return $dejavu[$GLOBALS['filtrer_javascript']][$arg];
77
78        if (!isset($wheel)){
79                $ruleset = SPIPTextWheelRuleset::loader(
80                        $GLOBALS['spip_wheels']['interdire_scripts']
81                );
82                // Pour le js, trois modes : parano (-1), prive (0), ok (1)
83                // desactiver la regle echappe-js si besoin
84                if ($GLOBALS['filtrer_javascript']==1
85                        OR ($GLOBALS['filtrer_javascript']==0 AND !test_espace_prive()))
86                        $ruleset->addRules (array('securite-js'=>array('disabled'=>true)));
87                $wheel = new TextWheel($ruleset);
88        }
89
90        $t = $wheel->text($arg);
91
92        // Reinserer les echappements des modeles
93        if (defined('_PROTEGE_JS_MODELES'))
94                $t = echappe_retour($t,"javascript"._PROTEGE_JS_MODELES);
95        if (defined('_PROTEGE_PHP_MODELES'))
96                $t = echappe_retour($t,"php"._PROTEGE_PHP_MODELES);
97
98        return $dejavu[$GLOBALS['filtrer_javascript']][$arg] = $t;
99}
100
101
102// Typographie generale
103// avec protection prealable des balises HTML et SPIP
104
105// http://doc.spip.org/@typo
106function typo($letexte, $echapper=true, $connect=null) {
107        // Plus vite !
108        if (!$letexte) return $letexte;
109
110        // les appels directs a cette fonction depuis le php de l'espace
111        // prive etant historiquement ecrit sans argment $connect
112        // on utilise la presence de celui-ci pour distinguer les cas
113        // ou il faut passer interdire_script explicitement
114        // les appels dans les squelettes (de l'espace prive) fournissant un $connect
115        // ne seront pas perturbes
116        $interdire_script = false;
117        if (is_null($connect)){
118                $connect = '';
119                $interdire_script = true;
120        }
121
122        // Echapper les codes <html> etc
123        if ($echapper)
124                $letexte = echappe_html($letexte, 'TYPO');
125
126        //
127        // Installer les modeles, notamment images et documents ;
128        //
129        // NOTE : propre() ne passe pas par ici mais directement par corriger_typo
130        // cf. inc/lien
131
132        $letexte = traiter_modeles($mem = $letexte, false, $echapper ? 'TYPO' : '', $connect);
133        if ($letexte != $mem) $echapper = true;
134        unset($mem);
135
136        $letexte = corriger_typo($letexte);
137        $letexte = echapper_faux_tags($letexte);
138
139        // reintegrer les echappements
140        if ($echapper)
141                $letexte = echappe_retour($letexte, 'TYPO');
142
143        // Dans les appels directs hors squelette, securiser ici aussi
144        if ($interdire_script)
145                $letexte = interdire_scripts($letexte);
146
147        return $letexte;
148}
149
150// Correcteur typographique
151
152define('_TYPO_PROTEGER', "!':;?~%-");
153define('_TYPO_PROTECTEUR', "\x1\x2\x3\x4\x5\x6\x7\x8");
154
155define('_TYPO_BALISE', ",</?[a-z!][^<>]*[".preg_quote(_TYPO_PROTEGER)."][^<>]*>,imsS");
156
157// http://doc.spip.org/@corriger_typo
158function corriger_typo($t, $lang='') {
159        // Plus vite !
160        if (!$t) return $t;
161
162        $t = pipeline('pre_typo', $t);
163
164        // Caracteres de controle "illegaux"
165        $t = corriger_caracteres($t);
166
167        // Proteger les caracteres typographiques a l'interieur des tags html
168        if (preg_match_all(_TYPO_BALISE, $t, $regs, PREG_SET_ORDER)) {
169                foreach ($regs as $reg) {
170                        $insert = $reg[0];
171                        // hack: on transforme les caracteres a proteger en les remplacant
172                        // par des caracteres "illegaux". (cf corriger_caracteres())
173                        $insert = strtr($insert, _TYPO_PROTEGER, _TYPO_PROTECTEUR);
174                        $t = str_replace($reg[0], $insert, $t);
175                }
176        }
177
178        // trouver les blocs multi et les traiter a part
179        $t = extraire_multi($e = $t, $lang, true);
180        $e = ($e === $t);
181
182        // Charger & appliquer les fonctions de typographie
183        $typographie = charger_fonction(lang_typo($lang), 'typographie');
184        $t = $typographie($t);
185
186        // Les citations en une autre langue, s'il y a lieu
187        if (!$e) $t = echappe_retour($t, 'multi');
188
189        // Retablir les caracteres proteges
190        $t = strtr($t, _TYPO_PROTECTEUR, _TYPO_PROTEGER);
191
192        // pipeline
193        $t = pipeline('post_typo', $t);
194
195        # un message pour abs_url - on est passe en mode texte
196        $GLOBALS['mode_abs_url'] = 'texte';
197
198        return $t;
199}
200
201
202//
203// Tableaux
204//
205
206define('_RACCOURCI_TH_SPAN', '\s*(:?{{[^{}]+}}\s*)?|<');
207
208// http://doc.spip.org/@traiter_tableau
209function traiter_tableau($bloc) {
210        // id "unique" pour les id du tableau
211        $tabid = substr(md5($bloc),0,4);
212
213        // Decouper le tableau en lignes
214        preg_match_all(',([|].*)[|]\n,UmsS', $bloc, $regs, PREG_PATTERN_ORDER);
215        $lignes = array();
216        $debut_table = $summary = '';
217        $l = 0;
218        $numeric = true;
219
220        // Traiter chaque ligne
221        $reg_line1 = ',^(\|(' . _RACCOURCI_TH_SPAN . '))+$,sS';
222        $reg_line_all = ',^('  . _RACCOURCI_TH_SPAN . ')$,sS';
223        $hc = $hl = array();
224        foreach ($regs[1] as $ligne) {
225                $l ++;
226
227                // Gestion de la premiere ligne :
228                if ($l == 1) {
229                // - <caption> et summary dans la premiere ligne :
230                //   || caption | summary || (|summary est optionnel)
231                        if (preg_match(',^\|\|([^|]*)(\|(.*))?$,sS', rtrim($ligne,'|'), $cap)) {
232                                $l = 0;
233                                if ($caption = trim($cap[1]))
234                                        $debut_table .= "<caption>".$caption."</caption>\n";
235                                $summary = ' summary="'.entites_html(trim($cap[3])).'"';
236                        }
237                // - <thead> sous la forme |{{titre}}|{{titre}}|
238                //   Attention thead oblige a avoir tbody
239                        else if (preg_match($reg_line1, $ligne, $thead)) {
240                                preg_match_all('/\|([^|]*)/S', $ligne, $cols);
241                                $ligne='';$cols= $cols[1];
242                                $colspan=1;
243                                for($c=count($cols)-1; $c>=0; $c--) {
244                                        $attr='';
245                                        if($cols[$c]=='<') {
246                                          $colspan++;
247                                        } else {
248                                          if($colspan>1) {
249                                                $attr= " colspan='$colspan'";
250                                                $colspan=1;
251                                          }
252                                          // inutile de garder le strong qui n'a servi que de marqueur
253                                          $cols[$c] = str_replace(array('{','}'), '', $cols[$c]);
254                                          $ligne= "<th id='id{$tabid}_c$c'$attr>$cols[$c]</th>$ligne";
255                                                $hc[$c] = "id{$tabid}_c$c"; // pour mettre dans les headers des td
256                                        }
257                                }
258
259                                $debut_table .= "<thead><tr class='row_first'>".
260                                        $ligne."</tr></thead>\n";
261                                $l = 0;
262                        }
263                }
264
265                // Sinon ligne normale
266                if ($l) {
267                        // Gerer les listes a puce dans les cellules
268                        if (strpos($ligne,"\n-*")!==false OR strpos($ligne,"\n-#")!==false)
269                                $ligne = traiter_listes($ligne);
270
271                        // Pas de paragraphes dans les cellules
272                        $ligne = preg_replace("/\n{2,}/", "<br /><br />\n", $ligne);
273
274                        // tout mettre dans un tableau 2d
275                        preg_match_all('/\|([^|]*)/S', $ligne, $cols);
276                        $lignes[]= $cols[1];
277                }
278        }
279
280        // maintenant qu'on a toutes les cellules
281        // on prepare une liste de rowspan par defaut, a partir
282        // du nombre de colonnes dans la premiere ligne.
283        // Reperer egalement les colonnes numeriques pour les cadrer a droite
284        $rowspans = $numeric = array();
285        $n = count($lignes[0]);
286        $k = count($lignes);
287        // distinguer les colonnes numeriques a point ou a virgule,
288        // pour les alignements eventuels sur "," ou "."
289        $numeric_class = array('.'=>'point',','=>'virgule');
290        for($i=0;$i<$n;$i++) {
291          $align = true;
292          for ($j=0;$j<$k;$j++) {
293                  $rowspans[$j][$i] = 1;
294                        if ($align AND preg_match('/^\d+([.,]?)\d*$/', trim($lignes[$j][$i]), $r)){
295                                if ($r[1])
296                                        $align = $r[1];
297                        }
298                        else
299                                $align = '';
300          }
301          $numeric[$i] = $align ? (" class='numeric ".$numeric_class[$align]."'") : '';
302        }
303        for ($j=0;$j<$k;$j++) {
304                if (preg_match($reg_line_all, $lignes[$j][0])) {
305                        $hl[$j] = "id{$tabid}_l$j"; // pour mettre dans les headers des td
306                }
307                else
308                        unset($hl[0]);
309        }
310        if (!isset($hl[0]))
311                $hl = array(); // toute la colonne ou rien
312
313        // et on parcourt le tableau a l'envers pour ramasser les
314        // colspan et rowspan en passant
315        $html = '';
316
317        for($l=count($lignes)-1; $l>=0; $l--) {
318                $cols= $lignes[$l];
319                $colspan=1;
320                $ligne='';
321
322                for($c=count($cols)-1; $c>=0; $c--) {
323                        $attr= $numeric[$c]; 
324                        $cell = trim($cols[$c]);
325                        if($cell=='<') {
326                          $colspan++;
327
328                        } elseif($cell=='^') {
329                          $rowspans[$l-1][$c]+=$rowspans[$l][$c];
330
331                        } else {
332                          if($colspan>1) {
333                                $attr .= " colspan='$colspan'";
334                                $colspan=1;
335                          }
336                          if(($x=$rowspans[$l][$c])>1) {
337                                $attr.= " rowspan='$x'";
338                          }
339                          $b = ($c==0 AND isset($hl[$l]))?'th':'td';
340                                $h = (isset($hc[$c])?$hc[$c]:'').' '.(($b=='td' AND isset($hl[$l]))?$hl[$l]:'');
341                                if ($h=trim($h))
342                                        $attr.=" headers='$h'";
343                                // inutile de garder le strong qui n'a servi que de marqueur
344                                if ($b=='th') {
345                                        $attr.=" id='".$hl[$l]."'";
346                                        $cols[$c] = str_replace(array('{','}'), '', $cols[$c]);
347                                }
348                          $ligne= "\n<$b".$attr.'>'.$cols[$c]."</$b>".$ligne;
349                        }
350                }
351
352                // ligne complete
353                $class = alterner($l+1, 'odd', 'even');
354                $html = "<tr class='row_$class $class'>$ligne</tr>\n$html";
355        }
356        return "\n\n<table".$GLOBALS['class_spip_plus'].$summary.">\n"
357                . $debut_table
358                . "<tbody>\n"
359                . $html
360                . "</tbody>\n"
361                . "</table>\n\n";
362}
363
364
365//
366// Traitement des listes (merci a Michael Parienti)
367//
368// http://doc.spip.org/@traiter_listes
369function traiter_listes ($texte) {
370        global $class_spip, $class_spip_plus;
371        $parags = preg_split(",\n[[:space:]]*\n,S", $texte);
372        $texte ='';
373
374        // chaque paragraphe est traite a part
375        while (list(,$para) = each($parags)) {
376                $niveau = 0;
377                $pile_li = $pile_type = array();
378                $lignes = explode("\n-", "\n" . $para);
379
380                // ne pas toucher a la premiere ligne
381                list(,$debut) = each($lignes);
382                $texte .= $debut;
383
384                // chaque item a sa profondeur = nb d'etoiles
385                $type ='';
386                while (list(,$item) = each($lignes)) {
387                        preg_match(",^([*]*|[#]*)([^*#].*)$,sS", $item, $regs);
388                        $profond = strlen($regs[1]);
389
390                        if ($profond > 0) {
391                                $ajout='';
392
393                                // changement de type de liste au meme niveau : il faut
394                                // descendre un niveau plus bas, fermer ce niveau, et
395                                // remonter
396                                $nouv_type = (substr($item,0,1) == '*') ? 'ul' : 'ol';
397                                $change_type = ($type AND ($type <> $nouv_type) AND ($profond == $niveau)) ? 1 : 0;
398                                $type = $nouv_type;
399
400                                // d'abord traiter les descentes
401                                while ($niveau > $profond - $change_type) {
402                                        $ajout .= $pile_li[$niveau];
403                                        $ajout .= $pile_type[$niveau];
404                                        if (!$change_type)
405                                                unset ($pile_li[$niveau]);
406                                        $niveau --;
407                                }
408
409                                // puis les identites (y compris en fin de descente)
410                                if ($niveau == $profond && !$change_type) {
411                                        $ajout .= $pile_li[$niveau];
412                                }
413
414                                // puis les montees (y compris apres une descente un cran trop bas)
415                                while ($niveau < $profond) {
416                                        if ($niveau == 0) $ajout .= "\n\n";
417                                        elseif (!isset($pile_li[$niveau])) {
418                                                $ajout .= "<li$class_spip>";
419                                                $pile_li[$niveau] = "</li>";
420                                        }
421                                        $niveau ++;
422                                        $ajout .= "<$type$class_spip_plus>";
423                                        $pile_type[$niveau] = "</$type>";
424                                }
425
426                                $ajout .= "<li$class_spip>";
427                                $pile_li[$profond] = "</li>";
428                        }
429                        else {
430                                $ajout = "\n-"; // puce normale ou <hr>
431                        }
432
433                        $texte .= $ajout . $regs[2];
434                }
435
436                // retour sur terre
437                $ajout = '';
438                while ($niveau > 0) {
439                        $ajout .= $pile_li[$niveau];
440                        $ajout .= $pile_type[$niveau];
441                        $niveau --;
442                }
443                $texte .= $ajout;
444
445                // paragraphe
446                $texte .= "\n\n";
447        }
448
449        // sucrer les deux derniers \n
450        return substr($texte, 0, -2);
451}
452
453// http://doc.spip.org/@traiter_poesie
454function traiter_poesie($letexte)
455{
456        if (preg_match_all(",<(poesie|poetry)>(.*)<\/(poesie|poetry)>,UimsS",
457        $letexte, $regs, PREG_SET_ORDER)) {
458                $u = "/\n[\s]*\n/S" . $GLOBALS['meta']['pcre_u'];
459                foreach ($regs as $reg) {
460                        $lecode = preg_replace(",\r\n?,S", "\n", $reg[2]);
461                        $lecode = preg_replace($u, "\n&nbsp;\n",$lecode);
462                        $lecode = "<blockquote class=\"spip_poesie\">\n<div>"
463                                .preg_replace("/\n+/", "</div>\n<div>", trim($lecode))
464                                ."</div>\n</blockquote>\n\n";
465                        $letexte = str_replace($reg[0], $lecode, $letexte);
466                }
467        }
468        return $letexte;
469}
470
471// Harmonise les retours chariots et mange les paragraphes html
472// http://doc.spip.org/@traiter_retours_chariots
473function traiter_retours_chariots($letexte) {
474        $letexte = preg_replace(",\r\n?,S", "\n", $letexte);
475        $letexte = preg_replace(",<p[>[:space:]],iS", "\n\n\\0", $letexte);
476        $letexte = preg_replace(",</p[>[:space:]],iS", "\\0\n\n", $letexte);
477        return $letexte;
478}
479
480// Ces deux constantes permettent de proteger certains caracteres
481// en les remplacanat par des caracteres "illegaux". (cf corriger_caracteres)
482
483define('_RACCOURCI_PROTEGER', "{}_-");
484define('_RACCOURCI_PROTECTEUR', "\x1\x2\x3\x4");
485
486define('_RACCOURCI_BALISE', ",</?[a-z!][^<>]*[".preg_quote(_RACCOURCI_PROTEGER)."][^<>]*>,imsS");
487
488// Nettoie un texte, traite les raccourcis autre qu'URL, la typo, etc.
489
490// mais d'abord, une callback de reconfiguration des raccourcis
491// a partir de globales (est-ce old-style ? on conserve quand meme
492// par souci de compat ascendante)
493function personnaliser_raccourcis(&$ruleset){
494        if (isset($GLOBALS['debut_intertitre']) AND $rule=$ruleset->getRule('intertitres')){
495                $rule->replace[0] = preg_replace(',<[^>]*>,Uims',$GLOBALS['debut_intertitre'],$rule->replace[0]);
496                $rule->replace[1] = preg_replace(',<[^>]*>,Uims',$GLOBALS['fin_intertitre'],$rule->replace[1]);
497                $ruleset->addRules(array('intertitres'=>$rule));
498        }
499        if (isset($GLOBALS['debut_gras']) AND $rule=$ruleset->getRule('gras')){
500                $rule->replace[0] = preg_replace(',<[^>]*>,Uims',$GLOBALS['debut_gras'],$rule->replace[0]);
501                $rule->replace[1] = preg_replace(',<[^>]*>,Uims',$GLOBALS['fin_gras'],$rule->replace[1]);
502                $ruleset->addRules(array('gras'=>$rule));
503        }
504        if (isset($GLOBALS['debut_italique']) AND $rule=$ruleset->getRule('italiques')){
505                $rule->replace[0] = preg_replace(',<[^>]*>,Uims',$GLOBALS['debut_italique'],$rule->replace[0]);
506                $rule->replace[1] = preg_replace(',<[^>]*>,Uims',$GLOBALS['fin_italique'],$rule->replace[1]);
507                $ruleset->addRules(array('italiques'=>$rule));
508        }
509        if (isset($GLOBALS['ligne_horizontale']) AND $rule=$ruleset->getRule('ligne-horizontale')){
510                $rule->replace = preg_replace(',<[^>]*>,Uims',$GLOBALS['ligne_horizontale'],$rule->replace);
511                $ruleset->addRules(array('ligne-horizontale'=>$rule));
512        }
513        if (isset($GLOBALS['toujours_paragrapher']) AND !$GLOBALS['toujours_paragrapher']
514          AND $rule=$ruleset->getRule('toujours-paragrapher')) {
515                $rule->disabled = true;
516                $ruleset->addRules(array('toujours-paragrapher'=>$rule));
517        }
518}
519
520// http://doc.spip.org/@traiter_raccourcis
521function traiter_raccourcis($t) {
522        static $wheel;
523        // Appeler les fonctions de pre_traitement
524        $t = pipeline('pre_propre', $t);
525
526        if (!isset($wheel)) {
527                $ruleset = SPIPTextWheelRuleset::loader(
528                        $GLOBALS['spip_wheels']['raccourcis'],'personnaliser_raccourcis'
529                );
530                $wheel = new TextWheel($ruleset);
531
532                if (_request('var_mode') == 'wheel'
533                AND autoriser('debug')) {
534                        $f = $wheel->compile();
535                        echo "<pre>\n".htmlspecialchars($f)."</pre>\n";
536                        exit;
537                }
538        }
539
540        // Gerer les notes (ne passe pas dans le pipeline)
541        $notes = charger_fonction('notes', 'inc');
542        list($t, $mes_notes) = $notes($t);
543
544        $t = $wheel->text($t);
545
546        // Appeler les fonctions de post-traitement
547        $t = pipeline('post_propre', $t);
548
549        if ($mes_notes)
550                $notes($mes_notes);
551
552        return $t;
553}
554
555
556// Filtre a appliquer aux champs du type #TEXTE*
557// http://doc.spip.org/@propre
558function propre($t, $connect=null) {
559        // les appels directs a cette fonction depuis le php de l'espace
560        // prive etant historiquement ecrits sans argment $connect
561        // on utilise la presence de celui-ci pour distinguer les cas
562        // ou il faut passer interdire_script explicitement
563        // les appels dans les squelettes (de l'espace prive) fournissant un $connect
564        // ne seront pas perturbes
565        $interdire_script = false;
566        if (is_null($connect)){
567                $connect = '';
568                $interdire_script = true;
569        }
570
571        if (!$t) return strval($t);
572
573        $t = echappe_html($t);
574        $t = expanser_liens($t,$connect);
575        $t = traiter_raccourcis($t);
576        $t = echappe_retour_modeles($t, $interdire_script);
577
578        return $t;
579}
580?>
Note: See TracBrowser for help on using the repository browser.