Section: Moteur d'urls significatives
« Moteur d'urls 'basic_significant' | ^ jUrl : des URLs automatiques |
− Table des matières
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 classiquesxmlrpcentrypoint
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 :
- il faut donner ce paramètre à
jUrl::get()
, et en général, on indique la valeur de la langue courante - 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). exRewriteBase /monapp/www/
si vous accédez à l'application parhttp://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