diff --git a/VERSIONS b/VERSIONS index 2ea7c475d51d81c9c1db40ebae584e779d82ad16..d58d557c7df74d530c491b0e840f6b1063541984 100644 --- a/VERSIONS +++ b/VERSIONS @@ -1,3 +1,8 @@ +02/03/2017 - v7.9.4 + + - ticket #56994 : Outils de migration de comptes lecteurs + + 27/02/2017 - v7.9.3 - ticket #50215 : Ressource Numériques : ajout du connecteur et du moissonnage de Bibliondemand. diff --git a/cosmogramme/php/classes/classe_abonne.php b/cosmogramme/php/classes/classe_abonne.php index 1deeb3986df7327660c0533e8237adbc6626e427..d9ef75ad53fb0df70c0c513d8598e9660577ffe6 100644 --- a/cosmogramme/php/classes/classe_abonne.php +++ b/cosmogramme/php/classes/classe_abonne.php @@ -152,6 +152,7 @@ class abonne $user ->updateAttributes($data) + ->setStatut(0) ->saveWithoutValidation(); Class_Users::clearCache(); diff --git a/library/Class/AvisNotice.php b/library/Class/AvisNotice.php index 597f9c427024988c6391ae4b0e65c4887c512c97..0e372babdf624b3b62493fd78c776a5e9c09342b 100644 --- a/library/Class/AvisNotice.php +++ b/library/Class/AvisNotice.php @@ -198,6 +198,14 @@ class AvisNoticeLoader extends Storm_Model_Loader { foreach ($comments as $comment) $comment->fixLostUserId(); } + + + public function keyForUser($user) { + return $user + ? sprintf('%s--%s--%s', + $user->getIdabon(), $user->getIdSite(), $user->getLogin()) + : ''; + } } @@ -412,8 +420,7 @@ class Class_AvisNotice extends Storm_Model_Abstract { $user = $this->getUser(); if (null !== $user) - $this->setUserKey(sprintf('%s--%s--%s', - $user->getIdabon(), $user->getIdSite(), $user->getLogin())); + $this->setUserKey($this->getLoader()->keyForUser($user)); $this->setAbonOuBib((null !== $user) && $user->isBibliothecaire() ? 1 : 0); } diff --git a/library/Class/User/Datas.php b/library/Class/User/Datas.php new file mode 100644 index 0000000000000000000000000000000000000000..2f80c2d4b076ad809c7783d68a63773024c30e5c --- /dev/null +++ b/library/Class/User/Datas.php @@ -0,0 +1,131 @@ +<?php +/** + * Copyright (c) 2012-2017, 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_User_Datas extends Class_Entity { + protected $_relations = []; + + public function __construct($user) { + $this->setUser($user); + + $this->_relations = [new Class_User_DatasRelation('notices_paniers', 'id_user'), + new Class_User_DatasRelationAvisNotice(), + new Class_User_DatasRelation('cms_avis', 'id_user'), + new Class_User_DatasRelation('suggestion_achat', 'user_id'), + new Class_User_DatasRelation('session_formation_inscriptions', 'stagiaire_id'), + new Class_User_DatasRelation('user_group_memberships', 'user_id'), + new Class_User_DatasRelation('formulaires', 'id_user'), + new Class_User_DatasRelation('multimedia_devicehold', 'id_user')]; + } + + + public function hasDatas() { + foreach($this->_relations as $relation) + if (0 < $this->_getCountOf($relation)) + return true; + + return false; + } + + + protected function _getCountOf($relation) { + return $relation->countFor($this->getUser()); + } + + + public function giveTo($params, $for_real) { + if (!$target = $this->_getTargetWith($params)) + return false; + + if (!$for_real) + return $target; + + foreach($this->_relations as $relation) + $this->_giveRelationTo($relation, $target); + + return $target; + } + + + protected function _giveRelationTo($relation, $target) { + $relation->giveFromTo($this->getUser(), $target); + return $this; + } + + + public function _getTargetWith($params) { + $targets = Class_Users::findAllBy($params); + if (!$targets) { + $this->setError('No target found'); + return; + } + + if (1 < count($targets)) { + $this->setError('Too many targets'); + return; + } + + return current($targets); + } +} + + + +class Class_User_DatasRelation extends Class_Entity { + public function __construct($table, $key) { + $this->setTable($table) + ->setKey($key) + ->setSql(Zend_Registry::get('sql')); + } + + + public function countFor($user) { + return $this + ->getSql() + ->fetchOne(sprintf('select count(id) from %s where %s=%s', + $this->getTable(), $this->getKey(), $user->getId())); + } + + + public function giveFromTo($from, $to) { + $this + ->getSql() + ->query(sprintf('update %s set %s=' . $to->getId() . ' where %s=' . $from->getId(), + $this->getTable(), $this->getKey(), $this->getKey())); + } +} + + + +class Class_User_DatasRelationAvisNotice extends Class_User_DatasRelation { + public function __construct() { + parent::__construct('notices_avis', 'id_user'); + } + + + public function giveFromTo($from, $to) { + parent::giveFromTo($from, $to); + + $this->getSql() + ->query(sprintf('update %s set user_key=\'' . Class_AvisNotice::keyForUser($to) . '\' where %s=' . $to->getId(), + $this->getTable(), $this->getKey())); + } +} diff --git a/library/startup.php b/library/startup.php index 65923327f977c5f1735301185d0e5958a78d95fd..83c14f605b06b2bf1589faee91e377a718112c7a 100644 --- a/library/startup.php +++ b/library/startup.php @@ -83,7 +83,7 @@ class Bokeh_Engine { function setupConstants() { defineConstant('BOKEH_MAJOR_VERSION','7.9'); - defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.3'); + defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.4'); defineConstant('BOKEH_REMOTE_FILES', 'http://git.afi-sa.fr/afi/opacce/'); diff --git a/scripts/user_data_migration.php b/scripts/user_data_migration.php new file mode 100644 index 0000000000000000000000000000000000000000..5721bce35b43e7790208fee0e87a8ee18a5b5979 --- /dev/null +++ b/scripts/user_data_migration.php @@ -0,0 +1,167 @@ +<?php +require './console.php'; + +echo 'User Data Migration by Stl And Pat since 1854 + + \ . ./ + \ .:";\'.:.." / + (M^^.^~~:.\'"). + - (/ . . . \ \) - + O ((| :. ~ ^ :. .|)) + |\\ - (\- | \ / | /) - + | T -\ \ / /- +/ \[_]..........................\ \ / / + +This will detect user marked for deletion and +try to give their datas to another user +based on a rule before deleting them. + +'; + +readline('press enter to continue...'); + +$valid_count = Class_Users::countBy(['statut' => 0, 'role_level' => 2]); +$invalid_count = Class_Users::countBy(['statut' => 1, 'role_level' => 2]); +echo $valid_count . ' valid users in database +' . $invalid_count . ' users marked for deletion + +'; + +if (0 == $valid_count) { + echo 'Oups, not enough valid users, aborting... + + ..-^~~~^-.. + .~ ~. + (;: :;) + (: :) + \':._ _.:\' + | | + (=====) + | | +\O/ | | + \ | | + /\ ((/ \)) +'; + exit(255); +} + + +class UserDataMigrationLog extends Class_Entity { + public function __construct($path) { + $this->setPath($path) + ->reset(); + } + + + public function write($content) { + file_put_contents($this->getPath(), implode('|', $content) . "\n", FILE_APPEND); + return $this; + } + + + public function reset() { + if (file_exists($this->getPath())) + unlink($this->getPath()); + + return $this; + } +} + + +$for_real = ('--force' == $argv[1]); +if ($for_real) + readline('CAUTION : you asked to run for real, users may be deleted !'); + +echo $for_real + ? 'OH MY, RUNNING FOR REAL, GOOD LUCK' + : 'pro tip: use --force option to run for real, with great powers comes great responsibility +Simulation mode engaged...'; +echo ' + +'; + +$without_datas = 0; +$without_target = 0; +$success = 0; + +$no_datas_log = (new UserDataMigrationLog('deleted_without_datas.txt')) + ->write(['id_bokeh', 'carte', 'nom', 'prenom', 'naissance']); + +$no_target_log = (new UserDataMigrationLog('not_deleted_target_problem.txt')) + ->write(['id_bokeh', 'carte', 'nom', 'prenom', 'naissance', 'cause']); + +$success_log = (new UserDataMigrationLog('deleted_with_datas.txt')) + ->write(['id_bokeh_source', 'carte_source', 'nom_source', 'prenom_source', 'naissance_source', + 'id_bokeh_dest', 'carte_dest', 'nom_dest', 'prenom_dest', 'naissance_dest',]); + + +echo 'Started at ' . date('c') . "\n"; + +foreach(Class_Users::findAllBy(['statut' => 1, 'role_level' => 2]) as $user) { + $datas = new Class_User_Datas($user); + if (!$datas->hasDatas()) { + $no_datas_log->write([$user->getId(), + $user->getLogin(), + $user->getNom(), + $user->getPrenom(), + $user->getNaissance()]); + + if ($for_real) + $user->delete(); + $without_datas++; + continue; + } + + if ('CHAM' != substr(strtoupper($user->getLogin()), 0, 4)) { + $no_target_log->write([$user->getId(), + $user->getLogin(), + $user->getNom(), + $user->getPrenom(), + $user->getNaissance(), + 'Card does not starts with CHAM']); + $without_target++; + echo 'F'; + continue; + } + + if (!$target = $datas->giveTo(['login' => substr($user->getLogin(), 4), + 'statut' => 0, + 'role_level' => 2], + $for_real)) { + $no_target_log->write([$user->getId(), + $user->getLogin(), + $user->getNom(), + $user->getPrenom(), + $user->getNaissance(), + $datas->getError()]); + $without_target++; + echo 'F'; + continue; + } + + $success_log->write([$user->getId(), + $user->getLogin(), + $user->getNom(), + $user->getPrenom(), + $user->getNaissance(), + + $target->getId(), + $target->getLogin(), + $target->getNom(), + $target->getPrenom(), + $target->getNaissance()]); + + if ($for_real) + $user->delete(); + $success++; + echo '.'; +} + +echo "\nEnded at " . date('c') . "\n"; + +printf(' +%s without datas, log in deleted_without_datas.txt +%s without target, log in not_deleted_target_problem.txt +%s success, log in deleted_with_datas.txt + +', $without_datas, $without_target, $success); \ No newline at end of file diff --git a/tests/library/Class/Cosmogramme/Integration/PhasePatronsTest.php b/tests/library/Class/Cosmogramme/Integration/PhasePatronsTest.php index cdcd4dcbe7dce15ef6b83f757cd6cdad522e0162..c3ea0dbd4423a8a6b2df3a7af2897ae7a1010c46 100644 --- a/tests/library/Class/Cosmogramme/Integration/PhasePatronsTest.php +++ b/tests/library/Class/Cosmogramme/Integration/PhasePatronsTest.php @@ -62,6 +62,14 @@ abstract class PhasePatronsTestCase extends Class_Cosmogramme_Integration_PhaseT 'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB, 'bib' => $bib_annecy, 'idabon' => '666']); + + $this->fixture('Class_Users', + ['id' => 2, + 'login' => 'A-000208', + 'password' => '2001', + 'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB, + 'bib' => $bib_annecy, + 'idabon' => 'A-000208']); } @@ -104,15 +112,15 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { /** @test */ - public function secondAbonneShouldBeBarryWhite() { - $barry = Class_Users::find(3); + public function barryWhiteShouldBeImported() { + $barry = Class_Users::findFirstBy(['login' => 'A-000033']); $this->assertEquals('white BARRY', $barry->getNomComplet()); return $barry; } /** - * @depends secondAbonneShouldBeBarryWhite + * @depends barryWhiteShouldBeImported * @test */ public function barryIdAbonShouldBeA000033($barry) { @@ -121,7 +129,7 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { /** - * @depends secondAbonneShouldBeBarryWhite + * @depends barryWhiteShouldBeImported * @test */ public function barryIdSIGBShouldBe47($barry) { @@ -131,7 +139,7 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { /** - * @depends secondAbonneShouldBeBarryWhite + * @depends barryWhiteShouldBeImported * @test */ public function barryOrdreabonShouldBeOne($barry) { @@ -140,7 +148,7 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { /** - * @depends secondAbonneShouldBeBarryWhite + * @depends barryWhiteShouldBeImported * @test */ public function barryMailShouldBeEmpty($barry) { @@ -149,7 +157,7 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { /** - * @depends secondAbonneShouldBeBarryWhite + * @depends barryWhiteShouldBeImported * @test */ public function barryPasswordShouldBe1978($barry) { @@ -158,7 +166,7 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { /** - * @depends secondAbonneShouldBeBarryWhite + * @depends barryWhiteShouldBeImported * @test */ public function barryNaissanceShouldBe1978_05_19($barry) { @@ -167,7 +175,7 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { /** - * @depends secondAbonneShouldBeBarryWhite + * @depends barryWhiteShouldBeImported * @test */ public function barryShouldNotHaveNULLAttribute($barry) { @@ -176,7 +184,7 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { /** - * @depends secondAbonneShouldBeBarryWhite + * @depends barryWhiteShouldBeImported * @test */ public function barryFinShouldBe2006_03_23($barry) { @@ -185,7 +193,7 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { /** - * @depends secondAbonneShouldBeBarryWhite + * @depends barryWhiteShouldBeImported * @test */ public function barryStatutShouldBeZero($barry) { @@ -205,6 +213,12 @@ class PhasePatronsFullImportTest extends PhasePatronsTestCase { $this->assertEquals(Class_Users::STATUT_TO_BE_DELETED, Class_Users::find(1)->getStatut()); } + + + /** @test */ + public function mieszkalskiShouldNotBeMarkedForDeletion() { + $this->assertEquals(0, Class_Users::find(2)->getStatut()); + } } @@ -231,7 +245,7 @@ class PhasePatronsInvalidProfilTest extends PhasePatronsTestCase { $this->_phase->run(); $this->assertLogContains('Configuration: colonne ' . $column . ' requise'); - $this->assertEquals(1, Class_Users::countBy([])); + $this->assertEquals(2, Class_Users::count()); } } @@ -303,13 +317,13 @@ class PhasePatronsFullImportXMLTest extends PhasePatronsTestCase { /** @test */ - public function nubmerOfPatronsShouldBeTwo() { - $this->assertEquals(2, Class_Users::countBy([])); + public function nubmerOfPatronsShouldBeThree() { + $this->assertEquals(3, Class_Users::count()); } /** @test */ - public function secondAbonneIdAbonShouldBe0003090() { - $this->assertEquals('00003090', Class_Users::find(2)->getIdabon()); + public function pirlyShouldBeImported() { + $this->assertNotNull($user = Class_Users::findFirstBy(['idabon' => '00003090'])); } } \ No newline at end of file diff --git a/tests/library/Class/UserDatasTest.php b/tests/library/Class/UserDatasTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8260fc02ff1d15d7d32e50d0d6cbfa2066d5914e --- /dev/null +++ b/tests/library/Class/UserDatasTest.php @@ -0,0 +1,135 @@ +<?php +/** + * Copyright (c) 2012-2017, 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 UserDatasTestCase extends ModelTestCase { + protected + $_storm_default_to_volatile = true, + $_sql; + + + public function setUp() { + parent::setUp(); + Zend_Registry::set('sql', $this->_sql = $this->mock()); + } +} + + + +class UserDatasTest extends UserDatasTestCase { + protected $_datas; + + public function setUp() { + parent::setUp(); + + $this->fixture('Class_Users', ['id' => 44, + 'login' => 'procyon', + 'password' => 'prof']); + + $this->fixture('Class_Users', ['id' => 45, + 'login' => 'actarus', + 'password' => 'vega4ever']); + + $this->fixture('Class_Users', ['id' => 46, + 'login' => 'venusia', + 'password' => 'vega4ever']); + + + $this->_sql->whenCalled('fetchOne') + ->answers(0); + + $this->_datas = new Class_User_Datas(Class_Users::find(44)); + } + + + /** @test */ + public function withoutDatasShouldNotHaveDatas() { + $this->assertFalse($this->_datas->hasDatas()); + } + + + /** @test */ + public function withRatingsShouldHaveDatas() { + $this->_sql->whenCalled('fetchOne') + ->with('select count(id) from cms_avis where id_user=44') + ->answers(35); + + $this->assertTrue($this->_datas->hasDatas()); + } + + + /** @test */ + public function givingToUnknownShouldDoNothing() { + $this->assertFalse($this->_datas->giveTo(['login' => 'alcor'], true)); + $this->assertEquals('No target found', $this->_datas->getError()); + } + + + /** @test */ + public function givingToMultipleTargetsShouldDoNothing() { + $this->assertFalse($this->_datas->giveTo(['password' => 'vega4ever'], true)); + $this->assertEquals('Too many targets', $this->_datas->getError()); + } + + + /** @test */ + public function givingToActarusShouldUpdateDatabase() { + $queries = [['notices_paniers', 'id_user'], + ['notices_avis', 'id_user'], + ['cms_avis', 'id_user'], + ['suggestion_achat', 'user_id'], + ['session_formation_inscriptions', 'stagiaire_id'], + ['user_group_memberships', 'user_id'], + ['formulaires', 'id_user'], + ['multimedia_devicehold', 'id_user'] + ]; + + foreach($queries as $query) + $this->_sql->whenCalled('query') + ->with(sprintf('update %s set %s=45 where %s=44', + $query[0], $query[1], $query[1])) + ->answers(1); + + $this->_sql->whenCalled('query') + ->with('update notices_avis set user_key=\'--0--actarus\' where id_user=45') + ->answers(1); + + $this->assertEquals(45, + $this->_datas->giveTo(['login' => 'actarus'], true)->getId()); + + $this + ->assertTrue($this->_sql + ->methodHasBeenCalledWithParams('query', + ['update notices_avis set id_user=45 where id_user=44'])); + + $this + ->assertTrue($this->_sql + ->methodHasBeenCalledWithParams('query', + ['update notices_avis set user_key=\'--0--actarus\' where id_user=45'])); + } + + + /** @test */ + public function givingToActarusNotForRealShouldDoNothing() { + $this->assertEquals(45, + $this->_datas->giveTo(['login' => 'actarus'], false)->getId()); + } +} \ No newline at end of file