diff --git a/README.md b/README.md
index 0a82367e152795c69265d45c0f0deeeb4ebe230b..fcb891c9140c354fb2dc0ef559394d0f96bcec74 100644
--- a/README.md
+++ b/README.md
@@ -19,4 +19,20 @@ C'est aussi le module de base (fondations) de toutes nos applications web qui :
 
 ## Documentation
 
+- [Installation](./doc/Installation.md)
+- [Configuration](./doc/Configuration.md)
+- [Services](./doc/Services.md)
+- [Modèles](./doc/Modeles.md)
+- [Plugins de contrôleur (controller plugins)](./doc/Plugins.md)
+- [Aides de vue (view helpers)](./doc/ViewHelpers.md)
+- [Contrôleurs](./doc/Controleurs.md)
+- [Formulaires](./doc/Formulaires.md)
+- [Filtres](./doc/Filtres.md)
+- [Validateurs](./doc/Validateurs.md)
+- [MVC](./doc/MVC.md)
+- [Message](./doc/Message.md)
+- [Divers](./doc/Divers.md)
+- [Historique](./doc/Historique.md)
+- [Eléments de page riches (mélangeant HTML et JS)](./doc/ElementsPageRiches.md)
+- [Javascript](./doc/Javascript.md)
 - [Widgets](./doc/widgets.md)
