diff --git a/library/Class/AdminVar.php b/library/Class/AdminVar.php
index d635d59fd253a601e14b0d39c836ed588f83d904..65e2a1ec9219607e36a15037ee9c00df537b44c6 100644
--- a/library/Class/AdminVar.php
+++ b/library/Class/AdminVar.php
@@ -137,7 +137,8 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
        'usergroup-agenda' => $this->_getRendezVousVars(),
        'templating' => $this->_getTemplatingVars(),
        'identity-providers' => $this->_getIdentityProvidersVars(),
-       'drive-checkout' => $this->_getDriveCheckoutVars()
+       'drive-checkout' => $this->_getDriveCheckoutVars(),
+       'journal' => $this->_getJournalVars(),
        ];
   }
 
@@ -544,6 +545,7 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
   }
 
 
+
   protected function _getIdentityProvidersVars() {
     return
       ['ENABLE_IDENTITY_PROVIDERS' => Class_AdminVar_Meta::newOnOff($this->_('Activer la gestion des fournisseurs d\'identité')),
@@ -567,6 +569,22 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
   }
 
 
+  protected function _getJournalVars() {
+    $stormModels = new Class_AdminVar_JournalStormModels();
+    $models = Class_AdminVar_Meta::newMultiInput($this->_('Liste des données à journaliser'),
+                                                 ['options' => ['fields' => [['name' => 'model',
+                                                                              'label' => $this->_('Donnée'),
+                                                                              'type' => 'select',
+                                                                              'options' => $stormModels->modelsMultioptions()],
+                                                                             ['name' => 'ids',
+                                                                              'label' => $this->_('Limité aux identifiants (séparés par ; , vide pour ne pas limiter)')]]],
+                                                  'value' => $stormModels->defaultValue()]);
+
+    return ['JOURNAL_ENABLE_STORM' => Class_AdminVar_Meta::newOnOff($this->_('Activer la journalisation des modifications en base de données'))->enable(),
+            'JOURNAL_STORM_MODELS' => $models];
+  }
+
+
   public function allVarsValues() {
     if (null !== static::$_all_vars_values)
       return static::$_all_vars_values;
@@ -1168,8 +1186,6 @@ class Class_AdminVar extends Storm_Model_Abstract {
   protected $_loader_class = 'Class_AdminVarLoader';
 
   protected $_fixed_id = true;
-  protected $_before_save_value;
-
 
   public function getValeur() {
     return $this->_get('valeur');
@@ -1224,19 +1240,6 @@ class Class_AdminVar extends Storm_Model_Abstract {
   public function afterSave() {
     $this->getMeta()->afterSave($this);
     Class_DigitalResource::getInstance()->enablePluginWith($this->getClef());
-    Class_Journal_Type::save($this);
-  }
-
-
-  public function beforeSave() {
-    $this->_before_save_value = isset($this->_attributes_in_db['valeur'])
-      ? $this->_attributes_in_db['valeur']
-      : null;
-  }
-
-
-  public function getBeforeSaveValue() {
-    return $this->_before_save_value;
   }
 
 
diff --git a/library/Class/AdminVar/JournalStormModels.php b/library/Class/AdminVar/JournalStormModels.php
new file mode 100644
index 0000000000000000000000000000000000000000..4518177e8efb0988eed9bd7750d0a2ee048e168f
--- /dev/null
+++ b/library/Class/AdminVar/JournalStormModels.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_AdminVar_JournalStormModels {
+  use Trait_Translator;
+
+  const
+    MODEL_KEY = 'model',
+    IDS_KEY = 'ids',
+    IDS_SEPARATOR = ';';
+
+  public function modelsMultioptions() {
+    return ['' => $this->_(''),
+            Class_AdminVar::class => $this->_('Variables'),
+            Class_Profil::class => $this->_('Profils')];
+  }
+
+
+  public function defaultValue() {
+    return json_encode([static::MODEL_KEY => [Class_AdminVar::class],
+                        static::IDS_KEY   => ['']]);
+  }
+
+
+  public function isEnabled() {
+    return ($map = $this->_loadMapping())
+      ? array_filter(array_keys($map))
+      : false;
+  }
+
+
+  public function modelMatch($model) {
+    if (!$map = $this->_loadMapping())
+      return false;
+
+    if (!array_key_exists(get_class($model), $map))
+      return false;
+
+    $ids = array_filter(explode(static::IDS_SEPARATOR, $map[get_class($model)]));
+
+    return !$ids || in_array($model->getId(), $ids);
+  }
+
+
+  protected function _loadMapping() {
+    if (!$conf = json_decode(Class_AdminVar::getValueOrDefault('JOURNAL_STORM_MODELS'), true))
+      return [];
+
+    if (!isset($conf[static::MODEL_KEY]) || !isset($conf[static::IDS_KEY]))
+      return [];
+
+    return ($map = array_combine($conf[static::MODEL_KEY], $conf[static::IDS_KEY]))
+      ? $map
+      : [];
+  }
+}
diff --git a/library/Class/Journal.php b/library/Class/Journal.php
index c12ad514d3b11cedd523396444bfd73b2baee96a..d859063b91cb9097ce2a744cf8937e7fb6af102d 100644
--- a/library/Class/Journal.php
+++ b/library/Class/Journal.php
@@ -99,4 +99,14 @@ class Class_Journal extends Storm_Model_Abstract {
     return (new Storm_Model_Collection($this->getDetails()))
       ->detect(function($detail) use($type) { return $detail->getType() == $type; });
   }
+
+
+  public function addDetail($type, $value) {
+    Class_JournalDetail::newInstance(['type' => $type,
+                                      'value' => $value,
+                                      'journal' => $this])
+      ->save();
+
+    return $this;
+  }
 }
diff --git a/library/Class/Journal/AdminVarType.php b/library/Class/Journal/AdminVarType.php
new file mode 100644
index 0000000000000000000000000000000000000000..76539c777ec875e0684f4bddc216c5327e96162c
--- /dev/null
+++ b/library/Class/Journal/AdminVarType.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_Journal_AdminVarType extends Class_Journal_Type {
+
+  const
+    MY_TYPE = 'ADMIN_VAR_SAVE',
+    ADMINVAR_ID = 'adminvar_id',
+    CURRENT_USER_ID = 'current_user_id',
+    PREVIOUS_VALUE = 'previous_value',
+    NEW_VALUE = 'new_value';
+
+
+  public static function save($model) {
+    if (!$model)
+      return;
+
+    if (!$journal = static::_newJournal())
+      return;
+
+    $journal->addDetail(static::ADMINVAR_ID, $model->getClef());
+    $journal->addDetail(static::CURRENT_USER_ID,
+                       ($user = Class_Users::getIdentity()) ? $user->getId() : '',
+                       $journal);
+    $journal->addDetail(static::PREVIOUS_VALUE, $model->getPreviousValue());
+    $journal->addDetail(static::NEW_VALUE, $model->getValeur());
+
+    return $journal;
+  }
+
+
+  public function getLabel() {
+    return $this->_('La variable "%s" a été modifiée de "%s" à "%s" par "%s"',
+                    $this->_getDetailLabel('adminvar_id'),
+                    $this->_getDetailLabel('previous_value'),
+                    $this->_getDetailLabel('new_value'),
+                    $this->_getDetailLabel('current_user_id')
+    );
+  }
+}
diff --git a/library/Class/Journal/ProfileType.php b/library/Class/Journal/ProfileType.php
new file mode 100644
index 0000000000000000000000000000000000000000..c73aa8643f24b6c4ac62a8ed1017722b0a5d6666
--- /dev/null
+++ b/library/Class/Journal/ProfileType.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_Journal_ProfileType extends Class_Journal_Type {
+
+  const
+    MY_TYPE = 'PROFILE_SAVE',
+    MODEL_ID = 'model_id',
+    CURRENT_USER_ID = 'current_user_id',
+    PREVIOUS_VALUE = 'previous_value',
+    NEW_VALUE = 'new_value',
+    STACK = 'stack';
+
+
+  public static function save($model) {
+    if (!$model)
+      return;
+
+    $current = $model->getRawAttributes();
+    $previous = $model->getRawDbAttributes();
+    if (!isset($previous['cfg_accueil']))
+      return;
+
+    if (!$journal = static::_newJournal())
+      return;
+
+    $journal->addDetail(static::MODEL_ID, $model->getId());
+    $journal->addDetail(static::CURRENT_USER_ID,
+                       ($user = Class_Users::getIdentity()) ? $user->getId() : '',
+                       $journal);
+    $journal->addDetail(static::PREVIOUS_VALUE, json_encode(static::_unserialize($previous['cfg_accueil'])));
+    $journal->addDetail(static::NEW_VALUE, json_encode(static::_unserialize($current['cfg_accueil'])));
+
+    ob_start();
+    debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+    $stack = ob_get_contents();
+    ob_end_clean();
+    $journal->addDetail(static::STACK, $stack);
+
+    return $journal;
+  }
+
+
+  protected static function _unserialize($data) {
+    try {
+      $unserialized = ZendAfi_Filters_Serialize::unserialize($data);
+    } catch (Exception $e) {
+      $unserialized = [];
+    }
+
+    if (!$unserialized)
+      return [];
+
+    return $unserialized;
+  }
+
+
+  public function getLabel() {
+    return $this->_('Le profil "%s" a été modifié par "%s"',
+                    $this->_getDetailLabel(static::MODEL_ID),
+                    $this->_getDetailLabel(static::CURRENT_USER_ID)
+    );
+  }
+}
diff --git a/library/Class/Journal/StormObserver.php b/library/Class/Journal/StormObserver.php
new file mode 100644
index 0000000000000000000000000000000000000000..1f3fb4f8dec38fd36ca4b3f31ede9d6207032798
--- /dev/null
+++ b/library/Class/Journal/StormObserver.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_Journal_StormObserver {
+  protected $_lock = false;
+
+  public static function register() {
+    if (!Class_AdminVar::isModuleEnabled('JOURNAL_ENABLE_STORM'))
+      return;
+
+    if (!(new Class_AdminVar_JournalStormModels)->isEnabled())
+      return;
+
+    Storm_Events::getInstance()->register(new static());
+  }
+
+
+  /**
+   * @param $event Storm_Event
+   */
+  public function __invoke($event) {
+    if ($this->_lock || (!$model = $event->getModel()))
+      return;
+
+    if ($this->_shouldSave($model))
+      Class_Journal_Type::save($model);
+  }
+
+
+  protected function _shouldSave($model) {
+    // lock to dodge cases where reading adminvar would save a model and then infinite loop here
+    return $this->_withLockDo(function() use($model)
+                              {
+                                return (new Class_AdminVar_JournalStormModels)
+                                  ->modelMatch($model);
+                              });
+  }
+
+
+  protected function _withLockDo($aBlock) {
+    $this->_lock = true;
+    $result = $aBlock();
+    $this->_lock = false;
+
+    return $result;
+  }
+}
diff --git a/library/Class/Journal/Type.php b/library/Class/Journal/Type.php
index deda6beb83783cef9b30e85b54c7505cd0af582a..8d84d9ecebaa6580b25a11f04b1592ac6fee732a 100644
--- a/library/Class/Journal/Type.php
+++ b/library/Class/Journal/Type.php
@@ -24,17 +24,15 @@ class Class_Journal_Type {
   use Trait_Translator;
 
   const
-    MY_TYPE = 'ADMIN_VAR_SAVE',
-    ADMINVAR_ID = 'adminvar_id',
-    CURRENT_USER_ID = 'current_user_id',
-    PREVIOUS_VALUE = 'previous_value',
-    NEW_VALUE = 'new_value';
+    MY_TYPE = 'UNKNOWN',
+    MAX_STACK = 100;
 
   protected static $_enabled = true;
 
   protected $_journal;
 
 
+
   /** @category testing */
   public static function disable() {
     static::$_enabled = false;
@@ -53,38 +51,55 @@ class Class_Journal_Type {
   }
 
 
+  /**
+   * Given a journal, make instance of its type
+   *
+   * @param $journal Class_Journal
+   * @return Class_Journal_Type
+   */
+  public static function newFor($journal) {
+    $possibles = [Class_Journal_AdminVarType::MY_TYPE => Class_Journal_AdminVarType::class,
+                  Class_Journal_ProfileType::MY_TYPE => Class_Journal_ProfileType::class];
+
+    return array_key_exists($journal->getType(), $possibles)
+      ? new $possibles[$journal->getType()]($journal)
+      : new static($journal);
+  }
+
+
+  /**
+   * @param $model Storm_Model_Abstract
+   * @return Class_Journal or null
+   */
   public static function save($model) {
-    if (!static::_isEnabled())
+    if (!$model || !static::_isEnabled())
       return;
 
+    $map = [Class_AdminVar::class => Class_Journal_AdminVarType::class,
+            Class_Profil::class   => Class_Journal_ProfileType::class];
+    $class = get_class($model);
+
+    return array_key_exists($class, $map)
+      ? call_user_func([$map[$class], 'save'], $model)
+      : null;
+  }
+
+
+  protected static function _newJournal() {
     $journal = Class_Journal::newInstance(['type' => static::MY_TYPE]);
+
     if (!$journal->save())
       return;
 
-    if (!$model)
-      return $journal;
-
-    static::_newDetail(static::ADMINVAR_ID, $model->getClef(), $journal);
-    static::_newDetail(static::CURRENT_USER_ID,
-                       ($user = Class_Users::getIdentity()) ? $user->getId() : '',
-                       $journal);
-    static::_newDetail(static::PREVIOUS_VALUE, $model->getPreviousValue(), $journal);
-    static::_newDetail(static::NEW_VALUE, $model->getValeur(), $journal);
+    static::_cleanStack();
 
     return $journal;
   }
 
 
-  protected static function _newDetail($type, $value, $journal) {
-    Class_JournalDetail::newInstance(['type' => $type,
-                                      'value' => $value,
-                                      'journal' => $journal])
-      ->save();
-  }
-
-
-  public static function newFor($journal) {
-    return new static($journal);
+  protected static function _cleanStack() {
+    while (static::MAX_STACK < Class_Journal::countBy(['type' => static::MY_TYPE]))
+      Class_Journal::findFirstBy(['order' => 'created_at'])->delete();
   }
 
 
@@ -94,12 +109,7 @@ class Class_Journal_Type {
 
 
   public function getLabel() {
-    return $this->_('La variable "%s" a été modifiée de "%s" à "%s" par "%s"',
-                    $this->_getDetailLabel('adminvar_id'),
-                    $this->_getDetailLabel('previous_value'),
-                    $this->_getDetailLabel('new_value'),
-                    $this->_getDetailLabel('current_user_id')
-    );
+    return '';
   }
 
 
diff --git a/library/startup.php b/library/startup.php
index 9f9980f2e1b640dfd8e8e466a95fa04147db4453..d155322b44698a214ae47c4a0c5d2904a72916d8 100644
--- a/library/startup.php
+++ b/library/startup.php
@@ -75,7 +75,8 @@ class Bokeh_Engine {
 
     Class_AdminVar::findAll();
 
-    return $this->setupLanguage();
+    return $this->setupLanguage()
+                ->setupStormObservers();
   }
 
 
@@ -145,13 +146,13 @@ class Bokeh_Engine {
   }
 
 
-  public function loadConfig($config_init_file_path = './config.ini') {
+  public function loadConfig($config_init_file_path = './config.ini', $allowModifications=false) {
     // load configuration (local ou production)
     if(array_isset('REMOTE_ADDR', $_SERVER) and $_SERVER['REMOTE_ADDR'] == '127.0.0.1')
       $serveur='local';
     else
       $serveur='production';
-    $this->_config = new Zend_Config_Ini($config_init_file_path, $serveur);
+    $this->_config = new Zend_Config_Ini($config_init_file_path, $serveur, $allowModifications);
     Zend_Registry::set('cfg', $this->_config);
 
     $locale = $this->_config->locale
@@ -240,6 +241,13 @@ class Bokeh_Engine {
     Zend_Registry::set('sql', $afi_sql);
     Zend_Db_Table::getDefaultAdapter()->query('set names "UTF8"');
     Zend_Db_Table::getDefaultAdapter()->query('set SQL_MODE = ""');
+
+    return $this;
+  }
+
+
+  public function setupStormObservers() {
+    Class_Journal_StormObserver::register();
     return $this;
   }
 
diff --git a/library/storm b/library/storm
index 67e32fa4f2114f69c7a3d9b96d9419186cb2e7bf..17d31fdfc59be5319cb5201ac2280dbb9e7c2c5b 160000
--- a/library/storm
+++ b/library/storm
@@ -1 +1 @@
-Subproject commit 67e32fa4f2114f69c7a3d9b96d9419186cb2e7bf
+Subproject commit 17d31fdfc59be5319cb5201ac2280dbb9e7c2c5b
diff --git a/tests/application/modules/AbstractControllerTestCase.php b/tests/application/modules/AbstractControllerTestCase.php
index f5fd9d5b7e44489324897e7ca4cd1932b971bcb8..852ba6e185c40723962b8825cbe5a16172f1d416 100644
--- a/tests/application/modules/AbstractControllerTestCase.php
+++ b/tests/application/modules/AbstractControllerTestCase.php
@@ -116,6 +116,8 @@ abstract class AbstractControllerTestCase extends Zend_Test_PHPUnit_ControllerTe
 
     $this->_initMockProfil();
 
+    Storm_Events::setInstance(null);
+
     parent::setUp();
 
     Zend_Registry::get('locale')->setLocale('fr');
diff --git a/tests/scenarios/Journal/JournalTest.php b/tests/scenarios/Journal/JournalTest.php
index ef2191b55761f217dc91f6e32dbe31b5b4ee75d7..26508d36791bfbeda12cee07416fb55a5e923b7d 100644
--- a/tests/scenarios/Journal/JournalTest.php
+++ b/tests/scenarios/Journal/JournalTest.php
@@ -20,9 +20,9 @@
  */
 
 
-abstract class JournalAdminVarTestCase extends Admin_AbstractControllerTestCase {
-  protected $_storm_default_to_volatile = true;
 
+abstract class JournalTestCase extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
 
   public function setUp() {
     parent::setUp();
@@ -33,10 +33,13 @@ abstract class JournalAdminVarTestCase extends Admin_AbstractControllerTestCase
 
 
 
-class JournalAdminVarPostTest extends JournalAdminVarTestCase {
+class JournalAdminVarPostTest extends JournalTestCase {
   public function setUp() {
     parent::setUp();
     Class_AdminVar::set('FACETTE_GENRE_LIBELLE', '');
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', 1);
+    (new Bokeh_Engine)->setupStormObservers();
+
     $this->postDispatch('/admin/index/adminvaredit/cle/FACETTE_GENRE_LIBELLE',
                         ['cle' => 'FACETTE_GENRE_LIBELLE',
                          'valeur' => "test"]);
@@ -84,10 +87,268 @@ class JournalAdminVarPostTest extends JournalAdminVarTestCase {
 
 
 
-class JournalIndexActionTest extends JournalAdminVarTestCase {
+
+class JournalAdminVarPostWithJournalDisabledTest extends JournalTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FACETTE_GENRE_LIBELLE', '');
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', '0');
+    (new Bokeh_Engine)->setupStormObservers();
+
+    $this->postDispatch('/admin/index/adminvaredit/cle/FACETTE_GENRE_LIBELLE',
+                        ['cle' => 'FACETTE_GENRE_LIBELLE',
+                         'valeur' => "test"]);
+  }
+
+
+  /** @test */
+  public function journalShouldNotBeCreated() {
+    $this->assertNull(Class_Journal::find(1));
+  }
+}
+
+
+
+
+class JournalAdminVarPostWithJournalEnabledButModelDisabledTest extends JournalTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FACETTE_GENRE_LIBELLE', '');
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', 1);
+    Class_AdminVar::set('JOURNAL_STORM_MODELS', json_encode(['model' => [Class_Profil::class],
+                                                             'ids' => ['']]));
+    (new Bokeh_Engine)->setupStormObservers();
+
+    $this->postDispatch('/admin/index/adminvaredit/cle/FACETTE_GENRE_LIBELLE',
+                        ['cle' => 'FACETTE_GENRE_LIBELLE',
+                         'valeur' => "test"]);
+  }
+
+
+  /** @test */
+  public function journalShouldNotBeCreated() {
+    $this->assertNull(Class_Journal::find(1));
+  }
+}
+
+
+
+
+abstract class JournalProfileTestCase extends JournalTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $simple_widgets = ['modules' => ['1' => ['division' => '4',
+                                             'type_module' => 'RECH_SIMPLE',
+                                             'preferences' => []],
+                                     '6' => ['division' => '3',
+                                             'type_module' => 'MENU_VERTICAL',
+                                             'preferences' => ['menu' => '5-8']],
+                                     '4' => ['division' => '4',
+                                             'type_module' => 'CALENDRIER']]];
+
+    Class_Profil::getCurrentProfil()
+      ->setCfgAccueil($simple_widgets)
+      ->save();
+  }
+}
+
+
+
+
+class JournalProfilePostTest extends JournalProfileTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', 1);
+    Class_AdminVar::set('JOURNAL_STORM_MODELS', json_encode(['model' => [Class_Profil::class],
+                                                             'ids' => ['']]));
+    (new Bokeh_Engine)->setupStormObservers();
+
+    $this->postDispatch('/admin/widget/edit-widget/id/6/id_profil/' . Class_Profil::getCurrentProfil()->getId(),
+                        ['menu' => '8']);
+  }
+
+
+  /** @test */
+  public function journalShouldBeCreated() {
+    $this->assertNotNull(Class_Journal::find(1));
+  }
+
+
+  /** @test */
+  public function journalShouldHaveModelIdDetail() {
+    $this->assertNotNull(Class_JournalDetail::findFirstBy(['type' => 'model_id',
+                                                           'value' => Class_Profil::getCurrentProfil()->getId(),
+                                                           ]));
+  }
+
+
+  /** @test */
+  public function journalShouldHaveUserDetail() {
+    $this->assertNotNull(Class_JournalDetail::findFirstBy(['type' => 'current_user_id',
+                                                           'value' => 666,
+                                                           ]));
+  }
+
+
+  /** @test */
+  public function journalShouldHavePreviousValueDetail() {
+    $this->assertEquals('{"modules":{"1":{"division":"4","type_module":"RECH_SIMPLE","preferences":[]},"6":{"division":"3","type_module":"MENU_VERTICAL","preferences":{"menu":"5-8"}},"4":{"division":"4","type_module":"CALENDRIER"}}}',
+                        Class_JournalDetail::findFirstBy(['type' => 'previous_value'])
+                        ->getValue());
+  }
+
+
+  /** @test */
+  public function journalShouldHaveNewValueDetail() {
+    $this->assertEquals('{"page_css":"","use_parent_css":"1","modules":{"1":{"division":"4","type_module":"RECH_SIMPLE","preferences":[]},"6":{"type_module":"MENU_VERTICAL","preferences":{"afficher_titre":"1","menu_deplie":"0","new_html":"0","toggle_menu":"0","menu":"8","boite":"","titre":"","profile_id":"2"},"division":"3","id_module":"6","profile_id":"2"},"4":{"division":"4","type_module":"CALENDRIER"}},"sitemap":"1"}',
+                        Class_JournalDetail::findFirstBy(['type' => 'new_value'])
+                        ->getValue());
+  }
+
+
+  /** @test */
+  public function journalShouldHaveStackDetail() {
+    $this->assertContains('#0  Class_Journal_ProfileType::save()',
+                          Class_JournalDetail::findFirstBy(['type' => 'stack'])->getValue());
+  }
+}
+
+
+
+
+class JournalProfilePostJournalDisabledTest extends JournalProfileTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', '0');
+    (new Bokeh_Engine)->setupStormObservers();
+
+    $this->postDispatch('/admin/widget/edit-widget/id/6/id_profil/' . Class_Profil::getCurrentProfil()->getId(),
+                        ['menu' => '8']);
+  }
+
+
+  /** @test */
+  public function journalShouldNotBeCreated() {
+    $this->assertNull(Class_Journal::find(1));
+  }
+}
+
+
+
+
+class JournalProfilePostJournalEnabledModelDisabledTest extends JournalProfileTestCase {
   public function setUp() {
     parent::setUp();
 
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', 1);
+    (new Bokeh_Engine)->setupStormObservers();
+
+    $this->postDispatch('/admin/widget/edit-widget/id/6/id_profil/' . Class_Profil::getCurrentProfil()->getId(),
+                        ['menu' => '8']);
+  }
+
+
+  /** @test */
+  public function journalShouldNotBeCreated() {
+    $this->assertNull(Class_Journal::find(1));
+  }
+}
+
+
+
+
+class JournalProfilePostEnabledButWithoutMatchingIdTest extends JournalProfileTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', 1);
+    Class_AdminVar::set('JOURNAL_STORM_MODELS', json_encode(['model' => [Class_Profil::class],
+                                                             'ids' => ['16']]));
+    (new Bokeh_Engine)->setupStormObservers();
+
+    $this->postDispatch('/admin/widget/edit-widget/id/6/id_profil/' . Class_Profil::getCurrentProfil()->getId(),
+                        ['menu' => '8']);
+  }
+
+
+  /** @test */
+  public function journalShouldNotBeCreated() {
+    $this->assertNull(Class_Journal::find(1));
+  }
+}
+
+
+
+
+class JournalProfilePostEnabledWithMatchingIdTest extends JournalProfileTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', 1);
+    Class_AdminVar::set('JOURNAL_STORM_MODELS',
+                        json_encode(['model' => [Class_Profil::class],
+                                     'ids' => ['16;nothing;' . Class_Profil::getCurrentProfil()->getId()]]));
+    (new Bokeh_Engine)->setupStormObservers();
+
+    $this->postDispatch('/admin/widget/edit-widget/id/6/id_profil/' . Class_Profil::getCurrentProfil()->getId(),
+                        ['menu' => '8']);
+  }
+
+
+  /** @test */
+  public function journalShouldBeCreated() {
+    $this->assertNotNull(Class_Journal::find(1));
+  }
+}
+
+
+
+
+class JournalProfilePostEnabledWithMatchingIdAndMaxStackTest extends JournalProfileTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', 1);
+    Class_AdminVar::set('JOURNAL_STORM_MODELS',
+                        json_encode(['model' => [Class_Profil::class],
+                                     'ids' => ['16;nothing;' . Class_Profil::getCurrentProfil()->getId()]]));
+    (new Bokeh_Engine)->setupStormObservers();
+
+    foreach(range(0, Class_Journal_Type::MAX_STACK + 10) as $i)
+      $this->fixture('Class_Journal',
+                     ['id' => 1000 + $i,
+                      'type' => Class_Journal_ProfileType::MY_TYPE]);
+
+    $this->postDispatch('/admin/widget/edit-widget/id/6/id_profil/' . Class_Profil::getCurrentProfil()->getId(),
+                        ['menu' => '8']);
+  }
+
+
+  /** @test */
+  public function journalShouldBeCreated() {
+    $this->assertNotNull(Class_Journal::find(1111));
+  }
+
+
+  /** @test */
+  public function journalStackShouldRespectMaximum() {
+    $this->assertEquals(Class_Journal_Type::MAX_STACK, Class_Journal::count());
+  }
+}
+
+
+
+
+class JournalIndexActionTest extends JournalTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    Class_AdminVar::set('JOURNAL_ENABLE_STORM', 1);
+    (new Bokeh_Engine)->setupStormObservers();
+
     Class_AdminVar::set('FACETTE_GENRE_LIBELLE', 'test');
     Class_Users::getIdentity()->setLogin('Harlock');
     $this->dispatch('/admin/journal');