Commit 905c4f4a authored by Patrick Barroca's avatar Patrick Barroca 🐧

journal observer

parent 5ac3b9bb
Pipeline #10517 passed with stage
in 50 minutes and 32 seconds
......@@ -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é')),
......@@ -562,6 +564,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;
......@@ -1158,8 +1176,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');
......@@ -1214,19 +1230,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;
}
......
<?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
: [];
}
}
......@@ -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;
}
}
<?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')
);
}
}
<?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)
);
}
}
<?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;
}
}
......@@ -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 '';
}
......
......@@ -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;
}
......
Subproject commit 67e32fa4f2114f69c7a3d9b96d9419186cb2e7bf
Subproject commit 17d31fdfc59be5319cb5201ac2280dbb9e7c2c5b
......@@ -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');
......
......@@ -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);