diff --git a/doc/Configuration.dokuwiki b/doc/Configuration.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..a4342ebec9ede353a0aee1acabcb91647237ab81
--- /dev/null
+++ b/doc/Configuration.dokuwiki
@@ -0,0 +1,95 @@
+====== Configuration ======
+
+Il s'agit ici d'adapter certaines options de configuration du module.
+
+===== Configuration globale =====
+
+  * Copier/coller/renommer le fichier ''config/unicaen-app.global.php.dist'' du module vers ''config/autoload/unicaen-app.global.php'' de votre projet.
+  * Adapter les options de config à votre contexte (sans modifier les noms des clés)...
+
+==== Fiche d'identité de l'appli ====
+
+Il s'agit de renseigner nom de l'appli, description, numéro et date de version, adresses et/ou numéros de téléphones de contact, URL des pages externes "mentions légales" et "informatique et libertés", exemple :
+
+<file php>
+$settings = array(
+    'app_infos' => array(
+        'nom'     => "GesNum",
+        'desc'    => "Gestion des services numériques",
+        'version' => "1.0.0",
+        'date'    => "05/09/2013",
+        'contact' => array('mail' => "dsi.applications@unicaen.fr", /*'tel' => "01 02 03 04 05"*/),
+        'mentionsLegales'        => "http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/",
+        'informatiqueEtLibertes' => "http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/",
+    ),
+);
+</file>
+
+
+===== Configuration locale =====
+
+  * Copier/coller/renommer le fichier ''config/unicaen-app.local.php.dist'' du module vers ''config/autoload/unicaen-app.local.php'' de votre projet.
+  * Adapter les options de config à votre contexte (sans modifier les noms des clés)...
+
+==== Annuaire LDAP ====
+
+Clé '''ldap''' : il faut configurer la connexion à l'annuaire LDAP via un compte omnipotent pour la récupération des coordonnées, affectations administratives, rôles, etc. de l'utilisateur connecté (notamment), exemple :
+
+<file php unicaen-app.local.php>
+    'ldap' => array(
+        'connection' => array(
+            'default' => array(
+                'params' => array(
+                    'host'                => 'localhost',
+                    'port'                => 389,
+                    'username'            => "uid=xxxxxxxxxxx,ou=system,dc=unicaen,dc=fr",
+                    'password'            => "yyyyyyyyyyyy",
+                    'baseDn'              => "ou=people,dc=unicaen,dc=fr",
+                    'bindRequiresDn'      => true,
+                    'accountFilterFormat' => "(&(objectClass=posixAccount)(supannAliasLogin=%s))",
+                )
+            )
+        )
+    ),
+</file>
+
+==== Envoi de mail ====
+
+Clé '''mail''' : options concernant l'envoi de mail par l'application, exemple :
+
+<file php unicaen-app.local.php>
+    'mail' => array(
+        // transport des mails
+        'transport_options' => array(
+            'host' => 'localhost',
+            'port' => 25,
+        ),
+        // adresses à substituer à celles des destinataires originaux ('CURRENT_USER' équivaut à l'utilisateur connecté)
+        'redirect_to' => array('bertrand.gauthier@unicaen.fr', /*'CURRENT_USER'*/),
+        // désactivation totale de l'envoi de mail par l'application
+        'do_not_send' => false,
+    ),
+</file>
+
+==== Mode maintenance ====
+
+Possibilité de passer l'appli en mode maintenance (indisponibilité totale) dans la config, sauf pour certaines adresses IP.
+
+Clé '''maintenance''' : options concernant le mode maintenance de l'application, exemple :
+
+<file php unicaen-app.local.php>
+        /**
+         * Mode maintenance (application indisponible)
+         */
+        'maintenance' => array(
+            // activation (true: activé, false: désactivé)
+            'enable' => true,
+            // liste blanche des adresses IP clientes non concernées
+            'white_list' => [
+                ['127.0.0.1'],   // localhost
+                ['10.60.11.88'], // Bertrand
+            ],
+        ),
+</file>
+
+<note important>En mode "CLI" (ligne de commande), c'est censé faire un exit(0) mais c'est pas sûr que ça fonctionne. Merci à celui qui en a la possibilité de tester ! </note>
diff --git a/doc/Configuration.md b/doc/Configuration.md
new file mode 100644
index 0000000000000000000000000000000000000000..01b160f3bd56a4f5c62f703732776455e0698e86
--- /dev/null
+++ b/doc/Configuration.md
@@ -0,0 +1,113 @@
+Configuration
+=============
+
+Il s\'agit ici d\'adapter certaines options de configuration du module.
+
+Configuration globale
+---------------------
+
+-   Copier/coller/renommer le fichier
+    `config/unicaen-app.global.php.dist` du module vers
+    `config/autoload/unicaen-app.global.php` de votre projet.
+-   Adapter les options de config à votre contexte (sans modifier les
+    noms des clés)\...
+
+### Fiche d\'identité de l\'appli
+
+Il s\'agit de renseigner nom de l\'appli, description, numéro et date de
+version, adresses et/ou numéros de téléphones de contact, URL des pages
+externes \"mentions légales\" et \"informatique et libertés\", exemple :
+
+``` {.php}
+$settings = array(
+    'app_infos' => array(
+        'nom'     => "GesNum",
+        'desc'    => "Gestion des services numériques",
+        'version' => "1.0.0",
+        'date'    => "05/09/2013",
+        'contact' => array('mail' => "dsi.applications@unicaen.fr", /*'tel' => "01 02 03 04 05"*/),
+        'mentionsLegales'        => "http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/",
+        'informatiqueEtLibertes' => "http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/",
+    ),
+);
+```
+
+Configuration locale
+--------------------
+
+-   Copier/coller/renommer le fichier
+    `config/unicaen-app.local.php.dist` du module vers
+    `config/autoload/unicaen-app.local.php` de votre projet.
+-   Adapter les options de config à votre contexte (sans modifier les
+    noms des clés)\...
+
+### Annuaire LDAP
+
+Clé `'ldap`\' : il faut configurer la connexion à l\'annuaire LDAP via
+un compte omnipotent pour la récupération des coordonnées, affectations
+administratives, rôles, etc. de l\'utilisateur connecté (notamment),
+exemple :
+
+``` {.php}
+    'ldap' => array(
+        'connection' => array(
+            'default' => array(
+                'params' => array(
+                    'host'                => 'localhost',
+                    'port'                => 389,
+                    'username'            => "uid=xxxxxxxxxxx,ou=system,dc=unicaen,dc=fr",
+                    'password'            => "yyyyyyyyyyyy",
+                    'baseDn'              => "ou=people,dc=unicaen,dc=fr",
+                    'bindRequiresDn'      => true,
+                    'accountFilterFormat' => "(&(objectClass=posixAccount)(supannAliasLogin=%s))",
+                )
+            )
+        )
+    ),
+```
+
+### Envoi de mail
+
+Clé `'mail`\' : options concernant l\'envoi de mail par l\'application,
+exemple :
+
+``` {.php}
+    'mail' => array(
+        // transport des mails
+        'transport_options' => array(
+            'host' => 'localhost',
+            'port' => 25,
+        ),
+        // adresses à substituer à celles des destinataires originaux ('CURRENT_USER' équivaut à l'utilisateur connecté)
+        'redirect_to' => array('bertrand.gauthier@unicaen.fr', /*'CURRENT_USER'*/),
+        // désactivation totale de l'envoi de mail par l'application
+        'do_not_send' => false,
+    ),
+```
+
+### Mode maintenance
+
+Possibilité de passer l\'appli en mode maintenance (indisponibilité
+totale) dans la config, sauf pour certaines adresses IP.
+
+Clé `'maintenance`\' : options concernant le mode maintenance de
+l\'application, exemple :
+
+``` {.php}
+        /**
+         * Mode maintenance (application indisponible)
+         */
+        'maintenance' => array(
+            // activation (true: activé, false: désactivé)
+            'enable' => true,
+            // liste blanche des adresses IP clientes non concernées
+            'white_list' => [
+                ['127.0.0.1'],   // localhost
+                ['10.60.11.88'], // Bertrand
+            ],
+        ),
+```
+
+\<note important\>En mode \"CLI\" (ligne de commande), c\'est censé
+faire un exit(0) mais c\'est pas sûr que ça fonctionne. Merci à celui
+qui en a la possibilité de tester ! \</note\>
diff --git a/doc/Controleurs.dokuwiki b/doc/Controleurs.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..0cab77bac002422b148fd49f54c45ecc9322748b
--- /dev/null
+++ b/doc/Controleurs.dokuwiki
@@ -0,0 +1,8 @@
+====== ApplicationController ======
+
+C'est la classe du contrôleur fournissant les actions que l'on retrouve dans toutes nos application :
+  * Affichage de la page "À propos"
+  * Affichage de la page "Contact"
+  * Affichage de la page "Plan de navigation"
+  * Affichage de la page "Mentions légales" (ou redirection)
+  * Affichage de la page "Informatique et libertés" (ou redirection)
diff --git a/doc/Controleurs.md b/doc/Controleurs.md
new file mode 100644
index 0000000000000000000000000000000000000000..54c6315605962985f57a6f785811c6f17c6ef371
--- /dev/null
+++ b/doc/Controleurs.md
@@ -0,0 +1,11 @@
+ApplicationController
+=====================
+
+C\'est la classe du contrôleur fournissant les actions que l\'on
+retrouve dans toutes nos application :
+
+-   Affichage de la page \"À propos\"
+-   Affichage de la page \"Contact\"
+-   Affichage de la page \"Plan de navigation\"
+-   Affichage de la page \"Mentions légales\" (ou redirection)
+-   Affichage de la page \"Informatique et libertés\" (ou redirection)
diff --git a/doc/Divers.dokuwiki b/doc/Divers.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..1fabd1e53e9927243eef535686f5805a41deaf8c
--- /dev/null
+++ b/doc/Divers.dokuwiki
@@ -0,0 +1,153 @@
+====== Divers ======
+
+===== Export CSV =====
+UnicaenApp intègre un ensemble de classes et de méthodes qui permettent de générer facilement des contenus téléchargeables au format CSV.
+
+Exemple d'utilisation :
+<file php DemoController.php>
+public function csvAction()
+{
+        /* Création d'un nouveau modèle de données au format CSV. L'en-tête peut être renseignée dès l'instanciation. */
+        $result = new \UnicaenApp\View\Model\CsvModel( ['header' => ['Nom','Prenom'] ]);
+
+        /* Les données peuvent être transmises par des variables (data, header, separator, filename, delimiter)... */
+        $result->setVariable('data', [
+            ['Lécluse','Laurent'],
+            ['Morin','Paul'],
+            ['Durand','Patrick']
+        ]);
+
+        /* .. ou bien par des accesseurs. Ici on attribue un nom de fichier pour l'export. */
+        $result->setFilename('test_export_csv.csv');
+
+        /* On peut aussi ajouter au besoin des lignes avec la méthode addLine ou des tableaux avec addLines... */
+        $result->addLine( [
+            'Anquetil', 'Pierre'
+        ] );
+
+        return $result;
+}
+</file>
+
+===== Export XML =====
+Il est possible de générer des fichiers XML à la volée et en utilisant le modèle MVC.
+
+Cela se passe de la manière suivante :
+
+Dans votre action de contrôleur :
+
+<file php controler.php>
+
+        $data = ['balise1' => 'contenu1', 'balise2' => 'contenu2'];
+
+        $viewModel = new \UnicaenApp\View\Model\XmlModel();
+        $viewModel->setVariable('data', $data); // on transfère les données à la vue
+        $viewModel->setFileName('mon-fichier-export.xml'); // facultatif
+        return $viewModel;
+</file>
+
+Dans votre vue :
+<file php view.phtml>
+<?php
+/**
+ * @var $data array
+ */
+
+echo "<body>\n";
+foreach( $data as $balise => $valeur ){
+    echo "\t".$this->tag($balise)->text($valeur)."\n";
+}
+echo '</body>';
+</file>
+
+<WRAP center round tip 60%>
+Dans cette vue, l'[[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:tag|Aide de vue Tag]] est utilisée ici pour générer des tags. Ceci évite de gérer explicitement les caractères foireux (<, >, &).
+</WRAP>
+
+
+Résultat :
+<file xml mon-fichier-export.xml>
+<?xml version="1.0" encoding="utf-8"?>
+<body>
+    <balise1>contenu1</balise1>
+    <balise2>contenu2</balise2>
+</body>
+</file>
+===== Export PDF =====
+La classe ''\UnicaenApp\Exporter\Pdf'' facilite la fabrication d'un document PDF et voici ses arguments principaux :
+  * Nourrissage à partir de marquage HTML, spécifié directement sous forme de chaînes de caractères ou bien par l'intermédiaire d'un script de vue.
+  * Format A4, A3, etc., portrait ou paysage.
+  * En-tête contenant le logo de l'UCBN (spécifié en dur, désolé!), un titre éventuel et un sous-titre éventuel. Possibilité de différencier en-tête des pages paires et en-tête des pages impaires.
+  * Pied de page contenant l'heure de génération du document, le numéro de page, le nombre total de pages et un titre éventuel. Possibilité de différencier pied des pages paires et pied des pages impaires.
+  * Un texte en filigrane éventuel (watermark).
+  * Spécification possible des opérations autorisées sur le document généré et le mot de passe éventuel permettant d'ouvrir le document.
+
+<note important>C'est la bibliothèque mPDF (http://www.mpdf1.com/mpdf/) qui motorise la génération PDF donc vous devez ajouter un truc comme ça dans les "require" de votre ''composer.json'' :
+<file php composer.json>"mpdf/mpdf": "v5.7.1"</file></note>
+
+Exemple d'utilisation :
+<file php DemoController.php>
+public function pdfAction()
+{
+    $renderer = $this->getServiceLocator()->get('view_renderer'); /* @var $renderer \Zend\View\Renderer\PhpRenderer */
+    $exporter = new \UnicaenApp\Exporter\Pdf($renderer, 'A4', true);
+    $exporter->addBodyHtml('<h1>Module UnicaenApp</h1>');
+    $exporter->addBodyHtml('<h2>Export PDF</h2>', false);
+    $exporter->addBodyScript('application/demo/paragraphe.phtml', false);
+    $exporter->addBodyScript('application/demo/nouvelle-page.phtml', true);
+    $exporter->export('export.pdf');
+}
+</file>
+
+===== Util =====
+La classe ''\UnicaenApp\Util'' regroupe quelques méthodes statiques utiles.
+
+==== generateStringTimestamp() ====
+Génère une chaine de caractères corespondant à la date spécifiée (ou l'instant présent) au format "aaaammjj_hhmmss", pouvant être utilisée dans un nom de fichier par exemple.
+Utilisation :
+<code php>
+$ts = \UnicaenApp\Util::generateStringTimestamp();
+var_dump($ts);
+// affiche '20121130_104056'
+</code>
+
+==== topChrono() ====
+Permet de chronométrer le temps écoulé à plusieurs endroits de votre code.
+Affiche le temps écoulé depuis le dernier top du chronomètre.
+Utilisation :
+<code php>
+\UnicaenApp\Util::topChrono();      // affiche 'chrono: 0'
+for ($i = 0; $i < 10000000; $i++) { }
+\UnicaenApp\Util::topChrono("Ici"); // affiche 'Ici: 1.2799' (par exemple)
+for ($i = 0; $i < 10000000; $i++) { }
+\UnicaenApp\Util::topChrono("Là");  // affiche 'Là: 1.2841' (par exemple)
+</code>
+
+==== zip() ====
+Compresse un fichier ou répertoire, en utilisant la classe "\ZipArchive" (l'extension PHP "php5_zip" est donc requise sur le serveur).
+Utilisation :
+<code php>
+\UnicaenApp\Util::zip($sourceDir, $zipfilePath);
+</code>
+
+==== removeFile() ====
+Supprime un fichier ou un répertoire.
+Utilisation :
+<code php>
+\UnicaenApp\Util::removeFile($fileOrDirectoryPath);
+</code>
+
+==== formattedFloat() ====
+Formatte un nombre flottant pour l'affichage.
+Utilisation :
+<code php>
+$value = 1200.61;
+echo \UnicaenApp\Util::formattedFloat($value, \NumberFormatter::DECIMAL, 1);
+// résultat: "1 200,6"
+echo \UnicaenApp\Util::formattedFloat($value, \NumberFormatter::CURRENCY);
+// résultat: "1 200,61 €"
+
+$value = 1247.0;
+echo \UnicaenApp\Util::formattedFloat($value, \NumberFormatter::DECIMAL, -1);
+// résultat: "1 247"
+</code>
diff --git a/doc/Divers.md b/doc/Divers.md
new file mode 100644
index 0000000000000000000000000000000000000000..4f281454e523c307f12ca5e8a341ea31e451f748
--- /dev/null
+++ b/doc/Divers.md
@@ -0,0 +1,197 @@
+Divers
+======
+
+Export CSV
+----------
+
+UnicaenApp intègre un ensemble de classes et de méthodes qui permettent
+de générer facilement des contenus téléchargeables au format CSV.
+
+Exemple d\'utilisation :
+
+``` {.php}
+public function csvAction()
+{
+        /* Création d'un nouveau modèle de données au format CSV. L'en-tête peut être renseignée dès l'instanciation. */
+        $result = new \UnicaenApp\View\Model\CsvModel( ['header' => ['Nom','Prenom'] ]);
+
+        /* Les données peuvent être transmises par des variables (data, header, separator, filename, delimiter)... */
+        $result->setVariable('data', [
+            ['Lécluse','Laurent'],
+            ['Morin','Paul'],
+            ['Durand','Patrick']
+        ]);
+
+        /* .. ou bien par des accesseurs. Ici on attribue un nom de fichier pour l'export. */
+        $result->setFilename('test_export_csv.csv');
+
+        /* On peut aussi ajouter au besoin des lignes avec la méthode addLine ou des tableaux avec addLines... */
+        $result->addLine( [
+            'Anquetil', 'Pierre'
+        ] );
+
+        return $result;
+}
+```
+
+Export XML
+----------
+
+Il est possible de générer des fichiers XML à la volée et en utilisant
+le modèle MVC.
+
+Cela se passe de la manière suivante :
+
+Dans votre action de contrôleur :
+
+``` {.php}
+
+        $data = ['balise1' => 'contenu1', 'balise2' => 'contenu2'];
+
+        $viewModel = new \UnicaenApp\View\Model\XmlModel();
+        $viewModel->setVariable('data', $data); // on transfère les données à la vue
+        $viewModel->setFileName('mon-fichier-export.xml'); // facultatif
+        return $viewModel;
+```
+
+Dans votre vue :
+
+``` {.php}
+<?php
+/**
+ * @var $data array
+ */
+
+echo "<body>\n";
+foreach( $data as $balise => $valeur ){
+    echo "\t".$this->tag($balise)->text($valeur)."\n";
+}
+echo '</body>';
+```
+
+\<WRAP center round tip 60%\> Dans cette vue, l\'[Aide de vue
+Tag](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/tag) est
+utilisée ici pour générer des tags. Ceci évite de gérer explicitement
+les caractères foireux (\<, \>, &). \</WRAP\>
+
+Résultat :
+
+``` {.xml}
+<?xml version="1.0" encoding="utf-8"?>
+<body>
+    <balise1>contenu1</balise1>
+    <balise2>contenu2</balise2>
+</body>
+```
+
+Export PDF
+----------
+
+La classe `\UnicaenApp\Exporter\Pdf` facilite la fabrication d\'un
+document PDF et voici ses arguments principaux :
+
+-   Nourrissage à partir de marquage HTML, spécifié directement sous
+    forme de chaînes de caractères ou bien par l\'intermédiaire d\'un
+    script de vue.
+-   Format A4, A3, etc., portrait ou paysage.
+-   En-tête contenant le logo de l\'UCBN (spécifié en dur, désolé!), un
+    titre éventuel et un sous-titre éventuel. Possibilité de
+    différencier en-tête des pages paires et en-tête des pages impaires.
+-   Pied de page contenant l\'heure de génération du document, le numéro
+    de page, le nombre total de pages et un titre éventuel. Possibilité
+    de différencier pied des pages paires et pied des pages impaires.
+-   Un texte en filigrane éventuel (watermark).
+-   Spécification possible des opérations autorisées sur le document
+    généré et le mot de passe éventuel permettant d\'ouvrir le document.
+
+\<note important\>C\'est la bibliothèque mPDF
+(<http://www.mpdf1.com/mpdf/>) qui motorise la génération PDF donc vous
+devez ajouter un truc comme ça dans les \"require\" de votre
+`composer.json` :
+
+``` {.php}
+"mpdf/mpdf": "v5.7.1"
+```
+
+\</note\>
+
+Exemple d\'utilisation :
+
+``` {.php}
+public function pdfAction()
+{
+    $renderer = $this->getServiceLocator()->get('view_renderer'); /* @var $renderer \Zend\View\Renderer\PhpRenderer */
+    $exporter = new \UnicaenApp\Exporter\Pdf($renderer, 'A4', true);
+    $exporter->addBodyHtml('<h1>Module UnicaenApp</h1>');
+    $exporter->addBodyHtml('<h2>Export PDF</h2>', false);
+    $exporter->addBodyScript('application/demo/paragraphe.phtml', false);
+    $exporter->addBodyScript('application/demo/nouvelle-page.phtml', true);
+    $exporter->export('export.pdf');
+}
+```
+
+Util
+----
+
+La classe `\UnicaenApp\Util` regroupe quelques méthodes statiques
+utiles.
+
+### generateStringTimestamp()
+
+Génère une chaine de caractères corespondant à la date spécifiée (ou
+l\'instant présent) au format \"aaaammjj\_hhmmss\", pouvant être
+utilisée dans un nom de fichier par exemple. Utilisation :
+
+``` {.php}
+$ts = \UnicaenApp\Util::generateStringTimestamp();
+var_dump($ts);
+// affiche '20121130_104056'
+```
+
+### topChrono()
+
+Permet de chronométrer le temps écoulé à plusieurs endroits de votre
+code. Affiche le temps écoulé depuis le dernier top du chronomètre.
+Utilisation :
+
+``` {.php}
+\UnicaenApp\Util::topChrono();      // affiche 'chrono: 0'
+for ($i = 0; $i < 10000000; $i++) { }
+\UnicaenApp\Util::topChrono("Ici"); // affiche 'Ici: 1.2799' (par exemple)
+for ($i = 0; $i < 10000000; $i++) { }
+\UnicaenApp\Util::topChrono("Là");  // affiche 'Là: 1.2841' (par exemple)
+```
+
+### zip()
+
+Compresse un fichier ou répertoire, en utilisant la classe
+\"\\ZipArchive\" (l\'extension PHP \"php5\_zip\" est donc requise sur le
+serveur). Utilisation :
+
+``` {.php}
+\UnicaenApp\Util::zip($sourceDir, $zipfilePath);
+```
+
+### removeFile()
+
+Supprime un fichier ou un répertoire. Utilisation :
+
+``` {.php}
+\UnicaenApp\Util::removeFile($fileOrDirectoryPath);
+```
+
+### formattedFloat()
+
+Formatte un nombre flottant pour l\'affichage. Utilisation :
+
+``` {.php}
+$value = 1200.61;
+echo \UnicaenApp\Util::formattedFloat($value, \NumberFormatter::DECIMAL, 1);
+// résultat: "1 200,6"
+echo \UnicaenApp\Util::formattedFloat($value, \NumberFormatter::CURRENCY);
+// résultat: "1 200,61 €"
+
+$value = 1247.0;
+echo \UnicaenApp\Util::formattedFloat($value, \NumberFormatter::DECIMAL, -1);
+// résultat: "1 247"
+```
diff --git a/doc/ElementsPageRiches.dokuwiki b/doc/ElementsPageRiches.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..51b89c28305581cf4c30e339cca0aaada9dab04e
--- /dev/null
+++ b/doc/ElementsPageRiches.dokuwiki
@@ -0,0 +1,110 @@
+====== Création d'éléments d'interface "riches" mélangeant HTML et Javascript ======
+Il est parfois nécessaire d'adjoindre à des éléments d'interface en HTML des traitements en Javascript.
+
+Il est préférable de concentrer le code Javascript au sein de widgets Jquery ([[https://jqueryui.com/widget/]]) enregistrées dans des fichiers au format <php>.js</php> dédiés.
+
+  * Ainsi la logique objet est préservée jusqu'au niveau de l'interface, avec tous les avantages en terme de lisibilité du sode, réutilisabilité, etc.
+
+  * Ensuite ce code n'est chargé qu'une fois par le navigateur, même si ce dernier est amené à être exploité à plusieurs endroits de la page.
+
+  * De plus, ce fichier JS peut être mis en cache par le navigateur, d'où une économie de bande passante et une rapidité accrue.
+
+  * Ensuite, le code javascript d'une application peut être "minifié", c'est-à-dire que les caractères superflus sont éliminés pour que le fichier soit encore plus rapide à charger et à analyser par le navigateur.
+
+  * Enfin, pour un développeur, il est plus simple de déboguer du code Javascript dans un fichier .js que directement au sein d'une page Web à l'aide des outils de développement livrés avec la plupart des navigateurs modernes (Firfox, Chrome, etc).
+
+Par ailleurs, ces éléments d'interface doivent être codés de telle manière qu'ils doivent fonctionner parfaitement même si plusieurs instances sont affichés dans une même page. Autrement dit, le codage de l'élément doit être fait de telle manière que la structure de la page web sous-jacente ne doit avoir aucun effet indésirable.
+
+En voici sans plus tarder un exemple de Widget :
+
+  * Dans ce fichier HTML, que du HTML, pas de bouts de Javascript!!
+  * La classe fenetre-confirmation permet de savoir que cet élément est bien une fenêtre de confirmation et donc que ceci doit être un Widget fenetreConfirmation.
+  * L'ID est indiqué pour pouvoir la sélectionner ensuite mais il n'est pas obligatoire de l'ajouter.
+
+<file html>
+<div class="fenetre-confirmation" id="fen1">
+    <p id="message">Ceci est un texte. L'avez-vous lu ?</p>
+    <button id="oui">OUI</button>
+    <button id="non">NON</button>
+</div>
+</file>
+
+Et voici le code du widget en Javascript :
+<file javascript>
+$.widget("unicaen.fenetreConfirmation", {
+
+    message: function (message)
+    {
+        if (message === undefined) {
+            return this.getMessageP().html();
+        } else {
+            this.getMessageP().html(message);
+        }
+    },
+
+    repondreOui: function ()
+    {
+        alert('Parfait, merci!');
+        this.fermer();
+    },
+
+    repondreNon: function ()
+    {
+        alert('Dommage...');
+    },
+
+    fermer: function ()
+    {
+        this.element.hide();
+    },
+
+    _create: function ()
+    {
+        var that = this;
+
+        // On initialise les callbacks
+        this.getBtnOui().on('click', function () { that.repondreOui(); });
+        this.getBtnNon().on('click', function () { that.repondreNon(); });
+    },
+
+    // c'est pas obligatoire mais c'est plus propre de définir des accesseurs pour les éléments du widget...
+    getMessageP: function () { return this.element.find("p#message"); },
+    getBtnOui: function () { return this.element.find("button#oui"); },
+    getBtnNon: function () { return this.element.find("button#non"); }
+
+});
+
+$(function ()
+{
+    WidgetInitializer.add('fenetre-confirmation', 'fenetreConfirmation');
+});
+</file>
+
+Grâce au [[develop:unicaen2:ModuleUnicaenUnicaenApp:js|WidgetInitializer]], il n'y a rien d'autre à faire : le click sur "Oui" va fermer la fenêtre de confirmation correspondante.
+Il est vrai que c'est plus verbeux que la méthode "tout intégré".\\
+Mais :
+  * c'est plus lisible car tout est structuré ;
+  * c'est aussi plus maintenable
+  * c'est également plus évolutif ;
+  * ce type d'architecture sera aussi valable pour des objets bien plus complexes ;
+  * c'est également plus puissant puisqu'on peut piloter la fenêtre de confirmation à l'aide d'appels comme suivent :
+<file javascript>
+
+// affiche le message de la fenêtre
+alert( $("#fen1").fenetreConfirmation("message") );
+
+// Change le message de la fenêtre
+$("#fen1").fenetreConfirmation("message", "Coucou c'est moi!!");
+
+// Déclenche la fermeture de la fenêtre
+$("#fen1").fenetreConfirmation("fermer");
+</file>
+
+<WRAP center round info 60%>
+Pour aller plus loin, vous pouvez aller consulter l'API de JQuery Widget
+[[http://api.jqueryui.com/jQuery.widget/]]
+
+Vous avez également de la documentation sur le sujet :
+[[https://learn.jquery.com/jquery-ui/widget-factory/]]
+
+</WRAP>
diff --git a/doc/ElementsPageRiches.md b/doc/ElementsPageRiches.md
new file mode 100644
index 0000000000000000000000000000000000000000..6ce802114fee470ccd1ac83547d38cad9be5f132
--- /dev/null
+++ b/doc/ElementsPageRiches.md
@@ -0,0 +1,149 @@
+Création d\'éléments d\'interface \"riches\" mélangeant HTML et Javascript
+==========================================================================
+
+Il est parfois nécessaire d\'adjoindre à des éléments d\'interface en
+HTML des traitements en Javascript.
+
+Il est préférable de concentrer le code Javascript au sein de widgets
+Jquery (<https://jqueryui.com/widget/>) enregistrées dans des fichiers
+au format `.js`{.php} dédiés.
+
+-   Ainsi la logique objet est préservée jusqu\'au niveau de
+    l\'interface, avec tous les avantages en terme de lisibilité du
+    sode, réutilisabilité, etc.
+
+<!-- -->
+
+-   Ensuite ce code n\'est chargé qu\'une fois par le navigateur, même
+    si ce dernier est amené à être exploité à plusieurs endroits de la
+    page.
+
+<!-- -->
+
+-   De plus, ce fichier JS peut être mis en cache par le navigateur,
+    d\'où une économie de bande passante et une rapidité accrue.
+
+<!-- -->
+
+-   Ensuite, le code javascript d\'une application peut être
+    \"minifié\", c\'est-à-dire que les caractères superflus sont
+    éliminés pour que le fichier soit encore plus rapide à charger et à
+    analyser par le navigateur.
+
+<!-- -->
+
+-   Enfin, pour un développeur, il est plus simple de déboguer du code
+    Javascript dans un fichier .js que directement au sein d\'une page
+    Web à l\'aide des outils de développement livrés avec la plupart des
+    navigateurs modernes (Firfox, Chrome, etc).
+
+Par ailleurs, ces éléments d\'interface doivent être codés de telle
+manière qu\'ils doivent fonctionner parfaitement même si plusieurs
+instances sont affichés dans une même page. Autrement dit, le codage de
+l\'élément doit être fait de telle manière que la structure de la page
+web sous-jacente ne doit avoir aucun effet indésirable.
+
+En voici sans plus tarder un exemple de Widget :
+
+-   Dans ce fichier HTML, que du HTML, pas de bouts de Javascript!!
+-   La classe fenetre-confirmation permet de savoir que cet élément est
+    bien une fenêtre de confirmation et donc que ceci doit être un
+    Widget fenetreConfirmation.
+-   L\'ID est indiqué pour pouvoir la sélectionner ensuite mais il
+    n\'est pas obligatoire de l\'ajouter.
+
+``` {.html}
+<div class="fenetre-confirmation" id="fen1">
+    <p id="message">Ceci est un texte. L'avez-vous lu ?</p>
+    <button id="oui">OUI</button>
+    <button id="non">NON</button>
+</div>
+```
+
+Et voici le code du widget en Javascript :
+
+``` {.javascript}
+$.widget("unicaen.fenetreConfirmation", {
+
+    message: function (message)
+    {
+        if (message === undefined) {
+            return this.getMessageP().html();
+        } else {
+            this.getMessageP().html(message);
+        }
+    },
+
+    repondreOui: function ()
+    {
+        alert('Parfait, merci!');
+        this.fermer();
+    },
+
+    repondreNon: function ()
+    {
+        alert('Dommage...');
+    },
+
+    fermer: function ()
+    {
+        this.element.hide();
+    },
+
+    _create: function ()
+    {
+        var that = this;
+
+        // On initialise les callbacks
+        this.getBtnOui().on('click', function () { that.repondreOui(); });
+        this.getBtnNon().on('click', function () { that.repondreNon(); });
+    },
+
+    // c'est pas obligatoire mais c'est plus propre de définir des accesseurs pour les éléments du widget...
+    getMessageP: function () { return this.element.find("p#message"); },
+    getBtnOui: function () { return this.element.find("button#oui"); },
+    getBtnNon: function () { return this.element.find("button#non"); }
+
+});
+
+$(function ()
+{
+    WidgetInitializer.add('fenetre-confirmation', 'fenetreConfirmation');
+});
+```
+
+Grâce au
+[WidgetInitializer](/develop/unicaen2/ModuleUnicaenUnicaenApp/js), il
+n\'y a rien d\'autre à faire : le click sur \"Oui\" va fermer la fenêtre
+de confirmation correspondante. Il est vrai que c\'est plus verbeux que
+la méthode \"tout intégré\".\
+Mais :
+
+-   c\'est plus lisible car tout est structuré ;
+-   c\'est aussi plus maintenable
+-   c\'est également plus évolutif ;
+-   ce type d\'architecture sera aussi valable pour des objets bien plus
+    complexes ;
+-   c\'est également plus puissant puisqu\'on peut piloter la fenêtre de
+    confirmation à l\'aide d\'appels comme suivent :
+
+``` {.javascript}
+
+// affiche le message de la fenêtre
+alert( $("#fen1").fenetreConfirmation("message") );
+
+// Change le message de la fenêtre
+$("#fen1").fenetreConfirmation("message", "Coucou c'est moi!!");
+
+// Déclenche la fermeture de la fenêtre
+$("#fen1").fenetreConfirmation("fermer");
+```
+
+\<WRAP center round info 60%\> Pour aller plus loin, vous pouvez aller
+consulter l\'API de JQuery Widget
+<http://api.jqueryui.com/jQuery.widget/>
+
+Vous avez également de la documentation sur le sujet :
+<https://learn.jquery.com/jquery-ui/widget-factory/>
+
+\</WRAP\>
diff --git a/doc/Filtres.dokuwiki b/doc/Filtres.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..0acecc18d4646c4073ae9c00f4b61759dc61550d
--- /dev/null
+++ b/doc/Filtres.dokuwiki
@@ -0,0 +1,19 @@
+====== Filtres ======
+
+===== BytesFormatter =====
+
+Convertit et formatte un nombre d'octets en ko, Mo, Go ou To.
+
+Exemples :
+<code php>
+$f = new \UnicaenApp\Filter\BytesFormatter();
+$result = $f->filter(765);   // retourne "765 o"
+$result = $f->filter(1024);  // retourne "1 ko"
+$result = $f->filter(79057); // retourne "77,2 ko"
+</code>
+
+Possibilité de spécifier le nombre voulu de chiffres après la virgule (qui est 1 par défaut) :
+
+<code php>
+$f->setPrecision(2);
+</code>
diff --git a/doc/Filtres.md b/doc/Filtres.md
new file mode 100644
index 0000000000000000000000000000000000000000..8b0e90822d6849834b402d5147e8eafd7219d455
--- /dev/null
+++ b/doc/Filtres.md
@@ -0,0 +1,23 @@
+Filtres
+=======
+
+BytesFormatter
+--------------
+
+Convertit et formatte un nombre d\'octets en ko, Mo, Go ou To.
+
+Exemples :
+
+``` {.php}
+$f = new \UnicaenApp\Filter\BytesFormatter();
+$result = $f->filter(765);   // retourne "765 o"
+$result = $f->filter(1024);  // retourne "1 ko"
+$result = $f->filter(79057); // retourne "77,2 ko"
+```
+
+Possibilité de spécifier le nombre voulu de chiffres après la virgule
+(qui est 1 par défaut) :
+
+``` {.php}
+$f->setPrecision(2);
+```
diff --git a/doc/Formulaires.dokuwiki b/doc/Formulaires.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..8a4b9426db792fdd7d5066fc5d5f463ace890b59
--- /dev/null
+++ b/doc/Formulaires.dokuwiki
@@ -0,0 +1,191 @@
+====== Formulaires ======
+
+===== Formulaires =====
+
+====== MultipageForm ======
+
+Classe mère des formulaire multi-pages (saisie en plusieurs étapes).
+
+Cf. documentation du plugin de contrôleur [[develop:unicaen2:moduleunicaenunicaenapp:controllerplugins#multipageform|MultipageForm]].
+
+===== Éléments de formulaire =====
+
+====== DateInfSup ======
+
+Elément de formulaire permettant de choisir une date inférieure (date de début) et une date supérieure éventuelle (date de fin).
+
+Le filtre d'entrée (input filter) "[[develop:unicaen2:moduleunicaenunicaenapp:form:dateinfsupinputfilter|DateInfSupInputFilter]]" permet de valider la saisie de cet élément.
+
+Exemple :
+
+<code php>
+class CaractEvtFieldset extends \Zend\Form\Fieldset implements \Zend\InputFilter\InputFilterProviderInterface
+{
+    const DATETIME_FORMAT_PHP   = 'd-m-Y H:i';
+    const DATETIME_FORMAT_HUMAN = 'jj-mm-aaaa hh:mm';
+
+    public function __construct($name = null, $options = array())
+    {
+        parent::__construct($name, $options);
+
+        $this->setLabel("Caractéristiques de l'événement")
+             ->add(new \UnicaenApp\Form\Element\DateInfSup('dates', array('label' => "Début et fin éventuelle")))
+             ->add(new \Zend\Form\Element\Text('duree_prevue', array('label' => _("Durée prévue"))))
+             ->add(new \Zend\Form\Element\Textarea('serv_affectes', array('label' => _("Services numériques affectés principalement"))))
+             ->add(new \Zend\Form\Element\Textarea('actions', array('label' => _("Moyens et actions mis en oeuvre"))));
+    }
+
+    public function getInputFilterSpecification()
+    {
+        $dateInfSupInputFilter = new \UnicaenApp\Form\Element\DateInfSupInputFilter();
+        $dateInfSupInputFilter->setDateInfRequired()
+                              ->setDateSupRequired();
+
+        return array(
+            'dates' => $dateInfSupInputFilter,
+            'serv_affectes' => array(
+                'required' => true,
+                'validators' => array(
+                    array(
+                        'name'=> 'NotEmpty',
+                        'break_chain_on_failure' => true,
+                        'options' => array(
+                            'messages' => array('isEmpty' => _("Ce champ est obligatoire")),
+                        )
+                    ),
+                ),
+            ),
+            'actions' => array(
+                'required' => true,
+                'validators' => array(
+                    array(
+                        'name'=> 'NotEmpty',
+                        'break_chain_on_failure' => true,
+                        'options' => array(
+                            'messages' => array('isEmpty' => _("Ce champ est obligatoire")),
+                        )
+                    ),
+                ),
+            ),
+        );
+    }
+}
+</code>
+
+<note>
+L'aide de vue (view helper) qui est utilisée pour générer le code HTML complet de cet élément (labels, champs et erreurs) est "[[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:FormRowDateInfSup|FormRowDateInfSup]]" (qui elle-même fait appel à l'aide de vue "[[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:FormDateInfSup|FormDateInfSup]]").
+</note>
+
+====== MultipageFormNav ======
+
+Élément composite de navigation au sein d'un formulaire multipage (formulaire en plusieurs étapes) [[develop:unicaen2:moduleunicaenunicaenapp:form:MultipageForm|MultipageForm]].
+
+Les boutons présents dépendent du contexte (de l'étape courante) et du paramétrage :
+  * "Précédent"
+  * "Suivant"
+  * "Terminer"
+  * "Confirmer et enregistrer"
+  * "Annuler"
+
+Cette aide de vue n'a pas vraiment vocation à être utilisée directement : elle est utilisée en interne par l'aide de vue [[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:multipageformrow|MultipageFormRow]].
+
+====== SearchAndSelect ======
+
+Élément de formulaire permettant de rechercher puis sélectionner quelque chose dans une source de données.
+
+Cet élément composite encapsule en fait 2 éléments :
+  * ''label'' : texte saisi dans un champ texte ;
+  * ''id'' : identifiant unique de la chose //sélectionnée// (ex: le persopass/etupass pour un individu recherché dans l'annuaire LDAP) dans un champ caché.
+
+NB: Il faut utiliser l'aide de vue [[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:formsearchandselect|FormSearchAndSelect]] pour dessiner cet élément.
+
+
+Lorsqu'un texte est saisi, une requête GET Ajax est exécutée et à charge au développeur de réaliser la recherche dans l'action correspondante. L'URL de l'action réalisant la recherche doit être spécifiée comme suit :
+<file php>
+$sas = new SearchAndSelect('sas');
+$sas->setAutocompleteSource('/ose/demo/rechercher');
+</file>
+
+La requête AJAX inclut le paramètre GET ''term'' ayant la valeur du texte à rechercher. L'action doit répondre à la requête AJAX un tableau JSON au format précis.
+Exemple :
+<file php DemoController.php>
+public function rechercherAction()
+{
+    if (($term = $this->params()->fromQuery('term'))) {
+        // réponse bidon
+        $result = array();
+        for ($index = 0; $index < 15; $index++) {
+            // mise en forme attendue par l'aide de vue FormSearchAndSelect
+            $result[] = array(
+                'id'    => $index,        // identifiant unique de l'item
+                'label' => uniqid($term), // libellé de l'item
+                'extra' => "Bla bla",     // infos complémentaires (facultatives) sur l'item
+            );
+        }
+        return new \Zend\View\Model\JsonModel($result);
+    }
+    exit;
+}
+</file>
+
+====== AdvancedMultiCheckBox ======
+===== Description =====
+
+Ceci est une aide de vue pour élément de formulaire.
+Il accepte les éléments de type <php>Zend\Form\Element\MultiCheckbox</php>.
+
+  * Il ajoute deux boutons au multicheckbox habituel : un pour tout sélectionner et un pour ne rien sélectionner.
+  * En outre, il encapsule les items dans une liste dont la hauteur est définissable au besoin. Ainsi pas de risque d'avoir une liste interminable à l'écran.
+
+<WRAP center round info 60%>
+Ce composant est codé sous forme de Widget JQuery ([[https://jqueryui.com/widget/]]).\\
+\\
+De plus, il exploite le [[develop:unicaen2:ModuleUnicaenUnicaenApp:js|WidgetInitializer]] donc inutile de l'initialiser, ça se fait automatiquement.
+</WRAP>
+
+===== Exemple =====
+
+Dans la vue, il s'exploite comme suit :
+
+<code php>
+$this->formControlGroup(
+    $form->get('votre-multicheckbox'),
+    $this->formAdvancedMultiCheckbox()->setHeight('20em')
+);
+</code>
+Là, la hauteur a été définie directement depuis le code PHP.
+
+Mais il est également possible de définir la hauteur de votre AdvancedMultiCheckBox en Javascript.
+
+Exemples d'utilisation :
+<code javascript>
+
+// Ici la hauteur est limitée à 20em
+$("votre_selecteur_de_div").formAdvancedMultiCheckbox("height", "20em");
+
+// Là on sélectionne tous les items
+$("votre_selecteur_de_div").formAdvancedMultiCheckbox("selectAll");
+
+</code>
+
+===== Liste des propriétés =====
+  * <php>height</php> : définit la hauteur maximale de votre widget
+  * <php>overflow</php> : gère l'overflow du widget
+
+Ces propriétés sont également utilisables directement en PHP via le ViewHelper.
+
+===== Liste des méthodes =====
+  * <php>selectAll</php> : sélectionne tous les items de la liste
+  * <php>selectNone</php> : désélectionne tous les items de la liste
+  * <php>getSelectAllBtn</php> : retourne le bouton qui permet de tout sélectionner
+  * <php>getSelectNoneBtn</php> : retorune le bouton qui permet de tout désélectionner
+
+
+
+===== Filtres d'entrée (input filters) =====
+
+====== DateInfSupInputFilter ======
+
+Filtre d'entrée associé à l'élément de formulaire "DateInfSup" à la saisie d'une date inférieure (date de début) et une date supérieure éventuelle (date de fin).
+
+Cf. exemple sur la page consacrée à l'élément de formulaire "[[develop:unicaen2:moduleunicaenunicaenapp:form:dateinfsup|DateInfSup]]".
\ No newline at end of file
diff --git a/doc/Formulaires.md b/doc/Formulaires.md
new file mode 100644
index 0000000000000000000000000000000000000000..208d1d6a331953ced2b8de28c81667879289b3ec
--- /dev/null
+++ b/doc/Formulaires.md
@@ -0,0 +1,243 @@
+Formulaires
+===========
+
+Formulaires
+-----------
+
+MultipageForm
+=============
+
+Classe mère des formulaire multi-pages (saisie en plusieurs étapes).
+
+Cf. documentation du plugin de contrôleur
+[MultipageForm](/develop/unicaen2/moduleunicaenunicaenapp/controllerplugins#multipageform).
+
+Éléments de formulaire
+----------------------
+
+DateInfSup
+==========
+
+Elément de formulaire permettant de choisir une date inférieure (date de
+début) et une date supérieure éventuelle (date de fin).
+
+Le filtre d\'entrée (input filter)
+\"[DateInfSupInputFilter](/develop/unicaen2/moduleunicaenunicaenapp/form/dateinfsupinputfilter)\"
+permet de valider la saisie de cet élément.
+
+Exemple :
+
+``` {.php}
+class CaractEvtFieldset extends \Zend\Form\Fieldset implements \Zend\InputFilter\InputFilterProviderInterface
+{
+    const DATETIME_FORMAT_PHP   = 'd-m-Y H:i';
+    const DATETIME_FORMAT_HUMAN = 'jj-mm-aaaa hh:mm';
+
+    public function __construct($name = null, $options = array())
+    {
+        parent::__construct($name, $options);
+
+        $this->setLabel("Caractéristiques de l'événement")
+             ->add(new \UnicaenApp\Form\Element\DateInfSup('dates', array('label' => "Début et fin éventuelle")))
+             ->add(new \Zend\Form\Element\Text('duree_prevue', array('label' => _("Durée prévue"))))
+             ->add(new \Zend\Form\Element\Textarea('serv_affectes', array('label' => _("Services numériques affectés principalement"))))
+             ->add(new \Zend\Form\Element\Textarea('actions', array('label' => _("Moyens et actions mis en oeuvre"))));
+    }
+
+    public function getInputFilterSpecification()
+    {
+        $dateInfSupInputFilter = new \UnicaenApp\Form\Element\DateInfSupInputFilter();
+        $dateInfSupInputFilter->setDateInfRequired()
+                              ->setDateSupRequired();
+
+        return array(
+            'dates' => $dateInfSupInputFilter,
+            'serv_affectes' => array(
+                'required' => true,
+                'validators' => array(
+                    array(
+                        'name'=> 'NotEmpty',
+                        'break_chain_on_failure' => true,
+                        'options' => array(
+                            'messages' => array('isEmpty' => _("Ce champ est obligatoire")),
+                        )
+                    ),
+                ),
+            ),
+            'actions' => array(
+                'required' => true,
+                'validators' => array(
+                    array(
+                        'name'=> 'NotEmpty',
+                        'break_chain_on_failure' => true,
+                        'options' => array(
+                            'messages' => array('isEmpty' => _("Ce champ est obligatoire")),
+                        )
+                    ),
+                ),
+            ),
+        );
+    }
+}
+```
+
+\<note\> L\'aide de vue (view helper) qui est utilisée pour générer le
+code HTML complet de cet élément (labels, champs et erreurs) est
+\"[FormRowDateInfSup](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/FormRowDateInfSup)\"
+(qui elle-même fait appel à l\'aide de vue
+\"[FormDateInfSup](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/FormDateInfSup)\").
+\</note\>
+
+MultipageFormNav
+================
+
+Élément composite de navigation au sein d\'un formulaire multipage
+(formulaire en plusieurs étapes)
+[MultipageForm](/develop/unicaen2/moduleunicaenunicaenapp/form/MultipageForm).
+
+Les boutons présents dépendent du contexte (de l\'étape courante) et du
+paramétrage :
+
+-   \"Précédent\"
+-   \"Suivant\"
+-   \"Terminer\"
+-   \"Confirmer et enregistrer\"
+-   \"Annuler\"
+
+Cette aide de vue n\'a pas vraiment vocation à être utilisée directement
+: elle est utilisée en interne par l\'aide de vue
+[MultipageFormRow](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/multipageformrow).
+
+SearchAndSelect
+===============
+
+Élément de formulaire permettant de rechercher puis sélectionner quelque
+chose dans une source de données.
+
+Cet élément composite encapsule en fait 2 éléments :
+
+-   `label` : texte saisi dans un champ texte ;
+-   `id` : identifiant unique de la chose *sélectionnée* (ex: le
+    persopass/etupass pour un individu recherché dans l\'annuaire LDAP)
+    dans un champ caché.
+
+NB: Il faut utiliser l\'aide de vue
+[FormSearchAndSelect](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/formsearchandselect)
+pour dessiner cet élément.
+
+Lorsqu\'un texte est saisi, une requête GET Ajax est exécutée et à
+charge au développeur de réaliser la recherche dans l\'action
+correspondante. L\'URL de l\'action réalisant la recherche doit être
+spécifiée comme suit :
+
+``` {.php}
+$sas = new SearchAndSelect('sas');
+$sas->setAutocompleteSource('/ose/demo/rechercher');
+```
+
+La requête AJAX inclut le paramètre GET `term` ayant la valeur du texte
+à rechercher. L\'action doit répondre à la requête AJAX un tableau JSON
+au format précis. Exemple :
+
+``` {.php}
+public function rechercherAction()
+{
+    if (($term = $this->params()->fromQuery('term'))) {
+        // réponse bidon
+        $result = array();
+        for ($index = 0; $index < 15; $index++) {
+            // mise en forme attendue par l'aide de vue FormSearchAndSelect
+            $result[] = array(
+                'id'    => $index,        // identifiant unique de l'item
+                'label' => uniqid($term), // libellé de l'item
+                'extra' => "Bla bla",     // infos complémentaires (facultatives) sur l'item
+            );
+        }
+        return new \Zend\View\Model\JsonModel($result);
+    }
+    exit;
+}
+```
+
+AdvancedMultiCheckBox
+=====================
+
+Description
+-----------
+
+Ceci est une aide de vue pour élément de formulaire. Il accepte les
+éléments de type `Zend\Form\Element\MultiCheckbox`{.php}.
+
+-   Il ajoute deux boutons au multicheckbox habituel : un pour tout
+    sélectionner et un pour ne rien sélectionner.
+-   En outre, il encapsule les items dans une liste dont la hauteur est
+    définissable au besoin. Ainsi pas de risque d\'avoir une liste
+    interminable à l\'écran.
+
+\<WRAP center round info 60%\> Ce composant est codé sous forme de
+Widget JQuery (<https://jqueryui.com/widget/>).\
+\
+De plus, il exploite le
+[WidgetInitializer](/develop/unicaen2/ModuleUnicaenUnicaenApp/js) donc
+inutile de l\'initialiser, ça se fait automatiquement. \</WRAP\>
+
+Exemple
+-------
+
+Dans la vue, il s\'exploite comme suit :
+
+``` {.php}
+$this->formControlGroup(
+    $form->get('votre-multicheckbox'),
+    $this->formAdvancedMultiCheckbox()->setHeight('20em')
+);
+```
+
+Là, la hauteur a été définie directement depuis le code PHP.
+
+Mais il est également possible de définir la hauteur de votre
+AdvancedMultiCheckBox en Javascript.
+
+Exemples d\'utilisation :
+
+``` {.javascript}
+
+// Ici la hauteur est limitée à 20em
+$("votre_selecteur_de_div").formAdvancedMultiCheckbox("height", "20em");
+
+// Là on sélectionne tous les items
+$("votre_selecteur_de_div").formAdvancedMultiCheckbox("selectAll");
+
+```
+
+Liste des propriétés
+--------------------
+
+-   `height`{.php} : définit la hauteur maximale de votre widget
+-   `overflow`{.php} : gère l\'overflow du widget
+
+Ces propriétés sont également utilisables directement en PHP via le
+ViewHelper.
+
+Liste des méthodes
+------------------
+
+-   `selectAll`{.php} : sélectionne tous les items de la liste
+-   `selectNone`{.php} : désélectionne tous les items de la liste
+-   `getSelectAllBtn`{.php} : retourne le bouton qui permet de tout
+    sélectionner
+-   `getSelectNoneBtn`{.php} : retorune le bouton qui permet de tout
+    désélectionner
+
+Filtres d\'entrée (input filters)
+---------------------------------
+
+DateInfSupInputFilter
+=====================
+
+Filtre d\'entrée associé à l\'élément de formulaire \"DateInfSup\" à la
+saisie d\'une date inférieure (date de début) et une date supérieure
+éventuelle (date de fin).
+
+Cf. exemple sur la page consacrée à l\'élément de formulaire
+\"[DateInfSup](/develop/unicaen2/moduleunicaenunicaenapp/form/dateinfsup)\".
diff --git a/doc/Historique.dokuwiki b/doc/Historique.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..8f05b7068f9204bcd96d79568aab30f408307064
--- /dev/null
+++ b/doc/Historique.dokuwiki
@@ -0,0 +1,69 @@
+====== Gestion de l'historique ======
+Un système de gestion des données historiques est disponible dans UnicaenApp.
+
+===== En principe =====
+L'historique permet de savoir, pour une entité donnée :
+  * Qui l'a créée et quand (propriétés <php>histoCreateur</php> et <php>histoCreation</php>) ;
+  * Qui l'a modifié et quand (propriétés <php>histoModificateur</php> et <php>histoModification</php>) ;
+  * Qui l'a supprimé et quand (propriétés <php>histoDestructeur</php> et <php>histoDestruction</php>).
+
+Les propriétés liées aux utilisateurs doivent être des <php>UnicaenAuth\Entity\Db\AbstractUser</php>.
+Les propriétés liées aux dates doivent être des <php>DateTime</php>.
+
+<WRAP center round important 80%>
+Avec l'historisation, il n'y a donc plus de suppression de données mais une simple modification.
+</WRAP>
+
+===== En pratique =====
+Il s'applique à une entité et fonctionne de la manière suivante :
+  * L'entité doit implémenter <php>UnicaenApp\Entity\HistoriqueAwareInterface</php> ;
+  * Au besoin, un Trait <php>UnicaenApp\Entity\HistoriqueAwareTrait</php> pourra être ajouté à l'entité pour implémenter l'interface correspondante.
+
+Si l'entité est issue d'une base de données et est générée par Doctrine, c'est pareil: l'entité doit implémenter l'interface d'historique. La table correspondante devra alors être dotée des 6 champs correspondants.
+
+==== Aide de vue ====
+
+Une aide de vue permet d'afficher des informations d'historique (du genre "Dernière modification: le jj/mm à hh:mm par Utilisateur").
+Il s'utilise à partir d'une vue de cette manière :
+<code php>
+
+echo $this->historique($entity);
+
+</code>
+On peut aussi lui fournir manuellement les propriétés suivantes sans lui fournir d'entité au besoin :
+  * histoModification (DateTime)
+  * histoModificateur (ZfcUser\Entity\UserInterface)
+  * isDeleted         (boolean)
+===== Avec Doctrine =====
+Pour que l'historique fonctionne, il faut :
+  * renseigner les valeurs des champs d'historique (listener) ;
+  * les exploiter lorsqu'on fait des requêtes (filtre) pour éviter que les données historisées ne polluent les résultats.
+
+==== Listener ====
+Doctrine doit se charger, par l'entremise du Listener de peupler automatiquement les champs liés aux historiques lorsque l'entité est enregistrée en base.
+
+Un Listener tout prêt est disponible : il s'agit de <php>UnicaenApp\ORM\Event\Listeners\HistoriqueListener</php>.
+Il n'est cependant pas activé par défaut.
+Pour l'activer, il suffit d'ajouter à la configuration du projet les données suivantes :
+<code php>
+<?php
+return [
+    'doctrine' => [
+        'eventmanager' => [
+            'orm_default' => [
+                'subscribers' => [
+                    'UnicaenApp\HistoriqueListener',
+                ],
+            ],
+        ],
+    ],
+];
+</code>
+
+==== Filter ====
+Le but des filtres est d'éviter que des entités dont les données ont été historisées ne soient collectées.
+Il n'existe pas de filtre générique. Il doit être créé sur mesure.
+<WRAP center round info 80%>
+Pour information, les filtres Doctrine héritent de la classe <php>Doctrine\ORM\Query\Filter\SQLFilter</php>.
+</WRAP>
+Il est également possible (voir recommandé) de filtrer au moment où les requêtes sont exécutées.
\ No newline at end of file
diff --git a/doc/Historique.md b/doc/Historique.md
new file mode 100644
index 0000000000000000000000000000000000000000..d7306f48b1137b4426ed19552ce4911bb5ee0d82
--- /dev/null
+++ b/doc/Historique.md
@@ -0,0 +1,105 @@
+Gestion de l\'historique
+========================
+
+Un système de gestion des données historiques est disponible dans
+UnicaenApp.
+
+En principe
+-----------
+
+L\'historique permet de savoir, pour une entité donnée :
+
+-   Qui l\'a créée et quand (propriétés `histoCreateur`{.php} et
+    `histoCreation`{.php}) ;
+-   Qui l\'a modifié et quand (propriétés `histoModificateur`{.php} et
+    `histoModification`{.php}) ;
+-   Qui l\'a supprimé et quand (propriétés `histoDestructeur`{.php} et
+    `histoDestruction`{.php}).
+
+Les propriétés liées aux utilisateurs doivent être des
+`UnicaenAuth\Entity\Db\AbstractUser`{.php}. Les propriétés liées aux
+dates doivent être des `DateTime`{.php}.
+
+\<WRAP center round important 80%\> Avec l\'historisation, il n\'y a
+donc plus de suppression de données mais une simple modification.
+\</WRAP\>
+
+En pratique
+-----------
+
+Il s\'applique à une entité et fonctionne de la manière suivante :
+
+-   L\'entité doit implémenter
+    `UnicaenApp\Entity\HistoriqueAwareInterface`{.php} ;
+-   Au besoin, un Trait `UnicaenApp\Entity\HistoriqueAwareTrait`{.php}
+    pourra être ajouté à l\'entité pour implémenter l\'interface
+    correspondante.
+
+Si l\'entité est issue d\'une base de données et est générée par
+Doctrine, c\'est pareil: l\'entité doit implémenter l\'interface
+d\'historique. La table correspondante devra alors être dotée des 6
+champs correspondants.
+
+### Aide de vue
+
+Une aide de vue permet d\'afficher des informations d\'historique (du
+genre \"Dernière modification: le jj/mm à hh:mm par Utilisateur\"). Il
+s\'utilise à partir d\'une vue de cette manière :
+
+``` {.php}
+
+echo $this->historique($entity);
+
+```
+
+On peut aussi lui fournir manuellement les propriétés suivantes sans lui
+fournir d\'entité au besoin :
+
+-   histoModification (DateTime)
+-   histoModificateur (ZfcUser\\Entity\\UserInterface)
+-   isDeleted (boolean)
+
+Avec Doctrine
+-------------
+
+Pour que l\'historique fonctionne, il faut :
+
+-   renseigner les valeurs des champs d\'historique (listener) ;
+-   les exploiter lorsqu\'on fait des requêtes (filtre) pour éviter que
+    les données historisées ne polluent les résultats.
+
+### Listener
+
+Doctrine doit se charger, par l\'entremise du Listener de peupler
+automatiquement les champs liés aux historiques lorsque l\'entité est
+enregistrée en base.
+
+Un Listener tout prêt est disponible : il s\'agit de
+`UnicaenApp\ORM\Event\Listeners\HistoriqueListener`{.php}. Il n\'est
+cependant pas activé par défaut. Pour l\'activer, il suffit d\'ajouter à
+la configuration du projet les données suivantes :
+
+``` {.php}
+<?php
+return [
+    'doctrine' => [
+        'eventmanager' => [
+            'orm_default' => [
+                'subscribers' => [
+                    'UnicaenApp\HistoriqueListener',
+                ],
+            ],
+        ],
+    ],
+];
+```
+
+### Filter
+
+Le but des filtres est d\'éviter que des entités dont les données ont
+été historisées ne soient collectées. Il n\'existe pas de filtre
+générique. Il doit être créé sur mesure. \<WRAP center round info 80%\>
+Pour information, les filtres Doctrine héritent de la classe
+`Doctrine\ORM\Query\Filter\SQLFilter`{.php}. \</WRAP\> Il est également
+possible (voir recommandé) de filtrer au moment où les requêtes sont
+exécutées.
diff --git a/doc/Installation.dokuwiki b/doc/Installation.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..696e424d6d4e662fb63baf876ae6be1067d3f67c
--- /dev/null
+++ b/doc/Installation.dokuwiki
@@ -0,0 +1,47 @@
+====== Installation ======
+
+Cette page traite de l'installation du module UnicaenApp au sein d'une application ne l'utilisant par encore.
+
+<note important>
+Ce module est une dépendance du module [[develop:unicaen2:moduleunicaenunicaenuser|UnicaenAuth]]. Si vous prévoyez d'installer ce dernier, inutile de lire cette page car l'installation de "unicaen/unicaen-app" se fera automatiquement.
+</note>
+
+  * Éditez le fichier ''composer.json'' se trouvant à la racine de votre projet et assurez-vous que le "repository" suivant est bien présent :
+<file json composer.json>
+    "repositories": [
+        {
+            "type": "composer",
+            "url": "http://dev.unicaen.fr/packagist"
+        }
+    ],
+</file>
+
+  * Ajoutez à présent la dépendance suivante :
+<file json composer.json>
+    "require": {
+        ...
+        "unicaen/unicaen-app": "dev-master"
+    },
+    "minimum-stability": "dev"
+</file>
+
+  * Placez-vous à la racine de votre projet et lancez la commande suivante dans un shell :
+<file>
+gauthierb@bertrand-crisi:~/workspace/gesnum$ php ../composer.phar update
+</file>
+<note>La commande ci-dessus fonctionne seulement si le binaire ''composer.phar'' se trouve dans le répertoire parent. Plus d'infos : [[http://getcomposer.org]].</note>
+
+  * Créer le répertoire ''data/DoctrineORMModule'' à la racine de votre projet et l'autoriser en écriture au serveur web en faisant exemple :
+<file>
+gauthierb@bertrand-crisi:~/workspace/gesnum$ mkdir -p data/DoctrineORMModule
+gauthierb@bertrand-crisi:~/workspace/gesnum$ sudo chown gauthierb:www-data data/DoctrineORMModule
+</file>
+
+  * Activez les modules suivants **dans cet ordre** dans le fichier ''config/application.config.php'' de l'application :
+<file php >
+    'modules' => array(
+        'Application',
+        'UnicaenApp', 'AssetManager',
+        // ...
+    ),
+</file>
\ No newline at end of file
diff --git a/doc/Installation.md b/doc/Installation.md
new file mode 100644
index 0000000000000000000000000000000000000000..2c0aba9a559b68bca9d6abcd20058137c3747304
--- /dev/null
+++ b/doc/Installation.md
@@ -0,0 +1,65 @@
+Installation
+============
+
+Cette page traite de l\'installation du module UnicaenApp au sein d\'une
+application ne l\'utilisant par encore.
+
+\<note important\> Ce module est une dépendance du module
+[UnicaenAuth](/develop/unicaen2/moduleunicaenunicaenuser). Si vous
+prévoyez d\'installer ce dernier, inutile de lire cette page car
+l\'installation de \"unicaen/unicaen-app\" se fera automatiquement.
+\</note\>
+
+-   Éditez le fichier `composer.json` se trouvant à la racine de votre
+    projet et assurez-vous que le \"repository\" suivant est bien
+    présent :
+
+``` {.json}
+    "repositories": [
+        {
+            "type": "composer",
+            "url": "http://dev.unicaen.fr/packagist"
+        }
+    ],
+```
+
+-   Ajoutez à présent la dépendance suivante :
+
+``` {.json}
+    "require": {
+        ...
+        "unicaen/unicaen-app": "dev-master"
+    },
+    "minimum-stability": "dev"
+```
+
+-   Placez-vous à la racine de votre projet et lancez la commande
+    suivante dans un shell :
+
+<!-- -->
+
+    gauthierb@bertrand-crisi:~/workspace/gesnum$ php ../composer.phar update
+
+\<note\>La commande ci-dessus fonctionne seulement si le binaire
+`composer.phar` se trouve dans le répertoire parent. Plus d\'infos :
+<http://getcomposer.org>.\</note\>
+
+-   Créer le répertoire `data/DoctrineORMModule` à la racine de votre
+    projet et l\'autoriser en écriture au serveur web en faisant exemple
+    :
+
+<!-- -->
+
+    gauthierb@bertrand-crisi:~/workspace/gesnum$ mkdir -p data/DoctrineORMModule
+    gauthierb@bertrand-crisi:~/workspace/gesnum$ sudo chown gauthierb:www-data data/DoctrineORMModule
+
+-   Activez les modules suivants **dans cet ordre** dans le fichier
+    `config/application.config.php` de l\'application :
+
+``` {.php}
+    'modules' => array(
+        'Application',
+        'UnicaenApp', 'AssetManager',
+        // ...
+    ),
+```
diff --git a/doc/Javascript.dokuwiki b/doc/Javascript.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..3a67d88686e099cffec599bd571928132eb1c9d4
--- /dev/null
+++ b/doc/Javascript.dokuwiki
@@ -0,0 +1,77 @@
+====== Fichier "unicaen.js" ======
+
+Javascript commun à toutes les applis.
+
+===== AjaxModalListener =====
+
+Classe d'installation d'un mécanisme d'ouverture de fenêtre modale Bootstrap 3 lorsqu'un lien ayant la classe CSS 'modal-action' est cliqué, et de gestion de la soumission du formulaire éventuel se trouvant dans cette fenêtre modale.
+
+===== WidgetInitializer =====
+Le WidgetInitializer est un système qui permet de lier automatiquement un widget JQuery à un ou plusieurs éléments.
+
+Par exemple, si en HTML un élément INPUT doit servir de datepicker; alors il faut faire : <code javascript>$("votre_input").datepicker()</code>
+
+Avec le WidgetInitializer ce n'est plus nécessaire. Il suffit de donner à votre élément la bonne classe et tout se fait automatiquement.
+
+Par exemple, à votre élément <input class="datepicker" /> se verra automatiquement associer le widget DatePicker.
+Ceci, bien sûr, si vous avez préalablement configuré le WidgetInitializer!!
+
+Pour ajouter un widget au WidgetInitializer, rien de plus simple. Il suffit de faire :
+<code javascript>
+$(function ()
+{
+    WidgetInitializer.add('form-advanced-multi-checkbox', 'formAdvancedMultiCheckbox');
+    // exemple pris pour le formAdvancedMultiCheckbox, qui est au demerant déjà pré-renseigné
+});
+</code>
+
+Dans certains cas (en particulier si votre code HTML est généré en Javascript), l'initialisation ne va pas se déclencher (cette dernière ne se fait qu'au chargement de la page et après avoir exécuté une requête AJAX).
+
+Pour que l'initialisation de votre widget se fasse il faut faire :
+<code javascript>
+WidgetInitializer.run();
+</code>
+Ceci entraînera l'initialisation de tous les widgets pas encore en place.
+
+<WRAP center round tip 60%>
+Certains widgets, en particulier ceux issus de la bibliothèque Unicaen, sont déjà déclarés auprès du WidgetInitializer. C'est par exemple le cas de [[develop:unicaen2:moduleunicaenunicaenapp:form:AdvancedMultiCheckBox|AdvancedMultiCheckBox]]. Dans ce cas, il n'y a rien d'autre à faire pour que le widget soit initialisé.
+</WRAP>
+
+
+===== Refresh =====
+Il est possible de rafraichir un élément en AJAX en lui donnant une URL qui sera chargée de lui fournir son nouveau contenu.
+
+==== Conditions de fonctionnement ====
+Ajouter à l'élément un attribut <html>data-url</html> avec comme valeur l'url qui fournira le nouveau contenu.
+
+==== Exécution ====
+Pour lancer le rafarpichissement, rien de plus simple. Il suffit de faire : <code javascript>$("//sélecteur de l'objet//").refresh();</code>
+
+Refresh peut accepter deux paramètres optionnels :
+  * <html>data</html> qui sera un tableau JSON des paramètres à transmettre au serveur
+  * <html>onEnd</html> qui est une fonction anonyme permettant d'exécuter un code après le rafraichissement de l'élément.
+
+
+===== alertFlash =====
+
+Fonction JS permettant d'afficher en bas de l'écran un message d'info/alerte temporaire.
+
+Exemple :
+<code javascript>
+// message de succès disparaissant au bout de 3 secondes
+alertFlash("Demande enregistrée avec succès.", "success", 3000);
+
+// message d'erreur disparaissant au bout de 5 secondes
+alertFlash("Erreur rencontrée lors de l'enregistrement de la demande!", "error", 5000);
+</code>
+
+Les sévérités disponibles sont :
+  * "info"
+  * "success"
+  * "warning"
+  * "error"
+
+
+===== Divers =====
+  * Détection de réponse "403 Unauthorized" aux requêtes AJAX pour rediriger vers la page de connexion.
+  * Installation d'un lien permettant de remonter en haut de la page. Ce lien apparaît lorsque c'est nécessaire.
\ No newline at end of file
diff --git a/doc/Javascript.md b/doc/Javascript.md
new file mode 100644
index 0000000000000000000000000000000000000000..ded6012db4561ab63364b6017e9b902968d1d392
--- /dev/null
+++ b/doc/Javascript.md
@@ -0,0 +1,114 @@
+Fichier \"unicaen.js\"
+======================
+
+Javascript commun à toutes les applis.
+
+AjaxModalListener
+-----------------
+
+Classe d\'installation d\'un mécanisme d\'ouverture de fenêtre modale
+Bootstrap 3 lorsqu\'un lien ayant la classe CSS \'modal-action\' est
+cliqué, et de gestion de la soumission du formulaire éventuel se
+trouvant dans cette fenêtre modale.
+
+WidgetInitializer
+-----------------
+
+Le WidgetInitializer est un système qui permet de lier automatiquement
+un widget JQuery à un ou plusieurs éléments.
+
+Par exemple, si en HTML un élément INPUT doit servir de datepicker;
+alors il faut faire : `$("votre_input").datepicker()`{.javascript}
+
+Avec le WidgetInitializer ce n\'est plus nécessaire. Il suffit de donner
+à votre élément la bonne classe et tout se fait automatiquement.
+
+Par exemple, à votre élément \<input class=\"datepicker\" /\> se verra
+automatiquement associer le widget DatePicker. Ceci, bien sûr, si vous
+avez préalablement configuré le WidgetInitializer!!
+
+Pour ajouter un widget au WidgetInitializer, rien de plus simple. Il
+suffit de faire :
+
+``` {.javascript}
+$(function ()
+{
+    WidgetInitializer.add('form-advanced-multi-checkbox', 'formAdvancedMultiCheckbox');
+    // exemple pris pour le formAdvancedMultiCheckbox, qui est au demerant déjà pré-renseigné
+});
+```
+
+Dans certains cas (en particulier si votre code HTML est généré en
+Javascript), l\'initialisation ne va pas se déclencher (cette dernière
+ne se fait qu\'au chargement de la page et après avoir exécuté une
+requête AJAX).
+
+Pour que l\'initialisation de votre widget se fasse il faut faire :
+
+``` {.javascript}
+WidgetInitializer.run();
+```
+
+Ceci entraînera l\'initialisation de tous les widgets pas encore en
+place.
+
+\<WRAP center round tip 60%\> Certains widgets, en particulier ceux
+issus de la bibliothèque Unicaen, sont déjà déclarés auprès du
+WidgetInitializer. C\'est par exemple le cas de
+[AdvancedMultiCheckBox](/develop/unicaen2/moduleunicaenunicaenapp/form/AdvancedMultiCheckBox).
+Dans ce cas, il n\'y a rien d\'autre à faire pour que le widget soit
+initialisé. \</WRAP\>
+
+Refresh
+-------
+
+Il est possible de rafraichir un élément en AJAX en lui donnant une URL
+qui sera chargée de lui fournir son nouveau contenu.
+
+### Conditions de fonctionnement
+
+Ajouter à l\'élément un attribut data-url avec comme valeur l\'url qui
+fournira le nouveau contenu.
+
+### Exécution
+
+Pour lancer le rafarpichissement, rien de plus simple. Il suffit de
+faire : `$("//sélecteur de l'objet//").refresh();`{.javascript}
+
+Refresh peut accepter deux paramètres optionnels :
+
+-   data qui sera un tableau JSON des paramètres à transmettre au
+    serveur
+-   onEnd qui est une fonction anonyme permettant d\'exécuter un code
+    après le rafraichissement de l\'élément.
+
+alertFlash
+----------
+
+Fonction JS permettant d\'afficher en bas de l\'écran un message
+d\'info/alerte temporaire.
+
+Exemple :
+
+``` {.javascript}
+// message de succès disparaissant au bout de 3 secondes
+alertFlash("Demande enregistrée avec succès.", "success", 3000);
+
+// message d'erreur disparaissant au bout de 5 secondes
+alertFlash("Erreur rencontrée lors de l'enregistrement de la demande!", "error", 5000);
+```
+
+Les sévérités disponibles sont :
+
+-   \"info\"
+-   \"success\"
+-   \"warning\"
+-   \"error\"
+
+Divers
+------
+
+-   Détection de réponse \"403 Unauthorized\" aux requêtes AJAX pour
+    rediriger vers la page de connexion.
+-   Installation d\'un lien permettant de remonter en haut de la page.
+    Ce lien apparaît lorsque c\'est nécessaire.
diff --git a/doc/MVC.dokuwiki b/doc/MVC.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..7d8ee1b6869ed44f0727d042e7056fe83d7f741c
--- /dev/null
+++ b/doc/MVC.dokuwiki
@@ -0,0 +1,67 @@
+====== MVC ======
+
+===== ModalListener =====
+
+C'est un composant qui simplifie l'ouverture de lien hypertexte dans une fenêtre modale Bootstrap 3.
+
+Le principe est l'écoute de l'événement MVC ''render'' pour imbriquer le modèle de vue fourni par l'action courante dans le modèle de vue correspondant au contenu interne d'une fenêtre modale (ModalInnerViewModel).
+
+<note important>
+Ce mécanisme est enclenché seulement si :
+  * un paramètre GET ou POST ''modal'' valant ''1'' est trouvé dans la requête.
+  * le modèle de vue fourni par l'action courante n'est pas déjà celui d'une fenêtre modale (ModalViewModel ou ModalInnerViewModel).
+</note>
+===== ExceptionStrategy =====
+
+Stratégie permettant d'afficher proprement un message d'erreur lorsqu'une exception maison est levée dans une action. La vue utilisée par cette stratégie est 'error/exception'.
+
+<note important>NB: Cette stratégie ne rentre en action que si l'exception lancée implémente l'interface ''UnicaenApp\Exception\ExceptionInterface''.</note>
+
+Cette stratégie est activée par défaut dans le module UnicaenApp :
+<file php Module.php>
+public function onBootstrap(EventInterface $e) /* @var $e \Zend\Mvc\MvcEvent */
+{
+    /* @var $application \Zend\Mvc\Application */
+    $application = $e->getApplication();
+    /* @var $services ServiceManager */
+    $services    = $application->getServiceManager();
+
+    // ...
+
+    $eventManager = $application->getEventManager();
+    $viewManager  = $services->get('view_manager');
+
+    $exceptionStrategy = new ExceptionStrategy();
+    $exceptionStrategy->setDisplayExceptions($viewManager->getExceptionStrategy()->displayExceptions());
+    $exceptionStrategy->attach($eventManager);
+
+    // ...
+}
+</file>
+
+===== AjaxStrategy =====
+
+Stratégie permettant de na pas retourner le template en AJAX.
+<file php Module.php>
+public function onBootstrap(EventInterface $e) /* @var $e \Zend\Mvc\MvcEvent */
+{
+    /* Déclare la dernière vue transmise comme terminale si on est en AJAX */
+    $sharedEvents = $e->getApplication()->getEventManager()->getSharedManager();
+    $sharedEvents->attach('Zend\Mvc\Controller\AbstractActionController','dispatch',
+         function($e) {
+            $result = $e->getResult();
+            if(is_array($result)){
+                $result = new \Zend\View\Model\ViewModel($result);
+                $e->setResult($result);
+            }elseif(empty($result)){
+                $result = new \Zend\View\Model\ViewModel();
+                $e->setResult($result);
+            }
+            if ($result instanceof \Zend\View\Model\ViewModel) {
+                $result->setTerminal($e->getRequest()->isXmlHttpRequest());
+            }
+    });
+
+    // ...
+}
+</file>
\ No newline at end of file
diff --git a/doc/MVC.md b/doc/MVC.md
new file mode 100644
index 0000000000000000000000000000000000000000..c72525a60eb5d790017eba6958ad6e8721ac3355
--- /dev/null
+++ b/doc/MVC.md
@@ -0,0 +1,85 @@
+MVC
+===
+
+ModalListener
+-------------
+
+C\'est un composant qui simplifie l\'ouverture de lien hypertexte dans
+une fenêtre modale Bootstrap 3.
+
+Le principe est l\'écoute de l\'événement MVC `render` pour imbriquer le
+modèle de vue fourni par l\'action courante dans le modèle de vue
+correspondant au contenu interne d\'une fenêtre modale
+(ModalInnerViewModel).
+
+\<note important\> Ce mécanisme est enclenché seulement si :
+
+-   un paramètre GET ou POST `modal` valant `1` est trouvé dans la
+    requête.
+-   le modèle de vue fourni par l\'action courante n\'est pas déjà celui
+    d\'une fenêtre modale (ModalViewModel ou ModalInnerViewModel).
+
+\</note\>
+
+ExceptionStrategy
+-----------------
+
+Stratégie permettant d\'afficher proprement un message d\'erreur
+lorsqu\'une exception maison est levée dans une action. La vue utilisée
+par cette stratégie est \'error/exception\'.
+
+\<note important\>NB: Cette stratégie ne rentre en action que si
+l\'exception lancée implémente l\'interface
+`UnicaenApp\Exception\ExceptionInterface`.\</note\>
+
+Cette stratégie est activée par défaut dans le module UnicaenApp :
+
+``` {.php}
+public function onBootstrap(EventInterface $e) /* @var $e \Zend\Mvc\MvcEvent */
+{
+    /* @var $application \Zend\Mvc\Application */
+    $application = $e->getApplication();
+    /* @var $services ServiceManager */
+    $services    = $application->getServiceManager();
+
+    // ...
+
+    $eventManager = $application->getEventManager();
+    $viewManager  = $services->get('view_manager');
+
+    $exceptionStrategy = new ExceptionStrategy();
+    $exceptionStrategy->setDisplayExceptions($viewManager->getExceptionStrategy()->displayExceptions());
+    $exceptionStrategy->attach($eventManager);
+
+    // ...
+}
+```
+
+AjaxStrategy
+------------
+
+Stratégie permettant de na pas retourner le template en AJAX.
+
+``` {.php}
+public function onBootstrap(EventInterface $e) /* @var $e \Zend\Mvc\MvcEvent */
+{
+    /* Déclare la dernière vue transmise comme terminale si on est en AJAX */
+    $sharedEvents = $e->getApplication()->getEventManager()->getSharedManager();
+    $sharedEvents->attach('Zend\Mvc\Controller\AbstractActionController','dispatch',
+         function($e) {
+            $result = $e->getResult();
+            if(is_array($result)){
+                $result = new \Zend\View\Model\ViewModel($result);
+                $e->setResult($result);
+            }elseif(empty($result)){
+                $result = new \Zend\View\Model\ViewModel();
+                $e->setResult($result);
+            }
+            if ($result instanceof \Zend\View\Model\ViewModel) {
+                $result->setTerminal($e->getRequest()->isXmlHttpRequest());
+            }
+    });
+
+    // ...
+}
+```
diff --git a/doc/Message.dokuwiki b/doc/Message.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..f78b1f9a7463d283e58960a89d13eac40d2c00ad
--- /dev/null
+++ b/doc/Message.dokuwiki
@@ -0,0 +1,103 @@
+====== Fourniture de messages selon le contexte ======
+
+===== Configuration =====
+
+<code php module.config.php>
+
+use UnicaenApp\Message\Specification\MessageSpecificationInterface;
+
+class MineurSpecification implements MessageSpecificationInterface {
+    public function isSatisfiedBy($age = null, array &$sentBackData = []) {
+        $sentBackData['nom'] = 'Junior';
+        return 0 <= $age && $age <  18;
+    }
+}
+class MajeurSpecification implements MessageSpecificationInterface {
+    public function isSatisfiedBy($age = null, array &$sentBackData = []) {
+        return $age >= 18;
+    }
+}
+
+return array(
+    'message' => [
+        'messages' => [
+            [
+                'id' => 'MSG_CLOSURE',
+                'data' => [
+                    "Vous êtes mineur(e), {nom}." => function($age) { return 0 <= $age && $age < 18; },
+                    "Vous êtes majeur(e), {nom}." => function($age) { return $age >= 18; },
+                    "Vous n'êtes pas né(e) !"     => true, // texte par défaut
+                ],
+            ],
+            [
+                'id' => 'MSG_CLASS',
+                'data' => [
+                    "Vous êtes mineur(e), {nom}." => new MineurSpecification(),
+                    "Vous êtes majeur(e), {nom}." => new MajeurSpecification(),
+                    "Vous n'êtes pas né(e) !"     => true,
+                ],
+            ],
+            [
+                'id' => 'MSG_OTHER',
+                'data' => [
+                    "Vous êtes mineur(e), {nom}." => 'mineur',
+                    "Vous êtes majeur(e), {nom}." => 'majeur',
+                    "Vous n'êtes pas né(e) !"     => true,
+                ],
+            ],
+        ]
+    ],
+    ...
+];
+</code>
+
+===== Dans un contrôleur (par exemple) =====
+
+<code php IndexController.php>
+class IndexController extends AbstractActionController
+{
+    public function indexAction()
+    {
+        /** @var \UnicaenApp\Message\MessageService $service */
+        $service = $this->getServiceLocator()->get('MessageService');
+        $service->setContext(15); // contexte global
+
+        var_dump(
+            $service->render('MSG_CLOSURE', ['nom'=>"Beber"], 19), // context = 19 (remplace 15)
+            $service->render('MSG_CLASS'), // context = 15
+            $service->render('MSG_OTHER', ['nom'=>"Bobby"], 'majeur'), // context = 'majeur' (égale la valeur de la spécification)
+            $service->render('MSG_OTHER') // context = 15
+        );
+
+        return new ViewModel();
+    }
+}
+</code>
+
+Affiche :
+
+<code>
+string 'Vous êtes majeur(e), Beber.' (length=28)
+string 'Vous êtes mineur(e), Junior.' (length=29)
+string 'Vous êtes majeur(e), Bobby.' (length=28)
+string 'Vous n'êtes pas né(e) !' (length=25)
+</code>
+
+
+===== Dans une vue =====
+
+<code php index.phtml>
+<p><?php echo $this->message()->render('MSG_CLOSURE', ['nom'=>"Beber"], 19); ?></p>
+<p><?php echo $this->message()->render('MSG_CLASS'); ?></p>
+<p><?php echo $this->message()->render('MSG_OTHER', ['nom'=>"Bobby"], 'majeur'); ?></p>
+<p><?php echo $this->message()->render('MSG_OTHER'); ?></p>
+</code>
+
+Donne
+
+<code html>
+<p>Vous êtes majeur(e), Beber.</p>
+<p>Vous êtes mineur(e), Junior.</p>
+<p>Vous êtes majeur(e), Bobby.</p>
+<p>Vous n'êtes pas né(e) !</p>
+</code>
\ No newline at end of file
diff --git a/doc/Message.md b/doc/Message.md
new file mode 100644
index 0000000000000000000000000000000000000000..454f0d594715d50ee92b67f3aaac268196e223a5
--- /dev/null
+++ b/doc/Message.md
@@ -0,0 +1,104 @@
+Fourniture de messages selon le contexte
+========================================
+
+Configuration
+-------------
+
+``` {.php}
+
+use UnicaenApp\Message\Specification\MessageSpecificationInterface;
+
+class MineurSpecification implements MessageSpecificationInterface {
+    public function isSatisfiedBy($age = null, array &$sentBackData = []) {
+        $sentBackData['nom'] = 'Junior';
+        return 0 <= $age && $age <  18;
+    }
+}
+class MajeurSpecification implements MessageSpecificationInterface {
+    public function isSatisfiedBy($age = null, array &$sentBackData = []) {
+        return $age >= 18;
+    }
+}
+
+return array(
+    'message' => [
+        'messages' => [
+            [
+                'id' => 'MSG_CLOSURE',
+                'data' => [
+                    "Vous êtes mineur(e), {nom}." => function($age) { return 0 <= $age && $age < 18; },
+                    "Vous êtes majeur(e), {nom}." => function($age) { return $age >= 18; },
+                    "Vous n'êtes pas né(e) !"     => true, // texte par défaut
+                ],
+            ],
+            [
+                'id' => 'MSG_CLASS',
+                'data' => [
+                    "Vous êtes mineur(e), {nom}." => new MineurSpecification(),
+                    "Vous êtes majeur(e), {nom}." => new MajeurSpecification(),
+                    "Vous n'êtes pas né(e) !"     => true,
+                ],
+            ],
+            [
+                'id' => 'MSG_OTHER',
+                'data' => [
+                    "Vous êtes mineur(e), {nom}." => 'mineur',
+                    "Vous êtes majeur(e), {nom}." => 'majeur',
+                    "Vous n'êtes pas né(e) !"     => true,
+                ],
+            ],
+        ]
+    ],
+    ...
+];
+```
+
+Dans un contrôleur (par exemple)
+--------------------------------
+
+``` {.php}
+class IndexController extends AbstractActionController
+{
+    public function indexAction()
+    {
+        /** @var \UnicaenApp\Message\MessageService $service */
+        $service = $this->getServiceLocator()->get('MessageService');
+        $service->setContext(15); // contexte global
+
+        var_dump(
+            $service->render('MSG_CLOSURE', ['nom'=>"Beber"], 19), // context = 19 (remplace 15)
+            $service->render('MSG_CLASS'), // context = 15
+            $service->render('MSG_OTHER', ['nom'=>"Bobby"], 'majeur'), // context = 'majeur' (égale la valeur de la spécification)
+            $service->render('MSG_OTHER') // context = 15
+        );
+
+        return new ViewModel();
+    }
+}
+```
+
+Affiche :
+
+    string 'Vous êtes majeur(e), Beber.' (length=28)
+    string 'Vous êtes mineur(e), Junior.' (length=29)
+    string 'Vous êtes majeur(e), Bobby.' (length=28)
+    string 'Vous n'êtes pas né(e) !' (length=25)
+
+Dans une vue
+------------
+
+``` {.php}
+<p><?php echo $this->message()->render('MSG_CLOSURE', ['nom'=>"Beber"], 19); ?></p>
+<p><?php echo $this->message()->render('MSG_CLASS'); ?></p>
+<p><?php echo $this->message()->render('MSG_OTHER', ['nom'=>"Bobby"], 'majeur'); ?></p>
+<p><?php echo $this->message()->render('MSG_OTHER'); ?></p>
+```
+
+Donne
+
+``` {.html}
+<p>Vous êtes majeur(e), Beber.</p>
+<p>Vous êtes mineur(e), Junior.</p>
+<p>Vous êtes majeur(e), Bobby.</p>
+<p>Vous n'êtes pas né(e) !</p>
+```
diff --git a/doc/Modeles.dokuwiki b/doc/Modeles.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..036528e8f914bbb1c802c3edaed330c0b6253552
--- /dev/null
+++ b/doc/Modeles.dokuwiki
@@ -0,0 +1,15 @@
+====== Modèles ======
+
+===== Entités LDAP =====
+
+==== Group ====
+Classe des groupes issus de l'annuaire LDAP.
+Ce sont des objets de ce type qui sont retournés par défaut par les méthodes de recherche du service [[develop:unicaen2:moduleunicaenunicaenapp:services#group|Group]].
+
+==== People ====
+Classe des individus issus de l'annuaire LDAP.
+Ce sont des objets de ce type qui sont retournés par défaut par les méthodes de recherche du service [[develop:unicaen2:moduleunicaenunicaenapp:services#people|People]].
+
+==== Structure ====
+Classe des structures issues de l'annuaire LDAP.
+Ce sont des objets de ce type qui sont retournés par défaut par les méthodes de recherche du service [[develop:unicaen2:moduleunicaenunicaenapp:services#structure|Structure]].
diff --git a/doc/Modeles.md b/doc/Modeles.md
new file mode 100644
index 0000000000000000000000000000000000000000..d0cbc593adc5eb2729458f0082d93be09ecfeb0b
--- /dev/null
+++ b/doc/Modeles.md
@@ -0,0 +1,26 @@
+Modèles
+=======
+
+Entités LDAP
+------------
+
+### Group
+
+Classe des groupes issus de l\'annuaire LDAP. Ce sont des objets de ce
+type qui sont retournés par défaut par les méthodes de recherche du
+service
+[Group](/develop/unicaen2/moduleunicaenunicaenapp/services#group).
+
+### People
+
+Classe des individus issus de l\'annuaire LDAP. Ce sont des objets de ce
+type qui sont retournés par défaut par les méthodes de recherche du
+service
+[People](/develop/unicaen2/moduleunicaenunicaenapp/services#people).
+
+### Structure
+
+Classe des structures issues de l\'annuaire LDAP. Ce sont des objets de
+ce type qui sont retournés par défaut par les méthodes de recherche du
+service
+[Structure](/develop/unicaen2/moduleunicaenunicaenapp/services#structure).
diff --git a/doc/Plugins.dokuwiki b/doc/Plugins.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..53de7dfce8494d57a42301b52269a8c4a782168d
--- /dev/null
+++ b/doc/Plugins.dokuwiki
@@ -0,0 +1,447 @@
+====== Plugins de contrôleur (controller plugins) ======
+
+===== Confirm =====
+
+Ce plugin forme un couple avec l'aide de vue du même nom.
+
+Dans l'exemple qui suit, on affiche une fenêtre (modale) de demande de confirmation lorsqu'on clique sur le lien de suppression d'un contrat.
+
+La requête de suppression du contrat est une requête AJAX (grâce au mécanisme installé sur les liens classés "ajax-modal", cf. ''AjaxModalListener'' dans "unicaen.js").
+
+Voici la vue affichant le lien de suppression d'un contrat :
+
+<code html index.phtml>
+<?php $urlSuppression  = $this->url('contrat/supprimer', ['contrat' => $this->contrat->getId()]); ?>
+
+<a href="<?php echo $urlSuppression ?>" class="btn btn-danger ajax-modal"
+   data-event="event-contrat-suppr">Supprimer <?php echo $this->contrat ?></a>
+
+<script>
+    $("body").on("event-contrat-suppr", function(event, data) {
+        event.div.modal('hide');  // ferme la fenêtre modale de demande de confirmation
+        window.location.reload(); // recharge la page courante
+    });
+</script>
+</code>
+
+Voici le contrôleur répondant aux requêtes de suppression de contrat (route 'contrat/supprimer') :
+
+<code php ContratController.php>
+public function supprimerAction()
+{
+    $result = $this->confirm()->execute();
+
+    // si un tableau est retourné par le plugin, l'opération a été confirmée
+    if (is_array($result)) {
+        $this->getServiceContrat()->supprimer($this->contrat);
+        $this->flashMessenger()->addSuccessMessage("Suppression du contrat effectuée avec succès.");
+    }
+
+    // récupération du modèle de vue auprès du plugin et passage de variables classique
+    $viewModel = $this->confirm()->getViewModel();
+
+    $viewModel->setVariables([
+        'title'   => "Suppression d'un contrat",
+        'contrat' => $this->contrat,
+    ]);
+
+    return $viewModel;
+}
+</code>
+
+Et voici la vue associée à l'action ''supprimerAction'' qui utilise l'aide de vue ''confirm'' :
+
+<code php supprimer.phtml>
+<h1><?php echo $this->title ?></h1>
+<?php
+echo $this->confirm("Confirmez-vous la suppression du contrat $this->contrat, svp ?");
+</code>
+
+
+
+===== Uploader =====
+
+Cf. [[develop:unicaen2:moduleunicaenunicaenapp:controllerplugins:uploader|page dédiée.]]
+
+
+===== LdapGroupService =====
+Fournit le service d'accès aux groupes LDAP. Exemple :
+<file php>
+$this->ldapGroupService()->getMapper()->findOneByDn('cn=support_info,ou=groups,dc=unicaen,dc=fr');
+</file>
+
+===== LdapPeopleService =====
+Fournit le service d'accès aux individus LDAP. Exemple :
+<file php>
+$this->ldapPeopleService()->getMapper()->findOneByUsername('gauthierb');
+</file>
+
+===== LdapStructureService =====
+Fournit le service d'accès aux structures LDAP. Exemple :
+<file php>
+$this->ldapStructureService()->getMapper()->findOneByDnOrCodeEntite('HS_C68');
+</file>
+
+===== Mail =====
+Fournit le service d'envoi de mail.
+Vous pouvez le paramétrer dans la config locale du module UnicaenApp, exemple :
+<file php unicaen-app.local.php>
+/**
+ * Options concernant l'envoi de mail par l'application.
+ */
+'mail' => array(
+    // transport des mails
+    'transport_options' => array(
+        'host' => 'smtp.unicaen.fr',
+        'port' => 25,
+    ),
+    // adresses à substituer à celles des destinataires originaux ('CURRENT_USER' équivaut à l'utilisateur connecté)
+    'redirect_to' => array('bertrand.gauthier@unicaen.fr', 'CURRENT_USER'),
+    // désactivation totale de l'envoi de mail par l'application
+    'do_not_send' => false,
+),
+</file>
+Exemple d'utilisation :
+<file php>
+// corps au format HTML
+$part = new \Zend\Mime\Part($html);
+$part->type = \Zend\Mime\Mime::TYPE_HTML;
+$part->charset = 'UTF-8';
+$body = new \Zend\Mime\Message();
+$body->addPart($part);
+// init
+$message = new \Zend\Mail\Message();
+$message->setEncoding('UTF-8')
+        ->addTo($ldapPeople->getMail(), $ldapPeople->getNomComplet(true))
+        ->setFrom('dsi.applications@unicaen.fr', "Contact Application")
+        ->setSubject("Blocage de votre connexion WiFi eduroam")
+        ->setBody($body);
+// envoi
+$this->mail()->send($message);
+</file>
+
+===== ModalInnerViewModel =====
+Encapsule le résultat d'une vue (ViewModel) dans le marquage attendu par Bootstrap pour l'intégrer dans une fenêtre modale.
+
+Exemple :
+<file php DemoController.php>
+public function modalAction()
+{
+    $terminal = $this->getRequest()->isXmlHttpRequest();
+
+    $viewModel = new \Zend\View\Model\ViewModel();
+    $viewModel->setTemplate('application/demo/modal')
+            ->setTerminal($terminal) // Turn off the layout for AJAX requests
+            ->setVariables(array(
+                'name'     => "Bertrand",
+            ));
+
+    if ($terminal) {
+        return $this->modalInnerViewModel($viewModel, "Fenêtre modale", false);
+    }
+
+    return $viewModel;
+}
+</file>
+
+
+===== PopoverInnerViewModel =====
+Encapsule le résultat d'une vue (ViewModel) dans le marquage attendu par Bootstrap pour l'intégrer dans un élément "popover".
+
+Exemple :
+<file php DemoController.php>
+public function popoverAction()
+{
+    $terminal = $this->getRequest()->isXmlHttpRequest();
+
+    $viewModel = new \Zend\View\Model\ViewModel();
+    $viewModel->setTemplate('application/demo/popover')
+            ->setTerminal($terminal) // Turn off the layout for AJAX requests
+            ->setVariables(array(
+                'name'     => "Laurent",
+            ));
+
+    if ($terminal) {
+        return $this->popoverInnerViewModel($viewModel, "Titre du Popover", false);
+    }
+
+    return $viewModel;
+}
+</file>
+
+===== MultipageForm =====
+
+Plugin de contrôleur facilitant la mise en œuvre d'un processus de saisie en plusieurs étapes (formulaire multi-pages) :
+  * les infos saisies à l'étape courante doivent être valides pour pouvoir passer à l'étape suivante
+  * stockage en session des infos saisies à chaque étape
+  * application du pattern [[http://fr.wikipedia.org/wiki/Post-Redirect-Get|Post-Redirect-Get]] (délégation au [[http://framework.zend.com/manual/2.0/en/modules/zend.mvc.plugins.html#the-post-redirect-get-plugin|plugin fourni par ZF2]])
+  * possibilité de revenir en arrière sans perdre les infos saisies (même en cas de saisie partielle à l'étape courante)
+
+==== Formulaire ====
+
+L'idée est de créer un formulaire global héritant de la classe ''\UnicaenApp\Form\MultipageForm'' composé de plusieurs fieldsets, chaque fieldset correspondant à une étape de la saisie.
+
+Exemple d'un formulaire de contact avec une saisie en 3 étapes (identité, coordonnées, divers) :
+
+<file php ContactForm.php>
+namespace Application\Form;
+
+use UnicaenApp\Form\MultipageForm;
+use Zend\Form\Element\Csrf;
+use Zend\Form\Element\Submit;
+
+class ContactForm extends MultipageForm
+{
+    public function __construct($name = null, $options = array())
+    {
+        parent::__construct($name, $options);
+
+        $this->addFieldsetFirst(new IdentFieldset('identite'))
+             ->addFieldsetNext(new CoordFieldset('coordonnees'))
+             ->addFieldsetLast(new DiversFieldset('divers'))
+             ->add(new Csrf('csrf'))
+             ->add(new Submit('ajouter', array('label'=>"Ajouter")));
+    }
+}
+</file>
+
+Chaque fieldset hérite de la classe ''\Zend\Form\Fieldset''.
+
+Exemple du fieldset de saisie des coordonnées :
+
+<file php CoordFieldset.php>
+namespace Application\Form;
+
+use Zend\Form\Element\Text;
+use Zend\Form\Element\Textarea;
+use Zend\Form\Fieldset;
+use Zend\InputFilter\InputFilterProviderInterface;
+
+class CoordFieldset extends Fieldset implements InputFilterProviderInterface
+{
+    public function __construct($name = null, $options = array())
+    {
+        parent::__construct($name, $options);
+
+        $this->setLabel("Coordonnées")
+             ->add(new Textarea('adresse', array('label'=>"Adresse postale")))
+             ->add(new Text('email', array('label'=>"Adresse mail")));
+    }
+
+    public function getInputFilterSpecification()
+    {
+        return array(
+            'adresse' => array(
+                'required' => false,
+                'filters'  => array(
+                    array('name' => '\Zend\Filter\StringTrim'),
+                ),
+            ),
+            'email' => array(
+                'required' => true,
+                'filters'  => array(
+                    array('name' => '\Zend\Filter\StringTrim'),
+                ),
+                'validators' => array(
+                    array(
+                        'name'=> 'NotEmpty',
+                        'break_chain_on_failure' => true,
+                        'options' => array(
+                            'messages' => array('isEmpty' => "L'adresse mail est requise"),
+                        )
+                    ),
+                    array(
+                        'name' => '\Zend\Validator\EmailAddress',
+                        'options' => array(
+                            'messages' => array('emailAddressInvalidFormat' => "L'adresse mail spécifiée est invalide"),
+                        ),
+                    ),
+                ),
+            ),
+        );
+    }
+}
+</file>
+
+<note>Implémenter l'interface ''InputFilterProviderInterface'' (méthode ''getInputFilterSpecification()'') n'est pas obligatoire mais elle permet de fournir les règles de filtrage et de validation des valeurs saisie dans le fieldset.</note>
+
+==== Contrôleur ====
+
+Pour fonctionner, le plugin a besoin de connaître :
+  * Le formulaire global :
+      * Il doit donc être fourni à **chaque** invocation du plugin : ''$this->multipageForm($this->getForm())''.
+  * La table de correspondance qui associe à chacun des fieldsets du formulaire une action du contrôleur :
+    * Par défaut, le nom de l'action correspondant à un fieldset est le nom de ce fieldset. Par exemple, le fieldset ''new CoordFieldset('coordonnees')'' correspondra à l'action nommée ''coordonnees'' (i.e. méthode ''coordonneesAction()'' du contrôleur).
+    * Cette table de correspondance est obtenue par le plugin auprès du formulaire, vous pouvez donc spécifier une table différente en appelant la méthode ''setFieldsetActionMapping()'' du formulaire.
+  * L'action du contrôleur correspondant au récapitulatif et à la demande de confirmation de la saisie complète (facultatif)
+    * Par défaut, c'est '''confirmer'''.
+    * Elle est obtenue par le plugin auprès du formulaire, vous pouvez donc spécifier une action différente grâce à la méthode ''setConfirmAction()'' du formulaire.
+  * L'action du contrôleur correspondant à l'enregistrement de la saisie
+    * Par défaut, c'est '''enregistrer'''.
+    * Elle est obtenue par le plugin auprès du formulaire, vous pouvez donc spécifier une action différente grâce à la méthode ''setProcessAction()'' du formulaire.
+  * L'action du contrôleur correspondant à l'abandon à tout moment de la saisie
+    * Par défaut, c'est '''annuler'''.
+    * Elle est obtenue par le plugin auprès du formulaire, vous pouvez donc spécifier une action différente grâce à la méthode ''setCancelAction()'' du formulaire.
+
+<note important>
+NB: il est possible de spécifier un préfixe à appliquer à **tous** les noms d'actions grâce à la méthode ''setActionPrefix()'' du formulaire.
+Dans notre exemple, le préfixe '''ajouter-''' est spécifié pour pointer vers les méthodes :
+  * ''ajouterIdentiteAction()'' plutôt que vers ''identiteAction()''
+  * ''ajouterCoordonneesAction()'' plutôt que vers ''coordonneesAction()''
+  * ''ajouterDiversAction()'' plutôt que vers ''diversAction()''
+  * ''ajouterConfirmerAction()'' plutôt que vers ''confirmerAction()''
+  * ''ajouterEnregistrerAction()'' plutôt que vers ''enregistrerAction()''
+  * ''ajouterAnnulerAction()'' plutôt que vers ''annulerAction()''
+</note>
+
+Exemple de contrôleur pour la saisie d'une candidature :
+  * l'action ''ajouter'' est le point d'entrée du processus
+  * l'action ''ajouter-identite'' est la première étape
+  * l'action ''ajouter-coordonnees'' est la deuxième étape
+  * l'action ''ajouter-divers'' est la troisième et dernière étape
+  * l'action ''ajouter-confirmer'' est chargée de récapituler les infos saisies et demander confirmation
+  * l'action ''ajouter-enregistrer'' est chargée d'enregistrer les infos saisies dans une base de données par exemple
+  * l'action ''ajouter-annuler'' correspond à la requête d'abandon de la saisie
+
+<file php ContactController.php>
+namespace Application\Controller;
+
+use Application\Form\ContactForm;
+use UnicaenApp\Form\MultipageForm;
+use Zend\Http\PhpEnvironment\Response;
+use Zend\Mvc\Controller\AbstractActionController;
+
+class DemandeController extends AbstractActionController
+{
+    protected $form;
+
+    public function ajouterAction()
+    {
+        return $this->multipageForm($this->getForm())->start(); // réinit du plugin et redirection vers la 1ère étape
+    }
+
+    public function ajouterIdentiteAction()
+    {
+        return $this->multipageForm($this->getForm())->process();
+    }
+
+    public function ajouterCoordonneesAction()
+    {
+        return $this->multipageForm($this->getForm())->process();
+    }
+
+    public function ajouterDiversAction()
+    {
+        return $this->multipageForm($this->getForm())->process();
+    }
+
+    public function ajouterAnnulerAction()
+    {
+        return $this->redirect()->toRoute('home');
+    }
+
+    public function ajouterConfirmerAction()
+    {
+        $response = $this->multipageForm($this->getForm())->process();
+        if ($response instanceof Response) {
+            return $response;
+        }
+        return array('form' => $this->getForm());
+    }
+
+    public function ajouterEnregistrerAction()
+    {
+        $data = $this->multipageForm($this->getForm())->getFormSessionData();
+        // ...
+        // enregistrement en base de données (par exemple)
+        // ...
+        return $this->redirect()->toRoute('home');
+    }
+
+    protected function getForm()
+    {
+        if (null === $this->form) {
+            $this->form = new ContactForm('contact');
+            $this->form->setActionPrefix('ajouter-');
+        }
+        return $this->form;
+    }
+}
+</file>
+
+==== Vue ====
+
+=== Étape de saisie ===
+Un script de vue par étape de saisie doit être fourni, dans lequel l'aide de vue ''MultipageFormFieldset'' doit être utilisée.
+Cette aide de vue génère un titre (h2) indiquant "Étape i sur N", le code HTML du fieldset de l'étape en cours ainsi que les boutons de navigation.
+
+Exemple :
+<file php ajouter-coordonnees.phtml>
+<h1>Formulaire de contact</h1>
+<?php echo $this->multipageFormFieldset(); ?>
+</file>
+
+<note important>Inscrivez le même titre h1 pour toutes les étapes.</note>
+
+=== Confirmation ===
+
+Vous devez fournir le script de vue pour la page de récapitulatif/confirmation et utiliser l'aide de vue ''MultipageFormRecap''. Cette aide de vue génère automatiquement la liste de toutes les informations saisies et les boutons de navigation.
+
+Exemple :
+<file php ajouter-confirmer.phtml>
+<h1>Formulaire de contact</h1>
+<h2>Récapitulatif et confirmation des informations saisies</h2>
+<?php echo $this->multipageFormRecap(); ?>
+</file>
+
+<note important>
+L'aide de vue ''MultipageFormRecap'' explore automatiquement tous les éléments visibles de tous les fieldsets du formulaire afin de collecter leur labels et leur valeurs et les présenter sous la forme d'une liste de définition (dl).
+Si vous souhaitez maîtriser plus finement la façon dont sont constitués les labels ou les valeurs d'un fieldset, faites implémenter à la classe de ce fieldset l'interface ''\UnicaenApp\Form\MultipageFormFieldsetInterface'' pour fournir via la méthode ''getLabelsAndValues()'' les labels et valeurs de tous les éléments de ce fieldset.
+
+Exemple :
+<file php DiversFieldset.php>
+namespace Application\Form;
+
+use Unicaen\Exception;
+use UnicaenApp\Form\MultipageFormFieldsetInterface;
+use Zend\Form\Element\Text;
+use Zend\Form\Fieldset;
+use Zend\InputFilter\InputFilterProviderInterface;
+
+class DiversFieldset extends Fieldset implements InputFilterProviderInterface, MultipageFormFieldsetInterface
+{
+    public function __construct($name = null, $options = array())
+    {
+        parent::__construct($name, $options);
+
+        $this->setLabel("Divers")
+                ->add(new Text('age', array('label' => "Age")))
+                ->add(new Text('profession', array('label' => "Profession", 'placeholder' => "Fonctionnaire")));
+    }
+
+    ...
+
+    public function getLabelsAndValues($data = null)
+    {
+        if (null === $data && !($data = $this->getValue())) {
+            throw new Exception("Aucune donnée saisie disponible.");
+        }
+        if (array_key_exists($this->getName(), $data)) {
+            $data = $data[$this->getName()];
+        }
+
+        $values = array();
+
+        $elem = $this->get('age');
+        $values[$elem->getName()]['label'] = $elem->getLabel();
+        $values[$elem->getName()]['value'] = $data[$elem->getName()] ? $data[$elem->getName()] . " ans" : "Non renseigné";
+
+        $elem = $this->get('profession');
+        $values[$elem->getName()]['label'] = $elem->getLabel();
+        $values[$elem->getName()]['value'] = $data[$elem->getName()] ? $data[$elem->getName()] : "Chômage";
+
+        return $values;
+    }
+}
+</file>
+</note>
diff --git a/doc/Plugins.md b/doc/Plugins.md
new file mode 100644
index 0000000000000000000000000000000000000000..ff7f73b7a18ca9cae3e20dca90056ab3a06c0f9b
--- /dev/null
+++ b/doc/Plugins.md
@@ -0,0 +1,517 @@
+Plugins de contrôleur (controller plugins)
+==========================================
+
+Confirm
+-------
+
+Ce plugin forme un couple avec l\'aide de vue du même nom.
+
+Dans l\'exemple qui suit, on affiche une fenêtre (modale) de demande de
+confirmation lorsqu\'on clique sur le lien de suppression d\'un contrat.
+
+La requête de suppression du contrat est une requête AJAX (grâce au
+mécanisme installé sur les liens classés \"ajax-modal\", cf.
+`AjaxModalListener` dans \"unicaen.js\").
+
+Voici la vue affichant le lien de suppression d\'un contrat :
+
+``` {.html}
+<?php $urlSuppression  = $this->url('contrat/supprimer', ['contrat' => $this->contrat->getId()]); ?>
+
+<a href="<?php echo $urlSuppression ?>" class="btn btn-danger ajax-modal"
+   data-event="event-contrat-suppr">Supprimer <?php echo $this->contrat ?></a>
+
+<script>
+    $("body").on("event-contrat-suppr", function(event, data) {
+        event.div.modal('hide');  // ferme la fenêtre modale de demande de confirmation
+        window.location.reload(); // recharge la page courante
+    });
+</script>
+```
+
+Voici le contrôleur répondant aux requêtes de suppression de contrat
+(route \'contrat/supprimer\') :
+
+``` {.php}
+public function supprimerAction()
+{
+    $result = $this->confirm()->execute();
+
+    // si un tableau est retourné par le plugin, l'opération a été confirmée
+    if (is_array($result)) {
+        $this->getServiceContrat()->supprimer($this->contrat);
+        $this->flashMessenger()->addSuccessMessage("Suppression du contrat effectuée avec succès.");
+    }
+
+    // récupération du modèle de vue auprès du plugin et passage de variables classique
+    $viewModel = $this->confirm()->getViewModel();
+
+    $viewModel->setVariables([
+        'title'   => "Suppression d'un contrat",
+        'contrat' => $this->contrat,
+    ]);
+
+    return $viewModel;
+}
+```
+
+Et voici la vue associée à l\'action `supprimerAction` qui utilise
+l\'aide de vue `confirm` :
+
+``` {.php}
+<h1><?php echo $this->title ?></h1>
+<?php
+echo $this->confirm("Confirmez-vous la suppression du contrat $this->contrat, svp ?");
+```
+
+Uploader
+--------
+
+Cf. [page
+dédiée.](/develop/unicaen2/moduleunicaenunicaenapp/controllerplugins/uploader)
+
+LdapGroupService
+----------------
+
+Fournit le service d\'accès aux groupes LDAP. Exemple :
+
+``` {.php}
+$this->ldapGroupService()->getMapper()->findOneByDn('cn=support_info,ou=groups,dc=unicaen,dc=fr');
+```
+
+LdapPeopleService
+-----------------
+
+Fournit le service d\'accès aux individus LDAP. Exemple :
+
+``` {.php}
+$this->ldapPeopleService()->getMapper()->findOneByUsername('gauthierb');
+```
+
+LdapStructureService
+--------------------
+
+Fournit le service d\'accès aux structures LDAP. Exemple :
+
+``` {.php}
+$this->ldapStructureService()->getMapper()->findOneByDnOrCodeEntite('HS_C68');
+```
+
+Mail
+----
+
+Fournit le service d\'envoi de mail. Vous pouvez le paramétrer dans la
+config locale du module UnicaenApp, exemple :
+
+``` {.php}
+/**
+ * Options concernant l'envoi de mail par l'application.
+ */
+'mail' => array(
+    // transport des mails
+    'transport_options' => array(
+        'host' => 'smtp.unicaen.fr',
+        'port' => 25,
+    ),
+    // adresses à substituer à celles des destinataires originaux ('CURRENT_USER' équivaut à l'utilisateur connecté)
+    'redirect_to' => array('bertrand.gauthier@unicaen.fr', 'CURRENT_USER'),
+    // désactivation totale de l'envoi de mail par l'application
+    'do_not_send' => false,
+),
+```
+
+Exemple d\'utilisation :
+
+``` {.php}
+// corps au format HTML
+$part = new \Zend\Mime\Part($html);
+$part->type = \Zend\Mime\Mime::TYPE_HTML;
+$part->charset = 'UTF-8';
+$body = new \Zend\Mime\Message();
+$body->addPart($part);
+// init
+$message = new \Zend\Mail\Message();
+$message->setEncoding('UTF-8')
+        ->addTo($ldapPeople->getMail(), $ldapPeople->getNomComplet(true))
+        ->setFrom('dsi.applications@unicaen.fr', "Contact Application")
+        ->setSubject("Blocage de votre connexion WiFi eduroam")
+        ->setBody($body);
+// envoi
+$this->mail()->send($message);
+```
+
+ModalInnerViewModel
+-------------------
+
+Encapsule le résultat d\'une vue (ViewModel) dans le marquage attendu
+par Bootstrap pour l\'intégrer dans une fenêtre modale.
+
+Exemple :
+
+``` {.php}
+public function modalAction()
+{
+    $terminal = $this->getRequest()->isXmlHttpRequest();
+
+    $viewModel = new \Zend\View\Model\ViewModel();
+    $viewModel->setTemplate('application/demo/modal')
+            ->setTerminal($terminal) // Turn off the layout for AJAX requests
+            ->setVariables(array(
+                'name'     => "Bertrand",
+            ));
+
+    if ($terminal) {
+        return $this->modalInnerViewModel($viewModel, "Fenêtre modale", false);
+    }
+
+    return $viewModel;
+}
+```
+
+PopoverInnerViewModel
+---------------------
+
+Encapsule le résultat d\'une vue (ViewModel) dans le marquage attendu
+par Bootstrap pour l\'intégrer dans un élément \"popover\".
+
+Exemple :
+
+``` {.php}
+public function popoverAction()
+{
+    $terminal = $this->getRequest()->isXmlHttpRequest();
+
+    $viewModel = new \Zend\View\Model\ViewModel();
+    $viewModel->setTemplate('application/demo/popover')
+            ->setTerminal($terminal) // Turn off the layout for AJAX requests
+            ->setVariables(array(
+                'name'     => "Laurent",
+            ));
+
+    if ($terminal) {
+        return $this->popoverInnerViewModel($viewModel, "Titre du Popover", false);
+    }
+
+    return $viewModel;
+}
+```
+
+MultipageForm
+-------------
+
+Plugin de contrôleur facilitant la mise en œuvre d\'un processus de
+saisie en plusieurs étapes (formulaire multi-pages) :
+
+-   les infos saisies à l\'étape courante doivent être valides pour
+    pouvoir passer à l\'étape suivante
+-   stockage en session des infos saisies à chaque étape
+-   application du pattern
+    [Post-Redirect-Get](http://fr.wikipedia.org/wiki/Post-Redirect-Get)
+    (délégation au [plugin fourni par
+    ZF2](http://framework.zend.com/manual/2.0/en/modules/zend.mvc.plugins.html#the-post-redirect-get-plugin))
+-   possibilité de revenir en arrière sans perdre les infos saisies
+    (même en cas de saisie partielle à l\'étape courante)
+
+### Formulaire
+
+L\'idée est de créer un formulaire global héritant de la classe
+`\UnicaenApp\Form\MultipageForm` composé de plusieurs fieldsets, chaque
+fieldset correspondant à une étape de la saisie.
+
+Exemple d\'un formulaire de contact avec une saisie en 3 étapes
+(identité, coordonnées, divers) :
+
+``` {.php}
+namespace Application\Form;
+
+use UnicaenApp\Form\MultipageForm;
+use Zend\Form\Element\Csrf;
+use Zend\Form\Element\Submit;
+
+class ContactForm extends MultipageForm
+{
+    public function __construct($name = null, $options = array())
+    {
+        parent::__construct($name, $options);
+
+        $this->addFieldsetFirst(new IdentFieldset('identite'))
+             ->addFieldsetNext(new CoordFieldset('coordonnees'))
+             ->addFieldsetLast(new DiversFieldset('divers'))
+             ->add(new Csrf('csrf'))
+             ->add(new Submit('ajouter', array('label'=>"Ajouter")));
+    }
+}
+```
+
+Chaque fieldset hérite de la classe `\Zend\Form\Fieldset`.
+
+Exemple du fieldset de saisie des coordonnées :
+
+``` {.php}
+namespace Application\Form;
+
+use Zend\Form\Element\Text;
+use Zend\Form\Element\Textarea;
+use Zend\Form\Fieldset;
+use Zend\InputFilter\InputFilterProviderInterface;
+
+class CoordFieldset extends Fieldset implements InputFilterProviderInterface
+{
+    public function __construct($name = null, $options = array())
+    {
+        parent::__construct($name, $options);
+
+        $this->setLabel("Coordonnées")
+             ->add(new Textarea('adresse', array('label'=>"Adresse postale")))
+             ->add(new Text('email', array('label'=>"Adresse mail")));
+    }
+
+    public function getInputFilterSpecification()
+    {
+        return array(
+            'adresse' => array(
+                'required' => false,
+                'filters'  => array(
+                    array('name' => '\Zend\Filter\StringTrim'),
+                ),
+            ),
+            'email' => array(
+                'required' => true,
+                'filters'  => array(
+                    array('name' => '\Zend\Filter\StringTrim'),
+                ),
+                'validators' => array(
+                    array(
+                        'name'=> 'NotEmpty',
+                        'break_chain_on_failure' => true,
+                        'options' => array(
+                            'messages' => array('isEmpty' => "L'adresse mail est requise"),
+                        )
+                    ),
+                    array(
+                        'name' => '\Zend\Validator\EmailAddress',
+                        'options' => array(
+                            'messages' => array('emailAddressInvalidFormat' => "L'adresse mail spécifiée est invalide"),
+                        ),
+                    ),
+                ),
+            ),
+        );
+    }
+}
+```
+
+\<note\>Implémenter l\'interface `InputFilterProviderInterface` (méthode
+`getInputFilterSpecification()`) n\'est pas obligatoire mais elle permet
+de fournir les règles de filtrage et de validation des valeurs saisie
+dans le fieldset.\</note\>
+
+### Contrôleur
+
+Pour fonctionner, le plugin a besoin de connaître :
+
+-   Le formulaire global :
+
+<!-- -->
+
+        * Il doit donc être fourni à **chaque** invocation du plugin : ''$this->multipageForm($this->getForm())''.
+    * La table de correspondance qui associe à chacun des fieldsets du formulaire une action du contrôleur :
+      * Par défaut, le nom de l'action correspondant à un fieldset est le nom de ce fieldset. Par exemple, le fieldset ''new CoordFieldset('coordonnees')'' correspondra à l'action nommée ''coordonnees'' (i.e. méthode ''coordonneesAction()'' du contrôleur).
+      * Cette table de correspondance est obtenue par le plugin auprès du formulaire, vous pouvez donc spécifier une table différente en appelant la méthode ''setFieldsetActionMapping()'' du formulaire.
+    * L'action du contrôleur correspondant au récapitulatif et à la demande de confirmation de la saisie complète (facultatif)
+      * Par défaut, c'est '''confirmer'''.
+      * Elle est obtenue par le plugin auprès du formulaire, vous pouvez donc spécifier une action différente grâce à la méthode ''setConfirmAction()'' du formulaire.
+    * L'action du contrôleur correspondant à l'enregistrement de la saisie
+      * Par défaut, c'est '''enregistrer'''.
+      * Elle est obtenue par le plugin auprès du formulaire, vous pouvez donc spécifier une action différente grâce à la méthode ''setProcessAction()'' du formulaire.
+    * L'action du contrôleur correspondant à l'abandon à tout moment de la saisie
+      * Par défaut, c'est '''annuler'''.
+      * Elle est obtenue par le plugin auprès du formulaire, vous pouvez donc spécifier une action différente grâce à la méthode ''setCancelAction()'' du formulaire.
+
+\<note important\> NB: il est possible de spécifier un préfixe à
+appliquer à **tous** les noms d\'actions grâce à la méthode
+`setActionPrefix()` du formulaire. Dans notre exemple, le préfixe
+`'ajouter-`\' est spécifié pour pointer vers les méthodes :
+
+-   `ajouterIdentiteAction()` plutôt que vers `identiteAction()`
+-   `ajouterCoordonneesAction()` plutôt que vers `coordonneesAction()`
+-   `ajouterDiversAction()` plutôt que vers `diversAction()`
+-   `ajouterConfirmerAction()` plutôt que vers `confirmerAction()`
+-   `ajouterEnregistrerAction()` plutôt que vers `enregistrerAction()`
+-   `ajouterAnnulerAction()` plutôt que vers `annulerAction()`
+
+\</note\>
+
+Exemple de contrôleur pour la saisie d\'une candidature :
+
+-   l\'action `ajouter` est le point d\'entrée du processus
+-   l\'action `ajouter-identite` est la première étape
+-   l\'action `ajouter-coordonnees` est la deuxième étape
+-   l\'action `ajouter-divers` est la troisième et dernière étape
+-   l\'action `ajouter-confirmer` est chargée de récapituler les infos
+    saisies et demander confirmation
+-   l\'action `ajouter-enregistrer` est chargée d\'enregistrer les infos
+    saisies dans une base de données par exemple
+-   l\'action `ajouter-annuler` correspond à la requête d\'abandon de la
+    saisie
+
+``` {.php}
+namespace Application\Controller;
+
+use Application\Form\ContactForm;
+use UnicaenApp\Form\MultipageForm;
+use Zend\Http\PhpEnvironment\Response;
+use Zend\Mvc\Controller\AbstractActionController;
+
+class DemandeController extends AbstractActionController
+{
+    protected $form;
+
+    public function ajouterAction()
+    {
+        return $this->multipageForm($this->getForm())->start(); // réinit du plugin et redirection vers la 1ère étape
+    }
+
+    public function ajouterIdentiteAction()
+    {
+        return $this->multipageForm($this->getForm())->process();
+    }
+
+    public function ajouterCoordonneesAction()
+    {
+        return $this->multipageForm($this->getForm())->process();
+    }
+
+    public function ajouterDiversAction()
+    {
+        return $this->multipageForm($this->getForm())->process();
+    }
+
+    public function ajouterAnnulerAction()
+    {
+        return $this->redirect()->toRoute('home');
+    }
+
+    public function ajouterConfirmerAction()
+    {
+        $response = $this->multipageForm($this->getForm())->process();
+        if ($response instanceof Response) {
+            return $response;
+        }
+        return array('form' => $this->getForm());
+    }
+
+    public function ajouterEnregistrerAction()
+    {
+        $data = $this->multipageForm($this->getForm())->getFormSessionData();
+        // ...
+        // enregistrement en base de données (par exemple)
+        // ...
+        return $this->redirect()->toRoute('home');
+    }
+
+    protected function getForm()
+    {
+        if (null === $this->form) {
+            $this->form = new ContactForm('contact');
+            $this->form->setActionPrefix('ajouter-');
+        }
+        return $this->form;
+    }
+}
+```
+
+### Vue
+
+#### Étape de saisie
+
+Un script de vue par étape de saisie doit être fourni, dans lequel
+l\'aide de vue `MultipageFormFieldset` doit être utilisée. Cette aide de
+vue génère un titre (h2) indiquant \"Étape i sur N\", le code HTML du
+fieldset de l\'étape en cours ainsi que les boutons de navigation.
+
+Exemple :
+
+``` {.php}
+<h1>Formulaire de contact</h1>
+<?php echo $this->multipageFormFieldset(); ?>
+```
+
+\<note important\>Inscrivez le même titre h1 pour toutes les
+étapes.\</note\>
+
+#### Confirmation
+
+Vous devez fournir le script de vue pour la page de
+récapitulatif/confirmation et utiliser l\'aide de vue
+`MultipageFormRecap`. Cette aide de vue génère automatiquement la liste
+de toutes les informations saisies et les boutons de navigation.
+
+Exemple :
+
+``` {.php}
+<h1>Formulaire de contact</h1>
+<h2>Récapitulatif et confirmation des informations saisies</h2>
+<?php echo $this->multipageFormRecap(); ?>
+```
+
+\<note important\> L\'aide de vue `MultipageFormRecap` explore
+automatiquement tous les éléments visibles de tous les fieldsets du
+formulaire afin de collecter leur labels et leur valeurs et les
+présenter sous la forme d\'une liste de définition (dl). Si vous
+souhaitez maîtriser plus finement la façon dont sont constitués les
+labels ou les valeurs d\'un fieldset, faites implémenter à la classe de
+ce fieldset l\'interface
+`\UnicaenApp\Form\MultipageFormFieldsetInterface` pour fournir via la
+méthode `getLabelsAndValues()` les labels et valeurs de tous les
+éléments de ce fieldset.
+
+Exemple :
+
+``` {.php}
+namespace Application\Form;
+
+use Unicaen\Exception;
+use UnicaenApp\Form\MultipageFormFieldsetInterface;
+use Zend\Form\Element\Text;
+use Zend\Form\Fieldset;
+use Zend\InputFilter\InputFilterProviderInterface;
+
+class DiversFieldset extends Fieldset implements InputFilterProviderInterface, MultipageFormFieldsetInterface
+{
+    public function __construct($name = null, $options = array())
+    {
+        parent::__construct($name, $options);
+
+        $this->setLabel("Divers")
+                ->add(new Text('age', array('label' => "Age")))
+                ->add(new Text('profession', array('label' => "Profession", 'placeholder' => "Fonctionnaire")));
+    }
+
+    ...
+
+    public function getLabelsAndValues($data = null)
+    {
+        if (null === $data && !($data = $this->getValue())) {
+            throw new Exception("Aucune donnée saisie disponible.");
+        }
+        if (array_key_exists($this->getName(), $data)) {
+            $data = $data[$this->getName()];
+        }
+
+        $values = array();
+
+        $elem = $this->get('age');
+        $values[$elem->getName()]['label'] = $elem->getLabel();
+        $values[$elem->getName()]['value'] = $data[$elem->getName()] ? $data[$elem->getName()] . " ans" : "Non renseigné";
+
+        $elem = $this->get('profession');
+        $values[$elem->getName()]['label'] = $elem->getLabel();
+        $values[$elem->getName()]['value'] = $data[$elem->getName()] ? $data[$elem->getName()] : "Chômage";
+
+        return $values;
+    }
+}
+```
+
+\</note\>
diff --git a/doc/Services.dokuwiki b/doc/Services.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..c06f3be53ab41a588575dbd51e88a2febaea0868
--- /dev/null
+++ b/doc/Services.dokuwiki
@@ -0,0 +1,24 @@
+====== Services ======
+
+===== LDAP =====
+
+==== Group ====
+Service d'accès aux groupes de l'annuaire LDAP.
+Avec le gestionnaire de service, ce service est accessible ainsi :
+<file php>
+$service = $sm->get('ldap_group_service'); /* @var $service \UnicaenApp\Service\Ldap\Group */
+</file>
+
+==== People ====
+Service d'accès aux individus de l'annuaire LDAP.
+Avec le gestionnaire de service, ce service est accessible ainsi :
+<file php>
+$service = $sm->get('ldap_people_service'); /* @var $service \UnicaenApp\Service\Ldap\People */
+</file>
+
+==== Structure ====
+Service d'accès aux structures de l'annuaire LDAP.
+Avec le gestionnaire de service, ce service est accessible ainsi :
+<file php>
+$service = $sm->get('ldap_structure_service'); /* @var $service \UnicaenApp\Service\Ldap\Structure */
+</file>
\ No newline at end of file
diff --git a/doc/Services.md b/doc/Services.md
new file mode 100644
index 0000000000000000000000000000000000000000..f378eba242279e80b14ca67b4ffd3ea5203d2599
--- /dev/null
+++ b/doc/Services.md
@@ -0,0 +1,32 @@
+Services
+========
+
+LDAP
+----
+
+### Group
+
+Service d\'accès aux groupes de l\'annuaire LDAP. Avec le gestionnaire
+de service, ce service est accessible ainsi :
+
+``` {.php}
+$service = $sm->get('ldap_group_service'); /* @var $service \UnicaenApp\Service\Ldap\Group */
+```
+
+### People
+
+Service d\'accès aux individus de l\'annuaire LDAP. Avec le gestionnaire
+de service, ce service est accessible ainsi :
+
+``` {.php}
+$service = $sm->get('ldap_people_service'); /* @var $service \UnicaenApp\Service\Ldap\People */
+```
+
+### Structure
+
+Service d\'accès aux structures de l\'annuaire LDAP. Avec le
+gestionnaire de service, ce service est accessible ainsi :
+
+``` {.php}
+$service = $sm->get('ldap_structure_service'); /* @var $service \UnicaenApp\Service\Ldap\Structure */
+```
diff --git a/doc/Validateurs.dokuwiki b/doc/Validateurs.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..30d218bd1c811a7bd799c36844fc5cbf51f7871b
--- /dev/null
+++ b/doc/Validateurs.dokuwiki
@@ -0,0 +1,32 @@
+====== Validateurs ======
+
+
+===== EarlierThan =====
+Permet de valider qu'une date est antérieure (ou égale) à une autre.
+Exemple:
+<code php>
+$now = new \DateTime; // instant présent
+$validator = new \UnicaenApp\Validator\EarlierThan(array('max' => $now, 'inclusive' => true));
+$date = \DateTime::createFromFormat('d-m-Y', '29-11-2012');
+if ($validator->isValid($date) {
+    // $date est antérieure ou égale à $now
+}
+else {
+    // $date est postérieure à $now
+}
+</code>
+
+===== LaterThan =====
+Permet de valider qu'une date est postérieure (ou égale) à une autre.
+Exemple:
+<code php>
+$now = new \DateTime; // instant présent
+$validator = new \UnicaenApp\Validator\LaterThan(array('min' => $now, 'inclusive' => false));
+$date = \DateTime::createFromFormat('d-m-Y', '29-11-2012');
+if ($validator->isValid($date) {
+    // $date est postérieure à $now
+}
+else {
+    // $date est antérieure ou égale à $now
+}
+</code>
diff --git a/doc/Validateurs.md b/doc/Validateurs.md
new file mode 100644
index 0000000000000000000000000000000000000000..9d6c0e97c7dd902eb0623af79beecf9636ee3a80
--- /dev/null
+++ b/doc/Validateurs.md
@@ -0,0 +1,38 @@
+Validateurs
+===========
+
+EarlierThan
+-----------
+
+Permet de valider qu\'une date est antérieure (ou égale) à une autre.
+Exemple:
+
+``` {.php}
+$now = new \DateTime; // instant présent
+$validator = new \UnicaenApp\Validator\EarlierThan(array('max' => $now, 'inclusive' => true));
+$date = \DateTime::createFromFormat('d-m-Y', '29-11-2012');
+if ($validator->isValid($date) {
+    // $date est antérieure ou égale à $now
+}
+else {
+    // $date est postérieure à $now
+}
+```
+
+LaterThan
+---------
+
+Permet de valider qu\'une date est postérieure (ou égale) à une autre.
+Exemple:
+
+``` {.php}
+$now = new \DateTime; // instant présent
+$validator = new \UnicaenApp\Validator\LaterThan(array('min' => $now, 'inclusive' => false));
+$date = \DateTime::createFromFormat('d-m-Y', '29-11-2012');
+if ($validator->isValid($date) {
+    // $date est postérieure à $now
+}
+else {
+    // $date est antérieure ou égale à $now
+}
+```
diff --git a/doc/ViewHelpers.dokuwiki b/doc/ViewHelpers.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..f394d0740f46b94266842c43ab812dc68651b9ba
--- /dev/null
+++ b/doc/ViewHelpers.dokuwiki
@@ -0,0 +1,1063 @@
+====== Aides de vue (view helpers) ======
+
+===== Application =====
+====== AppConnection ======
+
+Aide de vue générant l'encart de connexion à l'application.
+
+<note important>En l'occurence, cette aide de vue ne génère rien car une application n'utilisant que le module UnicaenApp ne fournit rien permettant à l'utilisateur de se connecter. Elle existe simplement dans le but d'être surchargée par le module UnicaenUser.</note>
+
+====== AppInfos ======
+
+Aide de vue permettant d'afficher les informations concernant l'application (nom, description, version, etc.), informations issues de la config du module UnicaenApp.
+
+Exemple d'utilisation :
+<file php apropos.phtml>
+<h1>À propos de cette application</h1>
+<?php echo $this->appInfos()->setHtmlListFormat() ?>
+</file>
+
+Exemple d'utilisation avec accès aux attributs individuellement :
+<file php apropos.phtml>
+<h1>À propos de cette application</h1>
+<dl>
+    <dt>Nom</dt>
+    <dd><?php echo $this->appInfos()->nom; ?></dd>
+    <dt>Description</dt>
+    <dd><?php echo $this->appInfos()->desc; ?></dd>
+    <dt>Version</dt>
+    <dd><?php echo $this->appInfos()->version; ?></dd>
+    <dt>Date</dt>
+    <dd><?php echo $this->appInfos()->date; ?></dd>
+    <dt>Contact</dt>
+    <dd><?php echo implode($this->appInfos()->contact->toArray(), ', '); ?></dd>
+</dl>
+</file>
+
+====== AppLink ======
+
+Aide de vue dessinant le nom de l'application sous forme de lien pointant vers la page d'accueil, ou une ancre si l'on s'y trouve déjà.
+
+Exemple d'utilisation :
+<file php layout.phtml>
+<?php echo $this->appLink(); ?>
+</file>
+
+
+===== Messages =====
+====== Messenger ======
+
+Aide de vue permettant de stocker une liste de messages d'information de différentes sévérités et de générer le code HTML pour les afficher (affublés d'un icône correspondant à leur sévérité).
+
+Offre la possibilité d'importer les messages du FlashMessenger pour les mettre en forme de la même manière.
+
+Exemple d'utilisation :
+
+<file php index.phtml>
+<?php echo $this->messenger()->addMessage("Info",       \UnicaenApp\View\Helper\Messenger::INFO)
+                              ->addMessage("Ok",         \UnicaenApp\View\Helper\Messenger::OK)
+                              ->addMessage("Attention",  \UnicaenApp\View\Helper\Messenger::WARNING)
+                              ->addMessage("Erreur",     \UnicaenApp\View\Helper\Messenger::ERROR); ?>
+</file>
+
+Rendu correspondant :
+<file html>
+<p class="messenger info text-info">
+    <i class="icon-info-sign"></i>
+    Info
+</p>
+<p class="messenger success text-success">
+    <i class="icon-ok-sign"></i>
+    Ok
+</p>
+<p class="messenger warning text-warning">
+    <i class="icon-warning-sign"></i>
+    Attention
+</p>
+<p class="messenger error text-error">
+    <i class="icon-exclamation-sign"></i>
+    Erreur
+</p>
+</file>
+
+Pour importer les éventuels messages présents dans le FlashMessenger (sans les effacer pour autant), invoquez l'aide de vue avec le premier paramètre à ''true'' :
+
+<file php index.phtml>
+
+<?php echo $this->messenger()
+                ->addMessagesFromFlashMessenger()
+                ->addMessage("Info", \UnicaenApp\View\Helper\Messenger::INFO); ?>
+</file>
+
+
+
+
+===== Navigation =====
+====== FilAriane ======
+
+Aide de vue générant le code HTML du "fil d'Ariane" (breadcrumbs) à partir des pages de navigation de l'application.
+
+Utilisation :
+<file php layout.phtml>
+<?php echo $this->navigation('navigation')->filAriane(); ?>
+</file>
+
+Exemple de rendu :
+<file html>
+<ul class="breadcrumb"><li><a href="/gesnum/" title="Page d'accueil de l'application">Accueil</a> <span class="divider">&gt;</span> </li>
+    <li><a href="/gesnum/demande" title="Liste de toute les demandes">Demandes</a> <span class="divider">&gt;</span> </li>
+    <li><a href="/gesnum/demande/recherche" title="Recherche de demandes">Recherche</a> <span class="divider">&gt;</span> </li>
+    <li>Détaillée</li>
+</ul>
+</file>
+
+====== MenuContextuel ======
+
+Aide de vue vouée au rendu d'une sous-partie du menu de navigation courant (qui tient compte des ACL), avec différents filtrages et manipulations d'attributs possibles.
+
+Hérite de \Zend\View\Helper\Navigation\Menu.
+
+<note important>Cette aide de vue ne fonctionne que si elle est en mesure de trouver la page de navigation active. Vous devez donc veiller à avoir une navigation bien configurée  : il doit être possible de déterminer la page de navigation correspondant à l'URL de la page courante.
+
+Dans le cas contraire, un message du genre "Page de navigation active introuvable." est affiché.
+</note>
+
+<note>
+La page de navigation active est la page de référence dont seules les pages filles sont prises en compte par cette aide de vue.
+</note>
+
+Dans la suite, considérez que la variable ''$mc'' est valuée comme suit :
+<file php>
+$mc = $this->navigation('navigation')->menuContextuel();
+</file>
+
+===== Filtrage des pages filles =====
+
+|              ^ Utilité ^ Exemple ^
+^ withParam    | Ne prendre en compte que les pages possédant le paramètre spécifié. Substitution possible de la valeur initiale du paramètre par une autre. | <file php>$mc->withParam('id', 12)</file> |
+^ withoutParam | Que les pages ne possédant pas le paramètre spécifié. | <file php>$mc->withoutParam('admin')</file> |
+^ withProp     | Ne prendre en compte que les pages possédant l'attribut spécifié et ayant la valeur spécifiée éventuelle. | <file php>$mc->withProp('class', 'iconify')</file> |
+^ withoutProp  | Ne prendre en compte que les pages ne possédant pas l'attribut spécifié | <file php>$mc->withoutProp('sitemap')</file> |
+^ withTarget  | Présence requise de la propriété de page 'withtarget'. Si une cible est spécifiée, un paramètre ayant la valeur de cette cible est ajouté à chaque page. Si la cible spécifiée est un objet avec un attribut 'id' on utilise cet attribut ; sinon on utilise sa représentation littéral. | <file php>$mc->withTarget($entity)</file> |
+^ withoutTarget  | Absence requise de la propriété de page 'withtarget' | <file php>$mc->withoutTarget()</file> |
+^ includeIf  | Spécifie une page à inclure, uniquement si la condition spécifiée est remplie. | <file php>$mc->includeIf($production, 'envoyer-mail')</file> |
+^ includeRouteIf  | Exclut toutes les pages à l'exception de celle spécifiée par sa route, uniquement si la condition est remplie (principe de la liste blanche). | <file php>$mc->includeRouteIf($condition, 'admin')</file> |
+^ except  | Spécifie une page à exclure par le nom de l'action et/ou du contrôleur et/ou de ses paramètres. | <file php>$mc->except('exporter')</file> |
+^ exceptIf  | Spécifie une page à exclure, uniquement si la condition spécifiée est remplie. | <file php>$mc->exceptIf(true === $testing, 'envoyer-mail')</file> |
+^ exceptRoute  | Exclut une page spécifiée par sa route. | <file php>$mc->exceptRoute('admin')</file> |
+^ exceptRouteIf  | Exclut une page spécifiée par sa route, uniquement si la condition spécifiée est remplie.. | <file php>$mc->exceptRouteIf($condition, 'admin')</file> |
+
+<note>NB: tous ces filtres sont cumulatifs (certains pouvant en contredire d'autres mais là c'est votre problèmes).</note>
+
+===== Ajout / suppression de paramètres ou d'attributs =====
+
+|              ^ Utilité ^ Exemple ^
+^ addParam    | Ajoute un paramètre à toutes les pages systématiquement. | <file php>$mc->addParam('format', 'html')</file> |
+^ addParams    | Ajoute plusieurs paramètres à toutes les pages systématiquement. | <file php>$mc->addParams(array('format' => 'html', 'type' => 2))</file> |
+^ removeParam    | Supprime un paramètre à toutes les pages systématiquement. | <file php>$mc->removeParam('format')</file> |
+^ addProp    | Ajoute une propriété toutes les pages systématiquement. | <file php>$mc->addProp('class', 'iconify modal-action')</file> |
+^ addProps    | Ajoute plusieurs propriétés à toutes les pages systématiquement. | <file php>$mc->addProps(array('class' => 'iconify', 'sitemap' => false))</file> |
+
+===== Réinitialisation =====
+
+Pour remettre à zero les filtres et les ajouts/suppressions de paramètres/propriétés :
+<file php>$mc->reset()</file>
+
+===== Icônes =====
+
+Si la classe CSS (propriété de page ''class'') ''iconify'' est présente sur une page ainsi que la propriété ''icon'', cette aide de vue remplacera le label du lien hypertexte par l'icône correspondant.
+
+Par exemple, imaginons que l'on ait la page de navigation suivante :
+<file php>
+'afficher' => array(
+    'label'  => 'Afficher cette ligne',
+    'title'  => "Afficher le détails de la ligne {id}",
+    'route'  => 'closerligne',
+    'params' => array('action' => 'afficher'),
+    'visible' => false,
+    'class' => 'action-afficher',
+    'icon' => 'glyphicon glyphicon-eye-open',
+    'withtarget' => true,
+),
+</file>
+
+Et ceci dans une vue :
+<file html index.phtml>
+<?php echo $this->navigation('navigation')->menuContextuel()->withTarget($ligne)->addProp('class' ,'iconify'); ?>
+</file>
+
+Le lien hypertexte généré ressemblerait à cela :
+<file html index.phtml>
+<a href="/closer/ligne/afficher/id-12"
+   class="action-afficher iconify"
+   title="Afficher le détails de la ligne 12"><span class="glyphicon glyphicon-eye-open"></span></a>
+</file>
+
+===== Classe CSS pour événement jQuery =====
+
+Cette aide de vue ajoute à chaque page de navigation une classe CSS ''event_xxx''. Cette classe est utile si vous utilisez l'aide de vue [[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:ajaxmodalform|AjaxModalDialog]].
+
+Le nom de cette classe est construit à partir de 2 propriétés de la la page de navigation :
+  * propriété ''route'' si elle est présente, c'est le nom de la route ;
+  * propriété ''action'', c'est le nom de l'action.
+Selon le motif suivant : ''event_[route_]action''.
+
+Par exemple, imaginons que l'on ait la page de navigation suivante :
+<file php>
+'afficher' => array(
+    'label'  => 'Afficher cette ligne',
+    'route'  => 'closerligne',
+    'params' => array('action' => 'afficher'),
+    'visible' => false,
+    'withtarget' => true,
+),
+</file>
+
+Et ceci dans une vue :
+<file html index.phtml>
+<?php echo $this->navigation('navigation')->menuContextuel()->withTarget($ligne); ?>
+</file>
+
+Le lien hypertexte généré ressemblerait à cela :
+<file html index.phtml>
+<a href="/closer/ligne/afficher/id-12" class="event_closerligne_afficher">Afficher cette ligne</a>
+</file>
+===== Exemple d'utilisation typique =====
+
+
+<file php index.phtml>
+if ($ligneId) {
+    echo $this->navigation('navigation')->menuContextuel()
+            ->withTarget($ligneId) // ajoutera un paramètre 'id' ayant la valeur $ligneId
+            ->setUlClass('action list-inline')
+            ->addParams(array('type' => $ligneType))
+            ->addProp('class', 'iconify modal-action');
+}
+else {
+    echo $this->navigation('navigation')->menuContextuel()
+            ->withoutTarget()
+            ->setUlClass('action')
+            ->addParams(array('type' => $ligneType))
+            ->addProp('class', 'modal-action');
+}
+</file>
+
+====== MenuPiedDePage ======
+
+Aide de vue héritant de ''\Zend\View\Helper\Navigation\Menu'' qui fabrique le code HTML du menu de pied de page à partir des pages de navigation de l'application.
+
+<note important>Seules les pages possédant la propriété ''footer'' à ''true'' sont inclues dans le menu de pied de page.</note>
+<note important>Les ACL de l'application sont respectées par cette aide de vue.</note>
+
+===== Pages de navigation utilisées pour nos exemples =====
+
+<file php module.config.php>
+namespace Application;
+
+return array(
+    ...
+    'navigation' => array(
+        'default' => array(
+            'home' => array(
+                'label'      => 'Accueil',
+                'title'      => "Page d'accueil de l'application",
+                'route'      => 'home',
+                'order'      => -100, // make sure home is the first page
+                'pages' => array(
+                    'etab' => array(
+                        'label'   => "Université de Caen - Basse Normandie",
+                        'title'   => "Page d'accueil du site de l'Université de Caen - Basse Normandie",
+                        'uri'     => 'http://www.unicaen.fr/',
+                        'class'   => 'ucbn',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                    ),
+                    'apropos' => array(
+                        'label'   => "À propos",
+                        'title'   => "À propos de cette application",
+                        'route'   => 'apropos',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'contact' => array(
+                        'label'   => 'Contact',
+                        'title'   => "Contact concernant l'application",
+                        'route'   => 'contact',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'plan' => array(
+                        'label'   => 'Plan de navigation',
+                        'title'   => "Plan de navigation au sein de l'application",
+                        'route'   => 'plan',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'ml' => array(
+                        'label'   => 'Mentions légales',
+                        'title'   => "Mentions légales",
+                        'uri'     => 'http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'il' => array(
+                        'label'   => 'Informatique et libertés',
+                        'title'   => "Informatique et libertés",
+                        'uri'     => 'http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                ),
+            ),
+        ),
+    ),
+);
+</file>
+
+===== Utilisation =====
+
+<file php layout.phtml>
+<?php echo $this->navigation('navigation')->menuPiedDePage(); ?>
+</file>
+
+Exemple de rendu correspondant :
+<file html>
+<ul class="navigation">
+    <li>
+        <a href="http://www.unicaen.fr/" class="ucbn" title="Page d'accueil du site de l'Université de Caen - Basse Normandie">Université de Caen - Basse Normandie</a>
+    </li>
+    <li>
+        <a href="/gesnum/apropos" title="À propos de cette application">À propos</a>
+    </li>
+    <li>
+        <a href="/gesnum/contact" title="Contact concernant l'application">Contact</a>
+    </li>
+    <li>
+        <a href="/gesnum/plan" title="Plan de navigation au sein de l'application">Plan de navigation</a>
+    </li>
+    <li>
+        <a href="http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/" title="Mentions légales">Mentions légales</a>
+    </li>
+    <li>
+        <a href="http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/" title="Informatique et libertés">Informatique et libertés</a>
+    </li>
+</ul>
+</file>
+
+====== MenuPrincipal ======
+
+Aide de vue héritant de ''\Zend\View\Helper\Navigation\Menu'' qui fabrique le code HTML du menu principal (pages de niveau 1 et 2 seulement) de l'application à partir des pages de navigation de l'application.
+
+<note important>Les ACL de l'application sont respectées par cette aide de vue.</note>
+
+===== Pages de navigation utilisées pour nos exemples =====
+
+Exemple :
+<file php module.config.php>
+namespace Application;
+
+return array(
+    ...
+    'navigation' => array(
+        'default' => array(
+            'home' => array(
+                'pages' => array(
+                    'demande' => array(
+                        'label'      => 'Demandes',
+                        'title'      => "Liste de toute les demandes",
+                        'action'     => 'index',
+                        'route'      => 'demande',
+                        'resource'   => 'controller/Application\Controller\Demande',  // ici, pas d'action spécifiée
+                        'pages' => array(
+                            'ajouter' => array(
+                                'label'      => 'Nouvelle demande',
+                                'title'      => "Faire une nouvelle demande",
+                                'action'     => 'ajouter',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                                'pages' => array(
+                                    'identite' => array(
+                                        'label'      => 'Identité',
+                                        'title'      => "Saisie des informations concernant l'identité",
+                                        'action'     => 'ajouter-identite',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                    'coordonnees' => array(
+                                        'label'      => 'Coordonnées',
+                                        'title'      => "Saisie des coordonnées",
+                                        'action'     => 'ajouter-coordonnees',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                    'divers' => array(
+                                        'label'      => 'Divers',
+                                        'title'      => "Saisie des informations diverses",
+                                        'action'     => 'ajouter-divers',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                ),
+                            ),
+                            'mienne' => array(
+                                'label'      => 'Miennes',
+                                'title'      => "Liste des demandes me concernant",
+                                'action'     => 'miennes',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                            ),
+                            'recherche' => array(
+                                'label'      => 'Recherche',
+                                'title'      => "Recherche de demandes",
+                                'action'     => 'recherche',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                                'pages' => array(
+                                    'simple' => array(
+                                        'label'      => 'Simple',
+                                        'title'      => "Recherche simple de demandes",
+                                        'action'     => 'recherche',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                    ),
+                                    'detaillee' => array(
+                                        'label'      => 'Détaillée',
+                                        'title'      => "Recherche détaillée de demandes",
+                                        'action'     => 'recherche-detaillee',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                    ),
+                                ),
+                            ),
+                        ),
+                    ),
+                ),
+            ),
+        ),
+    ),
+);
+</file>
+
+===== Le premier niveau seulement =====
+
+Exemple :
+<file php layout.phtml>
+<?php echo $this->navigation('navigation')->menuPrincipal(); ?>
+</file>
+
+Exemple de rendu correspondant :
+<file html>
+<ul class="nav menu-principal">
+    <li class="dropdown">
+        <a title="Liste de toute les demandes" href="/gesnum/demande">Demandes</a>
+    </li>
+    <li class="active dropdown">
+        <a href="/gesnum/administration/utilisateur">Administration</a>
+    </li>
+</ul>
+</file>
+
+===== Les deux premiers niveaux =====
+
+Exemple :
+<file php layout.phtml>
+<?php echo $this->navigation('navigation')->menuPrincipal()->setMaxDepth(2); ?>
+</file>
+
+Exemple de rendu correspondant :
+<file html>
+<nav>
+    <ul class="nav menu-principal">
+        <li class="active dropdown">
+            <a href="/gesnum/demande" title="Liste de toute les demandes">Demandes</a>
+            <ul class="dropdown-menu" style="display: none;">
+                <li class="active dropdown">
+                    <a href="/gesnum/demande/ajouter" title="Faire une nouvelle demande">Nouvelle demande</a>
+                </li>
+                <li class="">
+                    <a href="/gesnum/demande/miennes" title="Liste des demandes me concernant">Miennes</a>
+                </li>
+                <li class="dropdown">
+                    <a href="/gesnum/demande/recherche" title="Recherche de demandes">Recherche</a>
+                </li>
+            </ul>
+        </li>
+        <li class="dropdown">
+            <a href="/gesnum/administration/utilisateur">Administration</a>
+            <ul class="dropdown-menu" style="display: none;">
+                <li class="dropdown">
+                    <a href="/gesnum/administration/utilisateur">Utilisateurs</a>
+                </li>
+                <li class="dropdown">
+                    <a href="/gesnum/administration/parametre">Paramètres</a>
+                </li>
+            </ul>
+        </li>
+    </ul>
+</nav>
+</file>
+
+====== MenuSecondaire ======
+
+Aide de vue héritant de ''\Zend\View\Helper\Navigation\Menu'' qui fabrique le code HTML du menu secondaire de l'application à partir des pages de navigation de l'application.
+
+<note important>Les ACL de l'application sont respectées par cette aide de vue.</note>
+
+Utilisation :
+<file php layout.phtml>
+<?php echo $this->navigation('navigation')->menuSecondaire(); ?>
+</file>
+
+Exemple de rendu correspondant :
+<file html>
+<ul class="menu-secondaire">
+    <li>
+        <a href="/gesnum/demande/ajouter" title="Faire une nouvelle demande">Nouvelle demande</a>
+    </li>
+    <li>
+        <a href="/gesnum/demande/miennes" title="Liste des demandes me concernant">Miennes</a>
+    </li>
+    <li class="active">
+        <a href="/gesnum/demande/recherche" title="Recherche de demandes">Recherche</a>
+        <ul>
+            <li>
+                <a href="/gesnum/demande/recherche" title="Recherche simple de demandes">Simple</a>
+            </li>
+            <li class="active">
+                <a href="/gesnum/demande/recherche-detaillee" title="Recherche détaillée de demandes">Détaillée</a>
+            </li>
+        </ul>
+    </li>
+</ul>
+</file>
+
+====== Plan ======
+
+Aide de vue générant le code HTML du plan de navigation à partir des pages de navigation de l'application.
+
+<note important>Seules les pages de navigation visibles ou possédant la propriété ''sitemap'' à ''true'' sont inclues dans le plan.</note>
+<note important>Les ACL de l'application sont respectées par cette aide de vue.</note>
+
+===== Pages de navigation utilisées pour l'exemple =====
+
+Les pages de navigation sont issues de la fusion du fichier de config du module "vendor/UnicaenApp"" :
+
+<file php module.config.php>
+namespace UnicaenApp;
+
+return array(
+    ...
+    'navigation' => array(
+        'default' => array(
+            'home' => array(
+                'label'      => 'Accueil',
+                'title'      => "Page d'accueil de l'application",
+                'route'      => 'home',
+                'order'      => -100, // make sure home is the first page
+                'pages' => array(
+                    'etab' => array(
+                        'label'   => "Université de Caen - Basse Normandie",
+                        'title'   => "Page d'accueil du site de l'Université de Caen - Basse Normandie",
+                        'uri'     => 'http://www.unicaen.fr/',
+                        'class'   => 'ucbn',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                    ),
+                    'apropos' => array(
+                        'label'   => "À propos",
+                        'title'   => "À propos de cette application",
+                        'route'   => 'apropos',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'contact' => array(
+                        'label'   => 'Contact',
+                        'title'   => "Contact concernant l'application",
+                        'route'   => 'contact',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'plan' => array(
+                        'label'   => 'Plan de navigation',
+                        'title'   => "Plan de navigation au sein de l'application",
+                        'route'   => 'plan',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'ml' => array(
+                        'label'   => 'Mentions légales',
+                        'title'   => "Mentions légales",
+                        'uri'     => 'http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'il' => array(
+                        'label'   => 'Informatique et libertés',
+                        'title'   => "Informatique et libertés",
+                        'uri'     => 'http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                ),
+            ),
+        ),
+    ),
+);
+</file>
+
+... et du module "module/Application" :
+
+<file php module.config.php>
+namespace Application;
+
+return array(
+    ...
+    'navigation' => array(
+        'default' => array(
+            'home' => array(
+                'pages' => array(
+                    'demande' => array(
+                        'label'      => 'Demandes',
+                        'title'      => "Liste de toute les demandes",
+                        'action'     => 'index',
+                        'route'      => 'demande',
+                        'resource'   => 'controller/Application\Controller\Demande',  // ici, pas d'action spécifiée
+                        'pages' => array(
+                            'ajouter' => array(
+                                'label'      => 'Nouvelle demande',
+                                'title'      => "Faire une nouvelle demande",
+                                'action'     => 'ajouter',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                                'pages' => array(
+                                    'identite' => array(
+                                        'label'      => 'Identité',
+                                        'title'      => "Saisie des informations concernant l'identité",
+                                        'action'     => 'ajouter-identite',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                    'coordonnees' => array(
+                                        'label'      => 'Coordonnées',
+                                        'title'      => "Saisie des coordonnées",
+                                        'action'     => 'ajouter-coordonnees',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                    'divers' => array(
+                                        'label'      => 'Divers',
+                                        'title'      => "Saisie des informations diverses",
+                                        'action'     => 'ajouter-divers',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                ),
+                            ),
+                            'mienne' => array(
+                                'label'      => 'Miennes',
+                                'title'      => "Liste des demandes me concernant",
+                                'action'     => 'miennes',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                            ),
+                            'recherche' => array(
+                                'label'      => 'Recherche',
+                                'title'      => "Recherche de demandes",
+                                'action'     => 'recherche',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                                'pages' => array(
+                                    'simple' => array(
+                                        'label'      => 'Simple',
+                                        'title'      => "Recherche simple de demandes",
+                                        'action'     => 'recherche',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                    ),
+                                    'detaillee' => array(
+                                        'label'      => 'Détaillée',
+                                        'title'      => "Recherche détaillée de demandes",
+                                        'action'     => 'recherche-detaillee',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                    ),
+                                ),
+                            ),
+                        ),
+                    ),
+                ),
+            ),
+        ),
+    ),
+);
+</file>
+
+===== Utilisation =====
+
+<file php plan.phtml>
+<h1>Plan de navigation</h1>
+<?php echo $this->navigation('navigation')->plan(); ?>
+</file>
+
+Exemple de rendu correspondant :
+<file html>
+<h1>Plan de navigation</h1>
+<ul class="menu-footer">
+    <li class="active">
+        <a href="/gesnum/" title="Page d'accueil de l'application">Accueil</a>
+        <ul>
+            <li>
+                <a href="/gesnum/demande" title="Liste de toute les demandes">Demandes</a>
+                <ul>
+                    <li>
+                        <a href="/gesnum/demande/ajouter" title="Faire une nouvelle demande">Nouvelle demande</a>
+                    </li>
+                    <li>
+                        <a href="/gesnum/demande/miennes" title="Liste des demandes me concernant">Miennes</a>
+                    </li>
+                    <li>
+                        <a href="/gesnum/demande/recherche" title="Recherche de demandes">Recherche</a>
+                        <ul>
+                            <li>
+                                <a href="/gesnum/demande/recherche" title="Recherche simple de demandes">Simple</a>
+                            </li>
+                            <li>
+                                <a href="/gesnum/demande/recherche-detaillee" title="Recherche détaillée de demandes">Détaillée</a>
+                            </li>
+                        </ul>
+                    </li>
+                </ul>
+            </li>
+            <li>
+                <a href="/gesnum/apropos" title="À propos de cette application">À propos</a>
+            </li>
+            <li>
+                <a href="/gesnum/contact" title="Contact concernant l'application">Contact</a>
+            </li>
+            <li class="active">
+                <a href="/gesnum/plan" title="Plan de navigation au sein de l'application">Plan de navigation</a>
+            </li>
+            <li>
+                <a href="http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/" title="Mentions légales">Mentions légales</a>
+            </li>
+            <li>
+                <a href="http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/" title="Informatique et libertés">Informatique et libertés</a>
+            </li>
+            <li>
+                <a href="/gesnum/administration/utilisateur">Administration</a>
+                <ul>
+                    <li>
+                        <a href="/gesnum/administration/utilisateur">Utilisateurs</a>
+                        <ul>
+                            <li>
+                                <a href="/gesnum/administration/utilisateur/ajouter">Nouvel utilisateur</a>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <a href="/gesnum/administration/parametre">Paramètres</a>
+                        <ul>
+                            <li>
+                                <a href="/gesnum/administration/parametre/ajouter">Nouveau paramètre</a>
+                            </li>
+                        </ul>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+</file>
+
+
+===== Formulaire =====
+
+====== FormControlGroup ======
+
+Aide de vue générant le marquage HTML complet d'un élément de formulaire à la mode Bootsrap 3 (''div.form-group''), y compris les messages d'erreur de validation.
+
+<file html modifier.phtml>
+<?php echo $this->form()->openTag($form->prepare()) ?>
+<?php echo $this->formControlGroup($form->get('message')->setLabel("Votre message :")); ?>
+<?php echo $this->formSubmit($form->get('submit')->setValue("Enregistrer")); ?>
+<?php echo $this->form()->closeTag() ?>
+</file>
+
+En plus des éléments de formulaire standards, cette aide de vue peut recevoir un élément de type :
+  * \UnicaenApp\Form\Element\DateInfSup
+  * \UnicaenApp\Form\Element\SearchAndSelect
+
+Lorsqu'il y a des messages d'erreur de validation la ''div'' possède la classe CSS ''has-error''.
+
+====== FormDateInfSup ======
+
+Aide de vue générant le code HTML de l'élément de formulaire composite "[[develop:unicaen2:moduleunicaenunicaenapp:form:dateinfsup|DateInfSup]]", c'est à dire :
+  * le champ de saisie de la date inférieure ainsi que son label associé ;
+  * éventuellement, le champ de saisie éventuel de la date supérieure ainsi que son label associé (uniquement si la date supérieure est désactivée) ;
+  * éventuellement, un lien permettant de vider la date supérieure (via jQuery).
+
+====== FormErrors ======
+
+Génère le marquage HTML pour afficher à la fois un message d'erreur global de validation d'un formulaire ainsi que la liste des erreurs de tous les éléments de formulaires.
+
+Exemple :
+
+<file html modifier.phtml>
+<?php echo $this->formErrors($form->prepare(), "Oups!") ?>
+
+<?php echo $this->form()->openTag($form) ?>
+<?php echo $this->formControlGroup($form->get('nom')->setLabel("Votre nom :")); ?>
+<?php echo $this->formControlGroup($form->get('message')->setLabel("Votre message :")); ?>
+<?php echo $this->formSubmit($form->get('submit')->setValue("Enregistrer")); ?>
+<?php echo $this->form()->closeTag() ?>
+</file>
+
+====== FormRowDateInfSup ======
+
+Aide de vue héritant de l'aide de vue standard "FormRow" et générant le code HTML **complet** de l'élément de formulaire composite "[[develop:unicaen2:moduleunicaenunicaenapp:form:dateinfsup|DateInfSup]]", c'est à dire :
+  * le label global de l'élément composite ;
+  * les champs de saisie des dates et leurs labels (délégué à l'aide de vue "[[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:FormDateInfSup|FormDateInfSup]]") ;
+  * les messages d'erreurs de validation.
+
+====== FormSearchAndSelect ======
+
+Aide de vue générant le code HTML de l'élément de formulaire [[develop:unicaen2:moduleunicaenunicaenapp:form:SearchAndSelect|SearchAndSelect]].
+
+Exemple :
+<file php demo.phtml>
+$sas = $form->get('sas')
+        ->setLabel("Recherche :")
+        ->setAutocompleteSource($this->url('demo', array('action' => 'rechercher')));
+echo $this->formLabel($sas);
+echo $this->formSearchAndSelect($sas);
+</file>
+
+Le marquage HTML résultant est le suivant :
+
+<file html>
+<label for="sas">Recherche :</label>
+<input type="text" value="" name="sas[id]" class="sas" id="sas-532c128ecd5c2" style="display: none;">
+<input type="text" value="" name="sas[label]" class="form-control input-sm ui-autocomplete-input" id="sas-532c128ecd5c2-autocomplete" autocomplete="off">
+
+<style>
+    ul.ui-autocomplete {
+        z-index: 5000
+    }
+    .ui-autocomplete-loading {
+        background: white url("//gest.unicaen.fr/images/ajax-loader-r.gif") right center no-repeat;
+    }
+</style>
+
+<script>
+    // cache l'élément contenant l'id de la sélection
+    $("#sas-532c128ecd5c2").css('display', 'none');
+
+    $(function() {
+        // jQuery UI autocomplete
+        var autocomp = $("#sas-532c128ecd5c2-autocomplete");
+        autocomp.autocompleteUnicaen({// autocompleteUnicaen() définie dans "public/js/util.js"
+            elementDomId: 'sas-532c128ecd5c2',
+            source: '/ose/demo/rechercher',
+            minLength: 2,
+            delay: 750
+        });
+    });
+</script>
+</file>
+
+Lorsque le formulaire est soumis, la valeur POSTée pour cet élément ressemble à cela :
+<file php>
+'sas' => array(
+    'id' => '8',      // identifiant unique de l'item sélectionné (issu d'un champ caché)
+    'label' => 'Huit' // texte présent dans le champ de saisie
+)
+</file>
+
+====== MultipageFormFieldset ======
+
+Aide de vue à utiliser dans chaque vue associée aux étapes de saisie d'un formulaire multi-pages.
+
+Cette aide de vue génère un titre (h2) indiquant "Étape i sur N", le code HTML du fieldset de l'étape en cours ainsi que les boutons de navigation.
+
+Exemple :
+<file php ajouter-coordonnees.phtml>
+<h1>Formulaire de contact</h1>
+<?php echo $this->multipageFormFieldset(); ?>
+</file>
+
+<note important>Le fieldset courant est transmis à la vue par le plugin de contrôleur [[develop:unicaen2:moduleunicaenunicaenapp:controllerplugins:multipageform|MultipageForm]] via la variable de vue ''fieldset''.</note>
+
+====== MultipageFormNav ======
+
+Aide de vue générant l'élément de navigation (de type ''\UnicaenApp\Form\Element\MultipageFormNav'') au sein d'un formulaire multi-pages.
+
+====== MultipageFormRecap ======
+
+Aide de vue à utiliser dans la vue associée à l'étape de confirmation de la saisie d'un formulaire multi-pages.
+
+Cette aide de vue génère automatiquement la liste de toutes les informations saisies et les boutons de navigation.
+
+Exemple :
+<file php ajouter-confirmer.phtml>
+<h1>Récapitulatif et confirmation des informations saisies</h1>
+<?php echo $this->multipageFormRecap(); ?>
+</file>
+
+<note important>Le formulaire complet est transmis à la vue par le plugin de contrôleur [[develop:unicaen2:moduleunicaenunicaenapp:controllerplugins:multipageform|MultipageForm]] via la variable de vue ''form''.</note>
+
+<note important>
+Cette aide de vue explore automatiquement tous les éléments visibles de tous les fieldsets du formulaire afin de collecter leur labels et leur valeurs et les présenter sous la forme d'une liste de définition (dl).
+Si vous souhaitez maîtriser plus finement la façon dont sont constitués les labels ou les valeurs d'un fieldset, faites implémenter à la classe de ce fieldset l'interface ''\UnicaenApp\Form\MultipageFormFieldsetInterface'' pour fournir via la méthode ''getLabelsAndValues()'' les labels et valeurs de tous les éléments de ce fieldset.
+</note>
+====== MultipageFormRow ======
+
+Aide de vue générant chaque élément d'un fieldset de formulaire multi-page.
+
+Délègue le travail à l'aide de vue standard ''FormRow'' pour tous les éléments, sauf pour ceux de type ''MultipageFormNav'' traitée par l'aide de vue ''[[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:multipageformnav|MultipageFormNav]]''.
+
+
+===== Divers =====
+====== ToggleDetails ======
+
+Aide de vue qui génère un lien permettant d'afficher/masquer un container.
+
+<file php>
+
+<?php echo $this->toggleDetails('div-depot-initial', true)
+    ->setShowDetailsIconClass('glyphicon glyphicon-chevron-up')
+    ->setHideDetailsIconClass('glyphicon glyphicon-chevron-down')
+    ->setShowDetailsTitle("Parental Advisory : Explicit Content")
+    ->setHideDetailsTitle("Click to hide!")
+    ->setShowDetailsLabel('Show me')
+    ->setHideDetailsLabel('No more!') ?>
+<p style="display: none" id="div-depot-initial">
+    Bla bla bla...
+</p>
+</file>
+
+Le deuxième argument de ''$this->toggleDetails()'' permet d'activer ou désactiver le mécanisme de restauration de l'état de visibilité du container au chargement de la page (état mémorisé dans le localStorage du navigateur).
+
+<note>Si vous retirez le ''style="display: none"'' du container pour qu'il soit visible par défaut, l'aide de vue s'adapte automatiquement.</note>
+====== Aide de vue Tag ======
+
+Cette aide de vue sert à afficher des tags HTML de manière sécurisée, en particulier pour prévenir le "défaçement" de site Web.
+
+En voici quelques exemples d'utilisation :
+
+<code php>
+
+
+echo $this->tag('h1')->text('Ti<span>tre</span>' );
+// Résultat : <h1>Titre</h1>
+
+echo $this->tag('h1')->escaped('Ti<span>tre</span>');
+// Résultat : <h1>Ti&lt;span&gt;tre&lt;/span&gt;</h1>
+
+echo $this->tag('h1')->html('Ti<span>tre</span>');
+// Résultat : <h1>Ti<span>tre</span></h1>
+
+
+
+echo $this->tag('div', [
+                    'class' => 'row',
+                    'data-index' => 2
+                ]);
+// Résultat : <div class="row" data-index="2">
+
+
+echo $this->tag('h2');
+echo 'Titre H2';
+echo $this->tag('h2')->close();
+// Résultat : <h2>Titre H2</h2>
+
+</code>
+
+Et voici un exemple plus complet avec un cas de "défaçement" de site évité grâce à l'échappement de caractères :
+<code php>
+<?php
+$data = [
+    [
+        'term' => 'Bonjour',
+        'title' => 'Bonne journée',
+        'description' => 'Ceci est une politesse du matin lorsque nous nous retrouvons'
+    ],
+    [
+        'term' => 'Bonsoir',
+        'title' => 'Bonne soirée "><div style="background-color:black;font-size:90pt;color:white;position:absolute;left:0px;top:0px;width:100%;height:100%">Bouh ceci est un défaçement</div>',
+        'description' => 'Ceci <b>est</b> une <i>politesse</i> du soir lorsqu\'il est temps de partir...'
+    ],
+];
+
+// Bon exemple !!
+echo '<dl>';
+foreach( $data as $item ){
+    echo $this->tag('dt', ['title' => $item['title']])->text($item['term']);
+    echo $this->tag('dd')->html($item['description'], ['b']); // on laisse passer le gras ici (b)
+}
+echo '</dl>';
+
+// Résultat :
+
+?>
+<dl>
+<dt title="Bonne&#x20;journ&#xE9;e">Bonjour</dt>
+<dd>Ceci est une politesse du matin lorsque nous nous retrouvons</dd>
+<dt title="Bonne&#x20;soir&#xE9;e&#x20;&quot;&gt;&lt;div&#x20;style&#x3D;&quot;background-color&#x3A;black&#x3B;font-size&#x3A;90pt&#x3B;color&#x3A;white&#x3B;position&#x3A;absolute&#x3B;left&#x3A;0px&#x3B;top&#x3A;0px&#x3B;width&#x3A;100&#x25;&#x3B;height&#x3A;100&#x25;&quot;&gt;Bouh&#x20;ceci&#x20;est&#x20;un&#x20;d&#xE9;fa&#xE7;ement&lt;&#x2F;div&gt;">Bonsoir</dt>
+<dd>Ceci <b>est</b> une politesse du soir lorsqu'il est temps de partir...</dd>
+</dl>
+
+// Et voici le mauvais exemple :
+
+
+<dl>
+<?php foreach( $data as $item ): ?>
+    <dt title="<?php echo $item['title'] ?>"><?php echo $item['term'] ?></dt>
+    <dd><?php echo $item['description'] ?></dd>
+<?php endforeach ?>
+</dl>
+
+</code>
+
+<WRAP center round tip 60%>
+Vous noterez que le code sécurisé n'est pas plus "lourd" à écrire que le code non sécurisé!!
+</WRAP>
+
+===== Autres cas =====
+
+Pour finir, voici un autre exemple d'échappement de chaîne de caractères à utiliser pour écrire du HTML avec le Zend Framework :
+
+<code php><?php
+
+$texte = 'Bonne soirée "><div style="background-color:black;font-size:90pt;color:white;position:absolute;left:0px;top:0px;width:100%;height:100%">Bouh ceci est un défaçement qui a échoué</div>';
+
+// Affichage sécurisé grâce à l'aide de vue standard "escapeHtml"
+?>
+
+<p class="nouvel-exemple">
+<?php echo $this->escapeHtml($texte); ?>
+</p>
+
+
+</code>
+
+
+
+<WRAP center round tip 60%>
+Pour aller plus loin, vous pouvez consulter la documentation de la classe ici : [[https://ldev.unicaen.fr/doc/UnicaenApp/html/class_unicaen_app_1_1_view_1_1_helper_1_1_tag_view_helper.html]]
+</WRAP>
diff --git a/doc/ViewHelpers.md b/doc/ViewHelpers.md
new file mode 100644
index 0000000000000000000000000000000000000000..e474d330234f832aad2f101c0d651a393621e4a8
--- /dev/null
+++ b/doc/ViewHelpers.md
@@ -0,0 +1,1233 @@
+Aides de vue (view helpers)
+===========================
+
+Application
+-----------
+
+AppConnection
+=============
+
+Aide de vue générant l\'encart de connexion à l\'application.
+
+\<note important\>En l\'occurence, cette aide de vue ne génère rien car
+une application n\'utilisant que le module UnicaenApp ne fournit rien
+permettant à l\'utilisateur de se connecter. Elle existe simplement dans
+le but d\'être surchargée par le module UnicaenUser.\</note\>
+
+AppInfos
+========
+
+Aide de vue permettant d\'afficher les informations concernant
+l\'application (nom, description, version, etc.), informations issues de
+la config du module UnicaenApp.
+
+Exemple d\'utilisation :
+
+``` {.php}
+<h1>À propos de cette application</h1>
+<?php echo $this->appInfos()->setHtmlListFormat() ?>
+```
+
+Exemple d\'utilisation avec accès aux attributs individuellement :
+
+``` {.php}
+<h1>À propos de cette application</h1>
+<dl>
+    <dt>Nom</dt>
+    <dd><?php echo $this->appInfos()->nom; ?></dd>
+    <dt>Description</dt>
+    <dd><?php echo $this->appInfos()->desc; ?></dd>
+    <dt>Version</dt>
+    <dd><?php echo $this->appInfos()->version; ?></dd>
+    <dt>Date</dt>
+    <dd><?php echo $this->appInfos()->date; ?></dd>
+    <dt>Contact</dt>
+    <dd><?php echo implode($this->appInfos()->contact->toArray(), ', '); ?></dd>
+</dl>
+```
+
+AppLink
+=======
+
+Aide de vue dessinant le nom de l\'application sous forme de lien
+pointant vers la page d\'accueil, ou une ancre si l\'on s\'y trouve
+déjà.
+
+Exemple d\'utilisation :
+
+``` {.php}
+<?php echo $this->appLink(); ?>
+```
+
+Messages
+--------
+
+Messenger
+=========
+
+Aide de vue permettant de stocker une liste de messages d\'information
+de différentes sévérités et de générer le code HTML pour les afficher
+(affublés d\'un icône correspondant à leur sévérité).
+
+Offre la possibilité d\'importer les messages du FlashMessenger pour les
+mettre en forme de la même manière.
+
+Exemple d\'utilisation :
+
+``` {.php}
+<?php echo $this->messenger()->addMessage("Info",       \UnicaenApp\View\Helper\Messenger::INFO)
+                              ->addMessage("Ok",         \UnicaenApp\View\Helper\Messenger::OK)
+                              ->addMessage("Attention",  \UnicaenApp\View\Helper\Messenger::WARNING)
+                              ->addMessage("Erreur",     \UnicaenApp\View\Helper\Messenger::ERROR); ?>
+```
+
+Rendu correspondant :
+
+``` {.html}
+<p class="messenger info text-info">
+    <i class="icon-info-sign"></i>
+    Info
+</p>
+<p class="messenger success text-success">
+    <i class="icon-ok-sign"></i>
+    Ok
+</p>
+<p class="messenger warning text-warning">
+    <i class="icon-warning-sign"></i>
+    Attention
+</p>
+<p class="messenger error text-error">
+    <i class="icon-exclamation-sign"></i>
+    Erreur
+</p>
+```
+
+Pour importer les éventuels messages présents dans le FlashMessenger
+(sans les effacer pour autant), invoquez l\'aide de vue avec le premier
+paramètre à `true` :
+
+``` {.php}
+
+<?php echo $this->messenger()
+                ->addMessagesFromFlashMessenger()
+                ->addMessage("Info", \UnicaenApp\View\Helper\Messenger::INFO); ?>
+```
+
+Navigation
+----------
+
+FilAriane
+=========
+
+Aide de vue générant le code HTML du \"fil d\'Ariane\" (breadcrumbs) à
+partir des pages de navigation de l\'application.
+
+Utilisation :
+
+``` {.php}
+<?php echo $this->navigation('navigation')->filAriane(); ?>
+```
+
+Exemple de rendu :
+
+``` {.html}
+<ul class="breadcrumb"><li><a href="/gesnum/" title="Page d'accueil de l'application">Accueil</a> <span class="divider">&gt;</span> </li>
+    <li><a href="/gesnum/demande" title="Liste de toute les demandes">Demandes</a> <span class="divider">&gt;</span> </li>
+    <li><a href="/gesnum/demande/recherche" title="Recherche de demandes">Recherche</a> <span class="divider">&gt;</span> </li>
+    <li>Détaillée</li>
+</ul>
+```
+
+MenuContextuel
+==============
+
+Aide de vue vouée au rendu d\'une sous-partie du menu de navigation
+courant (qui tient compte des ACL), avec différents filtrages et
+manipulations d\'attributs possibles.
+
+Hérite de \\Zend\\View\\Helper\\Navigation\\Menu.
+
+\<note important\>Cette aide de vue ne fonctionne que si elle est en
+mesure de trouver la page de navigation active. Vous devez donc veiller
+à avoir une navigation bien configurée : il doit être possible de
+déterminer la page de navigation correspondant à l\'URL de la page
+courante.
+
+Dans le cas contraire, un message du genre \"Page de navigation active
+introuvable.\" est affiché. \</note\>
+
+\<note\> La page de navigation active est la page de référence dont
+seules les pages filles sont prises en compte par cette aide de vue.
+\</note\>
+
+Dans la suite, considérez que la variable `$mc` est valuée comme suit :
+
+``` {.php}
+$mc = $this->navigation('navigation')->menuContextuel();
+```
+
+Filtrage des pages filles
+-------------------------
+
+  ---------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------
+                   Utilité                                                                                                                                                                                                                                                                                        Exemple
+  withParam        Ne prendre en compte que les pages possédant le paramètre spécifié. Substitution possible de la valeur initiale du paramètre par une autre.                                                                                                                                                    `$mc->withParam('id', 12)`{.php}
+  withoutParam     Que les pages ne possédant pas le paramètre spécifié.                                                                                                                                                                                                                                          `$mc->withoutParam('admin')`{.php}
+  withProp         Ne prendre en compte que les pages possédant l\'attribut spécifié et ayant la valeur spécifiée éventuelle.                                                                                                                                                                                     `$mc->withProp('class', 'iconify')`{.php}
+  withoutProp      Ne prendre en compte que les pages ne possédant pas l\'attribut spécifié                                                                                                                                                                                                                       `$mc->withoutProp('sitemap')`{.php}
+  withTarget       Présence requise de la propriété de page \'withtarget\'. Si une cible est spécifiée, un paramètre ayant la valeur de cette cible est ajouté à chaque page. Si la cible spécifiée est un objet avec un attribut \'id\' on utilise cet attribut ; sinon on utilise sa représentation littéral.   `$mc->withTarget($entity)`{.php}
+  withoutTarget    Absence requise de la propriété de page \'withtarget\'                                                                                                                                                                                                                                         `$mc->withoutTarget()`{.php}
+  includeIf        Spécifie une page à inclure, uniquement si la condition spécifiée est remplie.                                                                                                                                                                                                                 `$mc->includeIf($production, 'envoyer-mail')`{.php}
+  includeRouteIf   Exclut toutes les pages à l\'exception de celle spécifiée par sa route, uniquement si la condition est remplie (principe de la liste blanche).                                                                                                                                                 `$mc->includeRouteIf($condition, 'admin')`{.php}
+  except           Spécifie une page à exclure par le nom de l\'action et/ou du contrôleur et/ou de ses paramètres.                                                                                                                                                                                               `$mc->except('exporter')`{.php}
+  exceptIf         Spécifie une page à exclure, uniquement si la condition spécifiée est remplie.                                                                                                                                                                                                                 `$mc->exceptIf(true === $testing, 'envoyer-mail')`{.php}
+  exceptRoute      Exclut une page spécifiée par sa route.                                                                                                                                                                                                                                                        `$mc->exceptRoute('admin')`{.php}
+  exceptRouteIf    Exclut une page spécifiée par sa route, uniquement si la condition spécifiée est remplie..                                                                                                                                                                                                     `$mc->exceptRouteIf($condition, 'admin')`{.php}
+  ---------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------
+
+\<note\>NB: tous ces filtres sont cumulatifs (certains pouvant en
+contredire d\'autres mais là c\'est votre problèmes).\</note\>
+
+Ajout / suppression de paramètres ou d\'attributs
+-------------------------------------------------
+
+  ------------- ------------------------------------------------------------------ ------------------------------------------------------------------------
+                Utilité                                                            Exemple
+  addParam      Ajoute un paramètre à toutes les pages systématiquement.           `$mc->addParam('format', 'html')`{.php}
+  addParams     Ajoute plusieurs paramètres à toutes les pages systématiquement.   `$mc->addParams(array('format' => 'html', 'type' => 2))`{.php}
+  removeParam   Supprime un paramètre à toutes les pages systématiquement.         `$mc->removeParam('format')`{.php}
+  addProp       Ajoute une propriété toutes les pages systématiquement.            `$mc->addProp('class', 'iconify modal-action')`{.php}
+  addProps      Ajoute plusieurs propriétés à toutes les pages systématiquement.   `$mc->addProps(array('class' => 'iconify', 'sitemap' => false))`{.php}
+  ------------- ------------------------------------------------------------------ ------------------------------------------------------------------------
+
+Réinitialisation
+----------------
+
+Pour remettre à zero les filtres et les ajouts/suppressions de
+paramètres/propriétés :
+
+``` {.php}
+$mc->reset()
+```
+
+Icônes
+------
+
+Si la classe CSS (propriété de page `class`) `iconify` est présente sur
+une page ainsi que la propriété `icon`, cette aide de vue remplacera le
+label du lien hypertexte par l\'icône correspondant.
+
+Par exemple, imaginons que l\'on ait la page de navigation suivante :
+
+``` {.php}
+'afficher' => array(
+    'label'  => 'Afficher cette ligne',
+    'title'  => "Afficher le détails de la ligne {id}",
+    'route'  => 'closerligne',
+    'params' => array('action' => 'afficher'),
+    'visible' => false,
+    'class' => 'action-afficher',
+    'icon' => 'glyphicon glyphicon-eye-open',
+    'withtarget' => true,
+),
+```
+
+Et ceci dans une vue :
+
+``` {.html}
+<?php echo $this->navigation('navigation')->menuContextuel()->withTarget($ligne)->addProp('class' ,'iconify'); ?>
+```
+
+Le lien hypertexte généré ressemblerait à cela :
+
+``` {.html}
+<a href="/closer/ligne/afficher/id-12"
+   class="action-afficher iconify"
+   title="Afficher le détails de la ligne 12"><span class="glyphicon glyphicon-eye-open"></span></a>
+```
+
+Classe CSS pour événement jQuery
+--------------------------------
+
+Cette aide de vue ajoute à chaque page de navigation une classe CSS
+`event_xxx`. Cette classe est utile si vous utilisez l\'aide de vue
+[AjaxModalDialog](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/ajaxmodalform).
+
+Le nom de cette classe est construit à partir de 2 propriétés de la la
+page de navigation :
+
+-   propriété `route` si elle est présente, c\'est le nom de la route ;
+-   propriété `action`, c\'est le nom de l\'action.
+
+Selon le motif suivant : `event_[route_]action`.
+
+Par exemple, imaginons que l\'on ait la page de navigation suivante :
+
+``` {.php}
+'afficher' => array(
+    'label'  => 'Afficher cette ligne',
+    'route'  => 'closerligne',
+    'params' => array('action' => 'afficher'),
+    'visible' => false,
+    'withtarget' => true,
+),
+```
+
+Et ceci dans une vue :
+
+``` {.html}
+<?php echo $this->navigation('navigation')->menuContextuel()->withTarget($ligne); ?>
+```
+
+Le lien hypertexte généré ressemblerait à cela :
+
+``` {.html}
+<a href="/closer/ligne/afficher/id-12" class="event_closerligne_afficher">Afficher cette ligne</a>
+```
+
+Exemple d\'utilisation typique
+------------------------------
+
+``` {.php}
+if ($ligneId) {
+    echo $this->navigation('navigation')->menuContextuel()
+            ->withTarget($ligneId) // ajoutera un paramètre 'id' ayant la valeur $ligneId
+            ->setUlClass('action list-inline')
+            ->addParams(array('type' => $ligneType))
+            ->addProp('class', 'iconify modal-action');
+}
+else {
+    echo $this->navigation('navigation')->menuContextuel()
+            ->withoutTarget()
+            ->setUlClass('action')
+            ->addParams(array('type' => $ligneType))
+            ->addProp('class', 'modal-action');
+}
+```
+
+MenuPiedDePage
+==============
+
+Aide de vue héritant de `\Zend\View\Helper\Navigation\Menu` qui fabrique
+le code HTML du menu de pied de page à partir des pages de navigation de
+l\'application.
+
+\<note important\>Seules les pages possédant la propriété `footer` à
+`true` sont inclues dans le menu de pied de page.\</note\> \<note
+important\>Les ACL de l\'application sont respectées par cette aide de
+vue.\</note\>
+
+Pages de navigation utilisées pour nos exemples
+-----------------------------------------------
+
+``` {.php}
+namespace Application;
+
+return array(
+    ...
+    'navigation' => array(
+        'default' => array(
+            'home' => array(
+                'label'      => 'Accueil',
+                'title'      => "Page d'accueil de l'application",
+                'route'      => 'home',
+                'order'      => -100, // make sure home is the first page
+                'pages' => array(
+                    'etab' => array(
+                        'label'   => "Université de Caen - Basse Normandie",
+                        'title'   => "Page d'accueil du site de l'Université de Caen - Basse Normandie",
+                        'uri'     => 'http://www.unicaen.fr/',
+                        'class'   => 'ucbn',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                    ),
+                    'apropos' => array(
+                        'label'   => "À propos",
+                        'title'   => "À propos de cette application",
+                        'route'   => 'apropos',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'contact' => array(
+                        'label'   => 'Contact',
+                        'title'   => "Contact concernant l'application",
+                        'route'   => 'contact',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'plan' => array(
+                        'label'   => 'Plan de navigation',
+                        'title'   => "Plan de navigation au sein de l'application",
+                        'route'   => 'plan',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'ml' => array(
+                        'label'   => 'Mentions légales',
+                        'title'   => "Mentions légales",
+                        'uri'     => 'http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'il' => array(
+                        'label'   => 'Informatique et libertés',
+                        'title'   => "Informatique et libertés",
+                        'uri'     => 'http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                ),
+            ),
+        ),
+    ),
+);
+```
+
+Utilisation
+-----------
+
+``` {.php}
+<?php echo $this->navigation('navigation')->menuPiedDePage(); ?>
+```
+
+Exemple de rendu correspondant :
+
+``` {.html}
+<ul class="navigation">
+    <li>
+        <a href="http://www.unicaen.fr/" class="ucbn" title="Page d'accueil du site de l'Université de Caen - Basse Normandie">Université de Caen - Basse Normandie</a>
+    </li>
+    <li>
+        <a href="/gesnum/apropos" title="À propos de cette application">À propos</a>
+    </li>
+    <li>
+        <a href="/gesnum/contact" title="Contact concernant l'application">Contact</a>
+    </li>
+    <li>
+        <a href="/gesnum/plan" title="Plan de navigation au sein de l'application">Plan de navigation</a>
+    </li>
+    <li>
+        <a href="http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/" title="Mentions légales">Mentions légales</a>
+    </li>
+    <li>
+        <a href="http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/" title="Informatique et libertés">Informatique et libertés</a>
+    </li>
+</ul>
+```
+
+MenuPrincipal
+=============
+
+Aide de vue héritant de `\Zend\View\Helper\Navigation\Menu` qui fabrique
+le code HTML du menu principal (pages de niveau 1 et 2 seulement) de
+l\'application à partir des pages de navigation de l\'application.
+
+\<note important\>Les ACL de l\'application sont respectées par cette
+aide de vue.\</note\>
+
+Pages de navigation utilisées pour nos exemples
+-----------------------------------------------
+
+Exemple :
+
+``` {.php}
+namespace Application;
+
+return array(
+    ...
+    'navigation' => array(
+        'default' => array(
+            'home' => array(
+                'pages' => array(
+                    'demande' => array(
+                        'label'      => 'Demandes',
+                        'title'      => "Liste de toute les demandes",
+                        'action'     => 'index',
+                        'route'      => 'demande',
+                        'resource'   => 'controller/Application\Controller\Demande',  // ici, pas d'action spécifiée
+                        'pages' => array(
+                            'ajouter' => array(
+                                'label'      => 'Nouvelle demande',
+                                'title'      => "Faire une nouvelle demande",
+                                'action'     => 'ajouter',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                                'pages' => array(
+                                    'identite' => array(
+                                        'label'      => 'Identité',
+                                        'title'      => "Saisie des informations concernant l'identité",
+                                        'action'     => 'ajouter-identite',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                    'coordonnees' => array(
+                                        'label'      => 'Coordonnées',
+                                        'title'      => "Saisie des coordonnées",
+                                        'action'     => 'ajouter-coordonnees',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                    'divers' => array(
+                                        'label'      => 'Divers',
+                                        'title'      => "Saisie des informations diverses",
+                                        'action'     => 'ajouter-divers',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                ),
+                            ),
+                            'mienne' => array(
+                                'label'      => 'Miennes',
+                                'title'      => "Liste des demandes me concernant",
+                                'action'     => 'miennes',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                            ),
+                            'recherche' => array(
+                                'label'      => 'Recherche',
+                                'title'      => "Recherche de demandes",
+                                'action'     => 'recherche',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                                'pages' => array(
+                                    'simple' => array(
+                                        'label'      => 'Simple',
+                                        'title'      => "Recherche simple de demandes",
+                                        'action'     => 'recherche',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                    ),
+                                    'detaillee' => array(
+                                        'label'      => 'Détaillée',
+                                        'title'      => "Recherche détaillée de demandes",
+                                        'action'     => 'recherche-detaillee',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                    ),
+                                ),
+                            ),
+                        ),
+                    ),
+                ),
+            ),
+        ),
+    ),
+);
+```
+
+Le premier niveau seulement
+---------------------------
+
+Exemple :
+
+``` {.php}
+<?php echo $this->navigation('navigation')->menuPrincipal(); ?>
+```
+
+Exemple de rendu correspondant :
+
+``` {.html}
+<ul class="nav menu-principal">
+    <li class="dropdown">
+        <a title="Liste de toute les demandes" href="/gesnum/demande">Demandes</a>
+    </li>
+    <li class="active dropdown">
+        <a href="/gesnum/administration/utilisateur">Administration</a>
+    </li>
+</ul>
+```
+
+Les deux premiers niveaux
+-------------------------
+
+Exemple :
+
+``` {.php}
+<?php echo $this->navigation('navigation')->menuPrincipal()->setMaxDepth(2); ?>
+```
+
+Exemple de rendu correspondant :
+
+``` {.html}
+<nav>
+    <ul class="nav menu-principal">
+        <li class="active dropdown">
+            <a href="/gesnum/demande" title="Liste de toute les demandes">Demandes</a>
+            <ul class="dropdown-menu" style="display: none;">
+                <li class="active dropdown">
+                    <a href="/gesnum/demande/ajouter" title="Faire une nouvelle demande">Nouvelle demande</a>
+                </li>
+                <li class="">
+                    <a href="/gesnum/demande/miennes" title="Liste des demandes me concernant">Miennes</a>
+                </li>
+                <li class="dropdown">
+                    <a href="/gesnum/demande/recherche" title="Recherche de demandes">Recherche</a>
+                </li>
+            </ul>
+        </li>
+        <li class="dropdown">
+            <a href="/gesnum/administration/utilisateur">Administration</a>
+            <ul class="dropdown-menu" style="display: none;">
+                <li class="dropdown">
+                    <a href="/gesnum/administration/utilisateur">Utilisateurs</a>
+                </li>
+                <li class="dropdown">
+                    <a href="/gesnum/administration/parametre">Paramètres</a>
+                </li>
+            </ul>
+        </li>
+    </ul>
+</nav>
+```
+
+MenuSecondaire
+==============
+
+Aide de vue héritant de `\Zend\View\Helper\Navigation\Menu` qui fabrique
+le code HTML du menu secondaire de l\'application à partir des pages de
+navigation de l\'application.
+
+\<note important\>Les ACL de l\'application sont respectées par cette
+aide de vue.\</note\>
+
+Utilisation :
+
+``` {.php}
+<?php echo $this->navigation('navigation')->menuSecondaire(); ?>
+```
+
+Exemple de rendu correspondant :
+
+``` {.html}
+<ul class="menu-secondaire">
+    <li>
+        <a href="/gesnum/demande/ajouter" title="Faire une nouvelle demande">Nouvelle demande</a>
+    </li>
+    <li>
+        <a href="/gesnum/demande/miennes" title="Liste des demandes me concernant">Miennes</a>
+    </li>
+    <li class="active">
+        <a href="/gesnum/demande/recherche" title="Recherche de demandes">Recherche</a>
+        <ul>
+            <li>
+                <a href="/gesnum/demande/recherche" title="Recherche simple de demandes">Simple</a>
+            </li>
+            <li class="active">
+                <a href="/gesnum/demande/recherche-detaillee" title="Recherche détaillée de demandes">Détaillée</a>
+            </li>
+        </ul>
+    </li>
+</ul>
+```
+
+Plan
+====
+
+Aide de vue générant le code HTML du plan de navigation à partir des
+pages de navigation de l\'application.
+
+\<note important\>Seules les pages de navigation visibles ou possédant
+la propriété `sitemap` à `true` sont inclues dans le plan.\</note\>
+\<note important\>Les ACL de l\'application sont respectées par cette
+aide de vue.\</note\>
+
+Pages de navigation utilisées pour l\'exemple
+---------------------------------------------
+
+Les pages de navigation sont issues de la fusion du fichier de config du
+module \"vendor/UnicaenApp\"\" :
+
+``` {.php}
+namespace UnicaenApp;
+
+return array(
+    ...
+    'navigation' => array(
+        'default' => array(
+            'home' => array(
+                'label'      => 'Accueil',
+                'title'      => "Page d'accueil de l'application",
+                'route'      => 'home',
+                'order'      => -100, // make sure home is the first page
+                'pages' => array(
+                    'etab' => array(
+                        'label'   => "Université de Caen - Basse Normandie",
+                        'title'   => "Page d'accueil du site de l'Université de Caen - Basse Normandie",
+                        'uri'     => 'http://www.unicaen.fr/',
+                        'class'   => 'ucbn',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                    ),
+                    'apropos' => array(
+                        'label'   => "À propos",
+                        'title'   => "À propos de cette application",
+                        'route'   => 'apropos',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'contact' => array(
+                        'label'   => 'Contact',
+                        'title'   => "Contact concernant l'application",
+                        'route'   => 'contact',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'plan' => array(
+                        'label'   => 'Plan de navigation',
+                        'title'   => "Plan de navigation au sein de l'application",
+                        'route'   => 'plan',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'ml' => array(
+                        'label'   => 'Mentions légales',
+                        'title'   => "Mentions légales",
+                        'uri'     => 'http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                    'il' => array(
+                        'label'   => 'Informatique et libertés',
+                        'title'   => "Informatique et libertés",
+                        'uri'     => 'http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/',
+                        'visible' => false,
+                        'footer'  => true, // propriété maison pour inclure cette page dans le menu de pied de page
+                        'sitemap' => true, // propriété maison pour inclure cette page dans le plan
+                    ),
+                ),
+            ),
+        ),
+    ),
+);
+```
+
+\... et du module \"module/Application\" :
+
+``` {.php}
+namespace Application;
+
+return array(
+    ...
+    'navigation' => array(
+        'default' => array(
+            'home' => array(
+                'pages' => array(
+                    'demande' => array(
+                        'label'      => 'Demandes',
+                        'title'      => "Liste de toute les demandes",
+                        'action'     => 'index',
+                        'route'      => 'demande',
+                        'resource'   => 'controller/Application\Controller\Demande',  // ici, pas d'action spécifiée
+                        'pages' => array(
+                            'ajouter' => array(
+                                'label'      => 'Nouvelle demande',
+                                'title'      => "Faire une nouvelle demande",
+                                'action'     => 'ajouter',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                                'pages' => array(
+                                    'identite' => array(
+                                        'label'      => 'Identité',
+                                        'title'      => "Saisie des informations concernant l'identité",
+                                        'action'     => 'ajouter-identite',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                    'coordonnees' => array(
+                                        'label'      => 'Coordonnées',
+                                        'title'      => "Saisie des coordonnées",
+                                        'action'     => 'ajouter-coordonnees',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                    'divers' => array(
+                                        'label'      => 'Divers',
+                                        'title'      => "Saisie des informations diverses",
+                                        'action'     => 'ajouter-divers',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                        'visible'    => false,
+                                    ),
+                                ),
+                            ),
+                            'mienne' => array(
+                                'label'      => 'Miennes',
+                                'title'      => "Liste des demandes me concernant",
+                                'action'     => 'miennes',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                            ),
+                            'recherche' => array(
+                                'label'      => 'Recherche',
+                                'title'      => "Recherche de demandes",
+                                'action'     => 'recherche',
+                                'route'      => 'demande',
+                                'resource'   => 'controller/Application\Controller\Demande',
+                                'pages' => array(
+                                    'simple' => array(
+                                        'label'      => 'Simple',
+                                        'title'      => "Recherche simple de demandes",
+                                        'action'     => 'recherche',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                    ),
+                                    'detaillee' => array(
+                                        'label'      => 'Détaillée',
+                                        'title'      => "Recherche détaillée de demandes",
+                                        'action'     => 'recherche-detaillee',
+                                        'route'      => 'demande',
+                                        'resource'   => 'controller/Application\Controller\Demande',
+                                    ),
+                                ),
+                            ),
+                        ),
+                    ),
+                ),
+            ),
+        ),
+    ),
+);
+```
+
+Utilisation
+-----------
+
+``` {.php}
+<h1>Plan de navigation</h1>
+<?php echo $this->navigation('navigation')->plan(); ?>
+```
+
+Exemple de rendu correspondant :
+
+``` {.html}
+<h1>Plan de navigation</h1>
+<ul class="menu-footer">
+    <li class="active">
+        <a href="/gesnum/" title="Page d'accueil de l'application">Accueil</a>
+        <ul>
+            <li>
+                <a href="/gesnum/demande" title="Liste de toute les demandes">Demandes</a>
+                <ul>
+                    <li>
+                        <a href="/gesnum/demande/ajouter" title="Faire une nouvelle demande">Nouvelle demande</a>
+                    </li>
+                    <li>
+                        <a href="/gesnum/demande/miennes" title="Liste des demandes me concernant">Miennes</a>
+                    </li>
+                    <li>
+                        <a href="/gesnum/demande/recherche" title="Recherche de demandes">Recherche</a>
+                        <ul>
+                            <li>
+                                <a href="/gesnum/demande/recherche" title="Recherche simple de demandes">Simple</a>
+                            </li>
+                            <li>
+                                <a href="/gesnum/demande/recherche-detaillee" title="Recherche détaillée de demandes">Détaillée</a>
+                            </li>
+                        </ul>
+                    </li>
+                </ul>
+            </li>
+            <li>
+                <a href="/gesnum/apropos" title="À propos de cette application">À propos</a>
+            </li>
+            <li>
+                <a href="/gesnum/contact" title="Contact concernant l'application">Contact</a>
+            </li>
+            <li class="active">
+                <a href="/gesnum/plan" title="Plan de navigation au sein de l'application">Plan de navigation</a>
+            </li>
+            <li>
+                <a href="http://www.unicaen.fr/outils-portail-institutionnel/mentions-legales/" title="Mentions légales">Mentions légales</a>
+            </li>
+            <li>
+                <a href="http://www.unicaen.fr/outils-portail-institutionnel/informatique-et-libertes/" title="Informatique et libertés">Informatique et libertés</a>
+            </li>
+            <li>
+                <a href="/gesnum/administration/utilisateur">Administration</a>
+                <ul>
+                    <li>
+                        <a href="/gesnum/administration/utilisateur">Utilisateurs</a>
+                        <ul>
+                            <li>
+                                <a href="/gesnum/administration/utilisateur/ajouter">Nouvel utilisateur</a>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <a href="/gesnum/administration/parametre">Paramètres</a>
+                        <ul>
+                            <li>
+                                <a href="/gesnum/administration/parametre/ajouter">Nouveau paramètre</a>
+                            </li>
+                        </ul>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+```
+
+Formulaire
+----------
+
+FormControlGroup
+================
+
+Aide de vue générant le marquage HTML complet d\'un élément de
+formulaire à la mode Bootsrap 3 (`div.form-group`), y compris les
+messages d\'erreur de validation.
+
+``` {.html}
+<?php echo $this->form()->openTag($form->prepare()) ?>
+<?php echo $this->formControlGroup($form->get('message')->setLabel("Votre message :")); ?>
+<?php echo $this->formSubmit($form->get('submit')->setValue("Enregistrer")); ?>
+<?php echo $this->form()->closeTag() ?>
+```
+
+En plus des éléments de formulaire standards, cette aide de vue peut
+recevoir un élément de type :
+
+-   \\UnicaenApp\\Form\\Element\\DateInfSup
+-   \\UnicaenApp\\Form\\Element\\SearchAndSelect
+
+Lorsqu\'il y a des messages d\'erreur de validation la `div` possède la
+classe CSS `has-error`.
+
+FormDateInfSup
+==============
+
+Aide de vue générant le code HTML de l\'élément de formulaire composite
+\"[DateInfSup](/develop/unicaen2/moduleunicaenunicaenapp/form/dateinfsup)\",
+c\'est à dire :
+
+-   le champ de saisie de la date inférieure ainsi que son label associé
+    ;
+-   éventuellement, le champ de saisie éventuel de la date supérieure
+    ainsi que son label associé (uniquement si la date supérieure est
+    désactivée) ;
+-   éventuellement, un lien permettant de vider la date supérieure (via
+    jQuery).
+
+FormErrors
+==========
+
+Génère le marquage HTML pour afficher à la fois un message d\'erreur
+global de validation d\'un formulaire ainsi que la liste des erreurs de
+tous les éléments de formulaires.
+
+Exemple :
+
+``` {.html}
+<?php echo $this->formErrors($form->prepare(), "Oups!") ?>
+
+<?php echo $this->form()->openTag($form) ?>
+<?php echo $this->formControlGroup($form->get('nom')->setLabel("Votre nom :")); ?>
+<?php echo $this->formControlGroup($form->get('message')->setLabel("Votre message :")); ?>
+<?php echo $this->formSubmit($form->get('submit')->setValue("Enregistrer")); ?>
+<?php echo $this->form()->closeTag() ?>
+```
+
+FormRowDateInfSup
+=================
+
+Aide de vue héritant de l\'aide de vue standard \"FormRow\" et générant
+le code HTML **complet** de l\'élément de formulaire composite
+\"[DateInfSup](/develop/unicaen2/moduleunicaenunicaenapp/form/dateinfsup)\",
+c\'est à dire :
+
+-   le label global de l\'élément composite ;
+-   les champs de saisie des dates et leurs labels (délégué à l\'aide de
+    vue
+    \"[FormDateInfSup](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/FormDateInfSup)\")
+    ;
+-   les messages d\'erreurs de validation.
+
+FormSearchAndSelect
+===================
+
+Aide de vue générant le code HTML de l\'élément de formulaire
+[SearchAndSelect](/develop/unicaen2/moduleunicaenunicaenapp/form/SearchAndSelect).
+
+Exemple :
+
+``` {.php}
+$sas = $form->get('sas')
+        ->setLabel("Recherche :")
+        ->setAutocompleteSource($this->url('demo', array('action' => 'rechercher')));
+echo $this->formLabel($sas);
+echo $this->formSearchAndSelect($sas);
+```
+
+Le marquage HTML résultant est le suivant :
+
+``` {.html}
+<label for="sas">Recherche :</label>
+<input type="text" value="" name="sas[id]" class="sas" id="sas-532c128ecd5c2" style="display: none;">
+<input type="text" value="" name="sas[label]" class="form-control input-sm ui-autocomplete-input" id="sas-532c128ecd5c2-autocomplete" autocomplete="off">
+
+<style>
+    ul.ui-autocomplete {
+        z-index: 5000
+    }
+    .ui-autocomplete-loading {
+        background: white url("//gest.unicaen.fr/images/ajax-loader-r.gif") right center no-repeat;
+    }
+</style>
+
+<script>
+    // cache l'élément contenant l'id de la sélection
+    $("#sas-532c128ecd5c2").css('display', 'none');
+
+    $(function() {
+        // jQuery UI autocomplete
+        var autocomp = $("#sas-532c128ecd5c2-autocomplete");
+        autocomp.autocompleteUnicaen({// autocompleteUnicaen() définie dans "public/js/util.js"
+            elementDomId: 'sas-532c128ecd5c2',
+            source: '/ose/demo/rechercher',
+            minLength: 2,
+            delay: 750
+        });
+    });
+</script>
+```
+
+Lorsque le formulaire est soumis, la valeur POSTée pour cet élément
+ressemble à cela :
+
+``` {.php}
+'sas' => array(
+    'id' => '8',      // identifiant unique de l'item sélectionné (issu d'un champ caché)
+    'label' => 'Huit' // texte présent dans le champ de saisie
+)
+```
+
+MultipageFormFieldset
+=====================
+
+Aide de vue à utiliser dans chaque vue associée aux étapes de saisie
+d\'un formulaire multi-pages.
+
+Cette aide de vue génère un titre (h2) indiquant \"Étape i sur N\", le
+code HTML du fieldset de l\'étape en cours ainsi que les boutons de
+navigation.
+
+Exemple :
+
+``` {.php}
+<h1>Formulaire de contact</h1>
+<?php echo $this->multipageFormFieldset(); ?>
+```
+
+\<note important\>Le fieldset courant est transmis à la vue par le
+plugin de contrôleur
+[MultipageForm](/develop/unicaen2/moduleunicaenunicaenapp/controllerplugins/multipageform)
+via la variable de vue `fieldset`.\</note\>
+
+MultipageFormNav
+================
+
+Aide de vue générant l\'élément de navigation (de type
+`\UnicaenApp\Form\Element\MultipageFormNav`) au sein d\'un formulaire
+multi-pages.
+
+MultipageFormRecap
+==================
+
+Aide de vue à utiliser dans la vue associée à l\'étape de confirmation
+de la saisie d\'un formulaire multi-pages.
+
+Cette aide de vue génère automatiquement la liste de toutes les
+informations saisies et les boutons de navigation.
+
+Exemple :
+
+``` {.php}
+<h1>Récapitulatif et confirmation des informations saisies</h1>
+<?php echo $this->multipageFormRecap(); ?>
+```
+
+\<note important\>Le formulaire complet est transmis à la vue par le
+plugin de contrôleur
+[MultipageForm](/develop/unicaen2/moduleunicaenunicaenapp/controllerplugins/multipageform)
+via la variable de vue `form`.\</note\>
+
+\<note important\> Cette aide de vue explore automatiquement tous les
+éléments visibles de tous les fieldsets du formulaire afin de collecter
+leur labels et leur valeurs et les présenter sous la forme d\'une liste
+de définition (dl). Si vous souhaitez maîtriser plus finement la façon
+dont sont constitués les labels ou les valeurs d\'un fieldset, faites
+implémenter à la classe de ce fieldset l\'interface
+`\UnicaenApp\Form\MultipageFormFieldsetInterface` pour fournir via la
+méthode `getLabelsAndValues()` les labels et valeurs de tous les
+éléments de ce fieldset. \</note\>
+
+MultipageFormRow
+================
+
+Aide de vue générant chaque élément d\'un fieldset de formulaire
+multi-page.
+
+Délègue le travail à l\'aide de vue standard `FormRow` pour tous les
+éléments, sauf pour ceux de type `MultipageFormNav` traitée par l\'aide
+de vue
+`[[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:multipageformnav|MultipageFormNav]]`.
+
+Divers
+------
+
+ToggleDetails
+=============
+
+Aide de vue qui génère un lien permettant d\'afficher/masquer un
+container.
+
+``` {.php}
+
+<?php echo $this->toggleDetails('div-depot-initial', true)
+    ->setShowDetailsIconClass('glyphicon glyphicon-chevron-up')
+    ->setHideDetailsIconClass('glyphicon glyphicon-chevron-down')
+    ->setShowDetailsTitle("Parental Advisory : Explicit Content")
+    ->setHideDetailsTitle("Click to hide!")
+    ->setShowDetailsLabel('Show me')
+    ->setHideDetailsLabel('No more!') ?>
+<p style="display: none" id="div-depot-initial">
+    Bla bla bla...
+</p>
+```
+
+Le deuxième argument de `$this->toggleDetails()` permet d\'activer ou
+désactiver le mécanisme de restauration de l\'état de visibilité du
+container au chargement de la page (état mémorisé dans le localStorage
+du navigateur).
+
+\<note\>Si vous retirez le `style="display: none"` du container pour
+qu\'il soit visible par défaut, l\'aide de vue s\'adapte
+automatiquement.\</note\>
+
+Aide de vue Tag
+===============
+
+Cette aide de vue sert à afficher des tags HTML de manière sécurisée, en
+particulier pour prévenir le \"défaçement\" de site Web.
+
+En voici quelques exemples d\'utilisation :
+
+``` {.php}
+
+
+echo $this->tag('h1')->text('Ti<span>tre</span>' );
+// Résultat : <h1>Titre</h1>
+
+echo $this->tag('h1')->escaped('Ti<span>tre</span>');
+// Résultat : <h1>Ti&lt;span&gt;tre&lt;/span&gt;</h1>
+
+echo $this->tag('h1')->html('Ti<span>tre</span>');
+// Résultat : <h1>Ti<span>tre</span></h1>
+
+
+
+echo $this->tag('div', [
+                    'class' => 'row',
+                    'data-index' => 2
+                ]);
+// Résultat : <div class="row" data-index="2">
+
+
+echo $this->tag('h2');
+echo 'Titre H2';
+echo $this->tag('h2')->close();
+// Résultat : <h2>Titre H2</h2>
+
+```
+
+Et voici un exemple plus complet avec un cas de \"défaçement\" de site
+évité grâce à l\'échappement de caractères :
+
+``` {.php}
+<?php
+$data = [
+    [
+        'term' => 'Bonjour',
+        'title' => 'Bonne journée',
+        'description' => 'Ceci est une politesse du matin lorsque nous nous retrouvons'
+    ],
+    [
+        'term' => 'Bonsoir',
+        'title' => 'Bonne soirée "><div style="background-color:black;font-size:90pt;color:white;position:absolute;left:0px;top:0px;width:100%;height:100%">Bouh ceci est un défaçement</div>',
+        'description' => 'Ceci <b>est</b> une <i>politesse</i> du soir lorsqu\'il est temps de partir...'
+    ],
+];
+
+// Bon exemple !!
+echo '<dl>';
+foreach( $data as $item ){
+    echo $this->tag('dt', ['title' => $item['title']])->text($item['term']);
+    echo $this->tag('dd')->html($item['description'], ['b']); // on laisse passer le gras ici (b)
+}
+echo '</dl>';
+
+// Résultat :
+
+?>
+<dl>
+<dt title="Bonne&#x20;journ&#xE9;e">Bonjour</dt>
+<dd>Ceci est une politesse du matin lorsque nous nous retrouvons</dd>
+<dt title="Bonne&#x20;soir&#xE9;e&#x20;&quot;&gt;&lt;div&#x20;style&#x3D;&quot;background-color&#x3A;black&#x3B;font-size&#x3A;90pt&#x3B;color&#x3A;white&#x3B;position&#x3A;absolute&#x3B;left&#x3A;0px&#x3B;top&#x3A;0px&#x3B;width&#x3A;100&#x25;&#x3B;height&#x3A;100&#x25;&quot;&gt;Bouh&#x20;ceci&#x20;est&#x20;un&#x20;d&#xE9;fa&#xE7;ement&lt;&#x2F;div&gt;">Bonsoir</dt>
+<dd>Ceci <b>est</b> une politesse du soir lorsqu'il est temps de partir...</dd>
+</dl>
+
+// Et voici le mauvais exemple :
+
+
+<dl>
+<?php foreach( $data as $item ): ?>
+    <dt title="<?php echo $item['title'] ?>"><?php echo $item['term'] ?></dt>
+    <dd><?php echo $item['description'] ?></dd>
+<?php endforeach ?>
+</dl>
+
+```
+
+\<WRAP center round tip 60%\> Vous noterez que le code sécurisé n\'est
+pas plus \"lourd\" à écrire que le code non sécurisé!! \</WRAP\>
+
+Autres cas
+----------
+
+Pour finir, voici un autre exemple d\'échappement de chaîne de
+caractères à utiliser pour écrire du HTML avec le Zend Framework :
+
+``` {.php}
+<?php
+
+$texte = 'Bonne soirée "><div style="background-color:black;font-size:90pt;color:white;position:absolute;left:0px;top:0px;width:100%;height:100%">Bouh ceci est un défaçement qui a échoué</div>';
+
+// Affichage sécurisé grâce à l'aide de vue standard "escapeHtml"
+?>
+
+<p class="nouvel-exemple">
+<?php echo $this->escapeHtml($texte); ?>
+</p>
+
+
+```
+
+\<WRAP center round tip 60%\> Pour aller plus loin, vous pouvez
+consulter la documentation de la classe ici :
+<https://ldev.unicaen.fr/doc/UnicaenApp/html/class_unicaen_app_1_1_view_1_1_helper_1_1_tag_view_helper.html>
+\</WRAP\>
diff --git a/doc/Widgets.dokuwiki b/doc/Widgets.dokuwiki
new file mode 100644
index 0000000000000000000000000000000000000000..6ea0528cf6dce925eaa122d5f44cfabc5afdf57d
--- /dev/null
+++ b/doc/Widgets.dokuwiki
@@ -0,0 +1,465 @@
+====== AjaxModalDialog ======
+
+Cette aide de vue :
+  * intercepte le clic sur un lien ayant la classe CSS ''ajax-modal'', lance la requête correspondante en AJAX et ouvre le résultat dans une fenêtre modale Bootstrap 3 ;
+  * simplifie la gestion d'un éventuel formulaire présent dans cette fenêtre modale.
+
+<note warning>
+Cette aide de vue "manuelle" est devenue caduque du fait que le mécanisme est désormais installé systématiquement sur tous les liens hypertextes ayant la classe CSS ''ajax-modal''. Cf.  [[develop:unicaen2:moduleunicaenunicaenapp:js|Javascript]].
+
+Le mécanisme d'événement évoqué plus bas reste néanmoins d'actualité.
+</note>
+
+<note tip>Rappel : il est possible de spécifier la classe ''ajax-modal'' sur les pages de navigation.</note>
+
+<note important>Le composant [[develop:unicaen2:moduleunicaenunicaenapp:mvc#modallistener|ModalListener]] (activé dans le module UnicaenApp) est indispensable au fonctionnement de cette aide de vue.</note>
+
+===== 1/ Sans formulaire  =====
+
+Si l'action à "ouvrir" dans la fenêtre modale ne contient aucun formulaire, ce paragraphe est pour vous.
+
+Exemple de vue contenant le lien à ouvrir :
+
+<file php index.phtml>
+<a title="Modifier le service" href="/ose/service/saisie/147"
+   class="ajax-modal"><span class="glyphicon glyphicon-edit"></span></a>
+<?php echo $this->modalAjaxDialog('ma-div-id'); ?>
+</file>
+
+<note important>Le lien hypertexte doit posséder la classe CSS ''ajax-modal''.</note>
+
+===== 2/ Avec formulaire  =====
+
+Ce paragraphe explique comment cette aide de vue peut vous simplifier la gestion d'un formulaire s'affichant dans la fenêtre modale.
+
+Le clic sur le bouton "submit" du formulaire (i.e. bouton ayant la classe CSS ''btn-primary'') présent dans la fenêtre modale est intercepté pour soumettre la requête en Ajax (en POST). Deux cas de figure possibles selon le contenu (HTML) de la réponse reçue :
+  * si une balise HTML ayant la classe CSS ''has-error'' ou ''has-errors'' ou ''alert'' ou ''input-error'' est trouvée, alors cela veut dire qu'il y a des erreurs de validation du formulaire : rien ne se passe.
+  * sinon, l'événement jQuery dont le nom est spécifié par l'attribut ''data-event'' du lien cliqué est déclenché sur le "body" de la page, donnant l'opportunité à qui écoute cet événement de mettre à jour l'interface par exemple et //notamment de fermer la fenêtre modale//.
+
+<note important>Le lien doit fournir via l'attribut ''data-event'' le nom de l'événement qui sera déclenché à la soumission de ce formulaire.</note>
+
+Exemple de vue contenant le lien à ouvrir :
+
+<file php index.phtml>
+<a title="Modifier le service" href="/ose/service/saisie/147"
+   class="ajax-modal"
+   data-event="service-modify-message"><span class="glyphicon glyphicon-edit"></span></a>
+<?php echo $this->modalAjaxDialog('ma-div-id'); ?>
+
+<script>
+    $(function() {
+        // écoute de l'événement
+        $("body").on("service-modify-message", function(event, data) {
+            /**
+             * event          : événement jQuery (cf. http://api.jquery.com/category/events/event-object/)
+             *   event.type   : nom de l'événement ("save-message" dans cet exemple)
+             *   event.div    : DIV correspondant à la fenêtre modale (objet jQuery)
+             *   event.a      : lien hypertexte cliqué
+             * data           : formulaire sérialisé en tableau à l'aide de jQuery.serializeArray()
+             */
+            console.log("Event received : ", event);
+            console.log("With args : ", data);
+            // ...
+            event.div.modal('hide'); // ferme la fenêtre modale
+        });
+    });
+</script>
+</file>
+
+
+====== AjaxPopover : Utilisation de base ======
+
+<WRAP center round alert 60%>
+Ce widget est déprécié et ne sera bientôt plus maintenu!
+Un nouveau widget, plus complet, le remplace :
+[[develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:PopAjax|PopAjax]]
+</WRAP>
+
+
+Le Popover de Bootstrap ([[http://getbootstrap.com/javascript/#popovers]]) permet d'afficher des informations sur un élément donné.
+
+Le rôle de l'AjaxPopover est de transformer un lien de type ancre (élément a) en popover.
+Concrètement, lorsqu'on clique sur ce lien, le popover s'affiche avec, en contenu, la page ciblée par l'attribut href du lien. Ce mécanisme s'apppuie sur Ajax.
+
+Si le code retourné comporte un formulaire, alors ce dernier est également géré par l'AjaxPopover.
+
+Pour signaler qu'un lien doit aboutir à l'ouverture d'un Popover, il suffit de lui affecter la classe "ajax-popover".
+
+Voici un exemple de popover :
+<code html>
+<a href="/application/lien/vers/formulaire" class="ajax-popover event_save-popover-form">TEST</a>
+</code>
+
+====== Usage avancé ======
+
+Un événement est émis par l'AjaxPopover lorsqu'un formulaire est soumis et que son résultat ne retourne aucune erreur.
+Il est donc possible de capturer cet événement pour effectuer diverses actions, par exemple fermer le popover.
+
+Voici, en Javascript, un exemple de capture d'événement :
+
+<code javascript>
+$(document).ready(function() {
+
+    $("body").on('save-popover-form', function(event,data){
+        /*
+
+        event comporte deux propriétés :
+         - event.a : objet Jquery du lien.
+           Il permet d'accéder au lien source du popover pour y récupérer, par exemple,
+           des données ou pour accéder au popover pour le fermer, par exemple.
+
+         - event.div : élément conteneur du popover. Utile pour modifier le contenu par exemple.
+
+        data contient les données du formulaire posté
+
+        */
+
+        event.a.popover('hide'); // on ferme le popover
+    });
+
+});
+</code>
+
+<WRAP center round tip 60%>
+L'événement "save-popover-form" sera généré sur le ou les liens disposant de la classe "event_save-popover-form". Après "event_", vous êtes libre de nommer l'événement comme bon vous semble.
+</WRAP>
+
+
+====== TabAjax ======
+Cette aide de ue sert à dessiner des listes d'onglets avec contenu chargé statiquement et/ou dynamiquement.
+Il est possible de mélanger des onglets dynamiques et d'autres statiques.
+
+Usage simple (dans une vue phtml):
+<code php>
+
+echo $this->tabajax([
+    [
+        'id'      => 'fiche',
+        'label'   => 'Fiche',
+        'content' => 'Ceci est le contenu de la fiche descriptive',
+    ],
+    [
+        'id'    => 'détails',
+        'label' => 'Détails',
+        'url'   => $this->url('fiche/details', ['fiche' => 1245]),
+    ],
+]);
+
+</code>
+Ici, le premier onglet (Fiche) a un contenu statique.
+Le deuxième onglet reçoit une URL, qui sera exploitée le moment venu pour charger son contenu en AJAX.
+Les ID permettent de nommer les onglets pour les exploiter ensuite en JS si besoin.\\
+<php>label</php> est le nom affiché sur l'onglet.\\
+<php>content</php> le contenu de l'onglet (si pré-chargé).
+
+
+===== Événement "loaded" =====
+
+Il est déclenché lorsqu'un onglet AJAX a terminé son chargement :
+
+<code javascript>
+    $(function(){
+        $('#zozo').tabAjax({
+            'loaded': function( event, tab ){
+                //console.log(event);
+                console.log(tab);
+            }
+        });
+
+    });
+</code>
+
+===== Chargement d'un onglet à la demande =====
+
+<code javascript>
+    $(function(){
+        $('#zozo').tabAjax('select', 'onglet-general');
+    });
+</code>
+
+
+====== PopAjax ======
+
+PopAjax est un "popover" reprenant le popover de Bootstrap et compatible Ajax.
+C'est un Widget conçu avec le Widget Factory de JQueryUI (inclus dans UnicaenApp).
+
+Il propose :
+  * de charger éventuellement son contenu en Ajax à partir d'une URL
+  * de créer simplement une DIV et de le paramétrer en lui passant des attributs data*.
+  * de pouvoir l'instancier en Javascript comme n'importe quel widget JQuery
+  * un système d'événements permettant de fermer le popover, de recharger la page ou de lancer l'événement de votre choix si le formulaire inclus
+  * de personnaliser plein de paramètres (où il s'affiche, quel message d'attente, etc.
+  * si besoin, un popajax peut être appelé depuis un autre popajax et ainsi de suite sans limites.
+  * quand on clique hors du popover, il se ferme tout seul.
+  * un système de boite de dialogue de confirmation (avec possibilité d'ajouter des éléments de formulaire au besoin)
+
+Voici ce que ça donne :
+{{:develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:popajax.png|}}
+
+===== Exemples : =====
+
+<code html>
+<!-- Le fait de préciser que le lien est de classe popajax suffit!!! data-submit-close fermera le popAjax dès qu'un formulaire sera posté -->
+<a href="<?php echo $url ?>" class="pop-ajax" data-submit-close="true">Lien 1</a>
+
+<!-- Ca marche aussi avec un bouton mais il faut lui préciser l'URL de chargement en passant par un attribut du dataset-->
+<button type="button" class="pop-ajax" data-url="http://www.google.com">Mon bouton</button>
+
+<!-- Exemple avec un événement -->
+<a href="<?php echo $url ?>" class="pop-ajax" data-submit-event="mon-formulaire-enregistre">Lien 1</a>
+<script>
+
+    $("body").on("mon-formulaire-enregistre", function (event, popAjax)
+    {
+        console.log(popAjax); // affiche en console votre objet popajax
+    });
+
+</script>
+</code>
+
+===== En bref =====
+==== Gestion des titres ====
+
+Le titre du popajax peut être adapté au contenu retourné par la requête AJAX :
+Si le code HTMl contient :
+  * un titre h1
+  * ou un élément de classe .popover-title
+  * ou un élément de classe .page-header
+alors cet élément sera supprimé du contenu du popover et son contenu sera injecté dans la partie titre du popover.
+
+Si aucun titre n'est disponible alors la zone de titre disparaît.
+
+==== Boites de dialogue de confirmation ====
+PopAjax vous permet de réaliser simplement des boites de dialogue de confirmation.
+
+Exemple :
+<code html>
+<a
+    class="pop-ajax"
+    href="<?php echo $url ?>"
+    data-content="Voulez-vous vraiement faire cela <input name='t1' value='t1' /> ?"
+    data-confirm="true"
+>Confirm</a>
+</code>
+Dans ce cas, le texte est posé au départ et l'URL ne sera appelée que lorsque le bouton de confirmation sera cliqué. J'ai même ajouté un input dont la valeur sera récupérable dans les données POST de l'action pour montrer que c'est possible.
+
+Il est même possible de ne pas fournir de contenu de de le charger en AJAX si besoin.
+Attention dans votre action de contrôleur de bien distinguer dans ce cas la demande (méthode GET) de l'action (méthode POST).
+
+Si vous ne souhaitez rien renvoyer de spécial hormis des messages informatifs, le modèle de vue <php>UnicaenApp\View\Model\MessengerViewModel</php> vous aidera à ne renvoyer que les messages collectés dans votre action sans avoir à créer de vue spécifique pour cela.
+
+Exemple côté PHP (dans une action de contrôleur):
+<code php>
+
+if ($this->getRequest()->isPost()){
+    try{
+        $this->getVotreService()->faireVotretravail();
+        $this->flashMessenger()->addSuccessMessage("Tout est OK.");
+    }catch(\Exception $e){
+        $this->flashMessenger()->addErrorMessage($e->getMessage());
+    }
+}else{
+    $this->flashMessenger()->addErrorMessage('Vous devez d\'abord confirmer.');
+}
+
+return new \UnicaenApp\View\Model\MessengerViewModel();
+
+</code>
+
+==== Bouton de fermeture ====
+
+Le clic sur tout élément ayant pour classe "pop-ajax-hide" engendrera la fermeture du popAjax.
+===== Options =====
+
+^Nom (dataset)   ^ Nom (Json) ^ Type ^ Valeur par défaut ^ Description ^
+| url             | url            | string | undefined ou href (si présent) | URL de chargement de la page AJAX |
+| content         | content        | string  | undefined | contenu (en HTML). si défini alors le chargement ne se fait plus à partir de l'URL (plus d'AJAX à l'affichage)... |
+| animation       | animation      | boolean | true | Détermine si une animation doit se faire quand le popover apparaît ou disparait |
+| delay           | delay          | integer | 200 | Délai de l'animation |
+| placement       | placement      | string  | auto | Placement. Valeurs possibles (par ordre de priorité) : auto, bottom, top, left, right. Si auto est déterminé alors le placement le plus judicieux sera calculé automatiquement |
+| submit-event    | submitEvent    | string  | undefined | nom d'un événement à transmettre. L'événement sera lancé si un formulaire est posté à l'intérieur du popover ET que le résultat ne contient aucune erreur |
+| submit-close    | submitClose    | boolean | false | Détermine on doit fermer le popover après le post d'un formulaire sans retour d'erreur |
+| submit-reload   | submitReload   | boolean | false | Détermine on doit recharger toute la page après le post d'un formulaire sans retour d'erreur |
+| min-width       | minWidth       | string  | 100px | largeur minimale du popover |
+| max-width       | maxWidth       | string  | 600px | largeur maximale du popover |
+| min-height      | minHeight      | string  | 50px | hauteur minimale du popover |
+| max-height      | maxHeight      | string  | none | hauteur maximale du popover (si le contenu dépasse alors un ascenseur apparaîtra) |
+| loading-title   | loadingTitle   | string  | Chargement... | Titre lorsque le popover est en cours de chargement |
+| loading-content | loadingContent | string  | <html><div class="loading"></div></html> | Contenu lorsque le popover est en cours de chargement |
+| title           | title          | string  | undefined | Titre à afficher si le contenu issu de la requête ne contient aucun titre à afficher |
+| confirm         | confirm        | boolean | false | transforme le popajax en boite de dialogue de confirmation |
+| confirm-button  | confirmButton  | string  | <html><span class="glyphicon glyphicon-ok"></span> OK</html> | Texte du bouton de confirmation (si vous fournissez un texte volontairement vide alors le bouton n'apparaitra pas) |
+| cancel-button  | cancelButton  | string  | <html><span class="glyphicon glyphicon-remove"></span> Annuler</html> | Texte du bouton d'annulation (si vous fournissez un texte volontairement vide alors le bouton n'apparaitra pas) |
+| auto-show  | autoShow  | boolean  | false | Affichage du popover dès son initialisation (sans attendre le click). utile si le popover est instancié en JS directement |
+===== Événements =====
+
+Les événements permettent de réagir à des changements d'état.
+Les closures ou les fonctions qui seront appelées se verront transmettre deux arguments :
+  - l'événement en lui-même
+  - l'objet popajax (ce qui permet de le manipuler ou de récupérer son contenu (méthode getContent), etc.
+
+Exemple d'utilisation d'événement :
+<code html>
+<!-- Ici, le widget est associé à l'élément a directement en Javascript et pas en passant par la classe popajax. Le titre est également transmis par le tableau JSON des options, de même que la closure pour l'événement show -->
+<a id="popajax_1" href="<?php echo $url ?>">TEST</a>
+<script>
+
+    $(function(){
+
+        $('#popajax_1').popAjax({title: 'Mon Titre', show: function(event, popAjax){
+            console.log('show');
+            console.log(event);
+            console.log(popAjax);
+        }});
+
+    });
+
+</script>
+
+</code>
+
+==== Liste des événements ====
+
+^ Événement ^ Description ^
+| show | se déclenche à l'affichage du popajax |
+| hide | se déclenche à la fermeture du popajax |
+| change | lorsqu'un changement de contenu est détecté |
+| submit | lorsqu'un formulaire a été posté et que le retour ne comporte aucune erreur |
+
+===== Méthodes =====
+Popajax comporte un certain nombre de méthodes :
+^ Nom ^ Arguments ^ Valeur de retour ^ Description ^
+| showHide | Aucun | this | Permet d'afficher le popover s'il n'est pas affiché ou de le masquer s'il est affiché. |
+| shown | Aucun | boolean | Retourne true s'il est affiché, false sinon |
+| show | Aucun | this | Affiche le popover |
+| hide | Aucun | this | Masque le popover |
+| errorsInContent | Aucun | boolean | Retourne true si un ou plusieurs messages d'alerte de danger ou d'erreur sont affichés dans son contenu |
+| posPop | Aucun | this | Repositionne le popover |
+| getContent | Aucun | element Jquery ou undefined | Retourne l'élément qui contient le texte du popover |
+| setContent | string | this | Permet de peupler le contenu du popover |
+
+
+
+====== Instadia ======
+
+Instadia est un système de messagerie instantanée persistant contextuel.
+Cela signifie que :
+  * Les messages sont stockés en base de données
+  * Chaque fil de messagerie peut être lié à un contexte particulier.
+  * Il est possible de dialoguer en temps réel avec des interlocuteurs.
+  * Les messages restent visibles sans pouvoir être effecés
+
+Instadia peut être intégré très simplement au coeur d'une application pour, par exemple, suivre un dossier ou commenter tel ou tel item.
+
+L'intérêt est que :
+  * les informations de suivi sont visibles dans leur contexte. Il n'y a pas besoin d'aller chercher l'historique d'une conversation dans sa boite mail.
+  * les informations sont PARTAGÉES. Un nouvel utilisateur peut voir ce qui c'est dit précédemment
+
+<WRAP center round important 60%>
+Dans Instadia, chaque fil de conversation correspond à une rubrique et éventuellement à une sous-rubrique.
+Par exemple dans Uniform nous avons une rubrique formation pour signaler qu'on parle de formation, et une sous-rubrique par code de formation. Ainsi nous avons bien un fil de discussion par formation.
+</WRAP>
+
+Instadia est intégré à UnicaenApp.
+Les préresuis pour l'utiliser sont les suivants :
+  * votre application doit reposer sur une base de données
+  * vous devez utiliser UnicaenAuth (uniquement si vous voulez authentifier vos utilisateurs)
+  * une table, nommée instadia, doit être créée. Vous en trouverez la structure ci-dessous (code pour Oracle):
+
+<code sql>
+CREATE
+  TABLE INSTADIA
+  (
+    ID            NUMBER (*,0) NOT NULL ,
+    USER_ID       NUMBER (*,0) ,
+    RUBRIQUE      VARCHAR2 (80 CHAR) NOT NULL ,
+    SOUS_RUBRIQUE VARCHAR2 (80 CHAR) ,
+    HORODATAGE    DATE NOT NULL ,
+    CONTENU       VARCHAR2 (3000 CHAR) NOT NULL
+  )
+  LOGGING ;
+ALTER TABLE INSTADIA ADD CONSTRAINT INSTADIA_PK PRIMARY KEY ( id ) ;
+
+ALTER TABLE INSTADIA ADD CONSTRAINT INSTADIA_USER_FK FOREIGN KEY ( USER_ID )
+REFERENCES "USER" ( ID ) ON DELETE SET NULL NOT DEFERRABLE ;
+
+CREATE SEQUENCE INSTADIA_ID_SEQ;
+</code>
+
+
+Une aide de vue <php>UnicaenApp\View\Helper\InstadiaViewHelper</php> a été créée pour simplifier l'intégration.
+En voici un exemple d'utilisation :
+<code php>
+/* Dans une vue */
+
+<?php echo $this->instadia()->setRubrique('essai')->setTitle('Mon test'); ?>
+
+</code>
+
+Voici le résultat (un clic sur le lien ouvre la fenêtre de CHAT) :
+{{ :develop:unicaen2:moduleunicaenunicaenapp:viewhelpers:instadia.png?nolink |}}
+
+Et voici maintenant ce qu'on peut faire avec le Widget :
+
+===== Options =====
+
+^Nom (dataset)       ^ Nom (Json)   ^ Type    ^ Valeur par défaut ^ Description ^
+| user-id            | userId       | integer | undefined | ID de l'utilisateur courant (facultatif) |
+| user-label         | userLabel    | string  | 'Anonyme' | Nom de l'utilisateur (ou undefined) |
+| user-hash          | userHash     | string  | undefined | Hash permettant de récupérer le Gravatar de l'utilisateur courant (formule : md5(strtolower(trim($email))) |
+| refresh-delay      | refreshDelay | integer | 2000 | Délai de rafraichissement de la fenêtre des messages (en millisecondes) |
+| title              | title        | string  | Messagerie instantanée | Titre de la fenêtre de Chat et nom du lien |
+| rubrique           | rubrique     | string  | undefined | Rubrique |
+| sousRubrique       | sous-rubrique| string  | undefined | Sous-rubrique |
+| url                | url          | string  | undefined | URL de communication avec le serveur |
+| information        | information  | string  | undefined | Message d'information sur la fenêtre de Chat |
+| width              | width        | integer | 500       | Largeur de la fenêtre de Chat |
+| height             | height       | integer | 700       | Hauteur de la fenêtre de Chat |
+| readOnly           | read-only    | boolean | false     | Fenêtre de Chat en simple visualisation |
+===== Événements =====
+
+Les événements permettent de réagir à des changements d'état.
+Les closures ou les fonctions qui seront appelées se verront transmettre deux arguments :
+  - l'événement en lui-même
+  - l'objet instadia (ce qui permet de le manipuler).
+==== Liste des événements ====
+
+^ Événement ^ Description ^
+| afficher | affiche la fenêtre de Chat |
+| cacher | ferme la fenêtre de Chat |
+| envoyer | Lorsqu'un message est envoyé |
+| addMessage | Lorsqu'un message est ajouté |
+
+===== Méthodes =====
+Popajax comporte un certain nombre de méthodes :
+^ Nom ^ Arguments ^ Valeur de retour ^ Description ^
+| afficherCacher | Aucun | this | Permet d'afficher la fenêtre de Chat si elle n'est pas affichée ou de la masquer si elle est affichée. |
+| estAffiche | Aucun | boolean | Retourne true si elle est affichée, false sinon |
+| afficher | Aucun | this | Affiche le Chat |
+| cacher | Aucun | this | Masque le Chat |
+| envoyer | Aucun | this | Envoie le message saisi |
+| addMessage | user, horodatage, content | this | Poste un nouveau message user = id,label,hash |
+| getMessage | Aucun | string | Retourne le texte en cours de saisie |
+| setMessage | string | this | Change le texte en cours de saisie |
+| getUser    | Aucun | id,label,hash | retourne l'utilisateur courant |
+
+====== Avancé ======
+Un service Instadia (<php> $sl->get('instadia');</php>) permet de :
+  * lister les messages : <php>getMessages($rubrique = null, $sousRubrique = null)</php>
+  * Sauvegarder un message : <php>save(UnicaenApp\Entity\Db\Instadia $instadia)</php>
+  * Créer un Hash pour un utilisateur donné : <php>makeHash(UnicaenAuth\Entity\Db\AbstractUser $user)</php>
+  * Réagir au post d'un nouveau message selon la rubrique : <php>onSend( $rubrique, $sousRubrique, $callback)</php>
+
+===== Système de callback pour générer une action suite à un post de message =====
+Ceci est une portion de code à placer dans la méthode onBootstrap de la classe Module de votre application.
+
+<code php>
+
+$sm = $e->getApplication()->getServiceManager();
+/** @var \UnicaenApp\Service\InstadiaService $instadia */
+$instadia = $sm->get('instadia');
+$instadia->onSend('maRubrique', null, function( \UnicaenApp\Entity\Db\Instadia $instadia){
+    /* DO WHAT YOU WANT */
+});
+
+</code>
diff --git a/doc/Widgets.md b/doc/Widgets.md
new file mode 100644
index 0000000000000000000000000000000000000000..4af3618b81125d07daec6b08d17aad6662654d9a
--- /dev/null
+++ b/doc/Widgets.md
@@ -0,0 +1,584 @@
+AjaxModalDialog
+===============
+
+Cette aide de vue :
+
+-   intercepte le clic sur un lien ayant la classe CSS `ajax-modal`,
+    lance la requête correspondante en AJAX et ouvre le résultat dans
+    une fenêtre modale Bootstrap 3 ;
+-   simplifie la gestion d\'un éventuel formulaire présent dans cette
+    fenêtre modale.
+
+\<note warning\> Cette aide de vue \"manuelle\" est devenue caduque du
+fait que le mécanisme est désormais installé systématiquement sur tous
+les liens hypertextes ayant la classe CSS `ajax-modal`. Cf.
+[Javascript](/develop/unicaen2/moduleunicaenunicaenapp/js).
+
+Le mécanisme d\'événement évoqué plus bas reste néanmoins d\'actualité.
+\</note\>
+
+\<note tip\>Rappel : il est possible de spécifier la classe `ajax-modal`
+sur les pages de navigation.\</note\>
+
+\<note important\>Le composant
+[ModalListener](/develop/unicaen2/moduleunicaenunicaenapp/mvc#modallistener)
+(activé dans le module UnicaenApp) est indispensable au fonctionnement
+de cette aide de vue.\</note\>
+
+1/ Sans formulaire
+------------------
+
+Si l\'action à \"ouvrir\" dans la fenêtre modale ne contient aucun
+formulaire, ce paragraphe est pour vous.
+
+Exemple de vue contenant le lien à ouvrir :
+
+``` {.php}
+<a title="Modifier le service" href="/ose/service/saisie/147"
+   class="ajax-modal"><span class="glyphicon glyphicon-edit"></span></a>
+<?php echo $this->modalAjaxDialog('ma-div-id'); ?>
+```
+
+\<note important\>Le lien hypertexte doit posséder la classe CSS
+`ajax-modal`.\</note\>
+
+2/ Avec formulaire
+------------------
+
+Ce paragraphe explique comment cette aide de vue peut vous simplifier la
+gestion d\'un formulaire s\'affichant dans la fenêtre modale.
+
+Le clic sur le bouton \"submit\" du formulaire (i.e. bouton ayant la
+classe CSS `btn-primary`) présent dans la fenêtre modale est intercepté
+pour soumettre la requête en Ajax (en POST). Deux cas de figure
+possibles selon le contenu (HTML) de la réponse reçue :
+
+-   si une balise HTML ayant la classe CSS `has-error` ou `has-errors`
+    ou `alert` ou `input-error` est trouvée, alors cela veut dire qu\'il
+    y a des erreurs de validation du formulaire : rien ne se passe.
+-   sinon, l\'événement jQuery dont le nom est spécifié par l\'attribut
+    `data-event` du lien cliqué est déclenché sur le \"body\" de la
+    page, donnant l\'opportunité à qui écoute cet événement de mettre à
+    jour l\'interface par exemple et *notamment de fermer la fenêtre
+    modale*.
+
+\<note important\>Le lien doit fournir via l\'attribut `data-event` le
+nom de l\'événement qui sera déclenché à la soumission de ce
+formulaire.\</note\>
+
+Exemple de vue contenant le lien à ouvrir :
+
+``` {.php}
+<a title="Modifier le service" href="/ose/service/saisie/147"
+   class="ajax-modal"
+   data-event="service-modify-message"><span class="glyphicon glyphicon-edit"></span></a>
+<?php echo $this->modalAjaxDialog('ma-div-id'); ?>
+
+<script>
+    $(function() {
+        // écoute de l'événement
+        $("body").on("service-modify-message", function(event, data) {
+            /**
+             * event          : événement jQuery (cf. http://api.jquery.com/category/events/event-object/)
+             *   event.type   : nom de l'événement ("save-message" dans cet exemple)
+             *   event.div    : DIV correspondant à la fenêtre modale (objet jQuery)
+             *   event.a      : lien hypertexte cliqué
+             * data           : formulaire sérialisé en tableau à l'aide de jQuery.serializeArray()
+             */
+            console.log("Event received : ", event);
+            console.log("With args : ", data);
+            // ...
+            event.div.modal('hide'); // ferme la fenêtre modale
+        });
+    });
+</script>
+```
+
+AjaxPopover : Utilisation de base
+=================================
+
+\<WRAP center round alert 60%\> Ce widget est déprécié et ne sera
+bientôt plus maintenu! Un nouveau widget, plus complet, le remplace :
+[PopAjax](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/PopAjax)
+\</WRAP\>
+
+Le Popover de Bootstrap (<http://getbootstrap.com/javascript/#popovers>)
+permet d\'afficher des informations sur un élément donné.
+
+Le rôle de l\'AjaxPopover est de transformer un lien de type ancre
+(élément a) en popover. Concrètement, lorsqu\'on clique sur ce lien, le
+popover s\'affiche avec, en contenu, la page ciblée par l\'attribut href
+du lien. Ce mécanisme s\'apppuie sur Ajax.
+
+Si le code retourné comporte un formulaire, alors ce dernier est
+également géré par l\'AjaxPopover.
+
+Pour signaler qu\'un lien doit aboutir à l\'ouverture d\'un Popover, il
+suffit de lui affecter la classe \"ajax-popover\".
+
+Voici un exemple de popover :
+
+``` {.html}
+<a href="/application/lien/vers/formulaire" class="ajax-popover event_save-popover-form">TEST</a>
+```
+
+Usage avancé
+============
+
+Un événement est émis par l\'AjaxPopover lorsqu\'un formulaire est
+soumis et que son résultat ne retourne aucune erreur. Il est donc
+possible de capturer cet événement pour effectuer diverses actions, par
+exemple fermer le popover.
+
+Voici, en Javascript, un exemple de capture d\'événement :
+
+``` {.javascript}
+$(document).ready(function() {
+
+    $("body").on('save-popover-form', function(event,data){
+        /*
+
+        event comporte deux propriétés :
+         - event.a : objet Jquery du lien.
+           Il permet d'accéder au lien source du popover pour y récupérer, par exemple,
+           des données ou pour accéder au popover pour le fermer, par exemple.
+
+         - event.div : élément conteneur du popover. Utile pour modifier le contenu par exemple.
+
+        data contient les données du formulaire posté
+
+        */
+
+        event.a.popover('hide'); // on ferme le popover
+    });
+
+});
+```
+
+\<WRAP center round tip 60%\> L\'événement \"save-popover-form\" sera
+généré sur le ou les liens disposant de la classe
+\"event\_save-popover-form\". Après \"event\_\", vous êtes libre de
+nommer l\'événement comme bon vous semble. \</WRAP\>
+
+TabAjax
+=======
+
+Cette aide de ue sert à dessiner des listes d\'onglets avec contenu
+chargé statiquement et/ou dynamiquement. Il est possible de mélanger des
+onglets dynamiques et d\'autres statiques.
+
+Usage simple (dans une vue phtml):
+
+``` {.php}
+
+echo $this->tabajax([
+    [
+        'id'      => 'fiche',
+        'label'   => 'Fiche',
+        'content' => 'Ceci est le contenu de la fiche descriptive',
+    ],
+    [
+        'id'    => 'détails',
+        'label' => 'Détails',
+        'url'   => $this->url('fiche/details', ['fiche' => 1245]),
+    ],
+]);
+
+```
+
+Ici, le premier onglet (Fiche) a un contenu statique. Le deuxième onglet
+reçoit une URL, qui sera exploitée le moment venu pour charger son
+contenu en AJAX. Les ID permettent de nommer les onglets pour les
+exploiter ensuite en JS si besoin.\
+`label`{.php} est le nom affiché sur l\'onglet.\
+`content`{.php} le contenu de l\'onglet (si pré-chargé).
+
+Événement \"loaded\"
+--------------------
+
+Il est déclenché lorsqu\'un onglet AJAX a terminé son chargement :
+
+``` {.javascript}
+    $(function(){
+        $('#zozo').tabAjax({
+            'loaded': function( event, tab ){
+                //console.log(event);
+                console.log(tab);
+            }
+        });
+
+    });
+```
+
+Chargement d\'un onglet à la demande
+------------------------------------
+
+``` {.javascript}
+    $(function(){
+        $('#zozo').tabAjax('select', 'onglet-general');
+    });
+```
+
+PopAjax
+=======
+
+PopAjax est un \"popover\" reprenant le popover de Bootstrap et
+compatible Ajax. C\'est un Widget conçu avec le Widget Factory de
+JQueryUI (inclus dans UnicaenApp).
+
+Il propose :
+
+-   de charger éventuellement son contenu en Ajax à partir d\'une URL
+-   de créer simplement une DIV et de le paramétrer en lui passant des
+    attributs data\*.
+-   de pouvoir l\'instancier en Javascript comme n\'importe quel widget
+    JQuery
+-   un système d\'événements permettant de fermer le popover, de
+    recharger la page ou de lancer l\'événement de votre choix si le
+    formulaire inclus
+-   de personnaliser plein de paramètres (où il s\'affiche, quel message
+    d\'attente, etc.
+-   si besoin, un popajax peut être appelé depuis un autre popajax et
+    ainsi de suite sans limites.
+-   quand on clique hors du popover, il se ferme tout seul.
+-   un système de boite de dialogue de confirmation (avec possibilité
+    d\'ajouter des éléments de formulaire au besoin)
+
+Voici ce que ça donne :
+![](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/popajax.png)
+
+Exemples :
+----------
+
+``` {.html}
+<!-- Le fait de préciser que le lien est de classe popajax suffit!!! data-submit-close fermera le popAjax dès qu'un formulaire sera posté -->
+<a href="<?php echo $url ?>" class="pop-ajax" data-submit-close="true">Lien 1</a>
+
+<!-- Ca marche aussi avec un bouton mais il faut lui préciser l'URL de chargement en passant par un attribut du dataset-->
+<button type="button" class="pop-ajax" data-url="http://www.google.com">Mon bouton</button>
+
+<!-- Exemple avec un événement -->
+<a href="<?php echo $url ?>" class="pop-ajax" data-submit-event="mon-formulaire-enregistre">Lien 1</a>
+<script>
+
+    $("body").on("mon-formulaire-enregistre", function (event, popAjax)
+    {
+        console.log(popAjax); // affiche en console votre objet popajax
+    });
+
+</script>
+```
+
+En bref
+-------
+
+### Gestion des titres
+
+Le titre du popajax peut être adapté au contenu retourné par la requête
+AJAX : Si le code HTMl contient :
+
+-   un titre h1
+-   ou un élément de classe .popover-title
+-   ou un élément de classe .page-header
+
+alors cet élément sera supprimé du contenu du popover et son contenu
+sera injecté dans la partie titre du popover.
+
+Si aucun titre n\'est disponible alors la zone de titre disparaît.
+
+### Boites de dialogue de confirmation
+
+PopAjax vous permet de réaliser simplement des boites de dialogue de
+confirmation.
+
+Exemple :
+
+``` {.html}
+<a
+    class="pop-ajax"
+    href="<?php echo $url ?>"
+    data-content="Voulez-vous vraiement faire cela <input name='t1' value='t1' /> ?"
+    data-confirm="true"
+>Confirm</a>
+```
+
+Dans ce cas, le texte est posé au départ et l\'URL ne sera appelée que
+lorsque le bouton de confirmation sera cliqué. J\'ai même ajouté un
+input dont la valeur sera récupérable dans les données POST de l\'action
+pour montrer que c\'est possible.
+
+Il est même possible de ne pas fournir de contenu de de le charger en
+AJAX si besoin. Attention dans votre action de contrôleur de bien
+distinguer dans ce cas la demande (méthode GET) de l\'action (méthode
+POST).
+
+Si vous ne souhaitez rien renvoyer de spécial hormis des messages
+informatifs, le modèle de vue
+`UnicaenApp\View\Model\MessengerViewModel`{.php} vous aidera à ne
+renvoyer que les messages collectés dans votre action sans avoir à créer
+de vue spécifique pour cela.
+
+Exemple côté PHP (dans une action de contrôleur):
+
+``` {.php}
+
+if ($this->getRequest()->isPost()){
+    try{
+        $this->getVotreService()->faireVotretravail();
+        $this->flashMessenger()->addSuccessMessage("Tout est OK.");
+    }catch(\Exception $e){
+        $this->flashMessenger()->addErrorMessage($e->getMessage());
+    }
+}else{
+    $this->flashMessenger()->addErrorMessage('Vous devez d\'abord confirmer.');
+}
+
+return new \UnicaenApp\View\Model\MessengerViewModel();
+
+```
+
+### Bouton de fermeture
+
+Le clic sur tout élément ayant pour classe \"pop-ajax-hide\" engendrera
+la fermeture du popAjax.
+
+Options
+-------
+
+  Nom (dataset)     Nom (Json)       Type      Valeur par défaut                                          Description
+  ----------------- ---------------- --------- ---------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  url               url              string    undefined ou href (si présent)                             URL de chargement de la page AJAX
+  content           content          string    undefined                                                  contenu (en HTML). si défini alors le chargement ne se fait plus à partir de l\'URL (plus d\'AJAX à l\'affichage)\...
+  animation         animation        boolean   true                                                       Détermine si une animation doit se faire quand le popover apparaît ou disparait
+  delay             delay            integer   200                                                        Délai de l\'animation
+  placement         placement        string    auto                                                       Placement. Valeurs possibles (par ordre de priorité) : auto, bottom, top, left, right. Si auto est déterminé alors le placement le plus judicieux sera calculé automatiquement
+  submit-event      submitEvent      string    undefined                                                  nom d\'un événement à transmettre. L\'événement sera lancé si un formulaire est posté à l\'intérieur du popover ET que le résultat ne contient aucune erreur
+  submit-close      submitClose      boolean   false                                                      Détermine on doit fermer le popover après le post d\'un formulaire sans retour d\'erreur
+  submit-reload     submitReload     boolean   false                                                      Détermine on doit recharger toute la page après le post d\'un formulaire sans retour d\'erreur
+  min-width         minWidth         string    100px                                                      largeur minimale du popover
+  max-width         maxWidth         string    600px                                                      largeur maximale du popover
+  min-height        minHeight        string    50px                                                       hauteur minimale du popover
+  max-height        maxHeight        string    none                                                       hauteur maximale du popover (si le contenu dépasse alors un ascenseur apparaîtra)
+  loading-title     loadingTitle     string    Chargement\...                                             Titre lorsque le popover est en cours de chargement
+  loading-content   loadingContent   string    <div class="loading"></div>                                Contenu lorsque le popover est en cours de chargement
+  title             title            string    undefined                                                  Titre à afficher si le contenu issu de la requête ne contient aucun titre à afficher
+  confirm           confirm          boolean   false                                                      transforme le popajax en boite de dialogue de confirmation
+  confirm-button    confirmButton    string    <span class="glyphicon glyphicon-ok"></span> OK            Texte du bouton de confirmation (si vous fournissez un texte volontairement vide alors le bouton n\'apparaitra pas)
+  cancel-button     cancelButton     string    <span class="glyphicon glyphicon-remove"></span> Annuler   Texte du bouton d\'annulation (si vous fournissez un texte volontairement vide alors le bouton n\'apparaitra pas)
+  auto-show         autoShow         boolean   false                                                      Affichage du popover dès son initialisation (sans attendre le click). utile si le popover est instancié en JS directement
+
+Événements
+----------
+
+Les événements permettent de réagir à des changements d\'état. Les
+closures ou les fonctions qui seront appelées se verront transmettre
+deux arguments :
+
+1.  l\'événement en lui-même
+2.  l\'objet popajax (ce qui permet de le manipuler ou de récupérer son
+    contenu (méthode getContent), etc.
+
+Exemple d\'utilisation d\'événement :
+
+``` {.html}
+<!-- Ici, le widget est associé à l'élément a directement en Javascript et pas en passant par la classe popajax. Le titre est également transmis par le tableau JSON des options, de même que la closure pour l'événement show -->
+<a id="popajax_1" href="<?php echo $url ?>">TEST</a>
+<script>
+
+    $(function(){
+
+        $('#popajax_1').popAjax({title: 'Mon Titre', show: function(event, popAjax){
+            console.log('show');
+            console.log(event);
+            console.log(popAjax);
+        }});
+
+    });
+
+</script>
+
+```
+
+### Liste des événements
+
+  Événement   Description
+  ----------- ------------------------------------------------------------------------------
+  show        se déclenche à l\'affichage du popajax
+  hide        se déclenche à la fermeture du popajax
+  change      lorsqu\'un changement de contenu est détecté
+  submit      lorsqu\'un formulaire a été posté et que le retour ne comporte aucune erreur
+
+Méthodes
+--------
+
+Popajax comporte un certain nombre de méthodes :
+
+  Nom               Arguments   Valeur de retour              Description
+  ----------------- ----------- ----------------------------- -----------------------------------------------------------------------------------------------------------
+  showHide          Aucun       this                          Permet d\'afficher le popover s\'il n\'est pas affiché ou de le masquer s\'il est affiché.
+  shown             Aucun       boolean                       Retourne true s\'il est affiché, false sinon
+  show              Aucun       this                          Affiche le popover
+  hide              Aucun       this                          Masque le popover
+  errorsInContent   Aucun       boolean                       Retourne true si un ou plusieurs messages d\'alerte de danger ou d\'erreur sont affichés dans son contenu
+  posPop            Aucun       this                          Repositionne le popover
+  getContent        Aucun       element Jquery ou undefined   Retourne l\'élément qui contient le texte du popover
+  setContent        string      this                          Permet de peupler le contenu du popover
+
+Instadia
+========
+
+Instadia est un système de messagerie instantanée persistant contextuel.
+Cela signifie que :
+
+-   Les messages sont stockés en base de données
+-   Chaque fil de messagerie peut être lié à un contexte particulier.
+-   Il est possible de dialoguer en temps réel avec des interlocuteurs.
+-   Les messages restent visibles sans pouvoir être effecés
+
+Instadia peut être intégré très simplement au coeur d\'une application
+pour, par exemple, suivre un dossier ou commenter tel ou tel item.
+
+L\'intérêt est que :
+
+-   les informations de suivi sont visibles dans leur contexte. Il n\'y
+    a pas besoin d\'aller chercher l\'historique d\'une conversation
+    dans sa boite mail.
+-   les informations sont PARTAGÉES. Un nouvel utilisateur peut voir ce
+    qui c\'est dit précédemment
+
+\<WRAP center round important 60%\> Dans Instadia, chaque fil de
+conversation correspond à une rubrique et éventuellement à une
+sous-rubrique. Par exemple dans Uniform nous avons une rubrique
+formation pour signaler qu\'on parle de formation, et une sous-rubrique
+par code de formation. Ainsi nous avons bien un fil de discussion par
+formation. \</WRAP\>
+
+Instadia est intégré à UnicaenApp. Les préresuis pour l\'utiliser sont
+les suivants :
+
+-   votre application doit reposer sur une base de données
+-   vous devez utiliser UnicaenAuth (uniquement si vous voulez
+    authentifier vos utilisateurs)
+-   une table, nommée instadia, doit être créée. Vous en trouverez la
+    structure ci-dessous (code pour Oracle):
+
+``` {.sql}
+CREATE
+  TABLE INSTADIA
+  (
+    ID            NUMBER (*,0) NOT NULL ,
+    USER_ID       NUMBER (*,0) ,
+    RUBRIQUE      VARCHAR2 (80 CHAR) NOT NULL ,
+    SOUS_RUBRIQUE VARCHAR2 (80 CHAR) ,
+    HORODATAGE    DATE NOT NULL ,
+    CONTENU       VARCHAR2 (3000 CHAR) NOT NULL
+  )
+  LOGGING ;
+ALTER TABLE INSTADIA ADD CONSTRAINT INSTADIA_PK PRIMARY KEY ( id ) ;
+
+ALTER TABLE INSTADIA ADD CONSTRAINT INSTADIA_USER_FK FOREIGN KEY ( USER_ID )
+REFERENCES "USER" ( ID ) ON DELETE SET NULL NOT DEFERRABLE ;
+
+CREATE SEQUENCE INSTADIA_ID_SEQ;
+```
+
+Une aide de vue `UnicaenApp\View\Helper\InstadiaViewHelper`{.php} a été
+créée pour simplifier l\'intégration. En voici un exemple d\'utilisation
+:
+
+``` {.php}
+/* Dans une vue */
+
+<?php echo $this->instadia()->setRubrique('essai')->setTitle('Mon test'); ?>
+
+```
+
+Voici le résultat (un clic sur le lien ouvre la fenêtre de CHAT) :
+![](/develop/unicaen2/moduleunicaenunicaenapp/viewhelpers/instadia.png){.align-center}
+
+Et voici maintenant ce qu\'on peut faire avec le Widget :
+
+Options
+-------
+
+  Nom (dataset)   Nom (Json)      Type      Valeur par défaut        Description
+  --------------- --------------- --------- ------------------------ --------------------------------------------------------------------------------------------------------------
+  user-id         userId          integer   undefined                ID de l\'utilisateur courant (facultatif)
+  user-label      userLabel       string    \'Anonyme\'              Nom de l\'utilisateur (ou undefined)
+  user-hash       userHash        string    undefined                Hash permettant de récupérer le Gravatar de l\'utilisateur courant (formule : md5(strtolower(trim(\$email)))
+  refresh-delay   refreshDelay    integer   2000                     Délai de rafraichissement de la fenêtre des messages (en millisecondes)
+  title           title           string    Messagerie instantanée   Titre de la fenêtre de Chat et nom du lien
+  rubrique        rubrique        string    undefined                Rubrique
+  sousRubrique    sous-rubrique   string    undefined                Sous-rubrique
+  url             url             string    undefined                URL de communication avec le serveur
+  information     information     string    undefined                Message d\'information sur la fenêtre de Chat
+  width           width           integer   500                      Largeur de la fenêtre de Chat
+  height          height          integer   700                      Hauteur de la fenêtre de Chat
+  readOnly        read-only       boolean   false                    Fenêtre de Chat en simple visualisation
+
+Événements
+----------
+
+Les événements permettent de réagir à des changements d\'état. Les
+closures ou les fonctions qui seront appelées se verront transmettre
+deux arguments :
+
+1.  l\'événement en lui-même
+2.  l\'objet instadia (ce qui permet de le manipuler).
+
+### Liste des événements
+
+  Événement    Description
+  ------------ -------------------------------
+  afficher     affiche la fenêtre de Chat
+  cacher       ferme la fenêtre de Chat
+  envoyer      Lorsqu\'un message est envoyé
+  addMessage   Lorsqu\'un message est ajouté
+
+Méthodes
+--------
+
+Popajax comporte un certain nombre de méthodes :
+
+  Nom              Arguments                   Valeur de retour   Description
+  ---------------- --------------------------- ------------------ ----------------------------------------------------------------------------------------------------------
+  afficherCacher   Aucun                       this               Permet d\'afficher la fenêtre de Chat si elle n\'est pas affichée ou de la masquer si elle est affichée.
+  estAffiche       Aucun                       boolean            Retourne true si elle est affichée, false sinon
+  afficher         Aucun                       this               Affiche le Chat
+  cacher           Aucun                       this               Masque le Chat
+  envoyer          Aucun                       this               Envoie le message saisi
+  addMessage       user, horodatage, content   this               Poste un nouveau message user = id,label,hash
+  getMessage       Aucun                       string             Retourne le texte en cours de saisie
+  setMessage       string                      this               Change le texte en cours de saisie
+  getUser          Aucun                       id,label,hash      retourne l\'utilisateur courant
+
+Avancé
+======
+
+Un service Instadia (` $sl->get('instadia');`{.php}) permet de :
+
+-   lister les messages :
+    `getMessages($rubrique = null, $sousRubrique = null)`{.php}
+-   Sauvegarder un message :
+    `save(UnicaenApp\Entity\Db\Instadia $instadia)`{.php}
+-   Créer un Hash pour un utilisateur donné :
+    `makeHash(UnicaenAuth\Entity\Db\AbstractUser $user)`{.php}
+-   Réagir au post d\'un nouveau message selon la rubrique :
+    `onSend( $rubrique, $sousRubrique, $callback)`{.php}
+
+Système de callback pour générer une action suite à un post de message
+----------------------------------------------------------------------
+
+Ceci est une portion de code à placer dans la méthode onBootstrap de la
+classe Module de votre application.
+
+``` {.php}
+
+$sm = $e->getApplication()->getServiceManager();
+/** @var \UnicaenApp\Service\InstadiaService $instadia */
+$instadia = $sm->get('instadia');
+$instadia->onSend('maRubrique', null, function( \UnicaenApp\Entity\Db\Instadia $instadia){
+    /* DO WHAT YOU WANT */
+});
+
+```
diff --git a/doc/dokuwiki2md.sh b/doc/dokuwiki2md.sh
new file mode 100755
index 0000000000000000000000000000000000000000..0330c4dd83dde9da17eee3d2856c350f8bcb723a
--- /dev/null
+++ b/doc/dokuwiki2md.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+pandoc -f dokuwiki -t markdown Installation.dokuwiki > Installation.md
+pandoc -f dokuwiki -t markdown Configuration.dokuwiki > Configuration.md
+pandoc -f dokuwiki -t markdown Services.dokuwiki > Services.md
+pandoc -f dokuwiki -t markdown Modeles.dokuwiki > Modeles.md
+pandoc -f dokuwiki -t markdown Plugins.dokuwiki > Plugins.md
+pandoc -f dokuwiki -t markdown ViewHelpers.dokuwiki > ViewHelpers.md
+pandoc -f dokuwiki -t markdown Controleurs.dokuwiki > Controleurs.md
+pandoc -f dokuwiki -t markdown Formulaires.dokuwiki > Formulaires.md
+pandoc -f dokuwiki -t markdown Filtres.dokuwiki > Filtres.md
+pandoc -f dokuwiki -t markdown Validateurs.dokuwiki > Validateurs.md
+pandoc -f dokuwiki -t markdown MVC.dokuwiki > MVC.md
+pandoc -f dokuwiki -t markdown Message.dokuwiki > Message.md
+pandoc -f dokuwiki -t markdown Divers.dokuwiki > Divers.md
+pandoc -f dokuwiki -t markdown Historique.dokuwiki > Historique.md
+pandoc -f dokuwiki -t markdown ElementsPageRiches.dokuwiki > ElementsPageRiches.md
+pandoc -f dokuwiki -t markdown Javascript.dokuwiki > Javascript.md
+pandoc -f dokuwiki -t markdown Widgets.dokuwiki > Widgets.md