diff --git a/application/modules/admin/controllers/ModoController.php b/application/modules/admin/controllers/ModoController.php index 57e57b1f79f0648ff7cd6b5bd88012744ddd581d..cf619e40354d692ff6b63440df11bb36ff34a03c 100644 --- a/application/modules/admin/controllers/ModoController.php +++ b/application/modules/admin/controllers/ModoController.php @@ -300,37 +300,30 @@ class Admin_ModoController extends ZendAfi_Controller_Action { public function rejectRegistrationAction() { $registration = Class_UsersNonValid::find($this->_getParam('id')); - if ($registration->hasUser()){ + if ($registration->hasUser()) { $registration->getUser()->delete(); $this->_helper->notify($this->_('Le compte "%s" a été supprimé', - $registration->getLogin())); + $registration->getLogin())); } - $registration->delete(); + $registration->delete(); $this->_helper->notify($this->_('La demande d\'inscription pour "%s" a été supprimée', $registration->getLogin())); + $this->_redirect('admin/modo/membreview'); } public function deleteRegistrationAction() { - if(!$id = $this->_getParam('id', null)) { - $this->_helper->notify($this->_('Aucune inscription supprimée')); - return $this->_redirect('admin/modo/membreview'); - } - - if(!$registration = Class_UsersNonValid::find($id)) { - $this->_helper->notify($this->_('Aucune inscription supprimée')); - return $this->_redirect('admin/modo/membreview'); - } + $this->_redirect('admin/modo/membreview'); - if($registration->delete()) { - $this->_helper->notify($this->_('Inscription de "%s" supprimée', $registration->getLogin())); - return $this->_redirect('admin/modo/membreview'); + if ((!$id = $this->_getParam('id', null)) + || (!$registration = Class_UsersNonValid::find($id))) { + return $this->_helper->notify($this->_('Aucune inscription supprimée')); } - $this->_helper->notify($this->_('Impossible de supprimer l\'inscription de "%s"', $registration->getLogin())); - return $this->_redirect('admin/modo/membreview'); + $registration->delete(); + $this->_helper->notify($this->_('Inscription de "%s" supprimée', $registration->getLogin())); } diff --git a/library/Class/Bib.php b/library/Class/Bib.php index aa742310a53b563645884241bffe264dd0ef10e9..0973376c9aee74039761e280a57d2b3054004b19 100644 --- a/library/Class/Bib.php +++ b/library/Class/Bib.php @@ -165,6 +165,19 @@ class BibLoader extends Storm_Model_Loader { } + public function getAvailableBibFor($user) { + if ($user->isSuperAdmin()) + return ['0' => $this->_('Portail')]; + + if ($user->isAbonne() || (!Class_Users::getIdentity()->isAdmin())) { + $library = $user->getBib(); + return [$library->getId() => $library->getLibelle()]; + } + + return ['0' => $this->_('Portail')] + Class_Bib::findAllLabels(); + } + + public function filterByCoordinates($libraries) { return (new Storm_Model_Collection($libraries)) ->select('hasLatitude') @@ -328,6 +341,13 @@ class Class_Bib extends Storm_Model_Abstract { } + public function shouldEscalateRegistrationRole (){ + if (! $this->hasIntBib()) + return false; + return $this->getIntBib()->shouldEscalateRegistrationRole(); + } + + public function describeAssociationsOn($associations) { $associations ->add(new Storm_Model_Association_HasOne('int_bib', ['model' => 'Class_IntBib', diff --git a/library/Class/IntBib.php b/library/Class/IntBib.php index 64d848c9b15f5d41cbfc9936145500581a55f536..5b0ca78e9fc0638f921476894cdc1dd257b23cb0 100644 --- a/library/Class/IntBib.php +++ b/library/Class/IntBib.php @@ -204,6 +204,17 @@ class Class_IntBib extends Storm_Model_Abstract { } + public function shouldEscalateRegistrationRole(){ + if (!$this->isCommKoha()) + return false; + + $comm_params = $this->getCommParamsAsArray(); + + return array_key_exists('use_card_number', $comm_params) + && $comm_params['use_card_number']; + } + + public function getCommParamsAsArray() { $a = unserialize($this->getCommParams()); if (!is_array($a)) @@ -314,13 +325,23 @@ class Class_IntBib extends Storm_Model_Abstract { } + public function beKoha() { + return $this->setSigb(static::SIGB_KOHA); + } + + public function isKoha() { return static::SIGB_KOHA == $this->getSigb(); } + public function beCommKoha() { + return $this->setCommSigb(static::COM_KOHA); + } + + public function isCommKoha() { - return Class_IntBib::COM_KOHA == $this->getCommSigb(); + return static::COM_KOHA == $this->getCommSigb(); } diff --git a/library/Class/Users.php b/library/Class/Users.php index 0769db3dece8fce47d94dd16b5934a5341b85e68..20d1d01ddcef109cbda26254877aeed4546eae5c 100644 --- a/library/Class/Users.php +++ b/library/Class/Users.php @@ -492,6 +492,11 @@ class Class_Users extends Storm_Model_Abstract { } + public function isInvite() { + return $this->getRoleLevel() == ZendAfi_Acl_AdminControllerRoles::INVITE; + } + + public function isAbonne() { return $this->getRoleLevel() == ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB; } diff --git a/library/Class/UsersNonValid.php b/library/Class/UsersNonValid.php index a3ebd40ee509d04c29a2df8cf5ded3ba9909c51a..0cebc9ce924fd7f85d2dd9b430306abcc0ef2fa4 100644 --- a/library/Class/UsersNonValid.php +++ b/library/Class/UsersNonValid.php @@ -123,10 +123,22 @@ class Class_UsersNonValid extends Storm_Model_Abstract { ->save(); $this->_user = $user; + + $this->_escalateRole(); return true; } + protected function _escalateRole() { + if (($bib = $this->getBib()) + && $bib->shouldEscalateRegistrationRole()){ + $this->_user + ->beAbonneSIGB() + ->save(); + } + } + + public function activateForNewsletter() { return $this->activate() ? $this->_user: ''; } diff --git a/library/ZendAfi/Acl/AdminControllerRoles.php b/library/ZendAfi/Acl/AdminControllerRoles.php index 8b54e8a589d93d7f09a852ced4cfb6ad72cad632..81b8e75e205b6f5c2fcdb2df7505c161667f847f 100644 --- a/library/ZendAfi/Acl/AdminControllerRoles.php +++ b/library/ZendAfi/Acl/AdminControllerRoles.php @@ -220,9 +220,6 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl { return $roles; } - //---------------------------------------------------------------------------------- - // Rend le libelle d'un role - //---------------------------------------------------------------------------------- public static function getLibelleRole($role_level) { return self::$listeRole[$role_level]["libelle"]; @@ -233,9 +230,6 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl { return self::$listeRole[$role_level]["abrege"]; } - //---------------------------------------------------------------------------------- - // Rend la combo des roles - //---------------------------------------------------------------------------------- public static function rendCombo($selected,$user_role_level,$tous=false) { @@ -259,6 +253,24 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl { } + public static function getAvailableRolesFor($user) { + $instance = new self(); + + if ($user->isSuperAdmin()) + return [static::SUPER_ADMIN => $instance->_('Super administrateur')]; + + + if ($user->isAbonne()) + return [static::ABONNE_SIGB => $instance->_('Abonné SIGB')]; + + + $current_user = Class_Users::getIdentity(); + $array_roles = static::getRolesLabelsWithOutSuperAdmin(); + + return array_splice($array_roles, 0, $current_user->getRoleLevel() + 1); + } + + public static function getRolesLabels() { $instance = new self(); return array_merge(static::getRolesLabelsWithOutSuperAdmin(), diff --git a/library/ZendAfi/Form/Admin/User.php b/library/ZendAfi/Form/Admin/User.php index ad56575b5d36b794acb40a43a55ebbfad6d54d10..ac20e773a9e1b8a57270437de16293a695d94364 100644 --- a/library/ZendAfi/Form/Admin/User.php +++ b/library/ZendAfi/Form/Admin/User.php @@ -112,25 +112,16 @@ class ZendAfi_Form_Admin_User extends ZendAfi_Form { protected function addAdminUserInfo() { - $multi_options = $this->_user->isSuperAdmin() - ? ZendAfi_Acl_AdminControllerRoles::getRolesLabels() - : ZendAfi_Acl_AdminControllerRoles::getRolesLabelsWithOutSuperAdmin(); - - $option=[]; - if ($this->_user->isSuperAdmin() || $this->_user->isAbonne() || (Class_Users::getIdentity()->getRoleLevel() <= ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL)) - $option=['disabled' => 'disabled']; - $this ->addElement('select', 'role_level', - array_merge(['label' => $this->_('Niveau d\'accès'), - 'multiOptions' => $multi_options],$option)) + ['label' => $this->_('Niveau d\'accès'), + 'multiOptions' => ZendAfi_Acl_AdminControllerRoles::getAvailableRolesFor($this->_user)]) + ->addElement('select', 'id_site', - array_merge( - ['label' => $this->_('Bibliothèque'), - 'multiOptions' => ['0' => $this->_('Portail')] + Class_Bib::findAllLabels()], - $option)) + ['label' => $this->_('Bibliothèque'), + 'multiOptions' => Class_Bib::getAvailableBibFor($this->_user)]) ->addElement('userGroup', 'user_group_ids', diff --git a/tests/application/modules/admin/controllers/ModoControllerTest.php b/tests/application/modules/admin/controllers/ModoControllerTest.php index 0603db5cee8e33b65b54d91538184ad6eb20bcd7..0ef32bd1384dbb0c15054bc2888f03844852af8f 100644 --- a/tests/application/modules/admin/controllers/ModoControllerTest.php +++ b/tests/application/modules/admin/controllers/ModoControllerTest.php @@ -899,13 +899,11 @@ abstract class ModoControllerRegistrationsTestCase extends Admin_AbstractControl ['id' => 2, 'login' => 'chewe', 'password' => 'ehhhhhh', - 'id_site'=> 3, + 'bib'=> $this->fixture('Class_Bib', + ['id' => 3, + 'libelle' => 'Tatoine']), 'date' => '2019-16-01', - 'mail' => 'chewe@afi-sa.fr'])->activate(); - - $this->fixture('Class_Bib', - ['id' => 3, - 'libelle' => 'Tatoine']); + 'mail' => 'chewe@afi-sa.fr'])->activate();; } } @@ -915,6 +913,7 @@ abstract class ModoControllerRegistrationsTestCase extends Admin_AbstractControl class ModoControllerMembreviewAsAdminTatoineTest extends ModoControllerRegistrationsTestCase { public function setUp() { parent::setUp(); + Class_Users::getIdentity() ->beAdminBib() ->setPassword('secret') diff --git a/tests/application/modules/admin/controllers/RedmineControllerTest.php b/tests/application/modules/admin/controllers/RedmineControllerTest.php index 7ede71a553c9743788ddf45c1c206a1c0194e803..3b84848c1d754d888c1d66658c3e6a7fecc1e992 100644 --- a/tests/application/modules/admin/controllers/RedmineControllerTest.php +++ b/tests/application/modules/admin/controllers/RedmineControllerTest.php @@ -306,7 +306,7 @@ class Admin_RedmineControllerIndexTest extends Admin_RedmineControllerWithApiTes /** @test */ public function menuHorizontalShouldContainsTwoIssues() { - $this->assertXPathContentContains('//div[contains(@class, "barre_nav")]//a[@title="Assistance"]//span[@class="menu_info"]', '2', $this->_response->getBody()); + $this->assertXPathContentContains('//div[contains(@class, "barre_nav")]//a[contains(@title, "demandes de support")]//span[@class="menu_info"]', '2', $this->_response->getBody()); } } @@ -331,7 +331,7 @@ class Admin_RedmineControllerCacheTest extends Admin_RedmineControllerWithApiTes public function onAdminIndexMenuHorizontalShouldGetResultFromCache() { $this->dispatch('/admin/index/index'); - $this->assertXPathContentContains('//div[contains(@class, "barre_nav")]//a[@title="Assistance"]//span[@class="menu_info"]', '2'); + $this->assertXPathContentContains('//div[contains(@class, "barre_nav")]//a[contains(@title, "demandes de support")]//span[@class="menu_info"]', '2'); } diff --git a/tests/application/modules/admin/controllers/UsersControllerTest.php b/tests/application/modules/admin/controllers/UsersControllerTest.php index 14827283b3669939949110429661b764925fe837..be4b9a9a9ea631f0fc3e61246067c70e128cbee4 100644 --- a/tests/application/modules/admin/controllers/UsersControllerTest.php +++ b/tests/application/modules/admin/controllers/UsersControllerTest.php @@ -73,8 +73,7 @@ abstract class UsersControllerWithMarcusTestCase extends AbstractControllerTestC ->setUserGroups([$group_vodeclic,$group_referent]) ->setIntBib($this->fixture('Class_IntBib', ['id' => 100, 'comm_sigb' => 0])); - $this->user_loader = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Users'); - + $this->user_loader = $this->onLoaderOfModel('Class_Users'); } @@ -238,6 +237,12 @@ class UsersControllerEditMarcusTest extends UsersControllerWithMarcusTestCase { $this->dispatch('/admin/users/edit/id/10', true); } + /** @test */ + public function comboBibShouldContainsOnlyMybib() { + $this->assertXPath('//select[@name="id_site"]//option[@value="100"]'); + $this->assertXPathCount(1,'//select[@name="id_site"]//option'); + } + /** @test **/ public function roleLevelShouldBeSIGBSubscriber() { @@ -253,7 +258,7 @@ class UsersControllerEditMarcusTest extends UsersControllerWithMarcusTestCase { /** @test **/ public function testPasswordIsMysecret() { - $this->assertXPath("//input[@name='password'][@value='mysecret']"); + $this->assertXPath("//input[@name='password'][@type='password'][@value='mysecret']"); } @@ -283,14 +288,7 @@ class UsersControllerEditMarcusTest extends UsersControllerWithMarcusTestCase { /** @test **/ public function testSelectedRoleIsAbonneSIGB() { - $this->assertXPath("//select[@name='role_level'][@disabled='disabled']"); - } - - - - /** @test **/ - public function testDisabledInputForRole() { - $this->assertXPath("//select[@name='role_level'][@disabled='disabled']//option[@value='2'][@selected='selected']"); + $this->assertXPath("//select[@name='role_level']//option[@value='2'][@selected='selected']"); } @@ -390,29 +388,6 @@ class UsersControllerEditMarcusTest extends UsersControllerWithMarcusTestCase { -class UsersControllerEditMarcusAsAbonPortailTest extends UsersControllerWithMarcusTestCase { - public function setUp() { - parent::setUp(); - } - - - /** @test */ - function comboBibShouldNotBeVisible() { - $this->dispatch('/admin/users/edit/id/10'); - $this->assertXPath('//select[@name="id_site"][@disabled]'); - } - - - /** @test */ - public function passwordShouldBeInputTypePassword() { - $this->dispatch('/admin/users/edit/id/10'); - $this->assertXPath('//input[@type="password"][@name="password"]'); - } -} - - - - class UsersControllerEditMarcusAsAdminPortailTest extends UsersControllerWithMarcusTestCase { public function setUp() { parent::setUp(); @@ -621,12 +596,13 @@ class UsersControllerPostMarcusInvalidDataTest extends UsersControllerWithMarcus } -class UsersControllerPostValidDataWithCommOpsysTest extends UsersControllerWithMarcusTestCase { +class UsersControllerPostValidDataWithCommOpsysTest + extends UsersControllerWithMarcusTestCase { public function setUp() { parent::setUp(); - $this->opsys_service = $this->createMock('MockOpsysService', ['saveEmprunteur']); + $this->opsys_service = $this->mock(); $this->emprunteur = new Class_WebService_SIGB_Emprunteur('2341', 'Marcus'); $this->emprunteur->setService($this->opsys_service); @@ -640,16 +616,6 @@ class UsersControllerPostValidDataWithCommOpsysTest extends UsersControllerWithM } - /** @test */ - public function idAbonShouldNotBeModified() { - $this->assertEquals('00123',Class_Users::find(10)->getIdabon()); - } - - /** @test */ - public function roleShouldNotBeModified() { - $this->assertEquals(ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB,Class_Users::find(10)->getRoleLevel()); - } - protected function _postData() { $this->_postEditData(array('login' => 'mdavis', 'password' => 'tutu', @@ -657,7 +623,7 @@ class UsersControllerPostValidDataWithCommOpsysTest extends UsersControllerWithM 'prenom' => 'Miles', 'pseudo' => '', 'mail' => 'mdavis@free.fr', - 'role_level' => '4', + 'role_level' => '2', 'idabon' => '2341', 'ordreabon' => '2', 'telephone' => '04 12 34 56 78', @@ -672,12 +638,12 @@ class UsersControllerPostValidDataWithCommOpsysTest extends UsersControllerWithM } - public function testWithNoSIGBErrorRedirectToUsers() { + /** @test */ + public function withoutSIGBErrorRedirectToUsers() { $this->opsys_service - ->expects($this->once()) - ->method('saveEmprunteur') + ->whenCalled('saveEmprunteur') ->with($this->emprunteur) - ->will($this->returnValue($this->opsys_service)); + ->answers($this->opsys_service); $this->_postData(); $this->assertRedirect(); @@ -687,14 +653,15 @@ class UsersControllerPostValidDataWithCommOpsysTest extends UsersControllerWithM } - public function testWithSIGBErrorDisplayErrorMessage() { + /** @test */ + public function withSIGBErrorDisplayErrorMessage() { $this->opsys_service - ->expects($this->once()) - ->method('saveEmprunteur') + ->whenCalled('saveEmprunteur') ->with($this->emprunteur) - ->will($this->throwException(new Exception("(234) L'abonné n'existe pas"))); + ->willDo(function() { throw new Exception("(234) L'abonné n'existe pas"); }); $this->_postData(); + $this->assertAction('edit'); $this->assertFlashMessengerContentContains("(234) L'abonné n'existe pas"); } @@ -1076,7 +1043,7 @@ class UsersControllerEditSuperAdminTest extends Admin_AbstractControllerTestCase /** @test */ public function roleLevelSuperShouldBePresent() { - $this->assertXPath('//form//select[@name="role_level"][@disabled]//option[@value="7"]', + $this->assertXPath('//form//select[@name="role_level"]//option[@value="7"]', $this->_response->getBody()); } } @@ -1123,6 +1090,62 @@ class Admin_UsersControllerChangeAdminSkinActionTest extends Admin_AbstractContr } +class Admin_UsersControllerChangeRoleLevelOfUserInMyBibAsAdminBibTest extends Admin_AbstractControllerTestCase { + protected $_storm_default_to_volatile = true; + + public function setUp() { + parent::setUp(); + + $adminbib = $this->fixture('Class_Users', + ['id' => 5, + 'login' => 'adminbib', + 'password' => 'secret', + 'id_site' => '5', + 'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB]); + + $this->fixture('Class_Bib', + ['id' => 5, + 'libelle' => 'PukaPuka' + ]); + + ZendAfi_Auth::getInstance()->logUser($adminbib); + + $this->fixture('Class_Users', + ['id' => 3, + 'login' => 'userbib', + 'password' => 'secret', + 'id_site' => '5', + 'role_level' => ZendAfi_Acl_AdminControllerRoles::INVITE]); + + + $this->dispatch('admin/users/edit/id/3'); + } + + /** @test */ + public function RoleLevelUserBibShouldBeEnabled() { + $this->assertXPath('//select[@name="role_level"][not(@disabled)]'); + } + + + /** @test */ + public function RoleLevelUserBibSelectedShouldBeInvite() { + $this->assertXPath('//select[@name="role_level"]//option[@value="0"][@selected]',$this->_response->getBody()); + } + + + /** @test */ + public function RoleLevelUserBibShouldContainsRoleAbonneSIGB() { + $this->assertXPath('//select[@name="role_level"]//option[@value="2"]'); + } + + + /** @test */ + public function IdSiteUserBibShouldBePukaPukaOnly() { + $this->assertXPath('//select[@name="id_site"]//option[@value="5"][@selected]'); + $this->assertXPathCount(1, '//select[@name="id_site"]//option'); + } +} + class UsersControllerWithAdminPortalTest extends Admin_AbstractControllerTestCase { diff --git a/tests/application/modules/opac/controllers/AuthControllerTest.php b/tests/application/modules/opac/controllers/AuthControllerTest.php index e8e88a808ed98e132d8e0d0db8e08120b7dc690d..e3f33e311b8d44e26c3cf91865117b80de07cc2c 100644 --- a/tests/application/modules/opac/controllers/AuthControllerTest.php +++ b/tests/application/modules/opac/controllers/AuthControllerTest.php @@ -385,11 +385,59 @@ class AuthControllerNobodyLoggedActivateSuccessTest extends AuthControllerNobody $this->assertEquals('harlock', Class_UsersNonValid::find(12)->getUser()->getLogin()); } + + + /** @test */ + public function userShouldBeInvite() { + $this->assertTrue(Class_UsersNonValid::find(12)->getUser()->isInvite()); + } } +class AuthControllerNobodyLoggedActivateWithKohaUseCardNumberParamSuccessTest extends AuthControllerNobodyLoggedTestCase { + public function setUp() { + parent::setUp(); + + $int_bib = $this->fixture('Class_IntBib', + ['id' => 10, + ]) + ->beKoha() + ->beCommKoha() + ->setCommParams(['use_card_number' => '000541A67']); + $int_bib->assertSave(); + + $this->fixture('Class_Bib', + ['id' => 10, + 'libelle'=>'PukaPuka', + 'int_bib' => $int_bib, + ]); + + $this->fixture('Class_UsersNonValid', ['id' => 12, + 'cle' => '777', + 'login' => 'harlock', + 'mail' => 'harlock@afi-sa.fr', + 'idabon'=> 12, + 'id_site'=> 10, + 'password' => 'cosmos']); + + $this->dispatch('/opac/auth/activeuser/c/777', true); + Class_UsersNonValid::clearCache(); + Class_Users::clearCache(); + } + + + /** @test */ + public function userShouldBeAbonne() { + $user = Class_UsersNonValid::find(12)->getUser(); + $this->assertTrue($user->isAbonne()); + $this->assertEquals(10, $user->getIdSite()); + } +} + + + class AuthControllerNobodyLoggedAndRegistrationAllowedBoiteLoginTest extends AuthControllerNobodyLoggedTestCase { public function setUp() { @@ -1579,11 +1627,12 @@ class AuthControllerNobodyLoggedRegisterPostRightDatasTest 'password' => 'secret', 'mail' => 'mario@afi-sa.fr', 'cle' => md5('mario@afi-sa.fr'), - 'id_site' => 0, + 'id_site' => null, 'idabon' => '', 'lastname' => 'bros', 'firstname' => 'mario', - 'date' => Class_Users::getCurrentDateTime()], + 'date' => Class_Users::getCurrentDateTime(), + 'bib_user_id' => null], $user->getRawAttributes()); } } diff --git a/tests/library/Class/UsersNonValidTest.php b/tests/library/Class/UsersNonValidTest.php index 02112526c7d71201b5bc8cf82578eef5929add1e..7026663645cc7bd6dfa0fef8b1f44fb1b4ca57b0 100644 --- a/tests/library/Class/UsersNonValidTest.php +++ b/tests/library/Class/UsersNonValidTest.php @@ -27,23 +27,25 @@ class Class_UsersNonValidActivateTest extends ModelTestCase { public function setUp() { parent::setUp(); - $this->fixture('Class_UsersNonValid', - ['id' => 3, - 'login' => 'jb', - 'lastname' => 'brown', - 'firstname' => 'james', - 'idabon' => '123', - 'id_site' => '3', - 'mail' => 'jb@sexmach.ne', - 'password' => 'ifeelgood'])->activate(); + $this + ->fixture('Class_UsersNonValid', + ['id' => 3, + 'login' => 'jb', + 'lastname' => 'brown', + 'firstname' => 'james', + 'idabon' => '123', + 'id_site' => '3', + 'mail' => 'jb@sexmach.ne', + 'password' => 'ifeelgood']) + ->activate(); $this->_new_user = Class_Users::find(1); } /** @test */ - public function usersNonValidShouldBeDeleted() { - $this->assertEmpty(Class_UsersNonValid::find(3)); + public function usersNonValidShouldNotBeDeleted() { + $this->assertNotNull(Class_UsersNonValid::find(3)); }