You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

990 lines
27 KiB

Correction de plusieurs soucis sur la fonction nettoyer_raccourcis_typo() . Cette fonction est utilisée par couper() ou pour calculer des attributs title ou encore pour indexer des documents par le plugin Indexer sur une base Sphinx. C'est dans ce dernier cadre que quelques problèmes se sont montrées : - la regexp qui supprimait les notes pouvait tuer PCRE si le texte était volumineux et avait des notes mal fermées. On simplifie cette expression. Cela ne provoque aucun changement sur les bases de tests que j’ai pu voir, améliorant même le retour de 3 articles qui renvoyaient vide avant à tord. - contrairement à ce qu’affirmait le commentaire, les tableaux n’étaient pas supprimés dans la plupart des cas. Effectivement la regexp cherchait uniquemnet des \r (sauts de paragraphe à cet endroit) et pas de \n (sauts de lignes à cet endroit). La regexp n’étant pas multiligne d’une part et textwheel sachant bien gérer les tableaux même s’il n’y a pas de ligne vide avant/après, on permet d’éliminer simplement les lignes de tableau en ajoutant \n à cet endroit. Plus généralement cette fonction… ne nettoyait pas tous les raccourcis. Une partie était faite par couper() uniquement. Il me semble que c'est un tord. On déplace donc de couper() dans la fonction nettoyer_raccourcis_typo() quelques nettoyages, notamment la suppression des caractères de liste. - les listes étaient retournées avec un saut de paragraphe entre chaque élément. Dans le cadre de ces fonctions ce n’est a priori pas utile, et on retourne du coup un simple saut de ligne à la place (note: couper remplace les sauts de ligne par des espaces ensuite — pas les sauts de paragraphe). Il y a donc un changement de comportement de ce point de vue. - le texte de sortie est trimmé (couper() s’en occupe aussi d’ailleurs). Conséquence notable pour Indexer : le contenu des tableau n’est plus retourné. Une option devrait être proposée à cette fonction pour retourner les contenus des tableaux, mais sans les | . On ajoute quelques fonctions de tests unitaires au passage. La fonction couper() du core va être modifiée en conséquence.
4 years ago
Correction de plusieurs soucis sur la fonction nettoyer_raccourcis_typo() . Cette fonction est utilisée par couper() ou pour calculer des attributs title ou encore pour indexer des documents par le plugin Indexer sur une base Sphinx. C'est dans ce dernier cadre que quelques problèmes se sont montrées : - la regexp qui supprimait les notes pouvait tuer PCRE si le texte était volumineux et avait des notes mal fermées. On simplifie cette expression. Cela ne provoque aucun changement sur les bases de tests que j’ai pu voir, améliorant même le retour de 3 articles qui renvoyaient vide avant à tord. - contrairement à ce qu’affirmait le commentaire, les tableaux n’étaient pas supprimés dans la plupart des cas. Effectivement la regexp cherchait uniquemnet des \r (sauts de paragraphe à cet endroit) et pas de \n (sauts de lignes à cet endroit). La regexp n’étant pas multiligne d’une part et textwheel sachant bien gérer les tableaux même s’il n’y a pas de ligne vide avant/après, on permet d’éliminer simplement les lignes de tableau en ajoutant \n à cet endroit. Plus généralement cette fonction… ne nettoyait pas tous les raccourcis. Une partie était faite par couper() uniquement. Il me semble que c'est un tord. On déplace donc de couper() dans la fonction nettoyer_raccourcis_typo() quelques nettoyages, notamment la suppression des caractères de liste. - les listes étaient retournées avec un saut de paragraphe entre chaque élément. Dans le cadre de ces fonctions ce n’est a priori pas utile, et on retourne du coup un simple saut de ligne à la place (note: couper remplace les sauts de ligne par des espaces ensuite — pas les sauts de paragraphe). Il y a donc un changement de comportement de ce point de vue. - le texte de sortie est trimmé (couper() s’en occupe aussi d’ailleurs). Conséquence notable pour Indexer : le contenu des tableau n’est plus retourné. Une option devrait être proposée à cette fonction pour retourner les contenus des tableaux, mais sans les | . On ajoute quelques fonctions de tests unitaires au passage. La fonction couper() du core va être modifiée en conséquence.
4 years ago
Correction de plusieurs soucis sur la fonction nettoyer_raccourcis_typo() . Cette fonction est utilisée par couper() ou pour calculer des attributs title ou encore pour indexer des documents par le plugin Indexer sur une base Sphinx. C'est dans ce dernier cadre que quelques problèmes se sont montrées : - la regexp qui supprimait les notes pouvait tuer PCRE si le texte était volumineux et avait des notes mal fermées. On simplifie cette expression. Cela ne provoque aucun changement sur les bases de tests que j’ai pu voir, améliorant même le retour de 3 articles qui renvoyaient vide avant à tord. - contrairement à ce qu’affirmait le commentaire, les tableaux n’étaient pas supprimés dans la plupart des cas. Effectivement la regexp cherchait uniquemnet des \r (sauts de paragraphe à cet endroit) et pas de \n (sauts de lignes à cet endroit). La regexp n’étant pas multiligne d’une part et textwheel sachant bien gérer les tableaux même s’il n’y a pas de ligne vide avant/après, on permet d’éliminer simplement les lignes de tableau en ajoutant \n à cet endroit. Plus généralement cette fonction… ne nettoyait pas tous les raccourcis. Une partie était faite par couper() uniquement. Il me semble que c'est un tord. On déplace donc de couper() dans la fonction nettoyer_raccourcis_typo() quelques nettoyages, notamment la suppression des caractères de liste. - les listes étaient retournées avec un saut de paragraphe entre chaque élément. Dans le cadre de ces fonctions ce n’est a priori pas utile, et on retourne du coup un simple saut de ligne à la place (note: couper remplace les sauts de ligne par des espaces ensuite — pas les sauts de paragraphe). Il y a donc un changement de comportement de ce point de vue. - le texte de sortie est trimmé (couper() s’en occupe aussi d’ailleurs). Conséquence notable pour Indexer : le contenu des tableau n’est plus retourné. Une option devrait être proposée à cette fonction pour retourner les contenus des tableaux, mais sans les | . On ajoute quelques fonctions de tests unitaires au passage. La fonction couper() du core va être modifiée en conséquence.
4 years ago
Correction de plusieurs soucis sur la fonction nettoyer_raccourcis_typo() . Cette fonction est utilisée par couper() ou pour calculer des attributs title ou encore pour indexer des documents par le plugin Indexer sur une base Sphinx. C'est dans ce dernier cadre que quelques problèmes se sont montrées : - la regexp qui supprimait les notes pouvait tuer PCRE si le texte était volumineux et avait des notes mal fermées. On simplifie cette expression. Cela ne provoque aucun changement sur les bases de tests que j’ai pu voir, améliorant même le retour de 3 articles qui renvoyaient vide avant à tord. - contrairement à ce qu’affirmait le commentaire, les tableaux n’étaient pas supprimés dans la plupart des cas. Effectivement la regexp cherchait uniquemnet des \r (sauts de paragraphe à cet endroit) et pas de \n (sauts de lignes à cet endroit). La regexp n’étant pas multiligne d’une part et textwheel sachant bien gérer les tableaux même s’il n’y a pas de ligne vide avant/après, on permet d’éliminer simplement les lignes de tableau en ajoutant \n à cet endroit. Plus généralement cette fonction… ne nettoyait pas tous les raccourcis. Une partie était faite par couper() uniquement. Il me semble que c'est un tord. On déplace donc de couper() dans la fonction nettoyer_raccourcis_typo() quelques nettoyages, notamment la suppression des caractères de liste. - les listes étaient retournées avec un saut de paragraphe entre chaque élément. Dans le cadre de ces fonctions ce n’est a priori pas utile, et on retourne du coup un simple saut de ligne à la place (note: couper remplace les sauts de ligne par des espaces ensuite — pas les sauts de paragraphe). Il y a donc un changement de comportement de ce point de vue. - le texte de sortie est trimmé (couper() s’en occupe aussi d’ailleurs). Conséquence notable pour Indexer : le contenu des tableau n’est plus retourné. Une option devrait être proposée à cette fonction pour retourner les contenus des tableaux, mais sans les | . On ajoute quelques fonctions de tests unitaires au passage. La fonction couper() du core va être modifiée en conséquence.
4 years ago
  1. <?php
  2. /***************************************************************************\
  3. * SPIP, Système de publication pour l'internet *
  4. * *
  5. * Copyright © avec tendresse depuis 2001 *
  6. * Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James *
  7. * *
  8. * Ce programme est un logiciel libre distribué sous licence GNU/GPL. *
  9. * Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne. *
  10. \***************************************************************************/
  11. if (!defined('_ECRIRE_INC_VERSION')) {
  12. return;
  13. }
  14. include_spip('base/abstract_sql');
  15. /**
  16. * Production de la balise a+href à partir des raccourcis `[xxx->url]` etc.
  17. *
  18. * @note
  19. * Compliqué car c'est ici qu'on applique typo(),
  20. * et en plus, on veut pouvoir les passer en pipeline
  21. *
  22. * @see typo()
  23. * @param string $lien
  24. * @param string $texte
  25. * @param string $class
  26. * @param string $title
  27. * @param string $hlang
  28. * @param string $rel
  29. * @param string $connect
  30. * @param array $env
  31. * @return string
  32. */
  33. function inc_lien_dist(
  34. $lien,
  35. $texte = '',
  36. $class = '',
  37. $title = '',
  38. $hlang = '',
  39. $rel = '',
  40. $connect = '',
  41. $env = []
  42. ) {
  43. static $u = null;
  44. if (!$u) {
  45. $u = url_de_base();
  46. }
  47. $typo = false;
  48. // Si une langue est demandee sur un raccourci d'article, chercher
  49. // la traduction ;
  50. // - [{en}->art2] => traduction anglaise de l'article 2, sinon art 2
  51. // - [{}->art2] => traduction en langue courante de l'art 2, sinon art 2
  52. // s'applique a tout objet traduit
  53. if (
  54. $hlang
  55. and $match = typer_raccourci($lien)
  56. ) {
  57. @list($type, , $id, , $args, , $ancre) = $match;
  58. $trouver_table = charger_fonction('trouver_table', 'base');
  59. $desc = $trouver_table(table_objet($type, $connect), $connect);
  60. if (
  61. $desc
  62. and $id_table_objet = $desc['key']['PRIMARY KEY']
  63. ) {
  64. $table_objet_sql = $desc['table'];
  65. if (
  66. $row = sql_fetsel('*', $table_objet_sql, "$id_table_objet=" . intval($id))
  67. and isset($row['id_trad'])
  68. and isset($row['lang'])
  69. and $id_dest = sql_getfetsel(
  70. $id_table_objet,
  71. $table_objet_sql,
  72. 'id_trad=' . intval($row['id_trad']) . ' AND lang=' . sql_quote($hlang)
  73. )
  74. and objet_test_si_publie($type, $id_dest)
  75. ) {
  76. $lien = "$type$id_dest";
  77. } else {
  78. $hlang = '';
  79. }
  80. } else {
  81. $hlang = '';
  82. }
  83. }
  84. $mode = ($texte and $class) ? 'url' : 'tout';
  85. $lang = '';
  86. $lien = calculer_url($lien, $texte, $mode, $connect);
  87. if ($mode === 'tout') {
  88. $texte = $lien['titre'];
  89. if (!$class and isset($lien['class'])) {
  90. $class = $lien['class'];
  91. }
  92. $lang = isset($lien['lang']) ? $lien['lang'] : '';
  93. $mime = isset($lien['mime']) ? " type='" . $lien['mime'] . "'" : '';
  94. $lien = $lien['url'];
  95. }
  96. $lien = trim($lien);
  97. if (strncmp($lien, '#', 1) == 0) { # ancres pures (internes a la page)
  98. $class = 'spip_ancre';
  99. } elseif (strncasecmp($lien, 'mailto:', 7) == 0) { # pseudo URL de mail
  100. $class = 'spip_mail';
  101. } elseif (strncmp($texte, '<html>', 6) == 0) { # cf traiter_lien_explicite
  102. $class = 'spip_url';
  103. # spip_out sur les URLs externes
  104. if (
  105. preg_match(',^\w+://,iS', $lien)
  106. and strncasecmp($lien, url_de_base(), strlen(url_de_base()))
  107. ) {
  108. $class .= ' spip_out';
  109. }
  110. } elseif (!$class) {
  111. # spip_out sur les URLs externes
  112. if (
  113. preg_match(',^\w+://,iS', $lien)
  114. and strncasecmp($lien, url_de_base(), strlen(url_de_base()))
  115. ) {
  116. $class = 'spip_out'; # si pas spip_in|spip_glossaire
  117. }
  118. }
  119. if ($class) {
  120. $class = " class='$class'";
  121. }
  122. // Si l'objet n'est pas de la langue courante, on ajoute hreflang
  123. if (!$hlang and isset($lang) and $lang !== $GLOBALS['spip_lang']) {
  124. $hlang = $lang;
  125. }
  126. $lang = ($hlang ? " hreflang='$hlang'" : '');
  127. if ($title) {
  128. $title = ' title="' . attribut_html($title) . '"';
  129. } else {
  130. $title = ''; // $title peut etre 'false'
  131. }
  132. // rel=external pour les liens externes
  133. if (
  134. (strncmp($lien, 'http://', 7) == 0 or strncmp($lien, 'https://', 8) == 0)
  135. and strncmp("$lien/", $u, strlen($u)) != 0
  136. ) {
  137. $rel = trim("$rel external");
  138. }
  139. if ($rel) {
  140. $rel = " rel='$rel'";
  141. }
  142. $lang_objet_prev = '';
  143. if ($hlang and $hlang !== $GLOBALS['spip_lang']) {
  144. $lang_objet_prev = isset($GLOBALS['lang_objet']) ? $GLOBALS['lang_objet'] : null;
  145. $GLOBALS['lang_objet'] = $hlang;
  146. }
  147. // si pas de modele dans le texte du lien, on peut juste passer typo sur le texte, c'est plus rapide
  148. // les rares cas de lien qui encapsule un modele passe en dessous, c'est plus lent
  149. if (traiter_modeles($texte, false, '', $connect, null, $env) == $texte) {
  150. $texte = typo($texte, true, $connect, $env);
  151. $lien = '<a href="' . str_replace(
  152. '"',
  153. '&quot;',
  154. $lien
  155. ) . "\"$class$lang$title$rel" . (isset($mime) ? $mime : '') . ">$texte</a>";
  156. if ($lang_objet_prev !== '') {
  157. if ($lang_objet_prev) {
  158. $GLOBALS['lang_objet'] = $lang_objet_prev;
  159. } else {
  160. unset($GLOBALS['lang_objet']);
  161. }
  162. }
  163. return $lien;
  164. }
  165. # ceci s'execute heureusement avant les tableaux et leur "|".
  166. # Attention, le texte initial est deja echappe mais pas forcement
  167. # celui retourne par calculer_url.
  168. # Penser au cas [<imgXX|right>->URL], qui exige typo('<a>...</a>')
  169. $lien = '<a href="' . str_replace('"', '&quot;', $lien) . "\"$class$lang$title$rel$mime>$texte</a>";
  170. #$res = typo($lien, true, $connect, $env);
  171. $p = $GLOBALS['toujours_paragrapher'];
  172. $GLOBALS['toujours_paragrapher'] = false;
  173. $res = propre($lien, $connect, $env);
  174. $GLOBALS['toujours_paragrapher'] = $p;
  175. // dans ce cas, echapons le resultat du modele pour que propre etc ne viennent pas pouicher le html
  176. $res = echappe_html("<html>$res</html>");
  177. if ($lang_objet_prev !== '') {
  178. if ($lang_objet_prev) {
  179. $GLOBALS['lang_objet'] = $lang_objet_prev;
  180. } else {
  181. unset($GLOBALS['lang_objet']);
  182. }
  183. }
  184. return $res;
  185. }
  186. /**
  187. * Générer le HTML d'un lien quelconque
  188. *
  189. * Cette fonction génère une balise `<a>` suivant de multiples arguments.
  190. *
  191. * @param array $args
  192. * Tableau des arguments disponibles pour générer le lien :
  193. * - texte : texte du lien, seul argument qui n'est pas un attribut
  194. * - href
  195. * - name
  196. * - etc, tout autre attribut supplémentaire…
  197. * @return string
  198. * Retourne une balise HTML de lien ou une chaîne vide.
  199. */
  200. function balise_a($args = []) {
  201. $balise_a = '';
  202. // Il faut soit au minimum un href OU un name pour réussir à générer quelque chose
  203. if (is_array($args) and (isset($args['href']) or isset($args['name']))) {
  204. include_spip('inc/filtres');
  205. $texte = '';
  206. // S'il y a un texte, on le récupère et on l'enlève des attributs
  207. if (isset($args['texte']) and is_scalar($args['texte'])) {
  208. $texte = $args['texte'];
  209. unset($args['texte']);
  210. } // Si on a un href sans texte, on en construit un avec l'URL
  211. elseif (isset($args['href']) and is_scalar($args['href'])) {
  212. static $lien_court;
  213. if (!$lien_court) {
  214. $lien_court = charger_fonction('lien_court', 'inc');
  215. }
  216. $texte = quote_amp($lien_court($args['href']));
  217. }
  218. // Il ne reste normalement plus que des attributs, on les ajoute à la balise
  219. $balise_a = '<a';
  220. foreach ($args as $attribut => $valeur) {
  221. if (is_scalar($valeur) and !empty($valeur)) {
  222. $balise_a .= ' ' . $attribut . '="' . attribut_html($valeur) . '"';
  223. }
  224. }
  225. // Puis on ajoute le texte
  226. $balise_a .= '>' . $texte . '</a>';
  227. }
  228. return $balise_a;
  229. }
  230. // Regexp des raccourcis, aussi utilisee pour la fusion de sauvegarde Spip
  231. // Laisser passer des paires de crochets pour la balise multi
  232. // mais refuser plus d'imbrications ou de mauvaises imbrications
  233. // sinon les crochets ne peuvent plus servir qu'a ce type de raccourci
  234. define('_RACCOURCI_LIEN', '/\[([^][]*?([[][^]>-]*[]][^][]*)*)->(>?)([^]]*)\]/msS');
  235. // https://code.spip.net/@expanser_liens
  236. function expanser_liens($t, $connect = '', $env = []) {
  237. $t = pipeline('pre_liens', $t);
  238. if (strpos($t, '\[') !== false or strpos($t, '\]') !== false) {
  239. $t = str_replace(['\[', '\]'], ["\x1\x5", "\x1\x6"], $t);
  240. }
  241. expanser_un_lien($connect, 'init', $env);
  242. if (strpos($t, '->') !== false) {
  243. $t = preg_replace_callback(_RACCOURCI_LIEN, 'expanser_un_lien', $t);
  244. }
  245. // on passe a traiter_modeles la liste des liens reperes pour lui permettre
  246. // de remettre le texte d'origine dans les parametres du modele
  247. $t = traiter_modeles($t, false, false, $connect, expanser_un_lien('', 'sources'), $env);
  248. if (strpos($t, "\x1") !== false) {
  249. $t = str_replace(["\x1\x5", "\x1\x6"], ['[', ']'], $t);
  250. }
  251. $t = corriger_typo($t);
  252. $t = expanser_un_lien($t, 'reinsert');
  253. return $t;
  254. }
  255. function expanser_un_lien($reg, $quoi = 'echappe', $env = null) {
  256. static $pile = [];
  257. static $inserts;
  258. static $sources;
  259. static $regs;
  260. static $k = 0;
  261. static $lien;
  262. static $connect = '';
  263. static $contexte = [];
  264. switch ($quoi) {
  265. case 'init':
  266. if (!$lien) {
  267. $lien = charger_fonction('lien', 'inc');
  268. }
  269. if (!is_null($env)) {
  270. $contexte = $env;
  271. }
  272. array_push($pile, [$inserts, $sources, $regs, $connect, $k]);
  273. $inserts = $sources = $regs = [];
  274. $connect = $reg; // stocker le $connect pour les appels a inc_lien_dist
  275. $k = 0;
  276. return;
  277. break;
  278. case 'echappe':
  279. $inserts[$k] = '@@SPIP_ECHAPPE_LIEN_' . $k . '@@';
  280. $sources[$k] = $reg[0];
  281. #$titre=$reg[1];
  282. list($titre, $bulle, $hlang) = traiter_raccourci_lien_atts($reg[1]);
  283. $r = end($reg);
  284. // la mise en lien automatique est passee par la a tort !
  285. // corrigeons pour eviter d'avoir un <a...> dans un href...
  286. if (strncmp($r, '<a', 2) == 0) {
  287. $href = extraire_attribut($r, 'href');
  288. // remplacons dans la source qui peut etre reinjectee dans les arguments
  289. // d'un modele
  290. $sources[$k] = str_replace($r, $href, $sources[$k]);
  291. // et prenons le href comme la vraie url a linker
  292. $r = $href;
  293. }
  294. $regs[$k] = $lien($r, $titre, '', $bulle, $hlang, '', $connect, $contexte);
  295. return $inserts[$k++];
  296. break;
  297. case 'reinsert':
  298. if (count($inserts)) {
  299. $reg = str_replace($inserts, $regs, $reg);
  300. }
  301. list($inserts, $sources, $regs, $connect, $k) = array_pop($pile);
  302. return $reg;
  303. break;
  304. case 'sources':
  305. return [$inserts, $sources];
  306. break;
  307. }
  308. }
  309. /**
  310. * Nettoie un texte en enlevant les raccourcis typo, sans les traiter
  311. *
  312. * On ne laisse que les titres des liens, en les explicitant si ce n’est pas fait.
  313. *
  314. * @param string $texte
  315. * @param string $connect
  316. * @return string
  317. */
  318. function nettoyer_raccourcis_typo($texte, $connect = '') {
  319. $texte = pipeline('nettoyer_raccourcis_typo', $texte);
  320. // on utilise les \r pour passer entre les gouttes
  321. $texte = str_replace("\r\n", "\n", $texte);
  322. $texte = str_replace("\r", "\n", $texte);
  323. // sauts de ligne et paragraphes
  324. $texte = preg_replace("/\n\n+/", "\r", $texte);
  325. // supprimer les traits, lignes etc
  326. $texte = preg_replace("/(^|\r|\n)(-[-#\*]*\s?|_ )/", "\n", $texte);
  327. // travailler en accents charset
  328. $texte = unicode2charset(html2unicode($texte, true /* secure */));
  329. if (preg_match_all(_RACCOURCI_LIEN, $texte, $regs, PREG_SET_ORDER)) {
  330. include_spip('inc/texte');
  331. foreach ($regs as $reg) {
  332. list($titre, , ) = traiter_raccourci_lien_atts($reg[1]);
  333. if (!$titre) {
  334. $match = typer_raccourci($reg[count($reg) - 1]);
  335. if (!isset($match[0])) {
  336. $match[0] = '';
  337. }
  338. @list($type, , $id, , , , ) = $match;
  339. if ($type) {
  340. $url = generer_url_entite($id, $type, '', '', true);
  341. if (is_array($url)) {
  342. list($type, $id) = $url;
  343. }
  344. $titre = traiter_raccourci_titre($id, $type, $connect);
  345. }
  346. $titre = $titre ? $titre['titre'] : $match[0];
  347. }
  348. $titre = corriger_typo(supprimer_tags($titre));
  349. $texte = str_replace($reg[0], $titre, $texte);
  350. }
  351. }
  352. // supprimer les ancres
  353. $texte = preg_replace(_RACCOURCI_ANCRE, '', $texte);
  354. // supprimer les notes
  355. $texte = preg_replace(',\[\[.*\]\],UimsS', '', $texte);
  356. // supprimer les codes typos
  357. $texte = str_replace(['}', '{'], '', $texte);
  358. // supprimer les tableaux
  359. $texte = preg_replace(",(?:^|\r|\n)\|.*\|(?:\r|\n|$),s", "\r", $texte);
  360. // indiquer les sauts de paragraphes
  361. $texte = str_replace("\r", "\n\n", $texte);
  362. $texte = str_replace("\n\n+", "\n\n", $texte);
  363. $texte = trim($texte);
  364. return $texte;
  365. }
  366. // Repere dans la partie texte d'un raccourci [texte->...]
  367. // la langue et la bulle eventuelles : [texte|title{lang}->...]
  368. // accepte un niveau de paire de crochets dans le texte :
  369. // [texte[]|title{lang}->...]
  370. // mais refuse
  371. // [texte[|title{lang}->...]
  372. // pour ne pas confondre avec un autre raccourci
  373. define('_RACCOURCI_ATTRIBUTS', '/^((?:[^[]*?(?:\[[^]]*\])?)*?)([|]([^<>]*?))?([{]([a-z_]*)[}])?$/');
  374. // https://code.spip.net/@traiter_raccourci_lien_atts
  375. function traiter_raccourci_lien_atts($texte) {
  376. $bulle = $hlang = false;
  377. // title et hreflang donnes par le raccourci ?
  378. if (
  379. strpbrk($texte, '|{') !== false
  380. and preg_match(_RACCOURCI_ATTRIBUTS, $texte, $m)
  381. ) {
  382. $n = count($m);
  383. // |infobulle ?
  384. if ($n > 2) {
  385. $bulle = $m[3];
  386. // {hreflang} ?
  387. if ($n > 4) {
  388. // si c'est un code de langue connu, on met un hreflang
  389. if (traduire_nom_langue($m[5]) <> $m[5]) {
  390. $hlang = $m[5];
  391. } elseif (!$m[5]) {
  392. $hlang = test_espace_prive() ?
  393. $GLOBALS['lang_objet'] : $GLOBALS['spip_lang'];
  394. // sinon c'est un italique ou un gras dans le title ou dans le texte du lien
  395. } else {
  396. if ($bulle) {
  397. $bulle .= $m[4];
  398. } else {
  399. $m[1] .= $m[2] . $m[4];
  400. }
  401. }
  402. }
  403. // S'il n'y a pas de hreflang sous la forme {}, ce qui suit le |
  404. // est peut-etre une langue
  405. else {
  406. if (preg_match('/^[a-z_]+$/', $m[3])) {
  407. // si c'est un code de langue connu, on met un hreflang
  408. // mais on laisse le title (c'est arbitraire tout ca...)
  409. if (traduire_nom_langue($m[3]) <> $m[3]) {
  410. $hlang = $m[3];
  411. }
  412. }
  413. }
  414. }
  415. $texte = $m[1];
  416. }
  417. if ($bulle) {
  418. $bulle = nettoyer_raccourcis_typo($bulle);
  419. $bulle = corriger_typo($bulle);
  420. }
  421. return [trim($texte), $bulle, $hlang];
  422. }
  423. define('_EXTRAIRE_DOMAINE', '/^(?:(?:[^\W_]((?:[^\W_]|-){0,61}[^\W_,])?\.)+[a-z0-9]{2,6}|localhost)\b/Si');
  424. define('_RACCOURCI_CHAPO', '/^(\W*)(\W*)(\w*\d+([?#].*)?)$/');
  425. /**
  426. * Retourne la valeur d'un champ de redirection (articles virtuels)
  427. *
  428. * L'entrée accepte plusiers types d'écritures :
  429. * - une URL compète,
  430. * - un lien SPIP tel que `[Lien->article23]`,
  431. * - ou un raccourcis SPIP comme `rub2` ou `rubrique2`
  432. *
  433. * @param string $virtuel
  434. * Texte qui définit la redirection, à analyser.
  435. * Plusieurs types peuvent être acceptés :
  436. * - un raccourci Spip habituel, tel que `[texte->TYPEnnn]`
  437. * - un ultra raccourci Spip, tel que `TYPEnnn`
  438. * - une URL standard
  439. * @param bool $url
  440. * false : retourne uniquement le nom du lien (TYPEnnn)
  441. * true : retourne l'URL calculée pour le lien
  442. * @return string
  443. * Nom du lien ou URL
  444. */
  445. function virtuel_redirige($virtuel, $url = false) {
  446. if (!strlen($virtuel)) {
  447. return '';
  448. }
  449. if (
  450. !preg_match(_RACCOURCI_LIEN, $virtuel, $m)
  451. and !preg_match(_RACCOURCI_CHAPO, $virtuel, $m)
  452. ) {
  453. return $virtuel;
  454. }
  455. return !$url ? $m[3] : traiter_lien_implicite($m[3]);
  456. }
  457. // Cherche un lien du type [->raccourci 123]
  458. // associe a une fonction generer_url_raccourci() definie explicitement
  459. // ou implicitement par le jeu de type_urls courant.
  460. //
  461. // Valeur retournee selon le parametre $pour:
  462. // 'tout' : tableau d'index url,class,titre,lang (vise <a href="U" class='C' hreflang='L'>T</a>)
  463. // 'titre': seulement T ci-dessus (i.e. le TITRE ci-dessus ou dans table SQL)
  464. // 'url': seulement U (i.e. generer_url_RACCOURCI)
  465. // https://code.spip.net/@calculer_url
  466. function calculer_url($ref, $texte = '', $pour = 'url', $connect = '', $echappe_typo = true) {
  467. $r = traiter_lien_implicite($ref, $texte, $pour, $connect, $echappe_typo);
  468. $r = ($r ? $r : traiter_lien_explicite($ref, $texte, $pour, $connect, $echappe_typo));
  469. return $r;
  470. }
  471. define('_EXTRAIRE_LIEN', ',^\s*(http:?/?/?|mailto:?)\s*$,iS');
  472. // https://code.spip.net/@traiter_lien_explicite
  473. function traiter_lien_explicite($ref, $texte = '', $pour = 'url', $connect = '', $echappe_typo = true) {
  474. if (preg_match(_EXTRAIRE_LIEN, $ref)) {
  475. return ($pour != 'tout') ? '' : ['', '', '', ''];
  476. }
  477. $lien = entites_html(trim($ref));
  478. // Liens explicites
  479. if (!$texte) {
  480. $texte = str_replace('"', '', $lien);
  481. static $lien_court;
  482. // evite l'affichage de trop longues urls.
  483. if (!$lien_court) {
  484. $lien_court = charger_fonction('lien_court', 'inc');
  485. }
  486. $texte = $lien_court($texte);
  487. if ($echappe_typo) {
  488. $texte = '<html>' . quote_amp($texte) . '</html>';
  489. }
  490. }
  491. // petites corrections d'URL
  492. if (preg_match('/^www\.[^@]+$/S', $lien)) {
  493. $lien = 'http://' . $lien;
  494. } else {
  495. if (strpos($lien, '@') && email_valide($lien)) {
  496. if (!$texte) {
  497. $texte = $lien;
  498. }
  499. $lien = 'mailto:' . $lien;
  500. }
  501. }
  502. if ($pour == 'url') {
  503. return $lien;
  504. }
  505. if ($pour == 'titre') {
  506. return $texte;
  507. }
  508. return ['url' => $lien, 'titre' => $texte];
  509. }
  510. function liens_implicite_glose_dist($texte, $id, $type, $args, $ancre, $connect = '') {
  511. if (function_exists($f = 'glossaire_' . $ancre)) {
  512. $url = $f($texte, $id);
  513. } else {
  514. $url = glossaire_std($texte);
  515. }
  516. return $url;
  517. }
  518. /**
  519. * Transformer un lien raccourci art23 en son URL
  520. * Par defaut la fonction produit une url prive si on est dans le prive
  521. * ou publique si on est dans le public.
  522. * La globale lien_implicite_cible_public permet de forcer un cas ou l'autre :
  523. * $GLOBALS['lien_implicite_cible_public'] = true;
  524. * => tous les liens raccourcis pointent vers le public
  525. * $GLOBALS['lien_implicite_cible_public'] = false;
  526. * => tous les liens raccourcis pointent vers le prive
  527. * unset($GLOBALS['lien_implicite_cible_public']);
  528. * => retablit le comportement automatique
  529. *
  530. * https://code.spip.net/@traiter_lien_implicite
  531. *
  532. * @param string $ref
  533. * @param string $texte
  534. * @param string $pour
  535. * @param string $connect
  536. * @return array|bool|string
  537. */
  538. function traiter_lien_implicite($ref, $texte = '', $pour = 'url', $connect = '') {
  539. $cible = ($connect ? $connect : (isset($GLOBALS['lien_implicite_cible_public']) ? $GLOBALS['lien_implicite_cible_public'] : null));
  540. if (!($match = typer_raccourci($ref))) {
  541. return false;
  542. }
  543. @list($type, , $id, , $args, , $ancre) = $match;
  544. # attention dans le cas des sites le lien doit pointer non pas sur
  545. # la page locale du site, mais directement sur le site lui-meme
  546. $url = '';
  547. if ($f = charger_fonction("implicite_$type", 'liens', true)) {
  548. $url = $f($texte, $id, $type, $args, $ancre, $connect);
  549. }
  550. if (!$url) {
  551. $url = generer_url_entite($id, $type, $args, $ancre, $cible);
  552. }
  553. if (!$url) {
  554. return false;
  555. }
  556. if (is_array($url)) {
  557. @list($type, $id) = $url;
  558. $url = generer_url_entite($id, $type, $args, $ancre, $cible);
  559. }
  560. if ($pour === 'url') {
  561. return $url;
  562. }
  563. $r = traiter_raccourci_titre($id, $type, $connect);
  564. if ($r) {
  565. $r['class'] = ($type == 'site') ? 'spip_out' : 'spip_in';
  566. }
  567. if ($texte = trim($texte)) {
  568. $r['titre'] = $texte;
  569. }
  570. if (!@$r['titre']) {
  571. $r['titre'] = _T($type) . " $id";
  572. }
  573. if ($pour == 'titre') {
  574. return $r['titre'];
  575. }
  576. $r['url'] = $url;
  577. // dans le cas d'un lien vers un doc, ajouter le type='mime/type'
  578. if (
  579. $type == 'document'
  580. and $mime = sql_getfetsel(
  581. 'mime_type',
  582. 'spip_types_documents',
  583. 'extension IN (' . sql_get_select('extension', 'spip_documents', 'id_document=' . sql_quote($id)) . ')',
  584. '',
  585. '',
  586. '',
  587. '',
  588. $connect
  589. )
  590. ) {
  591. $r['mime'] = $mime;
  592. }
  593. return $r;
  594. }
  595. // analyse des raccourcis issus de [TITRE->RACCOURCInnn] et connexes
  596. define('_RACCOURCI_URL', '/^\s*(\w*?)\s*(\d+)(\?(.*?))?(#([^\s]*))?\s*$/S');
  597. // https://code.spip.net/@typer_raccourci
  598. function typer_raccourci($lien) {
  599. if (!preg_match(_RACCOURCI_URL, $lien, $match)) {
  600. return [];
  601. }
  602. $f = $match[1];
  603. // valeur par defaut et alias historiques
  604. if (!$f) {
  605. $f = 'article';
  606. } else {
  607. if ($f == 'art') {
  608. $f = 'article';
  609. } else {
  610. if ($f == 'br') {
  611. $f = 'breve';
  612. } else {
  613. if ($f == 'rub') {
  614. $f = 'rubrique';
  615. } else {
  616. if ($f == 'aut') {
  617. $f = 'auteur';
  618. } else {
  619. if ($f == 'doc' or $f == 'im' or $f == 'img' or $f == 'image' or $f == 'emb') {
  620. $f = 'document';
  621. } else {
  622. if (preg_match('/^br..?ve$/S', $f)) {
  623. $f = 'breve'; # accents :(
  624. }
  625. }
  626. }
  627. }
  628. }
  629. }
  630. }
  631. $match[0] = $f;
  632. return $match;
  633. }
  634. /**
  635. * Retourne le titre et la langue d'un objet éditorial
  636. *
  637. * @param int $id Identifiant de l'objet
  638. * @param string $type Type d'objet
  639. * @param string|null $connect Connecteur SQL utilisé
  640. * @return array {
  641. * @var string $titre Titre si présent, sinon ''
  642. * @var string $lang Langue si présente, sinon ''
  643. * }
  644. **/
  645. function traiter_raccourci_titre($id, $type, $connect = null) {
  646. $trouver_table = charger_fonction('trouver_table', 'base');
  647. $desc = $trouver_table(table_objet($type));
  648. if (!($desc and $s = $desc['titre'])) {
  649. return [];
  650. }
  651. $_id = $desc['key']['PRIMARY KEY'];
  652. $r = sql_fetsel($s, $desc['table'], "$_id=$id", '', '', '', '', $connect);
  653. if (!$r) {
  654. return [];
  655. }
  656. $r['titre'] = supprimer_numero($r['titre']);
  657. if (!$r['titre'] and !empty($r['surnom'])) {
  658. $r['titre'] = $r['surnom'];
  659. }
  660. if (!isset($r['lang'])) {
  661. $r['lang'] = '';
  662. }
  663. return $r;
  664. }
  665. // traite les modeles (dans la fonction typo), en remplacant
  666. // le raccourci <modeleN|parametres> par la page calculee a
  667. // partir du squelette modeles/modele.html
  668. // Le nom du modele doit faire au moins trois caracteres (evite <h2>)
  669. // Si $doublons==true, on repere les documents sans calculer les modeles
  670. // mais on renvoie les params (pour l'indexation par le moteur de recherche)
  671. // https://code.spip.net/@traiter_modeles
  672. define(
  673. '_PREG_MODELE',
  674. '(<([a-z_-]{3,})' # <modele
  675. . '\s*([0-9]*)\s*' # id
  676. . '([|](?:<[^<>]*>|[^>])*?)?' # |arguments (y compris des tags <...>)
  677. . '\s*/?' . '>)' # fin du modele >
  678. );
  679. define(
  680. '_RACCOURCI_MODELE',
  681. _PREG_MODELE
  682. . '\s*(<\/a>)?' # eventuel </a>
  683. );
  684. define('_RACCOURCI_MODELE_DEBUT', '@^' . _RACCOURCI_MODELE . '@isS');
  685. // https://code.spip.net/@traiter_modeles
  686. function traiter_modeles($texte, $doublons = false, $echap = '', $connect = '', $liens = null, $env = []) {
  687. // preserver la compatibilite : true = recherche des documents
  688. if ($doublons === true) {
  689. $doublons = ['documents' => ['doc', 'emb', 'img']];
  690. }
  691. // detecter les modeles (rapide)
  692. if (
  693. strpos($texte, '<') !== false
  694. and preg_match_all('/<[a-z_-]{3,}\s*[0-9|]+/iS', $texte, $matches, PREG_SET_ORDER)
  695. ) {
  696. include_spip('public/assembler');
  697. $wrap_embed_html = charger_fonction('wrap_embed_html', 'inc', true);
  698. // Recuperer l'appel complet (y compris un eventuel lien)
  699. foreach ($matches as $match) {
  700. $a = strpos($texte, $match[0]);
  701. preg_match(_RACCOURCI_MODELE_DEBUT, substr($texte, $a), $regs);
  702. // s'assurer qu'il y a toujours un 5e arg, eventuellement vide
  703. while (count($regs) < 6) {
  704. $regs[] = '';
  705. }
  706. list(, $mod, $type, $id, $params, $fin) = $regs;
  707. if (
  708. $fin
  709. and preg_match('/<a\s[^<>]*>\s*$/i', substr($texte, 0, $a), $r)
  710. ) {
  711. $lien = [
  712. 'href' => extraire_attribut($r[0], 'href'),
  713. 'class' => extraire_attribut($r[0], 'class'),
  714. 'mime' => extraire_attribut($r[0], 'type'),
  715. 'title' => extraire_attribut($r[0], 'title'),
  716. 'hreflang' => extraire_attribut($r[0], 'hreflang')
  717. ];
  718. $n = strlen($r[0]);
  719. $a -= $n;
  720. $cherche = $n + strlen($regs[0]);
  721. } else {
  722. $lien = false;
  723. $cherche = strlen($mod);
  724. }
  725. // calculer le modele
  726. # hack indexation
  727. if ($doublons) {
  728. $texte .= preg_replace(',[|][^|=]*,s', ' ', $params);
  729. } # version normale
  730. else {
  731. // si un tableau de liens a ete passe, reinjecter le contenu d'origine
  732. // dans les parametres, plutot que les liens echappes
  733. if (!is_null($liens)) {
  734. $params = str_replace($liens[0], $liens[1], $params);
  735. }
  736. $modele = inclure_modele($type, $id, $params, $lien, $connect, $env);
  737. // en cas d'echec,
  738. // si l'objet demande a une url,
  739. // creer un petit encadre vers elle
  740. if ($modele === false) {
  741. $modele = substr($texte, $a, $cherche);
  742. if (!is_null($liens)) {
  743. $modele = str_replace($liens[0], $liens[1], $modele);
  744. }
  745. $contexte = array_merge($env, ['id' => $id, 'type' => $type, 'modele' => $modele]);
  746. if ($lien) {
  747. # un eventuel guillemet (") sera reechappe par #ENV
  748. $contexte['lien'] = str_replace('&quot;', '"', $lien['href']);
  749. $contexte['lien_class'] = $lien['class'];
  750. $contexte['lien_mime'] = $lien['mime'];
  751. $contexte['lien_title'] = $lien['title'];
  752. $contexte['lien_hreflang'] = $lien['hreflang'];
  753. }
  754. $modele = recuperer_fond('modeles/dist', $contexte, [], $connect);
  755. }
  756. // le remplacer dans le texte
  757. if ($modele !== false) {
  758. $modele = protege_js_modeles($modele);
  759. if ($wrap_embed_html) {
  760. $modele = $wrap_embed_html($mod, $modele);
  761. }
  762. $rempl = code_echappement($modele, $echap);
  763. $texte = substr($texte, 0, $a)
  764. . $rempl
  765. . substr($texte, $a + $cherche);
  766. }
  767. }
  768. // hack pour tout l'espace prive
  769. if (((!_DIR_RESTREINT) or ($doublons)) and ($id)) {
  770. foreach ($doublons ? $doublons : ['documents' => ['doc', 'emb', 'img']] as $quoi => $modeles) {
  771. if (in_array(strtolower($type), $modeles)) {
  772. $GLOBALS["doublons_{$quoi}_inclus"][] = $id;
  773. }
  774. }
  775. }
  776. }
  777. }
  778. return $texte;
  779. }
  780. //
  781. // Raccourcis ancre [#ancre<-]
  782. //
  783. define('_RACCOURCI_ANCRE', '/\[#?([^][]*)<-\]/S');
  784. // https://code.spip.net/@traiter_raccourci_ancre
  785. function traiter_raccourci_ancre($letexte) {
  786. if (preg_match_all(_RACCOURCI_ANCRE, $letexte, $m, PREG_SET_ORDER)) {
  787. foreach ($m as $regs) {
  788. $letexte = str_replace(
  789. $regs[0],
  790. '<a ' . (html5_permis() ? 'id' : 'name') . '="' . entites_html($regs[1]) . '"></a>',
  791. $letexte
  792. );
  793. }
  794. }
  795. return $letexte;
  796. }
  797. //
  798. // Raccourcis automatiques [?SPIP] vers un glossaire
  799. // Wikipedia par defaut, avec ses contraintes techniques
  800. // cf. http://fr.wikipedia.org/wiki/Wikip%C3%A9dia:Conventions_sur_les_titres
  801. define('_RACCOURCI_GLOSSAIRE', '/\[\?+\s*([^][<>]+)\]/S');
  802. define('_RACCOURCI_GLOSES', '/^([^|#{]*\w[^|#{]*)([^#]*)(#([^|{}]*))?(.*)$/S');
  803. // https://code.spip.net/@traiter_raccourci_glossaire
  804. function traiter_raccourci_glossaire($texte) {
  805. if (!preg_match_all(_RACCOURCI_GLOSSAIRE, $texte, $matches, PREG_SET_ORDER)) {
  806. return $texte;
  807. }
  808. include_spip('inc/charsets');
  809. $lien = charger_fonction('lien', 'inc');
  810. // Eviter les cas particulier genre "[?!?]"
  811. // et isoler le lexeme a gloser de ses accessoires
  812. // (#:url du glossaire, | bulle d'aide, {} hreflang)
  813. // Transformation en pseudo-raccourci pour passer dans inc_lien
  814. foreach ($matches as $regs) {
  815. if (preg_match(_RACCOURCI_GLOSES, $regs[1], $r)) {
  816. preg_match('/^(.*?)(\d*)$/', $r[4], $m);
  817. $_n = intval($m[2]);
  818. $gloss = $m[1] ? ('#' . $m[1]) : '';
  819. $t = $r[1] . $r[2] . $r[5];
  820. list($t, $bulle, $hlang) = traiter_raccourci_lien_atts($t);
  821. if ($bulle === false) {
  822. $bulle = $m[1];
  823. }
  824. $t = unicode2charset(charset2unicode($t), 'utf-8');
  825. $ref = $lien("glose$_n$gloss", $t, 'spip_glossaire', $bulle, $hlang);
  826. $texte = str_replace($regs[0], $ref, $texte);
  827. }
  828. }
  829. return $texte;
  830. }
  831. // https://code.spip.net/@glossaire_std
  832. function glossaire_std($terme) {
  833. global $url_glossaire_externe;
  834. static $pcre = null;
  835. if ($pcre === null) {
  836. $pcre = isset($GLOBALS['meta']['pcre_u']) ? $GLOBALS['meta']['pcre_u'] : '';
  837. if (strpos($url_glossaire_externe, '%s') === false) {
  838. $url_glossaire_externe .= '%s';
  839. }
  840. }
  841. $glosateur = str_replace(
  842. '@lang@',
  843. $GLOBALS['spip_lang'],
  844. $GLOBALS['url_glossaire_externe']
  845. );
  846. $terme = rawurlencode(preg_replace(',\s+,' . $pcre, '_', $terme));
  847. return str_replace('%s', $terme, $glosateur);
  848. }