Raccourcis : Contenu - rubriques - sous rubriques
EN FR
Jelix 1.7.0-rc.1

Section: fichier urls.xml

^ jUrl : des URLs automatiques
Changer de langue : EN

Le principe de configuration est d'indiquer dans un fichier var/config/urls.xml la correspondance entre des urls et les actions jelix. Voici un exemple de fichier :


<urls xmlns="http://jelix.org/ns/urls/1.0">
    <entrypoint name="index" default="true">
        <url pathinfo="/" module="main" action="default:index" />
        <url module="main" />

        <url pathinfo="/news/:annee/:mois/:id-:title" module="news" action="view">
           <param name="annee" type="year"/>
           <param name="mois"  type="month" />
           <param name="id"   type="number" />
           <param name="title"   escape="true" />
        </url>

        <url pathinfo="/articles/:rubrique/:id_art" module="cms" action="show">
           <param name="id_art" regexp="\d+"/>
        </url>

    </entrypoint>
    <entrypoint name="shop" type="classic">
       <url pathinfo="/:category/:product" module="unittest" action="url2">
          <param name="product"  regexp="\d{2}" />
          <static name="mystatic" value="valeur statique" />
        </url>
    </entrypoint>
    <entrypoint name="foo/bar">
       <url handler="urlsig" module="unittest" action="url4" />
    </entrypoint>

    <entrypoint name="news">
        <url module="news" />
    </entrypoint>
    <entrypoint name="xmlrpc" default="true" type="xmlrpc" />
    <entrypoint name="jsonrpc" default="true" type="jsonrpc"/>
</urls>

Balises entrypoint

La balise racine urls contient autant de balises entrypoint que de points d'entrée disponibles dans votre application.

Chacune de ces balises a un attribut name indiquant le nom du point d'entrée (sans l'extension .php), ou plus exactement, son chemin, relatif à la valeur du basePath dans la configuration générale. Et vous ajouterez éventuellement un attribut default indiquant si ce point d'entrée est celui par défaut pour le type de requête en question.

Il doit également y avoir un attribut type, qui indique le type de point d'entrée, et vaut donc classic, xmlrpc, jsonrpc etc.. Si cet attribut n'est pas présent, la valeur par défaut est classic.

Pour des raisons de compatibilité avec les versions précédentes de jelix, les balises entrypoint peuvent avoir d'autres noms. Leur nom exact précise le type de requête auquel ils sont affectés :

  • classicentrypoint pour les requêtes classiques
  • xmlrpcentrypoint pour du xmlrpc
  • etc ...

Mais cet usage est dorénavant obsolète.

Balise url

Chaque module est accessible à un point d'entrée spécifique, donc il faut déclarer les urls correspondantes à ces modules dans les balises de point d'entrée attitrées.

Spécifier l'action par défaut, la page d'accueil

Toute application a une page par défaut qui s'affiche quand il n'y a pas de pathinfo.

Il y a donc au moins une url à définir pour chaque point d'entrée de type "classic", en indiquant le module et l'action correspondants :


<urls xmlns="http://jelix.org/ns/urls/1.0">
    <entrypoint name="index" default="true">
        <url pathinfo="/" module="main" action="default:index" />
    </entrypoint>
</url>

Ici donc, l'url index.php/ correspond à la méthode index du contrôleur default du module main.

Note : avant Jelix 1.7, il fallait l'action par défaut au moyen des paramètres startModule et startAction dans le fichier de configuration ini du point d'entrée.

des URLs automatiques pour les modules

