[SPIP][PLUGINS] v3.0-->v3.2
[lhc/web/www.git] / www / plugins-dist / urls_etendues / urls / arbo.php
index 77bd2ef..b985c22 100644 (file)
@@ -10,7 +10,9 @@
  *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
-if (!defined("_ECRIRE_INC_VERSION")) return; // securiser
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+} // securiser
 
 # donner un exemple d'url pour le formulaire de choix
 define('URLS_ARBO_EXEMPLE', '/article/titre');
@@ -28,67 +30,103 @@ defined('CONFIRMER_MODIFIER_URL') || define('CONFIRMER_MODIFIER_URL', false);
  * que vous pourriez avoir mis dans ce fichier) ; si votre site est en
  * "sous-repertoire", vous devrez aussi editer la ligne "RewriteBase" ce fichier.
  * Les URLs definies seront alors redirigees vers les fichiers de SPIP.
- * 
+ *
  * Choisissez "arbo" dans les pages de configuration d'URL
  *
  * SPIP calculera alors ses liens sous la forme "Mon-titre-d-article".
  * Variantes :
- * 
+ *
  * Terminaison :
  * les terminaisons ne *sont pas* stockees en base, elles servent juste
  * a rendre les url jolies ou conformes a un usage
  * pour avoir des url terminant par html
  * define ('_terminaison_urls_arbo', '.html');
- * 
+ *
  * pour preciser des terminaisons particulieres pour certains types
  * $GLOBALS['url_arbo_terminaisons']=array(
  * 'rubrique' => '/',
  * 'mot' => '',
  * 'groupe' => '/',
  * 'defaut' => '.html');
- * 
+ *
  * pour avoir des url numeriques (id) du type 12/5/4/article/23
  * define ('_URLS_ARBO_MIN',255);
- * 
- * 
+ *
+ *
  * pour conserver la casse des titres dans les url
  * define ('_url_arbo_minuscules',0);
- * 
- * pour choisir le caractere de separation titre-id en cas de doublon 
+ *
+ * pour choisir le caractere de separation titre-id en cas de doublon
  * (ne pas utiliser '/')
  * define ('_url_arbo_sep_id','-');
- * 
+ *
  * pour modifier la hierarchie apparente dans la constitution des urls
  * ex pour que les mots soient classes par groupes
  * $GLOBALS['url_arbo_parents']=array(
- *                       'article'=>array('id_rubrique','rubrique'),
- *                       'rubrique'=>array('id_parent','rubrique'),
- *                       'breve'=>array('id_rubrique','rubrique'),
- *                       'site'=>array('id_rubrique','rubrique'),
- *                             'mot'=>array('id_groupe','groupes_mot'));
- * 
+ *        'article'=>array('id_rubrique','rubrique'),
+ *        'rubrique'=>array('id_parent','rubrique'),
+ *        'breve'=>array('id_rubrique','rubrique'),
+ *        'site'=>array('id_rubrique','rubrique'),
+ *        'mot'=>array('id_groupe','groupes_mot'));
+ *
  * pour personaliser les types
  * $GLOBALS['url_arbo_types']=array(
  * 'rubrique'=>'', // pas de type pour les rubriques
  * 'article'=>'a',
  * 'mot'=>'tags'
  * );
- * 
+ *
  */
 
+include_spip('inc/xcache');
+if (!function_exists('Cache')) {
+       function Cache() {
+               return null;
+       }
+}
 
-$config_urls_arbo = isset($GLOBALS['meta']['urls_arbo'])?unserialize($GLOBALS['meta']['urls_arbo']):array();
-if (!defined('_debut_urls_arbo')) define('_debut_urls_arbo', '');
-if (!defined('_terminaison_urls_arbo')) define('_terminaison_urls_arbo', '');
+$config_urls_arbo = isset($GLOBALS['meta']['urls_arbo']) ? unserialize($GLOBALS['meta']['urls_arbo']) : array();
+if (!defined('_debut_urls_arbo')) {
+       define('_debut_urls_arbo', '');
+}
+if (!defined('_terminaison_urls_arbo')) {
+       define('_terminaison_urls_arbo', '');
+}
 // pour choisir le caractere de separation titre-id en cas de doublon
 // (ne pas utiliser '/')
-if (!defined('_url_arbo_sep_id')) define('_url_arbo_sep_id',isset($config_urls_arbo['url_arbo_sep_id'])?$config_urls_arbo['url_arbo_sep_id']:'-');
+if (!defined('_url_arbo_sep_id')) {
+       define('_url_arbo_sep_id', isset($config_urls_arbo['url_arbo_sep_id']) ? $config_urls_arbo['url_arbo_sep_id'] : '-');
+}
 // option pour tout passer en minuscules
-if (!defined('_url_arbo_minuscules')) define('_url_arbo_minuscules',isset($config_urls_arbo['url_arbo_minuscules'])?$config_urls_arbo['url_arbo_minuscules']:1);
-if (!defined('_URLS_ARBO_MAX')) define('_URLS_ARBO_MAX', isset($config_urls_arbo['URLS_ARBO_MAX'])?$config_urls_arbo['URLS_ARBO_MAX']:80);
-if (!defined('_URLS_ARBO_MIN')) define('_URLS_ARBO_MIN', isset($config_urls_arbo['URLS_ARBO_MIN'])?$config_urls_arbo['URLS_ARBO_MIN']:3);
+if (!defined('_url_arbo_minuscules')) {
+       define('_url_arbo_minuscules', isset($config_urls_arbo['url_arbo_minuscules']) ? $config_urls_arbo['url_arbo_minuscules'] : 1);
+}
+if (!defined('_URLS_ARBO_MAX')) {
+       define('_URLS_ARBO_MAX', isset($config_urls_arbo['URLS_ARBO_MAX']) ? $config_urls_arbo['URLS_ARBO_MAX'] : 80);
+}
+if (!defined('_URLS_ARBO_MIN')) {
+       define('_URLS_ARBO_MIN', isset($config_urls_arbo['URLS_ARBO_MIN']) ? $config_urls_arbo['URLS_ARBO_MIN'] : 3);
+}
+
+if (!defined('_url_sep_id')) {
+       define('_url_sep_id', _url_arbo_sep_id);
+}
+
+// peut prendre plusieurs valeurs :
+// - false pour ne pas gerer le multilinguisme => fonctionnement historique
+//    define('_url_arbo_multilang',false);
+// - la valeur d'une langue pour forcer le calcul des URLs dans une langue donnee
+//    define('_url_arbo_multilang','en');
+// - true pour forcer la gestion complete du multilinguisme :
+//   calcul des URLs dans toutes les langues possibles,
+//   ajout d'un premier segment fr/ dans les URLs pour definir la langue
+//   prise en compte de l'argument lang=xx dans $args au moment de generer l'URL
+//    define('_url_arbo_multilang',true);
+
+if (!defined('_url_arbo_multilang')) {
+       define('_url_arbo_multilang',false);
+}
 
