source: spip-zone/_plugins_/multi_en_bloc/inc/texte.php @ 104337

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