source: spip-zone/_core_/plugins/urls_etendues/urls/arbo.php

Last change on this file was 119525, checked in by spip.franck@…, 10 months ago

Bonne année "urls_etendues"

File size: 29.0 KB
Line 
1<?php
2
3/***************************************************************************\
4 *  SPIP, Systeme de publication pour l'internet                           *
5 *                                                                         *
6 *  Copyright (c) 2001-2020                                                *
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')) {
14        return;
15} // securiser
16
17# donner un exemple d'url pour le formulaire de choix
18define('URLS_ARBO_EXEMPLE', '/article/titre');
19# specifier le form de config utilise pour ces urls
20define('URLS_ARBO_CONFIG', 'arbo');
21
22// TODO: une interface permettant de verifier qu'on veut effectivment modifier
23// une adresse existante
24defined('CONFIRMER_MODIFIER_URL') || define('CONFIRMER_MODIFIER_URL', false);
25
26/**
27 * - Comment utiliser ce jeu d'URLs ?
28 * Recopiez le fichier "htaccess.txt" du repertoire de base du site SPIP sous
29 * le sous le nom ".htaccess" (attention a ne pas ecraser d'autres reglages
30 * que vous pourriez avoir mis dans ce fichier) ; si votre site est en
31 * "sous-repertoire", vous devrez aussi editer la ligne "RewriteBase" ce fichier.
32 * Les URLs definies seront alors redirigees vers les fichiers de SPIP.
33 *
34 * Choisissez "arbo" dans les pages de configuration d'URL
35 *
36 * SPIP calculera alors ses liens sous la forme "Mon-titre-d-article".
37 * Variantes :
38 *
39 * Terminaison :
40 * les terminaisons ne *sont pas* stockees en base, elles servent juste
41 * a rendre les url jolies ou conformes a un usage
42 * pour avoir des url terminant par html
43 * define ('_terminaison_urls_arbo', '.html');
44 *
45 * pour preciser des terminaisons particulieres pour certains types
46 * $GLOBALS['url_arbo_terminaisons']=array(
47 * 'rubrique' => '/',
48 * 'mot' => '',
49 * 'groupe' => '/',
50 * 'defaut' => '.html');
51 *
52 * pour avoir des url numeriques (id) du type 12/5/4/article/23
53 * define ('_URLS_ARBO_MIN',255);
54 *
55 *
56 * pour conserver la casse des titres dans les url
57 * define ('_url_arbo_minuscules',0);
58 *
59 * pour choisir le caractere de separation titre-id en cas de doublon
60 * (ne pas utiliser '/')
61 * define ('_url_arbo_sep_id','-');
62 *
63 * pour modifier la hierarchie apparente dans la constitution des urls
64 * ex pour que les mots soient classes par groupes
65 * $GLOBALS['url_arbo_parents']=array(
66 *        'article'=>array('id_rubrique','rubrique'),
67 *        'rubrique'=>array('id_parent','rubrique'),
68 *        'breve'=>array('id_rubrique','rubrique'),
69 *        'site'=>array('id_rubrique','rubrique'),
70 *        'mot'=>array('id_groupe','groupes_mot'));
71 *
72 * pour personaliser les types
73 * $GLOBALS['url_arbo_types']=array(
74 * 'rubrique'=>'', // pas de type pour les rubriques
75 * 'article'=>'a',
76 * 'mot'=>'tags'
77 * );
78 *
79 */
80
81include_spip('inc/xcache');
82if (!function_exists('Cache')) {
83        function Cache() {
84                return null;
85        }
86}
87
88$config_urls_arbo = isset($GLOBALS['meta']['urls_arbo']) ? unserialize($GLOBALS['meta']['urls_arbo']) : array();
89if (!defined('_debut_urls_arbo')) {
90        define('_debut_urls_arbo', '');
91}
92if (!defined('_terminaison_urls_arbo')) {
93        define('_terminaison_urls_arbo', '');
94}
95// pour choisir le caractere de separation titre-id en cas de doublon
96// (ne pas utiliser '/')
97if (!defined('_url_arbo_sep_id')) {
98        define('_url_arbo_sep_id', isset($config_urls_arbo['url_arbo_sep_id']) ? $config_urls_arbo['url_arbo_sep_id'] : '-');
99}
100// option pour tout passer en minuscules
101if (!defined('_url_arbo_minuscules')) {
102        define('_url_arbo_minuscules', isset($config_urls_arbo['url_arbo_minuscules']) ? $config_urls_arbo['url_arbo_minuscules'] : 1);
103}
104if (!defined('_URLS_ARBO_MAX')) {
105        define('_URLS_ARBO_MAX', isset($config_urls_arbo['URLS_ARBO_MAX']) ? $config_urls_arbo['URLS_ARBO_MAX'] : 80);
106}
107if (!defined('_URLS_ARBO_MIN')) {
108        define('_URLS_ARBO_MIN', isset($config_urls_arbo['URLS_ARBO_MIN']) ? $config_urls_arbo['URLS_ARBO_MIN'] : 3);
109}
110
111if (!defined('_url_sep_id')) {
112        define('_url_sep_id', _url_arbo_sep_id);
113}
114
115// peut prendre plusieurs valeurs :
116// - false pour ne pas gerer le multilinguisme => fonctionnement historique
117//    define('_url_arbo_multilang',false);
118// - la valeur d'une langue pour forcer le calcul des URLs dans une langue donnee
119//    define('_url_arbo_multilang','en');
120// - true pour forcer la gestion complete du multilinguisme :
121//   calcul des URLs dans toutes les langues possibles,
122//   ajout d'un premier segment fr/ dans les URLs pour definir la langue
123//   prise en compte de l'argument lang=xx dans $args au moment de generer l'URL
124//    define('_url_arbo_multilang',true);
125
126if (!defined('_url_arbo_multilang')) {
127        define('_url_arbo_multilang',false);
128}
129
130
131// Ces chaines servaient de marqueurs a l'epoque ou les URL propres devaient
132// indiquer la table ou les chercher (articles, auteurs etc),
133// et elles etaient retirees par les preg_match dans la fonction ci-dessous.
134// Elles sont a present definies a "" pour avoir des URL plus jolies
135// mais les preg_match restent necessaires pour gerer les anciens signets.
136
137#define('_MARQUEUR_URL', serialize(array('rubrique1' => '-', 'rubrique2' => '-', 'breve1' => '+', 'breve2' => '+', 'site1' => '@', 'site2' => '@', 'auteur1' => '_', 'auteur2' => '_', 'mot1' => '+-', 'mot2' => '-+')));
138if (!defined('_MARQUEUR_URL')) {
139        define('_MARQUEUR_URL', false);
140}
141
142/**
143 * Definir les parentees utilisees pour construire des urls arborescentes
144 *
145 * @param string $type
146 * @return string
147 */
148function url_arbo_parent($type) {
149        static $parents = null;
150        if (is_null($parents)) {
151                $parents = array(
152                        'article' => array('id_rubrique', 'rubrique'),
153                        'rubrique' => array('id_parent', 'rubrique'),
154                        'breve' => array('id_rubrique', 'rubrique'),
155                        'site' => array('id_rubrique', 'rubrique')
156                );
157                if (isset($GLOBALS['url_arbo_parents']) and !isset($_REQUEST['url_arbo_parents'])) {
158                        $parents = array_merge($parents, $GLOBALS['url_arbo_parents']);
159                }
160        }
161
162        return (isset($parents[$type]) ? $parents[$type] : '');
163}
164
165/**
166 * Definir les terminaisons des urls :
167 * / pour une rubrique
168 * .html pour une page etc..
169 *
170 * @param string $type
171 * @return string
172 */
173function url_arbo_terminaison($type) {
174        static $terminaison_types = null;
175        if ($terminaison_types == null) {
176                $terminaison_types = array(
177                        'rubrique' => '/',
178                        'mot' => '',
179                        'defaut' => defined('_terminaison_urls_arbo') ? _terminaison_urls_arbo : '.html'
180                );
181                if (isset($GLOBALS['url_arbo_terminaisons'])) {
182                        $terminaison_types = array_merge($terminaison_types, $GLOBALS['url_arbo_terminaisons']);
183                }
184        }
185        // si c'est un appel avec type='' c'est pour avoir la liste des terminaisons
186        if (!$type) {
187                return array_unique(array_values($terminaison_types));
188        }
189        if (isset($terminaison_types[$type])) {
190                return $terminaison_types[$type];
191        } elseif (isset($terminaison_types['defaut'])) {
192                return $terminaison_types['defaut'];
193        }
194
195        return '';
196}
197
198/**
199 * Definir le prefixe qui designe le type et qu'on utilise pour chaque objet
200 * ex : "article"/truc
201 * par defaut les rubriques ne sont pas typees, mais le reste oui
202 *
203 * @param string $type
204 * @return array|string
205 */
206function url_arbo_type($type) {
207        static $synonymes_types = null;
208        if (!$synonymes_types) {
209                $synonymes_types = array('rubrique' => '');
210                if (isset($GLOBALS['url_arbo_types']) and is_array($GLOBALS['url_arbo_types'])) {
211                        $synonymes_types = array_merge($synonymes_types, $GLOBALS['url_arbo_types']);
212                }
213        }
214        // si c'est un appel avec type='' c'est pour avoir la liste inversee des synonymes
215        if (!$type) {
216                return array_flip($synonymes_types);
217        }
218
219        return
220                ($t = (isset($synonymes_types[$type]) ? $synonymes_types[$type] : $type))  // le type ou son synonyme
221                . ($t ? '/' : ''); // le / eventuel pour separer, si le synonyme n'est pas vide
222}
223
224/**
225 * Pipeline pour creation d'une adresse : il recoit l'url propose par le
226 * precedent, un tableau indiquant le titre de l'objet, son type, son id,
227 * et doit donner en retour une chaine d'url, sans se soucier de la
228 * duplication eventuelle, qui sera geree apres
229 * https://code.spip.net/@creer_chaine_url
230 *
231 * @param array $x
232 * @return array
233 */
234function urls_arbo_creer_chaine_url($x) {
235        // NB: ici url_old ne sert pas, mais un plugin qui ajouterait une date
236        // pourrait l'utiliser pour juste ajouter la
237        $url_old = $x['data'];
238        $objet = $x['objet'];
239        include_spip('inc/filtres');
240
241        include_spip('action/editer_url');
242        if (!$url = url_nettoyer(
243                $objet['titre'],
244                _URLS_ARBO_MAX,
245                _URLS_ARBO_MIN,
246                '-',
247                _url_arbo_minuscules ? 'spip_strtolower' : ''
248        )) {
249                $url = $objet['id_objet'];
250        }
251
252        // le type ou son synonyme
253        $prefixe = url_arbo_type($objet['type']);
254        if (strpos($prefixe, '<') !== false) {
255                $prefixe = extraire_multi($prefixe);
256                $prefixe = textebrut($prefixe);
257        }
258        $x['data'] = $prefixe . $url; // le titre
259
260        return $x;
261}
262
263/**
264 * Boucler sur le parent pour construire l'url complete a partir des segments
265 * https://code.spip.net/@declarer_url_arbo_rec
266 *
267 * @param string $url
268 * @param string $type
269 * @param string $parent
270 * @param string $type_parent
271 * @param array $contexte
272 * @return string
273 */
274function declarer_url_arbo_rec($url, $type, $parent, $type_parent, $contexte = array()) {
275        if (is_null($parent)) {
276                return $url;
277        }
278        // le contexte parent ne se transmet pas
279        if (isset($contexte['id_parent'])) {
280                unset($contexte['id_parent']);
281        }
282        // Si pas de parent ou si son URL est vide, on ne renvoit que l'URL de l'objet en court
283        if ($parent == 0 or !($url_parent = declarer_url_arbo($type_parent ? $type_parent : 'rubrique', $parent, $contexte))) {
284                return rtrim($url, '/');
285        } // Sinon on renvoit l'URL de l'objet concaténée avec celle du parent
286        else {
287                return rtrim($url_parent, '/') . '/' . rtrim($url, '/');
288        }
289}
290
291/**
292 * Renseigner les infos les plus recentes de l'url d'un objet
293 * et de quoi la (re)construire si besoin
294 *
295 * @param string $type
296 * @param int $id_objet
297 * @param array $contexte
298 *   id_parent : rubrique parent
299 * @return bool|null|array
300 */
301function renseigner_url_arbo($type, $id_objet, $contexte = array()) {
302        $urls = array();
303        $trouver_table = charger_fonction('trouver_table', 'base');
304        $desc = $trouver_table(table_objet($type));
305        $table = $desc['table'];
306        $col_id = @$desc['key']['PRIMARY KEY'];
307        if (!$col_id) {
308                return false;
309        } // Quand $type ne reference pas une table
310        $id_objet = intval($id_objet);
311
312        $id_parent = (isset($contexte['id_parent'])?$contexte['id_parent']:null);
313        $langue = (isset($contexte['langue'])?$contexte['langue']:'');
314
315        $champ_titre = $desc['titre'] ? $desc['titre'] : 'titre';
316
317        // parent
318        $champ_parent = url_arbo_parent($type);
319        $sel_parent = ', 0 as parent';
320        $order_by_parent = '';
321        if ($champ_parent) {
322                // si un parent est fourni est qu'il est legitime, on recherche une URL pour ce parent
323                if ($id_parent
324                        and $type_parent = end($champ_parent)
325                        and $url_verifier_parent_objet = charger_fonction('url_verifier_parent_objet', 'inc', true)
326                        and $url_verifier_parent_objet($type, $id_objet, $type_parent, $id_parent)) {
327                        $sel_parent = ', '.intval($id_parent) . ' as parent';
328                        // trouver l'url qui matche le parent en premier
329                        $order_by_parent = 'U.id_parent='.intval($id_parent).' DESC, ';
330                } else {
331                        // sinon on prend son parent direct fourni par $champ_parent
332                        $sel_parent = ', O.' . reset($champ_parent) . ' as parent';
333                        // trouver l'url qui matche le parent en premier
334                        $order_by_parent = 'O.' . reset($champ_parent) . '=U.id_parent DESC, ';
335                }
336        }
337        $order_by_langue = "U.langue='' DESC, ";
338        if ($langue) {
339                $order_by_langue = 'U.langue='.sql_quote($langue).' DESC, ' . $order_by_langue;
340        }
341
342        //  Recuperer une URL propre correspondant a l'objet.
343        $row = sql_fetsel(
344                "U.url, U.date, U.id_parent, U.perma, U.langue, $champ_titre $sel_parent",
345                "$table AS O LEFT JOIN spip_urls AS U ON (U.type='$type' AND U.id_objet=O.$col_id)",
346                "O.$col_id=$id_objet",
347                '',
348                $order_by_parent . 'U.perma DESC, ' . $order_by_langue . 'U.date DESC',
349                1
350        );
351        if ($row) {
352                $urls[$type][$id_objet] = $row;
353                $urls[$type][$id_objet]['type_parent'] = $champ_parent ? end($champ_parent) : '';
354        }
355
356        return isset($urls[$type][$id_objet]) ? $urls[$type][$id_objet] : null;
357}
358
359/**
360 * Retrouver/Calculer l'ensemble des segments d'url d'un objet
361 *
362 * https://code.spip.net/@declarer_url_arbo
363 *
364 * @param string $type
365 * @param int $id_objet
366 * @param array $contexte
367 *   id_parent : rubrique parent
368 *   langue : langue courante pour laquelle on veut l'URL
369 * @return string
370 */
371function declarer_url_arbo($type, $id_objet, $contexte = array()) {
372        static $urls = array();
373        // utiliser un cache memoire pour aller plus vite
374        if (!is_null($C = Cache())) {
375                return $C;
376        }
377        // contexte de langue si pas defini, en fonction de la configuration
378        if (!isset($contexte['langue'])) {
379                if (!_url_arbo_multilang) {
380                        $contexte['langue'] = '';
381                } elseif (_url_arbo_multilang === true) {
382                        $contexte['langue'] = $GLOBALS['spip_lang'];
383                } else {
384                        $contexte['langue'] = _url_arbo_multilang;
385                }
386        }
387        ksort($contexte);
388        $hash = json_encode($contexte);
389
390        // Se contenter de cette URL si elle existe ;
391        // sauf si on invoque par "voir en ligne" avec droit de modifier l'url
392
393        // l'autorisation est verifiee apres avoir calcule la nouvelle url propre
394        // car si elle ne change pas, cela ne sert a rien de verifier les autorisations
395        // qui requetent en base
396        $modifier_url = (defined('_VAR_URLS') and _VAR_URLS);
397
398        if (!isset($urls[$type][$id_objet][$hash]) or $modifier_url) {
399                $r = renseigner_url_arbo($type, $id_objet, $contexte);
400                // Quand $type ne reference pas une table
401                if ($r === false) {
402                        return false;
403                }
404
405                if (!is_null($r)) {
406                        $urls[$type][$id_objet][$hash] = $r;
407                }
408        }
409
410        if (!isset($urls[$type][$id_objet][$hash])) {
411                return '';
412        } # objet inexistant
413
414        $u = &$urls[$type][$id_objet][$hash];
415        $url_propre = $u['url'];
416
417        // si on a trouve l'url
418        // et que le parent est bon
419        // et (permanente ou pas de demande de modif)
420        if (!is_null($url_propre)
421                and $u['id_parent'] == $u['parent']
422                and ($u['perma'] or !$modifier_url)
423        ) {
424                return declarer_url_arbo_rec(
425                        $url_propre,
426                        $type,
427                        isset($u['parent']) ? $u['parent'] : 0,
428                        isset($u['type_parent']) ? $u['type_parent'] : null,
429                        $contexte
430                );
431        }
432
433        // Si URL inconnue ou maj forcee sur une url non permanente, recreer une url
434        $url = $url_propre;
435        $urls_langues = array();
436        if (is_null($url_propre) or ($modifier_url and !$u['perma'])) {
437                $langues = array();
438                if (_url_arbo_multilang === true) {
439                        include_spip('inc/lang');
440                        $langues = (isset($GLOBALS['meta']['langues_multilingue']) ? $GLOBALS['meta']['langues_multilingue'] : '');
441                        $langues = explode(',', $langues);
442                        if ($k = array_search(_LANGUE_PAR_DEFAUT, $langues)) {
443                                unset($langues[$k]);
444                                array_unshift($langues, _LANGUE_PAR_DEFAUT);
445                        }
446                }
447                if (!in_array($contexte['langue'], $langues)) {
448                        $langues[] = $contexte['langue'];
449                }
450
451                // on calcule l'URL de chaque langue utile (langue courante, langue forcee ou toutes les langues utilises)
452                $langue_courante = $GLOBALS['spip_lang'];
453
454                include_spip('inc/urls');
455                $objets = urls_liste_objets();
456
457                foreach ($langues as $l) {
458                        if ($l) {
459                                changer_langue($l);
460                        }
461                        $urls_langues[$l] = pipeline(
462                                'arbo_creer_chaine_url',
463                                array(
464                                        'data' => $url_propre,  // le vieux url_propre
465                                        'objet' => array_merge($u, array('type' => $type, 'id_objet' => $id_objet))
466                                )
467                        );
468
469                        // Eviter de tamponner les URLs a l'ancienne (cas d'un article
470                        // intitule "auteur2")
471                        if (preg_match(',^(' . $objets . ')[0-9]*$,', $urls_langues[$l], $r)
472                                and $r[1] != $type
473                        ) {
474                                $urls_langues[$l] = $urls_langues[$l] . _url_arbo_sep_id . $id_objet;
475                        }
476                }
477                // retablir la $langue_courante par securite, au cas ou on a change de langue
478                changer_langue($langue_courante);
479
480                $url = $urls_langues[$contexte['langue']];
481        }
482
483
484        // Pas de changement d'url ni de parent
485        if ($url == $url_propre
486                and $u['id_parent'] == $u['parent']
487        ) {
488                return declarer_url_arbo_rec($url_propre, $type, $u['parent'], $u['type_parent'], $contexte);
489        }
490
491        // verifier l'autorisation, maintenant qu'on est sur qu'on va agir
492        if ($modifier_url) {
493                include_spip('inc/autoriser');
494                $modifier_url = autoriser('modifierurl', $type, $id_objet);
495        }
496        // Verifier si l'utilisateur veut effectivement changer l'URL
497        if ($modifier_url
498                and CONFIRMER_MODIFIER_URL
499                and $url_propre
500                // on essaye pas de regenerer une url en -xxx (suffixe id anti collision)
501                and $url != preg_replace('/' . preg_quote(_url_propres_sep_id, '/') . '.*/', '', $url_propre)
502        ) {
503                $confirmer = true;
504        } else {
505                $confirmer = false;
506        }
507
508        if ($confirmer and !_request('ok')) {
509                die("vous changez d'url ? $url_propre -&gt; $url");
510        }
511
512        // on enregistre toutes les langues
513        include_spip('action/editer_url');
514        foreach ($urls_langues as $langue => $url) {
515                $set = array(
516                        'url' => $url,
517                        'type' => $type,
518                        'id_objet' => $id_objet,
519                        'id_parent' => $u['parent'],
520                        'langue' => $langue,
521                        'perma' => intval($u['perma'])
522                );
523                $res = url_insert($set, $confirmer, _url_arbo_sep_id);
524                if ($langue == $contexte['langue']) {
525                        if ($res) {
526                                $u['url'] = $set['url'];
527                                $u['id_parent'] = $set['id_parent'];
528                        } else {
529                                // l'insertion a echoue,
530                                //serveur out ? retourner au mieux
531                                $u['url'] = $url_propre;
532                        }
533                }
534        }
535
536        return declarer_url_arbo_rec($u['url'], $type, $u['parent'], $u['type_parent'], $contexte);
537}
538
539/**
540 * Generer l'url arbo complete constituee des segments + debut + fin
541 *
542 * https://code.spip.net/@_generer_url_arbo
543 *
544 * @param string $type
545 * @param int $id
546 * @param string $args
547 * @param string $ancre
548 * @return string
549 */
550function _generer_url_arbo($type, $id, $args = '', $ancre = '') {
551        if ($generer_url_externe = charger_fonction("generer_url_$type", 'urls', true)) {
552                $url = $generer_url_externe($id, $args, $ancre);
553                if (null != $url) {
554                        return $url;
555                }
556        }
557
558        $debut_langue = '';
559
560        // Mode propre
561        $c = array();
562
563        parse_str($args, $contexte);
564        // choisir le contexte de langue en fonction de la configuration
565        $c['langue'] = '';
566        if (_url_arbo_multilang === true) {
567                if (isset($contexte['lang']) and $contexte['lang']) {
568                        $c['langue'] = $contexte['lang'];
569                        $debut_langue = $c['langue'] .'/';
570                        unset($contexte['lang']);
571                        $args = http_build_query($contexte);
572                } elseif (isset($GLOBALS['spip_lang']) and $GLOBALS['spip_lang']) {
573                        $c['langue'] = $GLOBALS['spip_lang'];
574                        $debut_langue = $c['langue'] .'/';
575                }
576        } elseif (_url_arbo_multilang) {
577                $c['langue'] = _url_arbo_multilang;
578        }
579        $propre = declarer_url_arbo($type, $id, $c);
580
581        // si le parent est fourni en contexte dans le $args, verifier si l'URL relative a ce parent est la meme ou non
582        $champ_parent = url_arbo_parent($type);
583        if ($champ_parent
584          and $champ_parent = reset($champ_parent)
585          and isset($contexte[$champ_parent]) and $contexte[$champ_parent]) {
586                $c['id_parent'] = $contexte[$champ_parent];
587                $propre_contexte = declarer_url_arbo($type, $id, $c);
588                // si l'URL est differente on la prend et on enleve l'argument de l'URL (redondance puisque parent defini par l'URL elle meme)
589                if ($propre_contexte !== $propre) {
590                        $propre = $propre_contexte;
591                        unset($contexte[$champ_parent]);
592                        $args = http_build_query($contexte);
593                }
594        }
595
596
597        if ($propre === false) {
598                return '';
599        } // objet inconnu. raccourci ?
600
601        if ($propre) {
602                $url = _debut_urls_arbo
603                        . $debut_langue
604                        . rtrim($propre, '/')
605                        . url_arbo_terminaison($type);
606        } else {
607                // objet connu mais sans possibilite d'URL lisible, revenir au defaut
608                include_spip('base/connect_sql');
609                $id_type = id_table_objet($type);
610                $url = get_spip_script('./') . '?' . _SPIP_PAGE . "=$type&$id_type=$id";
611        }
612
613        // Ajouter les args
614        if ($args) {
615                $url .= ((strpos($url, '?') === false) ? '?' : '&') . $args;
616        }
617
618        // Ajouter l'ancre
619        if ($ancre) {
620                $url .= "#$ancre";
621        }
622
623        return _DIR_RACINE . $url;
624}
625
626
627/**
628 * API : retourner l'url d'un objet si i est numerique
629 * ou decoder cette url si c'est une chaine
630 * array([contexte],[type],[url_redirect],[fond]) : url decodee
631 *
632 * https://code.spip.net/@urls_arbo_dist
633 *
634 * @param string|int $i
635 * @param string $entite
636 * @param string|array $args
637 * @param string $ancre
638 * @return array|string
639 */
640function urls_arbo_dist($i, $entite, $args = '', $ancre = '') {
641        if (is_numeric($i)) {
642                return _generer_url_arbo($entite, $i, $args, $ancre);
643        }
644
645        // traiter les injections du type domaine.org/spip.php/cestnimportequoi/ou/encore/plus/rubrique23
646        if ($GLOBALS['profondeur_url'] > 0 and $entite == 'sommaire') {
647                $entite = 'type_urls';
648        }
649
650        // recuperer les &debut_xx;
651        if (is_array($args)) {
652                $contexte = $args;
653                $args = http_build_query($contexte);
654        } else {
655                parse_str($args, $contexte);
656        }
657
658        $url = $i;
659        $id_objet = $type = 0;
660        $url_redirect = null;
661
662        // Migration depuis anciennes URLs ?
663        // traiter les injections domain.tld/spip.php/n/importe/quoi/rubrique23
664        if ($GLOBALS['profondeur_url'] <= 0
665                and $_SERVER['REQUEST_METHOD'] != 'POST'
666        ) {
667                include_spip('inc/urls');
668                $r = nettoyer_url_page($i, $contexte);
669                if ($r) {
670                        list($contexte, $type, , , $suite) = $r;
671                        $_id = id_table_objet($type);
672                        $id_objet = $contexte[$_id];
673                        $url_propre = generer_url_entite($id_objet, $type);
674                        if (strlen($url_propre)
675                                and !strstr($url, $url_propre)
676                                and (
677                                        objet_test_si_publie($type, $id_objet)
678                                        OR (defined('_VAR_PREVIEW') and _VAR_PREVIEW and autoriser('voir', $type, $id_objet))
679                                )
680                        ) {
681                                list(, $hash) = array_pad(explode('#', $url_propre), 2, null);
682                                $args = array();
683                                foreach (array_filter(explode('&', $suite)) as $fragment) {
684                                        if ($fragment != "$_id=$id_objet") {
685                                                $args[] = $fragment;
686                                        }
687                                }
688                                $url_redirect = generer_url_entite($id_objet, $type, join('&', array_filter($args)), $hash);
689
690                                return array($contexte, $type, $url_redirect, $type);
691                        }
692                }
693        }
694        /* Fin compatibilite anciennes urls */
695
696        // Chercher les valeurs d'environnement qui indiquent l'url-propre
697        $url_propre = preg_replace(',[?].*,', '', $url);
698
699        // Mode Query-String ?
700        if (!$url_propre
701                and preg_match(',[?]([^=/?&]+)(&.*)?$,', $url, $r)
702        ) {
703                $url_propre = $r[1];
704        }
705
706        if (!$url_propre
707                or $url_propre == _DIR_RESTREINT_ABS
708                or $url_propre == _SPIP_SCRIPT
709        ) {
710                return;
711        } // qu'est-ce qu'il veut ???
712
713
714        include_spip('base/abstract_sql'); // chercher dans la table des URLS
715
716        // Revenir en utf-8 si encodage type %D8%A7 (farsi)
717        $url_propre = rawurldecode($url_propre);
718
719        // Compatibilite avec .htm/.html et autres terminaisons
720        $t = array_diff(array_unique(array_merge(array('.html', '.htm', '/'), url_arbo_terminaison(''))), array(''));
721        if (count($t)) {
722                $url_propre = preg_replace('{('
723                        . implode('|', array_map('preg_quote', $t)) . ')$}i', '', $url_propre);
724        }
725
726        if (strlen($url_propre) and !preg_match(',^[^/]*[.]php,', $url_propre)) {
727                $parents_vus = array();
728
729                // recuperer tous les objets de larbo xxx/article/yyy/mot/zzzz
730                // on parcourt les segments de gauche a droite
731                // pour pouvoir contextualiser un segment par son parent
732                $url_arbo = explode('/', $url_propre);
733                $url_arbo_new = array();
734                $dernier_parent_vu = false;
735                $objet_segments = 0;
736
737                $langue = '';
738                if (_url_arbo_multilang === true) {
739                        // la langue : si fourni en QS prioritaire car vient du skel ou de forcer_lang
740                        if (isset($contexte['lang'])) {
741                                $langue = $contexte['lang'];
742                        }
743                        // le premier segment peut etre la langue : l'extraire
744                        // on le prend en compte si lang non fournie par la QS sinon on l'ignore
745                        include_spip('action/editer_url'); // pour url_verifier_langue
746                        if (count($url_arbo) > 1
747                                and $first = reset($url_arbo)
748                          and url_verifier_langue($first)) {
749                                array_shift($url_arbo);
750                                if (!$langue) {
751                                        $contexte['lang'] = $langue = $first;
752                                }
753                        }
754                } elseif (_url_arbo_multilang) {
755                        $langue = _url_arbo_multilang;
756                }
757
758                while (count($url_arbo) > 0) {
759                        $type = null;
760                        if (count($url_arbo) > 1) {
761                                $type = array_shift($url_arbo);
762                        }
763                        $url_segment = array_shift($url_arbo);
764                        // Rechercher le segment de candidat
765                        // si on est dans un contexte de parent, donne par le segment precedent,
766                        // prefixer le segment recherche avec ce contexte
767                        $cp = '0'; // par defaut : parent racine, id=0
768                        if ($dernier_parent_vu) {
769                                $cp = $parents_vus[$dernier_parent_vu];
770                        }
771                        // d'abord recherche avec prefixe parent, en une requete car aucun risque de colision
772                        $row = sql_fetsel(
773                                'id_objet, type, url',
774                                'spip_urls',
775                                is_null($type)
776                                        ? 'url=' . sql_quote($url_segment, '', 'TEXT')
777                                        : sql_in('url', array("$type/$url_segment", $type)),
778                                '',
779                                // en priorite celui qui a le bon parent
780                                // puis la bonne langue puis la langue ''
781                                // puis les deux segments puis 1 seul segment
782                                //
783                                // si parent indefini on privilegie id_parent=0 avec la derniere clause du order
784                                (intval($cp) ? 'id_parent=' . intval($cp) . ' DESC, ' : 'id_parent>=0 DESC, ')
785                                . ($langue?'langue='.sql_quote($langue).' DESC, ':'') ."langue='' DESC,"
786                                . 'segments DESC, id_parent'
787                        );
788                        if ($row) {
789                                if (!is_null($type) and $row['url'] == $type) {
790                                        array_unshift($url_arbo, $url_segment);
791                                        $url_segment = $type;
792                                        $type = null;
793                                }
794                                $type = $row['type'];
795                                $col_id = id_table_objet($type);
796
797                                // le plus a droite l'emporte pour des objets presents plusieurs fois dans l'url (ie rubrique)
798                                $contexte[$col_id] = $row['id_objet'];
799
800                                $type_parent = '';
801                                if ($p = url_arbo_parent($type)) {
802                                        $type_parent = end($p);
803                                }
804                                // l'entite la plus a droite l'emporte, si le type de son parent a ete vu
805                                // sinon c'est un segment contextuel supplementaire a ignorer
806                                // ex : rub1/article/art1/mot1 : il faut ignorer le mot1, la vrai url est celle de l'article
807                                if (!$entite
808                                        or $dernier_parent_vu == $type_parent
809                                ) {
810                                        if ($objet_segments == 0) {
811                                                $entite = $type;
812                                        }
813                                } // sinon on change d'objet concerne
814                                else {
815                                        $objet_segments++;
816                                }
817
818                                $url_arbo_new[$objet_segments]['id_objet'] = $row['id_objet'];
819                                $url_arbo_new[$objet_segments]['objet'] = $type;
820                                $url_arbo_new[$objet_segments]['segment'][] = $row['url'];
821
822                                // on note le dernier parent vu de chaque type
823                                $parents_vus[$dernier_parent_vu = $type] = $row['id_objet'];
824                        } else {
825                                // un segment est inconnu
826                                if ($entite == '' or $entite == 'type_urls') {
827                                        // on genere une 404 comme il faut si on ne sait pas ou aller
828                                        return array(array(), '404');
829                                }
830                                // ici on a bien reconnu un segment en amont, mais le segment en cours est inconnu
831                                // on pourrait renvoyer sur le dernier segment identifie
832                                // mais de fait l'url entiere est inconnu : 404 aussi
833                                // mais conserver le contexte qui peut contenir un fond d'ou venait peut etre $entite (reecriture urls)
834                                return array($contexte, '404');
835                        }
836                }
837
838                if (count($url_arbo_new)) {
839                        $caller = debug_backtrace();
840                        $caller = $caller[1]['function'];
841                        // si on est appele par un autre module d'url c'est du decodage d'une ancienne URL
842                        // ne pas regenerer des segments arbo, mais rediriger vers la nouvelle URL
843                        // dans la nouvelle forme
844                        if (strncmp($caller, 'urls_', 5) == 0 and $caller !== 'urls_decoder_url') {
845                                // en absolue, car assembler ne gere pas ce cas particulier
846                                include_spip('inc/filtres_mini');
847                                $col_id = id_table_objet($entite);
848                                $url_new = generer_url_entite($contexte[$col_id], $entite, $args);
849                                // securite contre redirection infinie
850                                if ($url_new !== $url_propre
851                                        and rtrim($url_new, '/') !== rtrim($url_propre, '/')
852                                ) {
853                                        $url_redirect = url_absolue($url_new);
854                                }
855                        } else {
856                                foreach ($url_arbo_new as $k => $o) {
857                                        $c = array( 'langue' => $langue );
858                                        if (isset($parents_vus['rubrique'])) {
859                                                $c['id_parent'] = $parents_vus['rubrique'];
860                                        }
861                                        if ($s = declarer_url_arbo($o['objet'], $o['id_objet'], $c)) {
862                                                $url_arbo_new[$k] = $s;
863                                        } else {
864                                                $url_arbo_new[$k] = implode('/', $o['segment']);
865                                        }
866                                }
867                                $url_arbo_new = ltrim(implode('/', $url_arbo_new), '/');
868                                if ($langue and _url_arbo_multilang === true) {
869                                        $url_arbo_new = "$langue/" . $url_arbo_new;
870                                        if (strpos($args, 'lang=') !== false) {
871                                                parse_str($args, $cl);
872                                                unset($cl['lang']);
873                                                $args = http_build_query($cl);
874                                        }
875                                }
876                                if ($url_arbo_new !== $url_propre) {
877                                        //var_dump($url_arbo_new,$url_propre);
878                                        $url_redirect = _debut_urls_arbo
879                                                . $url_arbo_new
880                                                . url_arbo_terminaison($entite)
881                                                . ($args?"?$args":'');
882                                        // en absolue, car assembler ne gere pas ce cas particulier
883                                        include_spip('inc/filtres_mini');
884                                        $url_redirect = url_absolue($url_redirect);
885                                }
886                        }
887                }
888
889                // gerer le retour depuis des urls propres
890                if (($entite == '' or $entite == 'type_urls')
891                        and $GLOBALS['profondeur_url'] <= 0
892                ) {
893                        $urls_anciennes = charger_fonction('propres', 'urls');
894
895                        return $urls_anciennes($url_propre, $entite, $contexte);
896                }
897        }
898        if ($entite == '' or $entite == 'type_urls' /* compat .htaccess 2.0 */) {
899                if ($type) {
900                        $entite = objet_type($type);
901                } else {
902                        // Si ca ressemble a une URL d'objet, ce n'est pas la home
903                        // et on provoque un 404
904                        if (preg_match(',^[^\.]+(\.html)?$,', $url)) {
905                                $entite = '404';
906                                $contexte['erreur'] = ''; // qu'afficher ici ?  l'url n'existe pas... on ne sait plus dire de quel type d'objet il s'agit
907                        }
908                }
909        }
910        if (!defined('_SET_HTML_BASE')) {
911                define('_SET_HTML_BASE', 1);
912        }
913
914        return array($contexte, $entite, $url_redirect, null);
915}
Note: See TracBrowser for help on using the repository browser.