Comme vous le verrez un peu plus loin, vous pouvez définir des urls pour chaque action (une balise url d'action). Mais ce n'est pas une obligation.

Par défaut, les urls sont de la forme : <entrypoint>.php/<module>/<controleur>/<methode>.

Pour cela, il faut indiquer sur quel point d'entrée le module est accessible, et donc spécifier une balise URL de module, c'est à dire une balise <url> indiquant juste le nom du module.


    <entrypoint name="index" default="true">
        <url pathinfo="/" module="main" action="default:index" />
        <url module="main" />
    </entrypoint>
    <entrypoint name="actualites">
        <url module="news" />
    </entrypoint>

Ici toutes les urls du module news seront de la forme actualites.php/news/<controleur>/<methode>.

Vous pouvez aussi indiquer un pathinfo : il remplacera le nom du module dans l'url. Avec cet exemple :


    <entrypoint name="index" default="true">
        <url pathinfo="/" module="main" action="default:index" />
        <url pathinfo="/supere/actu/" module="news" />
    </entrypoint>

les urls du module news seront de la forme index.php/supere/actu/<controleur>/<methode>.

Si il n'y a pas de balise url de module pour un module donné, seul les actions du module qui sont spécifiées par une balise <url> d'action, seront accessible depuis le web.

Ainsi, dans l'exemple précedant, il n'y a qu'une balise url d'action pour le module main, donc uniquement l'action default:index sera accessible (et qui se trouve ici être la page d'accueil). Pour que les autres actions du module main puissent être accessible, soit on déclare une balise url d'action pour chaque action, soit on ajoute <url module="main" />.

Si aucune balise url (de module ou d'action) n'est définie pour un module donné, alors le module sera accessible par le point d'entrée qui est celui par défaut (<entrypoint default="true">).

des URLs automatiques pour les contrôleurs

Il est possible de définir un pathinfo pour un contrôleur en particulier, tout en laissant ajouter le nom de l'action dans l'url.

Pour cela, il faut indiquer le module, le pathinfo et le contrôleur dans un attribut controller.

Exemple :


    <entrypoint name="index" default="true">
        <!-- ... -->
        <url pathinfo="/cool" module="main" controller="system" />
    </entrypoint>

Les urls des actions du contrôleur system dans l'exemple, seront de la forme index.php/cool/<methode>.

Spécifier les urls pour des actions

Selon un pathinfo

Vous voulez indiquer le module et l'action à exécuter pour une forme particulière d'URL. Vous indiquerez alors un attribut pathinfo, indiquant le "modèle" de la partie pathinfo auquel l'URL doit ressembler, ainsi que le module et l'action dans des attributs module et action.

Dans cet exemple, toute URL qui aura pour pathinfo la valeur /foo/bar correspondra au module hello et l'action default:world.


   <url pathinfo="/foo/bar" module="hello" action="default:world" />

Vous pouvez mettre l'attribut optionalTrailingSlash="true" si vous voulez indiquer que l'éventuelle présence/absence d'un slash à la fin correspond à la même action ( /foo/bar/ et /foo/bar serait donc équivalent, ce qui n'est pas le cas par défaut). On peut mettre cet attribut sur la balise entrypoint pour que ce soit pris en compte pour toutes les urls définies.

Selon un pathinfo contenant des parties indéfinies

Il est possible d'indiquer des parties "dynamiques" dans le pathinfo. Elles sont à définir par un deux-points suivi d'un nom. La valeur trouvée sera alors stockée dans un paramètre de même nom pour le controleur. Dans l'exemple qui suit, le pathinfo contient deux parties dynamiques : rubrique et id_art.


   <url pathinfo="/articles/:rubrique/:id_art" module="cms" action="default:show" />

Si on appelle l'URL "/articles/aviation/544", alors les paramètres rubrique et id_art seront crées et auront pour valeurs respectives "aviation" et "544".

Attention : pour éviter les ressemblances avec d'autres URLs, il faut au moins une partie fixe (ici /articles/) dans l'URL pour la distinguer des autres.

Des parties indéfinies typées ou formatées

Un autre moyen d'éviter ces ressemblances est de spécifier le format ou le type de chaque paramètre. Par défaut le type est une chaîne classique.

Pour cela il faut indiquer des balises <param> pour chacun des paramètres dont on veut spécifier le type/format. Elles devront contenir un attribut name indiquant le paramètre et, soit un attribut type, soit un attribut regexp, contenant une expression régulière du format (sans délimiteur). Dans notre exemple, nous voulons spécifier que rubrique est une chaîne et une expression régulière pour id_art :


   <url pathinfo="/articles/:rubrique/:id_art" module="cms" action="show">
      <param name="rubrique" type="string" />
      <param name="id_art" regexp="\d+" />
   </url>

Si l'expression régulière de l'attribut regexp contient des parenthèses, il est nécessaire d'indiquer que celles-ci ne doivent pas être capturées. Exemple :


