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

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

complément à http://core.spip.org/projects/spip/repository/revisions/18050 pour l'optimisation des appels a charger_fonction()

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