Raccourcis : Contenu - rubriques - sous rubriques
EN FR

Le moteur d'url “significant” permet d'attribuer à chaque action une URL spécifique de la forme que l'on veut.

Le principe de configuration pour ce moteur est d'indiquer dans un fichier var/config/urls.xml toutes les formes d'URLs possibles de l'application et les actions qui leur sont associées. Voici un exemple de fichier :


<urls xmlns="http://jelix.org/ns/urls/1.0">
    <entrypoint name="index" default="true">

        <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>
    <classicentrypoint name="foo/bar">
       <url handler="urlsig" module="unittest" action="url4" />
    </classicentrypoint>

    <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 point d'entrée définit une ou plusieurs formes d'URLs possibles, sachant que celles qui ne sont pas définies seront acceptées lors d'une requête sur un point d'entrée spécifié "default".

Spécifier une forme d'URL complète

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.

L'attribut pathinfo doit donc contenir la valeur d'un pathinfo. Dans cet exemple, toute URL qui aura pour pathinfo la valeur "/foo/bar" correspondra au module et action indiqués (module hello, action default:world dans notre cas).


   <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 du même nom. 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 "statique" (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

Note : vous devrez bien entendu indiquer les valeurs de ces paramètres lors de l'appel à jUrl.

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érence.

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é)

Jelix 1.4 apporte des améliorations en ce sens. 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

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


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

Cela permet d'avoir dans le "pathinfo" de l'url l'information de la langue, sans même avoir à l'indiquer à jUrl::get(), mais 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".

Contrairement à Jelix 1.3 et inférieur, vous n'avez donc plus à dupliquer vos déclarations d'URL pour chaque langue.

Même principe avec type="locale", sauf que jUrl générera /articles/en_US/foo.

Utiliser un handler spécifique

Nous avons vu précédemment comment utiliser, en fin de compte, un système par défaut d'analyse et de génération d'URL avec ses pathinfos et paramètres. Il se peut que ce système ne soit pas suffisant. 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)){
            $urlact = new jUrlAction($url->params);
            $urlact->setParam('page',jUrl::unescape($match[1]));
            return $urlact;
        }else
            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 :


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

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


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

Une même URL pour plusieurs actions possibles

Imaginons que nous avons 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" />

Indiquer qu'un point d'entrée est dédié à un module particulier

Vous n'avez pas forcément envie d'indiquer une URL significative pour les actions d'un module particulier. Par contre vous avez créé un point d'entrée dédié à un module. Toutes ces actions passeront par ce point d'entrée. Vous avez juste alors à faire comme ceci :


    <classicentrypoint name="news">
        <url module="news" />
    </classicentrypoint>

Vous pouvez indiquer plusieurs modules de cette façon pour un même point d'entrée.

Spécifier des URLs en https à certaines actions

Pour certaines actions vous voulez que l'accès soit 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 (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.

Mod_rewrite et suppression du nom du point d'entrée dans l'URL

Vous préférez peut être que l'analyse des URLs se fasse par le serveur web, grâce au module rewrite pour apache par exemple. Vous allez donc écrire les règles de réécriture.

Réécriture complète par le serveur web

On appelle ici réécriture complète, la réécriture qui consiste à fournir à Jelix des URLs simples (index.php?module=...&action=...), à partir d'URLs significatives.

Pour cela, vous devez indiquer à Jelix, dans la configuration, de ne pas analyser les URLs :


 [urlengine]
  ..
  enableParser = off

Vous devez bien sûr remplir un fichier urls.xml de façon à ce que Jelix génère dans vos templates et ailleurs, les URLs attendues par vos règles de rewrite.

Et bien sûr, vous devez ensuite mettre dans un fichier .htaccess les règles de réécritures.

Si vous voulez définir des URLs qui ne contiennent pas le nom du point d'entrée, vous devez alors indiquer sur les balises <url> l'attribut noentrypoint="true" (ou sur la balise <entrypoint> si toutes les URLs de ce point d'entrée sont concernées). Ainsi Jelix n'ajoutera pas le nom du point d'entrée (index.php) dans l'URL.

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

Un exemple d'utilisation du mod_rewrite n'est pas forcément de transformer complétement les URLs comme indiqué précédement, mais par exemple d'ajouter le fichier index.php dans les URLs dans lesquelles on l'aurait supprimé pour faire "joli".

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.

Tout d'abord, il faut bien sûr utiliser le moteur d'URL "significant" et définir un fichier urls.xml. Comme pour la réécriture complète 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.

Par contre, contrairement à la réécriture complète, vous devez laisser enableParser à on:


 [urlengine]
  ..
  enableParser = on

Ensuite on va utiliser le rewriteEngine de Apache 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 : Tout prendre, et 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