-if (!defined('_url_sep_id')) define('_url_sep_id',_url_arbo_sep_id);
 
 // Ces chaines servaient de marqueurs a l'epoque ou les URL propres devaient
 // indiquer la table ou les chercher (articles, auteurs etc),
@@ -97,7 +135,9 @@ if (!defined('_url_sep_id')) define('_url_sep_id',_url_arbo_sep_id);
 // mais les preg_match restent necessaires pour gerer les anciens signets.
 
 #define('_MARQUEUR_URL', serialize(array('rubrique1' => '-', 'rubrique2' => '-', 'breve1' => '+', 'breve2' => '+', 'site1' => '@', 'site2' => '@', 'auteur1' => '_', 'auteur2' => '_', 'mot1' => '+-', 'mot2' => '-+')));
-if (!defined('_MARQUEUR_URL')) define('_MARQUEUR_URL', false);
+if (!defined('_MARQUEUR_URL')) {
+       define('_MARQUEUR_URL', false);
+}
 
 /**
  * Definir les parentees utilisees pour construire des urls arborescentes
@@ -105,19 +145,21 @@ if (!defined('_MARQUEUR_URL')) define('_MARQUEUR_URL', false);
  * @param string $type
  * @return string
  */
-function url_arbo_parent($type){
+function url_arbo_parent($type) {
        static $parents = null;
-       if (is_null($parents)){
+       if (is_null($parents)) {
                $parents = array(
-                         'article'=>array('id_rubrique','rubrique'),
-                         'rubrique'=>array('id_parent','rubrique'),
-                         'breve'=>array('id_rubrique','rubrique'),
-                         'site'=>array('id_rubrique','rubrique'));
-               if (isset($GLOBALS['url_arbo_parents']) AND !isset($_REQUEST['url_arbo_parents'])){
-                       $parents = array_merge($parents,$GLOBALS['url_arbo_parents']);
-               }                         
-       }
-       return (isset($parents[$type])?$parents[$type]:'');
+                       'article' => array('id_rubrique', 'rubrique'),
+                       'rubrique' => array('id_parent', 'rubrique'),
+                       'breve' => array('id_rubrique', 'rubrique'),
+                       'site' => array('id_rubrique', 'rubrique')
+               );
+               if (isset($GLOBALS['url_arbo_parents']) and !isset($_REQUEST['url_arbo_parents'])) {
+                       $parents = array_merge($parents, $GLOBALS['url_arbo_parents']);
+               }
+       }
+
+       return (isset($parents[$type]) ? $parents[$type] : '');
 }
 
 /**
@@ -128,21 +170,29 @@ function url_arbo_parent($type){
  * @param string $type
  * @return string
  */