<param name="type" regexp="(?:0|1|2){1}" />

Quand le paramètre est de type string il n'est pas obligatoire de mettre une balise param. Les types disponibles sont :

string une chaîne
letter une seule lettre
number un nombre entier, équivalent aussi à 'int' et 'integer'
digit un chiffre
date une date au format AAAA-MM-JJ
year une année sur 4 chiffres
month un mois sur deux chiffres
day un jour sur deux chiffres
path un sous chemin. dans ce cas ce paramètre doit être le dernier dans l'url
lang un code langue (2 ou 3 lettres), voir section plus bas sur les paramètres automatiques de langue
locale un code de locale (code langue + code pays), voir section plus bas sur les paramètres automatiques de langue

Vous devrez bien entendu indiquer les valeurs de ces paramètres lors de l'appel à jUrl, afin de générer la bonne URL.

Des paramètres statiques

Il peut être nécessaire parfois de rajouter des paramètres "statiques", attendues par l'action (celle-ci pouvant être attribuées à plusieurs URLs différentes), mais non présent dans l'URL. Pour cela il faut ajouter une balise <static>, avec nom et valeur, comme dans cet exemple :


   <url pathinfo="/:category/:product" module="shop" action="product:view">
      <param name="product"  regexp="\d{2}" />
      <static name="details" value="0" />
   </url>
   <url pathinfo="/:category/:product/details" module="shop" action="product:view">
      <param name="product"  regexp="\d{2}" />
      <static name="details" value="1" />
   </url>

Ici on utilise la même action pour deux URLs différentes. Son traitement différera en partie selon le paramètre details, qu'il ne faut donc pas oublier de donner à jUrl::get(). Dans un cas on afficherait le produit d'un catalogue avec ses caractéristiques générales, et dans l'autre avec ses caractéristiques générales et détaillées. Cela évite donc de créer deux actions différentes pour si peu de différences.

On peut aussi utiliser ce mécanisme pour supporter plusieurs langues :


   <url pathinfo="/articles/en/:page" module="cms" action="page:view">
      <param name="page"/>
      <static name="lang" value="en_US" />
   </url>
   <url pathinfo="/articles/fr/:page" module="cms" action="page:view">
      <param name="page"/>
      <static name="lang" value="fr_FR" />
   </url>

Là encore, il ne faut pas oublier de donner le paramètre lang à jUrl pour que ce dernier puisse savoir quelle url il doit renvoyer.


jUrl::get('cms~page:view', array('page'=>'foo', 'lang'=>jApp::config()->locale));
// ou
jUrl::get('cms~page:view', array('page'=>'foo', 'lang'=>"en"));

<a href="{jurl 'cms~page:view', array('page'=>'foo', 'lang'=>$j_locale}">my link</a>
<a href="{jurl 'cms~page:view', array('page'=>'foo', 'lang'=>'fr_FR'}">my link</a>

Paramètres automatiques de langues

Avec les paramètres statiques

