PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/spip/ecrire/inc/texte.php

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