3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2016 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
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 \***************************************************************************/
13 if (!defined('_ECRIRE_INC_VERSION')) {
17 # donner un exemple d'url pour le formulaire de choix
18 define('URLS_PROPRES_EXEMPLE', 'Titre-de-l-article -Rubrique-');
19 # specifier le form de config utilise pour ces urls
20 define('URLS_PROPRES_CONFIG', 'propres');
22 // TODO: une interface permettant de verifier qu'on veut effectivment modifier
23 // une adresse existante
24 defined('CONFIRMER_MODIFIER_URL') ||
define('CONFIRMER_MODIFIER_URL', false);
28 - Comment utiliser ce jeu d'URLs ?
30 Recopiez le fichier "htaccess.txt" du repertoire de base du site SPIP sous
31 le sous le nom ".htaccess" (attention a ne pas ecraser d'autres reglages
32 que vous pourriez avoir mis dans ce fichier) ; si votre site est en
33 "sous-repertoire", vous devrez aussi editer la ligne "RewriteBase" ce fichier.
34 Les URLs definies seront alors redirigees vers les fichiers de SPIP.
36 Dans les pages de configuration, choisissez 'propres' comme type d'url
38 SPIP calculera alors ses liens sous la forme
39 "Mon-titre-d-article".
41 La variante 'propres2' ajoutera '.html' aux adresses generees :
42 "Mon-titre-d-article.html"
44 Variante 'qs' (experimentale) : ce systeme fonctionne en "Query-String",
45 c'est-a-dire sans utilisation de .htaccess ; les adresses sont de la forme
46 "/?Mon-titre-d-article"
49 if (!defined('_terminaison_urls_propres')) {
50 define('_terminaison_urls_propres', '');
52 if (!defined('_debut_urls_propres')) {
53 define('_debut_urls_propres', '');
56 $config_urls_propres = isset($GLOBALS['meta']['urls_propres']) ?
unserialize($GLOBALS['meta']['urls_propres']) : array();
57 // pour choisir le caractere de separation titre-id en cas de doublon
58 // (ne pas utiliser '/')
59 if (!defined('_url_propres_sep_id')) {
60 define('_url_propres_sep_id', isset($config_urls_propres['url_propres_sep_id']) ?
$config_urls_propres['url_propres_sep_id'] : '-');
62 // option pour tout passer en minuscules
63 if (!defined('_url_minuscules')) {
64 define('_url_minuscules', isset($config_urls_propres['url_minuscules']) ?
$config_urls_propres['url_minuscules'] : 0);
66 if (!defined('_URLS_PROPRES_MAX')) {
67 define('_URLS_PROPRES_MAX', isset($config_urls_propres['URLS_PROPRES_MAX']) ?
$config_urls_propres['URLS_PROPRES_MAX'] : 80);
69 if (!defined('_URLS_PROPRES_MIN')) {
70 define('_URLS_PROPRES_MIN', isset($config_urls_propres['URLS_PROPRES_MIN']) ?
$config_urls_propres['URLS_PROPRES_MIN'] : 3);
73 if (!defined('_url_sep_id')) {
74 define('_url_sep_id', _url_propres_sep_id
);
77 // Ces chaines servaient de marqueurs a l'epoque ou les URL propres devaient
78 // indiquer la table ou les chercher (articles, auteurs etc),
79 // et elles etaient retirees par les preg_match dans la fonction ci-dessous.
80 // Elles peuvent a present etre definies a "" pour avoir des URL plus jolies.
81 // Les preg_match restent necessaires pour gerer les anciens signets.
83 if (!defined('_MARQUEUR_URL')) {
84 define('_MARQUEUR_URL', serialize(array(
98 // Retire les marqueurs de type dans une URL propre ancienne maniere
100 // https://code.spip.net/@retirer_marqueurs_url_propre
101 function retirer_marqueurs_url_propre($url_propre) {
102 if (preg_match(',^[+][-](.*?)[-][+]$,', $url_propre, $regs)) {
105 if (preg_match(',^([-+_@])(.*?)\1?$,', $url_propre, $regs)) {
110 // les articles n'ont pas de marqueur
115 // Pipeline pour creation d'une adresse : il recoit l'url propose par le
116 // precedent, un tableau indiquant le titre de l'objet, son type, son id,
117 // et doit donner en retour une chaine d'url, sans se soucier de la
118 // duplication eventuelle, qui sera geree apres
119 // https://code.spip.net/@creer_chaine_url
120 function urls_propres_creer_chaine_url($x) {
121 // NB: ici url_old ne sert pas, mais un plugin qui ajouterait une date
122 // pourrait l'utiliser pour juste ajouter la
123 $url_old = $x['data'];
124 $objet = $x['objet'];
125 include_spip('inc/filtres');
127 include_spip('action/editer_url');
128 if (!$url = url_nettoyer(
133 _url_minuscules ?
'spip_strtolower' : ''
135 $url = $objet['type'] . $objet['id_objet'];
143 // Trouver l'URL associee a la n-ieme cle primaire d'une table SQL
145 // https://code.spip.net/@declarer_url_propre
146 function declarer_url_propre($type, $id_objet) {
147 $trouver_table = charger_fonction('trouver_table', 'base');
148 $desc = $trouver_table(table_objet($type));
149 $table = $desc['table'];
150 $champ_titre = $desc['titre'] ?
$desc['titre'] : 'titre';
151 $col_id = @$desc['key']['PRIMARY KEY'];
154 } // Quand $type ne reference pas une table
156 $id_objet = intval($id_objet);
159 // Recuperer une URL propre correspondant a l'objet.
160 // mais urls a 1 segment uniquement (pas d'urls /)
161 // de preference avec id_parent=0, puis perma, puis langue='' puis par date desc
163 "U.url, U.date, U.id_parent, U.perma, $champ_titre",
164 "$table AS O LEFT JOIN spip_urls AS U ON (U.type='$type' AND U.id_objet=O.$col_id)",
165 "O.$col_id=$id_objet AND (U.segments IS NULL OR U.segments=1)",
167 'U.id_parent=0 DESC, U.perma DESC, U.langue=\'\' DESC, U.date DESC',
171 // en SQLite le left join retourne du vide si il y a une url mais qui ne correspond pas pour la condition sur le segment
172 // on verifie donc que l'objet existe bien avant de sortir ou de creer une url pour cet objet
175 "'' as url, '' as date, 0 as id_parent, 0 as perma, $champ_titre",
177 "O.$col_id=$id_objet"
183 } # Quand $id_objet n'est pas un numero connu
185 $url_propre = $row['url'];
187 // si url_propre connue mais avec id_parent non nul, essayer de reinserer tel quel avec id_parent=0
188 if ($url_propre and $row['id_parent']) {
189 include_spip('action/editer_url');
190 $set = array('url' => $url_propre, 'type' => $type, 'id_objet' => $id_objet, 'perma' => $row['perma']);
191 // si on arrive pas a reinserer tel quel, on annule url_propre pour forcer un recalcul d'url
192 if (!url_insert($set, false, _url_propres_sep_id
)) {
195 $url_propre = $row['url'] = $set['url'];
199 // Se contenter de cette URL si elle existe ;
200 // sauf si on invoque par "voir en ligne" avec droit de modifier l'url
202 // l'autorisation est verifiee apres avoir calcule la nouvelle url propre
203 // car si elle ne change pas, cela ne sert a rien de verifier les autorisations
204 // qui requetent en base
205 $modifier_url = (defined('_VAR_URLS') and _VAR_URLS
and !$row['perma']);
206 if ($url_propre and !$modifier_url) {
210 // Sinon, creer une URL
212 'propres_creer_chaine_url',
214 'data' => $url_propre, // le vieux url_propre
215 'objet' => array_merge(
217 array('type' => $type, 'id_objet' => $id_objet)
222 // Eviter de tamponner les URLs a l'ancienne (cas d'un article
223 // intitule "auteur2")
224 include_spip('inc/urls');
225 $objets = urls_liste_objets();
226 if (preg_match(',^(' . $objets . ')[0-9]+$,', $url, $r)
229 $url = $url . _url_propres_sep_id
. $id_objet;
232 // Pas de changement d'url
233 if ($url == $url_propre) {
237 // verifier l'autorisation, maintenant qu'on est sur qu'on va agir
239 include_spip('inc/autoriser');
240 $modifier_url = autoriser('modifierurl', $type, $id_objet);
243 // Verifier si l'utilisateur veut effectivement changer l'URL
245 and CONFIRMER_MODIFIER_URL
247 and $url != preg_replace('/' . preg_quote(_url_propres_sep_id
, '/') . '.*/', '', $url_propre)
254 if ($confirmer and !_request('ok')) {
255 die("vous changez d'url ? $url_propre -> $url");
258 $set = array('url' => $url, 'type' => $type, 'id_objet' => $id_objet);
259 include_spip('action/editer_url');
260 if (!url_insert($set, $confirmer, _url_propres_sep_id
)) {
262 } //serveur out ? retourner au mieux
267 // https://code.spip.net/@_generer_url_propre
268 function _generer_url_propre($type, $id, $args = '', $ancre = '') {
270 if ($generer_url_externe = charger_fonction("generer_url_$type", 'urls', true)) {
271 $url = $generer_url_externe($id, $args, $ancre);
277 // Mode compatibilite pour conserver la distinction -Rubrique-
279 $marqueur = unserialize(_MARQUEUR_URL
);
280 $marqueur1 = isset($marqueur[$type . '1']) ?
$marqueur[$type . '1'] : ''; // debut '+-'
281 $marqueur2 = isset($marqueur[$type . '2']) ?
$marqueur[$type . '2'] : ''; // fin '-+'
283 $marqueur1 = $marqueur2 = '';
288 $propre = declarer_url_propre($type, $id);
290 if ($propre === false) {
292 } // objet inconnu. raccourci ?
295 $url = _debut_urls_propres
299 . _terminaison_urls_propres
;
301 // les urls de type /1234 sont interpretees comme urls courte vers article 1234
302 // on les encadre d'un - : /-1234-
303 if (is_numeric($url)) {
304 $url = '-' . $url . '-';
307 if (!defined('_SET_HTML_BASE') or !_SET_HTML_BASE
) {
308 // Repositionne l'URL par rapport a la racine du site (#GLOBALS)
309 $url = str_repeat('../', $GLOBALS['profondeur_url']) . $url;
311 $url = _DIR_RACINE
. $url;
314 // objet connu mais sans possibilite d'URL lisible, revenir au defaut
315 include_spip('base/connect_sql');
316 $id_type = id_table_objet($type);
317 $url = _DIR_RACINE
. get_spip_script('./') . '?' . _SPIP_PAGE
. "=$type&$id_type=$id";
322 $url .= ((strpos($url, '?') === false) ?
'?' : '&') . $args;
333 // retrouve le fond et les parametres d'une URL propre
334 // ou produit une URL propre si on donne un parametre
335 // @return array([contexte],[type],[url_redirect],[fond]) : url decodee
336 // https://code.spip.net/@urls_propres_dist
337 function urls_propres_dist($i, $entite, $args = '', $ancre = '') {
339 if (is_numeric($i)) {
340 return _generer_url_propre($entite, $i, $args, $ancre);
344 $id_objet = $type = 0;
345 $url_redirect = null;
346 // recuperer les &debut_xx;
347 if (is_array($args)) {
350 parse_str($args, $contexte);
354 // Migration depuis anciennes URLs ?
355 // traiter les injections domain.tld/spip.php/n/importe/quoi/rubrique23
356 if ($GLOBALS['profondeur_url'] <= 0
357 and $_SERVER['REQUEST_METHOD'] != 'POST'
359 include_spip('inc/urls');
360 $r = nettoyer_url_page($i, $contexte);
362 list($contexte, $type, , , $suite) = $r;
363 $_id = id_table_objet($type);
364 $id_objet = $contexte[$_id];
365 $url_propre = generer_url_entite($id_objet, $type);
366 if (strlen($url_propre)
367 and !strstr($url, $url_propre)
369 objet_test_si_publie($type, $id_objet)
370 OR (defined('_VAR_PREVIEW') and _VAR_PREVIEW
and autoriser('voir', $type, $id_objet))
373 list(, $hash) = array_pad(explode('#', $url_propre), 2, null);
375 foreach (array_filter(explode('&', $suite)) as $fragment) {
376 if ($fragment != "$_id=$id_objet") {
380 $url_redirect = generer_url_entite($id_objet, $type, join('&', array_filter($args)), $hash);
382 return array($contexte, $type, $url_redirect, $type);
386 /* Fin compatibilite anciennes urls */
388 // Chercher les valeurs d'environnement qui indiquent l'url-propre
389 $url_propre = preg_replace(',[?].*,', '', $url);
391 // Mode Query-String ?
394 and preg_match(',[?]([^=/?&]+)(&.*)?$,', $url, $r)
401 or $url_propre == _DIR_RESTREINT_ABS
402 or $url_propre == _SPIP_SCRIPT
405 } // qu'est-ce qu'il veut ???
408 // gerer le cas de retour depuis des urls arbos
409 // mais si url arbo ne trouve pas, on veut une 404 par securite
410 if ($GLOBALS['profondeur_url'] > 0 and !defined('_FORCE_URLS_PROPRES')) {
411 $urls_anciennes = charger_fonction('arbo', 'urls');
413 return $urls_anciennes($url_propre, $entite, $contexte);
416 include_spip('base/abstract_sql'); // chercher dans la table des URLS
418 // Compatibilite avec propres2
419 $url_propre = preg_replace(',\.html$,i', '', $url_propre);
421 // Revenir en utf-8 si encodage type %D8%A7 (farsi)
422 $url_propre = rawurldecode($url_propre);
424 // Compatibilite avec les anciens marqueurs d'URL propres
425 // Tester l'entree telle quelle (avec 'url_libre' des sites ont pu avoir des entrees avec marqueurs dans la table spip_urls)
426 if (!$row = sql_fetsel('id_objet, type, date, url', 'spip_urls', 'url=' . sql_quote($url_propre, '', 'TEXT'))) {
427 // Sinon enlever les marqueurs eventuels
428 $url_propre2 = retirer_marqueurs_url_propre($url_propre);
430 $row = sql_fetsel('id_objet, type, date, url', 'spip_urls', 'url=' . sql_quote($url_propre2, '', 'TEXT'));
434 $type = $row['type'];
435 $col_id = id_table_objet($type);
436 $contexte[$col_id] = $row['id_objet'];
437 $entite = $row['type'];
439 // Si l'url est vieux, donner le nouveau
440 if ($recent = declarer_url_propre($row['type'], $row['id_objet'])
441 and $recent !== $row['url']) {
442 // Mode compatibilite pour conserver la distinction -Rubrique-
444 $marqueur = unserialize(_MARQUEUR_URL
);
445 $marqueur1 = $marqueur[$type . '1']; // debut '+-'
446 $marqueur2 = $marqueur[$type . '2']; // fin '-+'
448 $marqueur1 = $marqueur2 = '';
450 $url_redirect = $marqueur1 . $recent . $marqueur2;
454 if ($entite == '' or $entite == 'type_urls' /* compat .htaccess 2.0 */) {
456 $entite = objet_type($type);
458 // Si ca ressemble a une URL d'objet, ce n'est pas la home
459 // et on provoque un 404
460 if (preg_match(',^[^\.]+(\.html)?$,', $url)) {
462 $contexte['erreur'] = '';
464 // l'url n'existe pas...
465 // on ne sait plus dire de quel type d'objet il s'agit
466 // sauf si on a le marqueur. et la c'est un peu sale...
468 $fmarqueur = @array_flip
(unserialize(_MARQUEUR_URL
));
469 preg_match(',^([+][-]|[-+@_]),', $url_propre, $regs);
470 $objet = $regs ?
substr($fmarqueur[$regs[1]], 0, n
- 1) : 'article';
471 $contexte['erreur'] = _T(
472 ($objet == 'rubrique' or $objet == 'breve')
473 ?
'public:aucune_' . $objet
474 : 'public:aucun_' . $objet
481 return array($contexte, $entite, $url_redirect, $is_qs ?
$entite : null);