Nous avons vu que l'on pouvait déclarer des paramètres statiques contenant le code d'une langue, pour différencier deux URLS pointant vers une même action. Cependant, cette solution, telle que décrite, a deux inconvénients :

  1. il faut donner ce paramètre à jUrl::get(), et en général, on indique la valeur de la langue courante
  2. Dans l'action, il faut tenir compte de ce paramètre, et en général, il s'agit de changer la configuration à la volée (le plugin autolocale n'étant pas forcément utilisé)

Le moteur d'URL apporte une solution : il suffit d'ajouter l'attribut type="locale" sur la balise <static>, pour que tout soit automatique. Vous définissez par exemple :


   <url pathinfo="/articles/english/:page" module="cms" action="page:view">
      <param name="page"/>
      <static name="lang" value="en_US" type="locale"/>
   </url>
   <url pathinfo="/articles/francais/:page" module="cms" action="page:view">
      <param name="page"/>
      <static name="lang" value="fr_FR" type="locale" />
   </url>

Si vous faites simplement jUrl::get('cms~page:view', array('page'=>'foo')), alors jUrl générera /articles/english/foo si la langue courante est "en_US", ou /articles/francais/foo si la langue courante est "fr_FR". Vous n'avez pas besoin d'indiquer le code langue comme avant : jUrl::get('cms~page:view', array('page'=>'foo', 'lang'=>jApp::config()->locale)). Cependant, vous pouvez toujours le faire pour forcer le code langue.

À l'inverse, lors de la demande de la page /articles/english/foo ou /articles/francais/foo, Jelix configurera automatiquement la locale, le temps de l'execution de l'action. Ainsi, si l'utilisateur appelle la page /articles/english/foo, la langue courante deviendra "en_US".

Vous pouvez aussi utiliser type="lang". Dans ce cas, vous pouvez indiquer juste le code langue ('en', 'fr'...), que ce soit dans l'attribut value, ou à jUrl::get().


    <static name="lang" value="fr" type="lang" />

Avec les paramètres dynamiques

Si il y a plus de 2-3 langues, l'utilisation des paramètres statiques peut devenir fastidieuses : il faut autant d'URL que de langues.

Heureusement, vous pouvez avoir des paramètres dynamiques de type "locale" ou "lang".


   <param name="lg" type="lang"/>
   <!-- ou -->
   <param name="lg" type="locale"/>

Notez qu'il s'agit d'une balise <param> et non plus <static>.

Cela permet d'avoir dans le "pathinfo" de l'url l'information de la langue, sans même avoir à l'indiquer à jUrl::get(). Cela permet aussi de configurer la langue courante automatiquement pendant l'éxecution de l'action lors de l'appel de l'URL en question.

Ainsi, l'exemple utilisé plus haut :


   <url pathinfo="/articles/en/:page" module="cms" action="page:view">
      <param name="page" />
      <static name="lang" value="en_US" />
   </url>
   <url pathinfo="/articles/fr/:page" module="cms" action="page:view">
      <param name="page" />
      <static name="lang" value="fr_FR" />
   </url>

peut en fait se résumer à :


   <url pathinfo="/articles/:lang/:page" module="cms" action="page:view">
      <param name="page"/>
      <param name="lang" type="lang" />
   </url>

Si vous faites juste jUrl::get('cms~page:view', array('page'=>'foo')), alors jUrl générera /articles/en/foo si la langue courante est "en_US", ou /articles/fr/foo si la langue courante est "fr_FR".

Et si un navigateur appelle l'url /articles/en/foo, la langue courante deviendra automatiquement "en_US".

Même principe avec type="locale", sauf que jUrl générera un code de locale, c'est à dire code langue + code pays : /articles/en_US/foo.

Utiliser un handler spécifique

Jusqu'ici, nous avons vu un système d'analyse et de génération d'URL avec ses pathinfos et paramètres, assez générique. Cependant, pour certains usages, il peut être insuffisant en terme de flexibilité.

Imaginons par exemple que vous vouliez transformer une partie de l'information contenue dans le pathinfo, comme par exemple chercher dans une base de données un id correspondant à un titre contenu dans le pathinfo, et fournir ainsi aux actions, non pas un titre, mais un id (qui n'est pas contenu dans cette URL).

Ou encore que la partie pathinfo puisse être variable.

Aussi c'est à vous de développer l'analyse de l'URL et d'indiquer l'action à exécuter. Vous le ferez dans une classe spécialisée :



class myHandlerUrlsHandler implements jIUrlSignificantHandler {

    function parse($url){
        if (preg_match("/^\/(.*)$/",$url->pathInfo,$match)) {
            // le pathinfo correspond, on traite
            $urlact = new jUrlAction($url->params);
            $urlact->setParam('page',jUrl::unescape($match[1]));
            return $urlact;
        }
        else {
            // le pathinfo ne correspond pas, on ne traite pas l'url
            return false;
        }
    }

    function create($urlact, $url){

        $p = jUrl::escape($url->getParam('page'));

        $url->pathInfo = "/$p";
        $url->delParam('page');
    }
}

Le nom de la classe doit commencer par un préfixe que vous voulez et se terminer par UrlsHandler. La classe sera stockée dans le répertoire classes/ du module indiqué dans la balise url, sous le nom <prefixe>.urlhandler.php. Pour notre exemple, cela sera myHandler.urlhandler.php.

