source: spip-zone/_core_/branches/spip-3.0/plugins/textwheel/inc/texte.php @ 70279

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

report de [r70263] les tableaux lisent mieux les nombres, merci a denisb et esj

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