-function url_arbo_terminaison($type){
+function url_arbo_terminaison($type) {
        static $terminaison_types = null;
-       if ($terminaison_types==null){
-               $terminaison_types = array('rubrique' => '/','mot' => '','defaut' => defined('_terminaison_urls_arbo')?_terminaison_urls_arbo:'.html');
-               if (isset($GLOBALS['url_arbo_terminaisons']))
-                       $terminaison_types = array_merge($terminaison_types,$GLOBALS['url_arbo_terminaisons']);
+       if ($terminaison_types == null) {
+               $terminaison_types = array(
+                       'rubrique' => '/',
+                       'mot' => '',
+                       'defaut' => defined('_terminaison_urls_arbo') ? _terminaison_urls_arbo : '.html'
+               );
+               if (isset($GLOBALS['url_arbo_terminaisons'])) {
+                       $terminaison_types = array_merge($terminaison_types, $GLOBALS['url_arbo_terminaisons']);
+               }
        }
        // si c'est un appel avec type='' c'est pour avoir la liste des terminaisons
-       if (!$type)
+       if (!$type) {
                return array_unique(array_values($terminaison_types));
-       if (isset($terminaison_types[$type]))
+       }
+       if (isset($terminaison_types[$type])) {
                return $terminaison_types[$type];
-       elseif (isset($terminaison_types['defaut']))
+       } elseif (isset($terminaison_types['defaut'])) {
                return $terminaison_types['defaut'];
-       return "";
+       }
+
+       return '';
 }
 
 /**
@@ -153,19 +203,22 @@ function url_arbo_terminaison($type){
  * @param string $type
  * @return array|string
  */
-function url_arbo_type($type){
+function url_arbo_type($type) {
        static $synonymes_types = null;
-       if (!$synonymes_types){
-               $synonymes_types = array('rubrique'=>'');
-               if (isset($GLOBALS['url_arbo_types']) AND is_array($GLOBALS['url_arbo_types']))
-                       $synonymes_types = array_merge($synonymes_types,$GLOBALS['url_arbo_types']);
+       if (!$synonymes_types) {
+               $synonymes_types = array('rubrique' => '');
+               if (isset($GLOBALS['url_arbo_types']) and is_array($GLOBALS['url_arbo_types'])) {
+                       $synonymes_types = array_merge($synonymes_types, $GLOBALS['url_arbo_types']);
+               }
        }
        // si c'est un appel avec type='' c'est pour avoir la liste inversee des synonymes
-       if (!$type)
+       if (!$type) {
                return array_flip($synonymes_types);
-       return 
-           ($t=(isset($synonymes_types[$type])?$synonymes_types[$type]:$type))  // le type ou son synonyme
-         . ($t?'/':''); // le / eventuel pour separer, si le synonyme n'est pas vide
+       }
+
+       return
+               ($t = (isset($synonymes_types[$type]) ? $synonymes_types[$type] : $type))  // le type ou son synonyme
+               . ($t ? '/' : ''); // le / eventuel pour separer, si le synonyme n'est pas vide
 }
 
 /**
@@ -180,18 +233,29 @@ function url_arbo_type($type){
  */
 function urls_arbo_creer_chaine_url($x) {
        // NB: ici url_old ne sert pas, mais un plugin qui ajouterait une date
-       // pourrait l'utiliser pour juste ajouter la 
+       // pourrait l'utiliser pour juste ajouter la
        $url_old = $x['data'];
        $objet = $x['objet'];
        include_spip('inc/filtres');
 
        include_spip('action/editer_url');
-       if (!$url = url_nettoyer($objet['titre'],_URLS_ARBO_MAX,_URLS_ARBO_MIN,'-',_url_arbo_minuscules?'strtolower':''))
+       if (!$url = url_nettoyer(
+               $objet['titre'],
+               _URLS_ARBO_MAX,
+               _URLS_ARBO_MIN,
+               '-',
+               _url_arbo_minuscules ? 'spip_strtolower' : ''
+       )) {
                $url = $objet['id_objet'];
-       
-       $x['data'] =
-                 url_arbo_type($objet['type']) // le type ou son synonyme
-         . $url; // le titre
+       }
+
+       // le type ou son synonyme
+       $prefixe = url_arbo_type($objet['type']);
+       if (strpos($prefixe, '<') !== false) {
+               $prefixe = extraire_multi($prefixe);
+               $prefixe = textebrut($prefixe);
+       }
+       $x['data'] = $prefixe . $url; // le titre
 
        return $x;
 }
@@ -204,60 +268,92 @@ function urls_arbo_creer_chaine_url($x) {
  * @param string $type
  * @param string $parent
  * @param string $type_parent
+ * @param array $contexte
  * @return string
  */
-function declarer_url_arbo_rec($url,$type,$parent,$type_parent){
-       if (is_null($parent)){
+function declarer_url_arbo_rec($url, $type, $parent, $type_parent, $contexte = array()) {
+       if (is_null($parent)) {
                return $url;
        }
-       // Si pas de parent ou si son URL est vide, on ne renvoit que l'URL de l'objet en court
-       if ($parent==0 or !($url_parent = declarer_url_arbo($type_parent?$type_parent:'rubrique',$parent))){
-               return rtrim($url,'/');
+       // le contexte parent ne se transmet pas
+       if (isset($contexte['id_parent'])) {
+               unset($contexte['id_parent']);
        }
-       // Sinon on renvoit l'URL de l'objet concaténée avec celle du parent
+       // Si pas de parent ou si son URL est vide, on ne renvoit que l'URL de l'objet en court
+       if ($parent == 0 or !($url_parent = declarer_url_arbo($type_parent ? $type_parent : 'rubrique', $parent, $contexte))) {
+               return rtrim($url, '/');
+       } // Sinon on renvoit l'URL de l'objet concaténée avec celle du parent
        else {
-               return rtrim($url_parent,'/') . '/' . rtrim($url,'/');
+               return rtrim($url_parent, '/') . '/' . rtrim($url, '/');
        }
 }
 
 /**
  * Renseigner les infos les plus recentes de l'url d'un objet
  * et de quoi la (re)construire si besoin
+ *
  * @param string $type
  * @param int $id_objet
+ * @param array $contexte
+ *   id_parent : rubrique parent
  * @return bool|null|array
  */
-function renseigner_url_arbo($type,$id_objet){
+function renseigner_url_arbo($type, $id_objet, $contexte = array()) {
        $urls = array();
        $trouver_table = charger_fonction('trouver_table', 'base');
        $desc = $trouver_table(table_objet($type));
        $table = $desc['table'];
-       $col_id =  @$desc['key']["PRIMARY KEY"];
-       if (!$col_id) return false; // Quand $type ne reference pas une table
+       $col_id = @$desc['key']['PRIMARY KEY'];
+       if (!$col_id) {
+               return false;
+       } // Quand $type ne reference pas une table
        $id_objet = intval($id_objet);
 
+       $id_parent = (isset($contexte['id_parent'])?$contexte['id_parent']:null);
+       $langue = (isset($contexte['langue'])?$contexte['langue']:'');
+
        $champ_titre = $desc['titre'] ? $desc['titre'] : 'titre';
-       
+
        // parent
        $champ_parent = url_arbo_parent($type);
        $sel_parent = ', 0 as parent';
-       $order_by_parent = "";
-       if ($champ_parent){
-               $sel_parent = ", O.".reset($champ_parent).' as parent';
-               // trouver l'url qui matche le parent en premier
-               $order_by_parent = "O.".reset($champ_parent)."=U.id_parent DESC, ";
+       $order_by_parent = '';
+       if ($champ_parent) {
+               // si un parent est fourni est qu'il est legitime, on recherche une URL pour ce parent
+               if ($id_parent
+                       and $type_parent = end($champ_parent)
+                       and $url_verifier_parent_objet = charger_fonction('url_verifier_parent_objet', 'inc', true)
+                       and $url_verifier_parent_objet($type, $id_objet, $type_parent, $id_parent)) {
+                       $sel_parent = ', '.intval($id_parent) . ' as parent';
+                       // trouver l'url qui matche le parent en premier
+                       $order_by_parent = 'U.id_parent='.intval($id_parent).' DESC, ';
+               } else {
+                       // sinon on prend son parent direct fourni par $champ_parent
+                       $sel_parent = ', O.' . reset($champ_parent) . ' as parent';
+                       // trouver l'url qui matche le parent en premier
+                       $order_by_parent = 'O.' . reset($champ_parent) . '=U.id_parent DESC, ';
+               }
+       }
+       $order_by_langue = "U.langue='' DESC, ";
+       if ($langue) {
+               $order_by_langue = 'U.langue='.sql_quote($langue).' DESC, ' . $order_by_langue;
        }
+
        //  Recuperer une URL propre correspondant a l'objet.
-       $row = sql_fetsel("U.url, U.date, U.id_parent, U.perma, $champ_titre $sel_parent",
-                                                                               "$table AS O LEFT JOIN spip_urls AS U ON (U.type='$type' AND U.id_objet=O.$col_id)",
-                                                                               "O.$col_id=$id_objet",
-                                                                               '',
-                                                                               $order_by_parent.'U.perma DESC, U.date DESC', 1);
-       if ($row){
+       $row = sql_fetsel(
+               "U.url, U.date, U.id_parent, U.perma, U.langue, $champ_titre $sel_parent",
+               "$table AS O LEFT JOIN spip_urls AS U ON (U.type='$type' AND U.id_objet=O.$col_id)",
+               "O.$col_id=$id_objet",
+               '',
+               $order_by_parent . 'U.perma DESC, ' . $order_by_langue . 'U.date DESC',
+               1
+       );
+       if ($row) {
                $urls[$type][$id_objet] = $row;
-               $urls[$type][$id_objet]['type_parent'] = $champ_parent?end($champ_parent):'';
+               $urls[$type][$id_objet]['type_parent'] = $champ_parent ? end($champ_parent) : '';
        }
-       return isset($urls[$type][$id_objet])?$urls[$type][$id_objet]:null;
+
+       return isset($urls[$type][$id_objet]) ? $urls[$type][$id_objet] : null;
 }
 
 /**
@@ -267,10 +363,29 @@ function renseigner_url_arbo($type,$id_objet){
  *
  * @param string $type
  * @param int $id_objet
+ * @param array $contexte
+ *   id_parent : rubrique parent
+ *   langue : langue courante pour laquelle on veut l'URL
  * @return string
  */
-function declarer_url_arbo($type, $id_objet) {
-       static $urls=array();
+function declarer_url_arbo($type, $id_objet, $contexte = array()) {
+       static $urls = array();
+       // utiliser un cache memoire pour aller plus vite
+       if (!is_null($C = Cache())) {
+               return $C;
+       }
+       // contexte de langue si pas defini, en fonction de la configuration
+       if (!isset($contexte['langue'])) {
+               if (!_url_arbo_multilang) {
+                       $contexte['langue'] = '';
+               } elseif (_url_arbo_multilang === true) {
+                       $contexte['langue'] = $GLOBALS['spip_lang'];
+               } else {
+                       $contexte['langue'] = _url_arbo_multilang;
+               }
+       }
+       ksort($contexte);
+       $hash = json_encode($contexte);
 
        // Se contenter de cette URL si elle existe ;
        // sauf si on invoque par "voir en ligne" avec droit de modifier l'url
@@ -278,58 +393,100 @@ function declarer_url_arbo($type, $id_objet) {
        // l'autorisation est verifiee apres avoir calcule la nouvelle url propre
        // car si elle ne change pas, cela ne sert a rien de verifier les autorisations
        // qui requetent en base
-       $modifier_url = (defined('_VAR_URLS') AND _VAR_URLS);
-       
-       if (!isset($urls[$type][$id_objet]) OR $modifier_url) {
-               $r = renseigner_url_arbo($type,$id_objet);
+       $modifier_url = (defined('_VAR_URLS') and _VAR_URLS);
+
+       if (!isset($urls[$type][$id_objet][$hash]) or $modifier_url) {
+               $r = renseigner_url_arbo($type, $id_objet, $contexte);
                // Quand $type ne reference pas une table
-               if ($r===false)
+               if ($r === false) {
                        return false;
+               }
 
-               if (!is_null($r))
-                       $urls[$type][$id_objet] = $r;
+               if (!is_null($r)) {
+                       $urls[$type][$id_objet][$hash] = $r;
+               }
        }
 
-       if (!isset($urls[$type][$id_objet])) return ""; # objet inexistant
+       if (!isset($urls[$type][$id_objet][$hash])) {
+               return '';
+       } # objet inexistant
 
-       $url_propre = $urls[$type][$id_objet]['url'];
+       $u = &$urls[$type][$id_objet][$hash];
+       $url_propre = $u['url'];
 
        // si on a trouve l'url
        // et que le parent est bon
        // et (permanente ou pas de demande de modif)
        if (!is_null($url_propre)
-         AND $urls[$type][$id_objet]['id_parent'] == $urls[$type][$id_objet]['parent']
-         AND ($urls[$type][$id_objet]['perma'] OR !$modifier_url))
-               return declarer_url_arbo_rec($url_propre,$type,
-                 isset($urls[$type][$id_objet]['parent'])?$urls[$type][$id_objet]['parent']:0,
-                 isset($urls[$type][$id_objet]['type_parent'])?$urls[$type][$id_objet]['type_parent']:null);
+               and $u['id_parent'] == $u['parent']
+               and ($u['perma'] or !$modifier_url)
+       ) {
+               return declarer_url_arbo_rec(
+                       $url_propre,
+                       $type,
+                       isset($u['parent']) ? $u['parent'] : 0,
+                       isset($u['type_parent']) ? $u['type_parent'] : null,
+                       $contexte
+               );
+       }
 
        // Si URL inconnue ou maj forcee sur une url non permanente, recreer une url
        $url = $url_propre;
-       if (is_null($url_propre) OR ($modifier_url AND !$urls[$type][$id_objet]['perma'])) {
-               $url = pipeline('arbo_creer_chaine_url',
-                       array(
-                               'data' => $url_propre,  // le vieux url_propre
-                               'objet' => array_merge($urls[$type][$id_objet],
-                                       array('type' => $type, 'id_objet' => $id_objet)
-                               )
-                       )
-               );
+       $urls_langues = array();
+       if (is_null($url_propre) or ($modifier_url and !$u['perma'])) {
+               $langues = array();
+               if (_url_arbo_multilang === true) {
+                       include_spip('inc/lang');
+                       $langues = (isset($GLOBALS['meta']['langues_multilingue']) ? $GLOBALS['meta']['langues_multilingue'] : '');
+                       $langues = explode(',', $langues);
+                       if ($k = array_search(_LANGUE_PAR_DEFAUT, $langues)) {
+                               unset($langues[$k]);
+                               array_unshift($langues, _LANGUE_PAR_DEFAUT);
+                       }
+               }
+               if (!in_array($contexte['langue'], $langues)) {
+                       $langues[] = $contexte['langue'];
+               }
+
+               // on calcule l'URL de chaque langue utile (langue courante, langue forcee ou toutes les langues utilises)
+               $langue_courante = $GLOBALS['spip_lang'];
 
-               // Eviter de tamponner les URLs a l'ancienne (cas d'un article
-               // intitule "auteur2")
                include_spip('inc/urls');
                $objets = urls_liste_objets();
-               if (preg_match(',^('.$objets.')[0-9]*$,', $url, $r)
-               AND $r[1] != $type)
-                       $url = $url._url_arbo_sep_id.$id_objet;
+
+               foreach ($langues as $l) {
+                       if ($l) {
+                               changer_langue($l);
+                       }
+                       $urls_langues[$l] = pipeline(
+                               'arbo_creer_chaine_url',
+                               array(
+                                       'data' => $url_propre,  // le vieux url_propre
+                                       'objet' => array_merge($u, array('type' => $type, 'id_objet' => $id_objet))
+                               )
+                       );
+
+                       // Eviter de tamponner les URLs a l'ancienne (cas d'un article
+                       // intitule "auteur2")
+                       if (preg_match(',^(' . $objets . ')[0-9]*$,', $urls_langues[$l], $r)
+                               and $r[1] != $type
+                       ) {
+                               $urls_langues[$l] = $urls_langues[$l] . _url_arbo_sep_id . $id_objet;
+                       }
+               }
+               // retablir la $langue_courante par securite, au cas ou on a change de langue
+               changer_langue($langue_courante);
+
+               $url = $urls_langues[$contexte['langue']];
        }
 
 
        // Pas de changement d'url ni de parent
        if ($url == $url_propre
-         AND $urls[$type][$id_objet]['id_parent'] == $urls[$type][$id_objet]['parent'])
-               return declarer_url_arbo_rec($url_propre,$type,$urls[$type][$id_objet]['parent'],$urls[$type][$id_objet]['type_parent']);
+               and $u['id_parent'] == $u['parent']
+       ) {
+               return declarer_url_arbo_rec($url_propre, $type, $u['parent'], $u['type_parent'], $contexte);
+       }
 
        // verifier l'autorisation, maintenant qu'on est sur qu'on va agir
        if ($modifier_url) {
@@ -338,31 +495,45 @@ function declarer_url_arbo($type, $id_objet) {
        }
        // Verifier si l'utilisateur veut effectivement changer l'URL
        if ($modifier_url
-               AND CONFIRMER_MODIFIER_URL
-               AND $url_propre
+               and CONFIRMER_MODIFIER_URL
+               and $url_propre
                // on essaye pas de regenerer une url en -xxx (suffixe id anti collision)
-               AND $url != preg_replace('/'.preg_quote(_url_propres_sep_id,'/').'.*/', '', $url_propre))
+               and $url != preg_replace('/' . preg_quote(_url_propres_sep_id, '/') . '.*/', '', $url_propre)
+       ) {
                $confirmer = true;
-       else
+       } else {
                $confirmer = false;
+       }
 
-       if ($confirmer AND !_request('ok')) {
-               die ("vous changez d'url ? $url_propre -&gt; $url");
+       if ($confirmer and !_request('ok')) {
+               die("vous changez d'url ? $url_propre -&gt; $url");
        }
 
-       $set = array('url' => $url, 'type' => $type, 'id_objet' => $id_objet, 'id_parent'=>$urls[$type][$id_objet]['parent'],'perma'=>intval($urls[$type][$id_objet]['perma']));
+       // on enregistre toutes les langues
        include_spip('action/editer_url');
-       if (url_insert($set,$confirmer,_url_arbo_sep_id)){
-               $urls[$type][$id_objet]['url'] = $set['url'];
-               $urls[$type][$id_objet]['id_parent'] = $set['id_parent'];
-       }
-       else {
-               // l'insertion a echoue,
-               //serveur out ? retourner au mieux
-               $urls[$type][$id_objet]['url']=$url_propre;
+       foreach ($urls_langues as $langue => $url) {
+               $set = array(
+                       'url' => $url,
+                       'type' => $type,
+                       'id_objet' => $id_objet,
+                       'id_parent' => $u['parent'],
+                       'langue' => $langue,
+                       'perma' => intval($u['perma'])
+               );
+               $res = url_insert($set, $confirmer, _url_arbo_sep_id);
+               if ($langue == $contexte['langue']) {
+                       if ($res) {
+                               $u['url'] = $set['url'];
+                               $u['id_parent'] = $set['id_parent'];
+                       } else {
+                               // l'insertion a echoue,
+                               //serveur out ? retourner au mieux
+                               $u['url'] = $url_propre;
+                       }
+               }
        }
 
-       return declarer_url_arbo_rec($urls[$type][$id_objet]['url'],$type,$urls[$type][$id_objet]['parent'],$urls[$type][$id_objet]['type_parent']);
+       return declarer_url_arbo_rec($u['url'], $type, $u['parent'], $u['type_parent'], $contexte);
 }
 
 /**
@@ -376,37 +547,78 @@ function declarer_url_arbo($type, $id_objet) {
  * @param string $ancre
  * @return string
  */
-function _generer_url_arbo($type, $id, $args='', $ancre='') {
-
-       if ($generer_url_externe = charger_fonction("generer_url_$type",'urls',true)) {
+function _generer_url_arbo($type, $id, $args = '', $ancre = '') {
+       if ($generer_url_externe = charger_fonction("generer_url_$type", 'urls', true)) {
                $url = $generer_url_externe($id, $args, $ancre);
-               if (NULL != $url) return $url;
+               if (null != $url) {
+                       return $url;
+               }
        }
 
+       $debut_langue = '';
+
        // Mode propre
-       $propre = declarer_url_arbo($type, $id);
+       $c = array();
+
+       parse_str($args, $contexte);
+       // choisir le contexte de langue en fonction de la configuration
+       $c['langue'] = '';
+       if (_url_arbo_multilang === true) {
+               if (isset($contexte['lang']) and $contexte['lang']) {
+                       $c['langue'] = $contexte['lang'];
+                       $debut_langue = $c['langue'] .'/';
+                       unset($contexte['lang']);
+                       $args = http_build_query($contexte);
+               } elseif (isset($GLOBALS['spip_lang']) and $GLOBALS['spip_lang']) {
+                       $c['langue'] = $GLOBALS['spip_lang'];
+                       $debut_langue = $c['langue'] .'/';
+               }
+       } elseif (_url_arbo_multilang) {
+               $c['langue'] = _url_arbo_multilang;
+       }
+       $propre = declarer_url_arbo($type, $id, $c);
 
-       if ($propre === false) return ''; // objet inconnu. raccourci ? 
+       // si le parent est fourni en contexte dans le $args, verifier si l'URL relative a ce parent est la meme ou non
+       $champ_parent = url_arbo_parent($type);
+       if ($champ_parent
+         and $champ_parent = reset($champ_parent)
+         and isset($contexte[$champ_parent]) and $contexte[$champ_parent]) {
+               $c['id_parent'] = $contexte[$champ_parent];
+               $propre_contexte = declarer_url_arbo($type, $id, $c);
+               // 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)
+               if ($propre_contexte !== $propre) {
+                       $propre = $propre_contexte;
+                       unset($contexte[$champ_parent]);
+                       $args = http_build_query($contexte);
+               }
+       }
+
+
+       if ($propre === false) {
+               return '';
+       } // objet inconnu. raccourci ?
 
        if ($propre) {
                $url = _debut_urls_arbo
-                       . rtrim($propre,'/')
+                       . $debut_langue
+                       . rtrim($propre, '/')
                        . url_arbo_terminaison($type);
        } else {
-
                // objet connu mais sans possibilite d'URL lisible, revenir au defaut
                include_spip('base/connect_sql');
                $id_type = id_table_objet($type);
-               $url = get_spip_script('./')."?"._SPIP_PAGE."=$type&$id_type=$id";
+               $url = get_spip_script('./') . '?' . _SPIP_PAGE . "=$type&$id_type=$id";
        }
 
        // Ajouter les args
-       if ($args)
-               $url .= ((strpos($url, '?')===false) ? '?' : '&') . $args;
+       if ($args) {
+               $url .= ((strpos($url, '?') === false) ? '?' : '&') . $args;
+       }
 
        // Ajouter l'ancre
-       if ($ancre)
+       if ($ancre) {
                $url .= "#$ancre";
+       }
 
        return _DIR_RACINE . $url;
 }
@@ -425,20 +637,23 @@ function _generer_url_arbo($type, $id, $args='', $ancre='') {
  * @param string $ancre
  * @return array|string
  */
-function urls_arbo_dist($i, $entite, $args='', $ancre='') {
-       if (is_numeric($i))
+function urls_arbo_dist($i, $entite, $args = '', $ancre = '') {
+       if (is_numeric($i)) {
                return _generer_url_arbo($entite, $i, $args, $ancre);
+       }
 
        // traiter les injections du type domaine.org/spip.php/cestnimportequoi/ou/encore/plus/rubrique23
-       if ($GLOBALS['profondeur_url']>0 AND $entite=='sommaire'){
+       if ($GLOBALS['profondeur_url'] > 0 and $entite == 'sommaire') {
                $entite = 'type_urls';
        }
 
        // recuperer les &debut_xx;
-       if (is_array($args))
+       if (is_array($args)) {
                $contexte = $args;
-       else
-               parse_str($args,$contexte);
+               $args = http_build_query($contexte);
+       } else {
+               parse_str($args, $contexte);
+       }
 
        $url = $i;
        $id_objet = $type = 0;
@@ -446,24 +661,31 @@ function urls_arbo_dist($i, $entite, $args='', $ancre='') {
 
        // Migration depuis anciennes URLs ?
        // traiter les injections domain.tld/spip.php/n/importe/quoi/rubrique23
-       if ($GLOBALS['profondeur_url']<=0
-       AND $_SERVER['REQUEST_METHOD'] != 'POST') {
+       if ($GLOBALS['profondeur_url'] <= 0
+               and $_SERVER['REQUEST_METHOD'] != 'POST'
+       ) {
                include_spip('inc/urls');
                $r = nettoyer_url_page($i, $contexte);
                if ($r) {
-                       list($contexte, $type,,, $suite) = $r;
+                       list($contexte, $type, , , $suite) = $r;
                        $_id = id_table_objet($type);
                        $id_objet = $contexte[$_id];
                        $url_propre = generer_url_entite($id_objet, $type);
                        if (strlen($url_propre)
-                       AND !strstr($url,$url_propre)) {
-                               list(,$hash) = explode('#', $url_propre);
+                               and !strstr($url, $url_propre)
+                               and (
+                                       objet_test_si_publie($type, $id_objet)
+                                       OR (defined('_VAR_PREVIEW') and _VAR_PREVIEW and autoriser('voir', $type, $id_objet))
+                               )
+                       ) {
+                               list(, $hash) = array_pad(explode('#', $url_propre), 2, null);
                                $args = array();
-                               foreach(array_filter(explode('&', $suite)) as $fragment) {
-                                       if ($fragment != "$_id=$id_objet")
+                               foreach (array_filter(explode('&', $suite)) as $fragment) {
+                                       if ($fragment != "$_id=$id_objet") {
                                                $args[] = $fragment;
+                                       }
                                }
-                               $url_redirect = generer_url_entite($id_objet, $type, join('&',array_filter($args)), $hash);
+                               $url_redirect = generer_url_entite($id_objet, $type, join('&', array_filter($args)), $hash);
 
                                return array($contexte, $type, $url_redirect, $type);
                        }
@@ -472,29 +694,21 @@ function urls_arbo_dist($i, $entite, $args='', $ancre='') {
        /* Fin compatibilite anciennes urls */
 
        // Chercher les valeurs d'environnement qui indiquent l'url-propre
-       if (isset($_SERVER['REDIRECT_url_propre']))
-               $url_propre = $_SERVER['REDIRECT_url_propre'];
-       elseif (isset($_ENV['url_propre']))
-               $url_propre = $_ENV['url_propre'];
-       else {
-               // ne prendre que le segment d'url qui correspond, en fonction de la profondeur calculee
-               $url = ltrim($url,'/');
-               $url = explode('/',$url);
-               while (count($url)>$GLOBALS['profondeur_url']+1)
-                       array_shift($url);
-               $url = implode('/',$url);
-               $url_propre = preg_replace(',[?].*,', '', $url);
-       }
+       $url_propre = preg_replace(',[?].*,', '', $url);
 
        // Mode Query-String ?
        if (!$url_propre
-       AND preg_match(',[?]([^=/?&]+)(&.*)?$,', $url, $r)) {
+               and preg_match(',[?]([^=/?&]+)(&.*)?$,', $url, $r)
+       ) {
                $url_propre = $r[1];
        }
 
        if (!$url_propre
-         OR $url_propre==_DIR_RESTREINT_ABS
-         OR $url_propre==_SPIP_SCRIPT) return; // qu'est-ce qu'il veut ???
+               or $url_propre == _DIR_RESTREINT_ABS
+               or $url_propre == _SPIP_SCRIPT
+       ) {
+               return;
+       } // qu'est-ce qu'il veut ???
 
 
        include_spip('base/abstract_sql'); // chercher dans la table des URLS
@@ -503,70 +717,101 @@ function urls_arbo_dist($i, $entite, $args='', $ancre='') {
        $url_propre = rawurldecode($url_propre);
 
        // Compatibilite avec .htm/.html et autres terminaisons
-       $t = array_diff(array_unique(array_merge(array('.html','.htm','/'),url_arbo_terminaison(''))),array(''));
-       if (count($t))
+       $t = array_diff(array_unique(array_merge(array('.html', '.htm', '/'), url_arbo_terminaison(''))), array(''));
+       if (count($t)) {
                $url_propre = preg_replace('{('
-                 .implode('|',array_map('preg_quote',$t)).')$}i', '', $url_propre);
+                       . implode('|', array_map('preg_quote', $t)) . ')$}i', '', $url_propre);
+       }
 
-       if (strlen($url_propre) AND !preg_match(',^[^/]*[.]php,',$url_propre)){
+       if (strlen($url_propre) and !preg_match(',^[^/]*[.]php,', $url_propre)) {
                $parents_vus = array();
-               
+
                // recuperer tous les objets de larbo xxx/article/yyy/mot/zzzz
                // on parcourt les segments de gauche a droite
                // pour pouvoir contextualiser un segment par son parent
-               $url_arbo = explode('/',$url_propre);
+               $url_arbo = explode('/', $url_propre);
                $url_arbo_new = array();
                $dernier_parent_vu = false;
                $objet_segments = 0;
-               while (count($url_arbo)>0){
-                       $type=null;
-                       if (count($url_arbo)>1)
+
+               $langue = '';
+               if (_url_arbo_multilang === true) {
+                       // la langue : si fourni en QS prioritaire car vient du skel ou de forcer_lang
+                       if (isset($contexte['lang'])) {
+                               $langue = $contexte['lang'];
+                       }
+                       // le premier segment peut etre la langue : l'extraire
+                       // on le prend en compte si lang non fournie par la QS sinon on l'ignore
+                       include_spip('action/editer_url'); // pour url_verifier_langue
+                       if (count($url_arbo) > 1
+                               and $first = reset($url_arbo)
+                         and url_verifier_langue($first)) {
+                               array_shift($url_arbo);
+                               if (!$langue) {
+                                       $contexte['lang'] = $langue = $first;
+                               }
+                       }
+               } elseif (_url_arbo_multilang) {
+                       $langue = _url_arbo_multilang;
+               }
+
+               while (count($url_arbo) > 0) {
+                       $type = null;
+                       if (count($url_arbo) > 1) {
                                $type = array_shift($url_arbo);
+                       }
                        $url_segment = array_shift($url_arbo);
                        // Rechercher le segment de candidat
                        // si on est dans un contexte de parent, donne par le segment precedent,
                        // prefixer le segment recherche avec ce contexte
-                       $cp = "0"; // par defaut : parent racine, id=0
-                       if ($dernier_parent_vu)
+                       $cp = '0'; // par defaut : parent racine, id=0
+                       if ($dernier_parent_vu) {
                                $cp = $parents_vus[$dernier_parent_vu];
+                       }
                        // d'abord recherche avec prefixe parent, en une requete car aucun risque de colision
-                       $row=sql_fetsel('id_objet, type, url',
-                                                                                       'spip_urls',
-                                                                                       is_null($type)
-                                                                                               ? "url=".sql_quote($url_segment, '', 'TEXT')
-                                                                                               : sql_in('url',array("$type/$url_segment",$type)),
-                                                                                       '',
-                                                                                       // en priorite celui qui a le bon parent et les deux segments
-                                                                                       // puis le bon parent avec 1 segment
-                                                                                       // puis un parent indefini (le 0 de preference) et les deux segments
-                                                                                       // puis un parent indefini (le 0 de preference) et 1 segment
-                                                                                       (intval($cp)?"id_parent=".intval($cp)." DESC, ":"id_parent>=0 DESC, ")."segments DESC, id_parent"
+                       $row = sql_fetsel(
+                               'id_objet, type, url',
+                               'spip_urls',
+                               is_null($type)
+                                       ? 'url=' . sql_quote($url_segment, '', 'TEXT')
+                                       : sql_in('url', array("$type/$url_segment", $type)),
+                               '',
+                               // en priorite celui qui a le bon parent
+                               // puis la bonne langue puis la langue ''
+                               // puis les deux segments puis 1 seul segment
+                               //
+                               // si parent indefini on privilegie id_parent=0 avec la derniere clause du order
+                               (intval($cp) ? 'id_parent=' . intval($cp) . ' DESC, ' : 'id_parent>=0 DESC, ')
+                               . ($langue?'langue='.sql_quote($langue).' DESC, ':'') ."langue='' DESC,"
+                               . 'segments DESC, id_parent'
                        );
-                       if ($row){
-                               if (!is_null($type) AND $row['url']==$type){
-                                       array_unshift($url_arbo,$url_segment);
+                       if ($row) {
+                               if (!is_null($type) and $row['url'] == $type) {
+                                       array_unshift($url_arbo, $url_segment);
                                        $url_segment = $type;
                                        $type = null;
                                }
                                $type = $row['type'];
                                $col_id = id_table_objet($type);
-                               
+
                                // le plus a droite l'emporte pour des objets presents plusieurs fois dans l'url (ie rubrique)
                                $contexte[$col_id] = $row['id_objet'];
 
                                $type_parent = '';
-                               if ($p = url_arbo_parent($type))
-                                       $type_parent=end($p);
+                               if ($p = url_arbo_parent($type)) {
+                                       $type_parent = end($p);
+                               }
                                // l'entite la plus a droite l'emporte, si le type de son parent a ete vu
                                // sinon c'est un segment contextuel supplementaire a ignorer
                                // ex : rub1/article/art1/mot1 : il faut ignorer le mot1, la vrai url est celle de l'article
                                if (!$entite
-                                 OR $dernier_parent_vu == $type_parent){
-                                       if ($objet_segments==0)
+                                       or $dernier_parent_vu == $type_parent
+                               ) {
+                                       if ($objet_segments == 0) {
                                                $entite = $type;
-                               }
-                               // sinon on change d'objet concerne
-                               else{
+                                       }
+                               // sinon on change d'objet concerne
+                               else {
                                        $objet_segments++;
                                }
 
@@ -576,47 +821,64 @@ function urls_arbo_dist($i, $entite, $args='', $ancre='') {
 
                                // on note le dernier parent vu de chaque type
                                $parents_vus[$dernier_parent_vu = $type] = $row['id_objet'];
-                       }
-                       else {
+                       } else {
                                // un segment est inconnu
-                               if ($entite=='' OR $entite=='type_urls') {
+                               if ($entite == '' or $entite == 'type_urls') {
                                        // on genere une 404 comme il faut si on ne sait pas ou aller
-                                       return array(array(),'404');
+                                       return array(array(), '404');
                                }
                                // ici on a bien reconnu un segment en amont, mais le segment en cours est inconnu
                                // on pourrait renvoyer sur le dernier segment identifie
                                // mais de fait l'url entiere est inconnu : 404 aussi
                                // mais conserver le contexte qui peut contenir un fond d'ou venait peut etre $entite (reecriture urls)
-                               return array($contexte,'404');
+                               return array($contexte, '404');
                        }
                }
 
-               if (count($url_arbo_new)){
+               if (count($url_arbo_new)) {
                        $caller = debug_backtrace();
                        $caller = $caller[1]['function'];
                        // si on est appele par un autre module d'url c'est du decodage d'une ancienne URL
                        // ne pas regenerer des segments arbo, mais rediriger vers la nouvelle URL
                        // dans la nouvelle forme
-                       if (strncmp($caller,"urls_",5)==0 AND $caller!=="urls_decoder_url"){
+                       if (strncmp($caller, 'urls_', 5) == 0 and $caller !== 'urls_decoder_url') {
                                // en absolue, car assembler ne gere pas ce cas particulier
                                include_spip('inc/filtres_mini');
                                $col_id = id_table_objet($entite);
-                               $url_new = generer_url_entite($contexte[$col_id],$entite);
+                               $url_new = generer_url_entite($contexte[$col_id], $entite, $args);
                                // securite contre redirection infinie
-                               if ($url_new!==$url_propre
-                                       AND rtrim($url_new,"/")!==rtrim($url_propre,"/"))
+                               if ($url_new !== $url_propre
+                                       and rtrim($url_new, '/') !== rtrim($url_propre, '/')
+                               ) {
                                        $url_redirect = url_absolue($url_new);
-                       }
-                       else {
-                               foreach($url_arbo_new as $k=>$o)
-                                       if ($s = declarer_url_arbo($o['objet'],$o['id_objet']))
+                               }
+                       } else {
+                               foreach ($url_arbo_new as $k => $o) {
+                                       $c = array( 'langue' => $langue );
+                                       if (isset($parents_vus['rubrique'])) {
+                                               $c['id_parent'] = $parents_vus['rubrique'];
+                                       }
+                                       if ($s = declarer_url_arbo($o['objet'], $o['id_objet'], $c)) {
                                                $url_arbo_new[$k] = $s;
-                                       else
-                                               $url_arbo_new[$k] = implode('/',$o['segment']);
-                               $url_arbo_new = ltrim(implode('/',$url_arbo_new),'/');
-
-                               if ($url_arbo_new!==$url_propre){
-                                       $url_redirect = $url_arbo_new;
+                                       } else {
+                                               $url_arbo_new[$k] = implode('/', $o['segment']);
+                                       }
+                               }
+                               $url_arbo_new = ltrim(implode('/', $url_arbo_new), '/');
+                               if ($langue and _url_arbo_multilang === true) {
+                                       $url_arbo_new = "$langue/" . $url_arbo_new;
+                                       if (strpos($args, 'lang=') !== false) {
+                                               parse_str($args, $cl);
+                                               unset($cl['lang']);
+                                               $args = http_build_query($cl);
+                                       }
+                               }
+                               if ($url_arbo_new !== $url_propre) {
+                                       //var_dump($url_arbo_new,$url_propre);
+                                       $url_redirect = _debut_urls_arbo
+                                               . $url_arbo_new
+                                               . url_arbo_terminaison($entite)
+                                               . ($args?"?$args":'');
                                        // en absolue, car assembler ne gere pas ce cas particulier
                                        include_spip('inc/filtres_mini');
                                        $url_redirect = url_absolue($url_redirect);
@@ -625,16 +887,18 @@ function urls_arbo_dist($i, $entite, $args='', $ancre='') {
                }
 
                // gerer le retour depuis des urls propres
-               if (($entite=='' OR $entite=='type_urls')
-               AND $GLOBALS['profondeur_url']<=0){
-                       $urls_anciennes = charger_fonction('propres','urls');
+               if (($entite == '' or $entite == 'type_urls')
+                       and $GLOBALS['profondeur_url'] <= 0
+               ) {
+                       $urls_anciennes = charger_fonction('propres', 'urls');
+
                        return $urls_anciennes($url_propre, $entite, $contexte);
                }
        }
-       if ($entite=='' OR $entite=='type_urls' /* compat .htaccess 2.0 */) {
-               if ($type)
-                       $entite = objet_type ($type);
-               else {
+       if ($entite == '' or $entite == 'type_urls' /* compat .htaccess 2.0 */) {
+               if ($type) {
+                       $entite = objet_type($type);
+               else {
                        // Si ca ressemble a une URL d'objet, ce n'est pas la home
                        // et on provoque un 404
                        if (preg_match(',^[^\.]+(\.html)?$,', $url)) {
@@ -643,11 +907,9 @@ function urls_arbo_dist($i, $entite, $args='', $ancre='') {
                        }
                }
        }
-       if (!defined('_SET_HTML_BASE')){
-               define('_SET_HTML_BASE',1);
+       if (!defined('_SET_HTML_BASE')) {
+               define('_SET_HTML_BASE', 1);
        }
 
        return array($contexte, $entite, $url_redirect, null);
 }
-
-?>