La méthode parse() est chargée d'analyser une URL (objet jUrl que vous recevez en paramètre), en l'occurence, l'URL demandée dans l'application. Si votre handler reconnaît l'URL, parse() doit renvoyer un objet jUrlAction qui contient toutes les données pour l'action. Sinon il doit renvoyer false.

La méthode create() est appelée à chaque fois que l'application demande la création d'une URL à partir de paramètres d'action. Elle reçoit donc un objet de type jUrlAction, et un objet de type jUrl. $urlaction contient donc les paramètres de l'action. Ces paramètres ont déjà été inclus dans l'objet $url. Vous devez donc modifier $url de façon à produire l'URL correspondante à l'action demandée. Dans l'exemple, on récupère le paramètre page, que l'on incorpore dans le pathinfo. Et comme il est dans le pathinfo, on le supprime alors des paramètres.

Notez l'usage de jUrl::escape() et jUrl::unescape(), pour "nettoyer" les chaînes à inclure dans un pathinfo (accents enlevés par ex).

Enfin dans le fichier urls.xml, vous devez indiquer l'utilisation de ce handler :


   <entrypoint name="wiki">
       <url handler="myHandler" module="unittest" action="url4" />
    </entrypoint>

Vous pouvez bien sûr indiquer un handler d'un autre module. Exemple :


   <entrypoint name="wiki">
       <url handler="autremodule~myHandler" module="unittest" action="url4" />
    </entrypoint>

Une même URL pour plusieurs actions possibles

Imaginons que nous ayons une URL de ce type /article/54-titre et par défaut, cela affiche l'article 54 avec une action nommée view, par exemple :


   <url pathinfo="/article/:id_art-:title" module="cms" action="view" />

On veut pouvoir, sans changer l'url, indiquer d'autres actions dans certains cas, avec un paramètre action :

  • /article/54-titre?action=edit
  • /article/54-titre?action=delete

Note : On pourrait faire aussi /article/54-titre/edit ou /article/54-titre/delete , avec donc plusieurs balises urls, ce qui nous éviterait ce qui suit. Mais ce n'est pas très pratique quand l'URL est appelée suite à un formulaire par exemple.

Pour indiquer les actions alternatives autorisées, on ajoute un attribut actionoverride, contenant la liste des actions séparées par un espace ou une virgule :


   <url pathinfo="/article/:id_art-:title" module="cms" action="view" actionoverride="edit,delete" />

Spécifier des URLs en https à certaines actions

Pour certaines actions vous voulez que l'accès soit uniquement en mode sécurisé de http (SSL). Vous indiquez alors un attribut https sur les balises <url> en question avec la valeur true. Si cela concerne toutes les actions/URLs d'un point d'entrée, alors vous mettrez cet attribut sur la balise <entrypoint> correspondante.

Attention : pour le moment, Jelix ne vérifie pas lors de l'exécution d'une action que la requête a été faite en https ou non.

Inclure un fichier d'urls d'un module particulier

Il est possible de déclarer les urls dans chaque module, donc dans des fichiers séparés. Cela évite d'avoir un fichier urls.xml volumineux, mais surtout évite d'avoir à définir ou à "recopier" les urls d'un module réalisé par un tiers.

Pour ce faire, vous devez créer un fichier urls.xml dans le répertoire du module (vous pouvez choisir un autre nom que urls.xml). Ce fichier ne contient pas une balise racine urls mais suburls, et contient uniquement des balises url, et donc pas entrypoint. Voici celui du module jauth comme exemple :


<?xml version="1.0" encoding="utf-8"?>
<suburls xmlns="http://jelix.org/ns/suburls/1.0">
    <url pathinfo="/dologin" action="login:in" />
    <url pathinfo="/dologout" action="login:out" />
    <url pathinfo="/login" action="login:form"/>
</suburls>

Et dans le fichier urls.xml principal, dans une des balises entrypoint, vous indiquez


  <entrypoint name="index">
    ...
     <url pathinfo="/auth" module="jauth" include="urls.xml" />
  </entrypoint>

Cela signifie : il faut inclure toutes les urls qui sont dans le fichier urls.xml du module jauth, et préfixer le pathinfo de ces urls par le chemin "/auth/". Autrement dit, l'url pour l'action "jauth~login:in" sera "/auth/dologin".

Cela signifie aussi que les urls que vous indiquez dans un fichier de module ne sont pas des urls complètes. Cela permet à l'utilisateur du module de choisir le chemin (en tout cas, le début du chemin) pour les urls du module, et éviter donc des doublons avec d'autres modules.

Dans un fichier URL de module, vous pouvez déclarer des urls avec un pathinfo complet, avec un pathinfo avec variables (formatées ou non), avec des paramètres statiques, avec un handler. Par contre, il est obligatoire de spécifier une url pour chaque action. Notez aussi qu'il ne faut pas utiliser l'attribut module, cela n'aurait pas de sens.

Bien sûr, vous devez faire un fichier d'urls pour chaque type d'action : un fichier pour le point d'entrée classique, un fichier pour chaque autre type de point d'entrée etc.

Suppression du nom du point d'entrée dans l'URL

Vous pourriez vouloir ne plus avoir le nom du point d'entrée dans les URLS.

Par exemple nos URLs sont de la forme :


 http://monsite.com/index.php/news/2017-02-08-il-neige-a-lille.html

Et on souhaite qu'elles deviennent :


 http://monsite.com/news/2017-02-08-il-neige-a-lille.html

Voyons comment faire.

Vous devez indiquer sur les balises <url> ou <entrypoint> l'attribut noentrypoint="true". Ainsi Jelix n'ajoutera pas le nom du point d'entrée (index.php) dans les URLs concernées.

Ensuite il faut configurer correctement votre serveur web.

Pour Nginx, vous créez une configuration de ce type :


server {
    
    location / {
        try_files $uri $uri/ @mysite;
    }

    location @mysite {
            fastcgi_split_path_info ^()(/.*)$;
            set $path_info $fastcgi_path_info;
            include fastcgi_params;

            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME "$document_root/index.php";
            fastcgi_param PATH_INFO       $fastcgi_path_info;
            fastcgi_param PATH_TRANSLATED "$document_root/index.php";
            fastcgi_param SCRIPT_NAME "/index.php";
            fastcgi_pass unix:/var/run/php5-fpm.sock;
    }
}

Pour Apache, vous pouvez utiliser le module rewrite, d'une façon toute simple : le chemin appelé sera rajouté après index.php. En clair il faut copier ceci dans un fichier .htaccess dans le dossier www/ de votre application (ou dans la section virtualhost de la configuration apache):


<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php/$1 [L,QSA]
</IfModule>

Observez bien :

  • 2e ligne : vous n'en avez pas besoin si vous écrivez ces instructions dans un .htaccess dans le répertoire www de l'appli. Par contre, dans un autre .htaccess ou dans un virtual host, vous devez la mettre, et adapter le chemin : / si le www correspond au documentRoot, ou le chemin pour accéder au répertoire www à partir d'une url (basePath). ex RewriteBase /monapp/www/ si vous accédez à l'application par http://localhost.local/monapp/www/index.php
  • 3e et 4e ligne : Réécrire l'URL seulement si la requête ne correspond pas à un fichier déjà existant, ou un dossier déjà existant. Ainsi on préserve ses dossiers d'image, de feuille de style, etc. de la règle de réécriture.
  • 5e ligne : prendre tout le contenu de l'url, et le mettre après index.php/

On retrouve ainsi notre point d'entrée index.php ainsi que notre pathinfo.

Il se peut que sur certaines configurations, vous ayez une erreur apache du type "Error: No input file specified", en particulier quand PHP est exécuté en CGI avec cgi.fix_pathinfo=0 dans php.ini. Mais il y a une solution. Il faut changer la dernière rêgle en :


    RewriteRule ^(.*)$ index.php?jpathinfo=/$1 [L,QSA]

Ainsi le but est de mettre le pathinfo dans un paramétre de la requête. Pour que cela fonctionne pleinement, il faut ajouter dans la configuration de Jelix, dans la section urlengine, le paramètre pathInfoInQueryParameter qui doit contenir le nom du paramètre (ici dans l'exemple jpathinfo) :


[urlengine]
...
pathInfoInQueryParameter = jpathinfo