From e2a658bb8de149fbe5896032ce982cd378e1bca8 Mon Sep 17 00:00:00 2001 From: "baychae@gmail.com" Date: Tue, 1 Aug 2017 17:00:03 +0100 Subject: [PATCH 1/8] Add deep inherits when array passed to addInherit --- Library/Phalcon/Acl/Adapter/Redis.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Library/Phalcon/Acl/Adapter/Redis.php b/Library/Phalcon/Acl/Adapter/Redis.php index 9b4063a99..33a98af62 100644 --- a/Library/Phalcon/Acl/Adapter/Redis.php +++ b/Library/Phalcon/Acl/Adapter/Redis.php @@ -119,6 +119,16 @@ public function addInherit($roleName, $roleToInherit) $roleToInherit = $roleToInherit->getName(); } + /** + * Deep inherits + */ + if( is_array( $roleToInherit ) ) { + foreach ($roleToInherit as $roleToInherit) { + $this->redis->sAdd("rolesInherits:$roleName", $roleToInherit); + } + return true; + } + $this->redis->sAdd("rolesInherits:$roleName", $roleToInherit); } From a0c2fb670acf765626b77b534ecd81442a1f186e Mon Sep 17 00:00:00 2001 From: "baychae@gmail.com" Date: Tue, 1 Aug 2017 19:14:23 +0100 Subject: [PATCH 2/8] Recurse through inheritance chain for implicit role inheritance. --- Library/Phalcon/Acl/Adapter/Redis.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Library/Phalcon/Acl/Adapter/Redis.php b/Library/Phalcon/Acl/Adapter/Redis.php index 33a98af62..2156072fa 100644 --- a/Library/Phalcon/Acl/Adapter/Redis.php +++ b/Library/Phalcon/Acl/Adapter/Redis.php @@ -120,15 +120,25 @@ public function addInherit($roleName, $roleToInherit) } /** - * Deep inherits + * Deep inherits Explicit tests array, Implicit recurs through inheritance chain */ if( is_array( $roleToInherit ) ) { + foreach ($roleToInherit as $roleToInherit) { $this->redis->sAdd("rolesInherits:$roleName", $roleToInherit); } return true; } + if($this->redis->exists("rolesInherits:$roleToInherit")) { + + $deeperInherits = $this->redis->sGetMembers("rolesInherits:$roleToInherit"); + + foreach($deeperInherits as $deeperInherit) { + $this->addInherit($roleName,$deeperInherit); + } + } + $this->redis->sAdd("rolesInherits:$roleName", $roleToInherit); } From 72d8cb46c09f274491c9ca805a39cc2b708c92f4 Mon Sep 17 00:00:00 2001 From: "baychae@gmail.com" Date: Tue, 1 Aug 2017 20:45:00 +0100 Subject: [PATCH 3/8] Code to PSR2 Standards --- Library/Phalcon/Acl/Adapter/Redis.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Library/Phalcon/Acl/Adapter/Redis.php b/Library/Phalcon/Acl/Adapter/Redis.php index 2156072fa..a3139e3df 100644 --- a/Library/Phalcon/Acl/Adapter/Redis.php +++ b/Library/Phalcon/Acl/Adapter/Redis.php @@ -122,20 +122,18 @@ public function addInherit($roleName, $roleToInherit) /** * Deep inherits Explicit tests array, Implicit recurs through inheritance chain */ - if( is_array( $roleToInherit ) ) { - + if (is_array($roleToInherit)) { foreach ($roleToInherit as $roleToInherit) { $this->redis->sAdd("rolesInherits:$roleName", $roleToInherit); } return true; } - if($this->redis->exists("rolesInherits:$roleToInherit")) { - + if ($this->redis->exists("rolesInherits:$roleToInherit")) { $deeperInherits = $this->redis->sGetMembers("rolesInherits:$roleToInherit"); - foreach($deeperInherits as $deeperInherit) { - $this->addInherit($roleName,$deeperInherit); + foreach ($deeperInherits as $deeperInherit) { + $this->addInherit($roleName, $deeperInherit); } } From 561adebbcfc23bea5b40f30ec77a4c7535ceb2cf Mon Sep 17 00:00:00 2001 From: "baychae@gmail.com" Date: Wed, 2 Aug 2017 00:54:34 +0100 Subject: [PATCH 4/8] Adding example usage to @inheritdoc --- Library/Phalcon/Acl/Adapter/Redis.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Library/Phalcon/Acl/Adapter/Redis.php b/Library/Phalcon/Acl/Adapter/Redis.php index a3139e3df..491ed44e3 100644 --- a/Library/Phalcon/Acl/Adapter/Redis.php +++ b/Library/Phalcon/Acl/Adapter/Redis.php @@ -101,6 +101,12 @@ public function addRole($role, $accessInherits = null) /** * {@inheritdoc} * + * Example: + * //Administrator implicitly inherits all descendants of 'consultor' unless explicity set in an Array + * $acl->addInherit('administrator', new Phalcon\Acl\Role('consultor')); + * $acl->addInherit('administrator', 'consultor'); + * $acl->addInherit('administrator', ['consultor', 'poweruser']); + * * @param string $roleName * @param \Phalcon\Acl\Role|string $roleToInherit * @throws \Phalcon\Acl\Exception From 61fd0e36827d8f5f44083f80260ecd96afac64f4 Mon Sep 17 00:00:00 2001 From: Hisune Date: Thu, 3 Aug 2017 14:28:18 +0800 Subject: [PATCH 5/8] minor mistake of syntax minor mistake of syntax --- Library/Phalcon/Db/Adapter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Phalcon/Db/Adapter/README.md b/Library/Phalcon/Db/Adapter/README.md index ab45c2bc1..6112b1e93 100644 --- a/Library/Phalcon/Db/Adapter/README.md +++ b/Library/Phalcon/Db/Adapter/README.md @@ -88,7 +88,7 @@ $di->setShared('mongo', function () { $dsn = 'mongodb://' . $config->database->mongo->host; } else { $dsn = sprintf( - 'mongodb://%s:%s@%s' + 'mongodb://%s:%s@%s', $config->database->mongo->username, $config->database->mongo->password, $config->database->mongo->host From 73fc40c35de28cb26acd207ac7962aad4a070b68 Mon Sep 17 00:00:00 2001 From: Ruud Boon Date: Thu, 21 Sep 2017 06:43:29 +0200 Subject: [PATCH 6/8] Update README.md Prevent message: You are using the deprecated option "dev". Dev packages are installed by default --- tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index 1c4e22d1d..ec18ccc73 100644 --- a/tests/README.md +++ b/tests/README.md @@ -12,7 +12,7 @@ The testing suites can be run on your own machine. The main dependency is [Codec ```bash # run this command from project root -composer install --dev --prefer-source +composer install --prefer-source ``` You can read more about installing and configuring Codeception from the following resources: From b47c1a9a5a3caa3da77be2aad7520eb67e5e83bf Mon Sep 17 00:00:00 2001 From: sergeysviridenko Date: Mon, 25 Sep 2017 10:51:56 +0300 Subject: [PATCH 7/8] Added fix aerospike test --- .travis.yml | 6 +-- tests/_ci/install_aerospike.sh | 14 ++---- .../Annotations/Adapter/AerospikeTest.php | 49 ++++++++++--------- .../aerospike/Cache/Backend/AerospikeTest.php | 2 +- .../Session/Adapter/AerospikeTest.php | 6 +-- 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/.travis.yml b/.travis.yml index 14c9a501e..9ce45108b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,7 @@ before_install: install: - travis_retry composer install --prefer-dist --no-interaction --quiet --no-ansi --no-progress --optimize-autoloader --dev --no-suggest --ignore-platform-reqs # See https://github.com/aerospike/aerospike-client-php/issues/127 - - '[[ "$PHP_MAJOR" == "7" ]] || bash ${TRAVIS_BUILD_DIR}/tests/_ci/install_aerospike.sh' + - '[[ "$PHP_MAJOR" == "5" ]] || bash ${TRAVIS_BUILD_DIR}/tests/_ci/install_aerospike.sh' - if [ ! -f $HOME/ext/$PHP_VERSION/$PHALCON_VERSION/phalcon.so ]; then cd $HOME/cphalcon/$PHALCON_VERSION/build && bash ./install --phpize $(phpenv which phpize) --php-config $(phpenv which php-config) && mkdir -p $HOME/ext/$PHP_VERSION/$PHALCON_VERSION && cp $PHP_EXTENSION_DIR/phalcon.so $HOME/ext/$PHP_VERSION/$PHALCON_VERSION/phalcon.so; fi; - if [ -f $HOME/ext/$PHP_VERSION/$PHALCON_VERSION/phalcon.so ]; then cp $HOME/ext/$PHP_VERSION/$PHALCON_VERSION/phalcon.so $PHP_EXTENSION_DIR/phalcon.so; fi; - phpenv config-add $HOME/cphalcon/$PHALCON_VERSION/tests/_ci/phalcon.ini @@ -74,13 +74,13 @@ before_script: - echo "GRANT ALL PRIVILEGES ON incubator.* TO 'incubator'@'%' WITH GRANT OPTION" | mysql -u root - cat ${TRAVIS_BUILD_DIR}/tests/_data/dump.sql | mysql -u root incubator # See https://github.com/aerospike/aerospike-client-php/issues/127 - - '[[ "${PHP_MAJOR:0:1}" == "7" ]] || bash ${TRAVIS_BUILD_DIR}/tests/_ci/install_aserver.sh' + - '[[ "${PHP_MAJOR:0:1}" == "5" ]] || bash ${TRAVIS_BUILD_DIR}/tests/_ci/install_aserver.sh' script: - vendor/bin/phpcs - vendor/bin/codecept build - vendor/bin/codecept run -v tests/unit - - '[[ "$PHP_MAJOR" == "7" ]] || vendor/bin/codecept run -v tests/aerospike' + - '[[ "$PHP_MAJOR" == "5" ]] || vendor/bin/codecept run -v tests/aerospike' - '[[ "$PHP_MAJOR" == "7" ]] || vendor/bin/codecept run -v tests/unit5x' notifications: diff --git a/tests/_ci/install_aerospike.sh b/tests/_ci/install_aerospike.sh index 263597bdc..b2d51b817 100755 --- a/tests/_ci/install_aerospike.sh +++ b/tests/_ci/install_aerospike.sh @@ -21,18 +21,14 @@ mkdir -p /tmp/aerospike-ext sudo mkdir -p /usr/local/aerospike/{lua,usr-lua} sudo chmod -R ugoa+rwx /usr/local/aerospike +git clone --depth=1 -q https://github.com/aerospike/aerospike-client-php /tmp/aerospike-ext +cd /tmp/aerospike-ext/src/ + sudo ln -sf /usr/lib/x86_64-linux-gnu/libcrypto.so /usr/local/lib/libcrypto.so sudo ln -sf /usr/lib/x86_64-linux-gnu/libcrypto.a /usr/local/lib/libcrypto.a -cd /tmp/aerospike-ext -wget -O aerospike.zip https://github.com/aerospike/aerospike-client-php/archive/master.zip -unzip aerospike.zip -x "aerospike-client-php-master/doc/*" -x "aerospike-client-php-master/examples/*" -x "aerospike-client-php-master/src/aerospike/tests/*" - -mkdir -p aerospike-client-php-master/src/aerospike/tests/ -cd aerospike-client-php-master/src/aerospike - -./build.sh --loglevel OFF > /dev/null 2>&1 -make --silent install +bash ./build.sh +make install find . -type f -name aerospike.so | xargs sudo cp -t $(php-config --extension-dir) phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/aerospike.ini diff --git a/tests/aerospike/Annotations/Adapter/AerospikeTest.php b/tests/aerospike/Annotations/Adapter/AerospikeTest.php index f5dd2e38f..6e0b23fce 100644 --- a/tests/aerospike/Annotations/Adapter/AerospikeTest.php +++ b/tests/aerospike/Annotations/Adapter/AerospikeTest.php @@ -67,7 +67,7 @@ public function testHasSetProperty() */ public function testShouldReadAndWriteToAerospikeWithoutPrefix($key, $data) { - $object = new Aerospike(['hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]]]); + $object = new Aerospike(['hosts' => $this->getHostConfig()]); $object->write($key, $data); $this->assertEquals($data, $object->read($key)); @@ -80,7 +80,7 @@ public function testShouldReadAndWriteToAerospikeWithoutPrefix($key, $data) */ public function testShouldReadAndWriteToAerospikeWithPrefix($key, $data) { - $object = new Aerospike(['hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], 'prefix' => 'test_']); + $object = new Aerospike(['hosts' => $this->getHostConfig(), 'prefix' => 'test_']); $object->write($key, $data); $this->assertEquals($data, $object->read($key)); @@ -88,7 +88,7 @@ public function testShouldReadAndWriteToAerospikeWithPrefix($key, $data) public function testShouldGetCacheBackendThroughGetter() { - $object = new Aerospike(['hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]]]); + $object = new Aerospike(['hosts' => $this->getHostConfig()]); $reflectedMethod = new ReflectionMethod(get_class($object), 'getCacheBackend'); $reflectedMethod->setAccessible(true); @@ -97,7 +97,7 @@ public function testShouldGetCacheBackendThroughGetter() public function testShouldGetCacheBackendThroughReflectionSetter() { - $object = new Aerospike(['hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]]]); + $object = new Aerospike(['hosts' => $this->getHostConfig()]); $mock = $this->getMock(CacheBackend::class, [], [], '', false); $reflectedProperty = new ReflectionProperty(get_class($object), 'aerospike'); @@ -115,7 +115,7 @@ public function testShouldGetCacheBackendThroughReflectionSetter() */ public function testShouldPrepareKey($key) { - $object = new Aerospike(['hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]]]); + $object = new Aerospike(['hosts' => $this->getHostConfig()]); $reflectedMethod = new ReflectionMethod(get_class($object), 'prepareKey'); $reflectedMethod->setAccessible(true); @@ -192,14 +192,14 @@ public function providerInvalidConstructor() public function providerConstructor() { - return [ + return [ //$this->getConfig() [ [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 23 ], [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 23, 'prefix' => '', 'persistent' => false, @@ -208,7 +208,7 @@ public function providerConstructor() ], [ [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 23, 'options' => [ 1 => 1250, @@ -216,7 +216,7 @@ public function providerConstructor() ] ], [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 23, 'prefix' => '', 'persistent' => false, @@ -228,12 +228,12 @@ public function providerConstructor() ], [ [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 23, 'persistent' => true ], [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 23, 'prefix' => '', 'persistent' => true, @@ -242,11 +242,11 @@ public function providerConstructor() ], [ [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'prefix' => 'test_' ], [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 8600, 'prefix' => 'test_', 'persistent' => false, @@ -255,11 +255,11 @@ public function providerConstructor() ], [ [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'randomValue' => 'test_' ], [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'randomValue' => 'test_', 'lifetime' => 8600, 'prefix' => '', @@ -269,11 +269,11 @@ public function providerConstructor() ], [ [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 123 => 'test_' ], [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 123 => 'test_', 'lifetime' => 8600, 'prefix' => '', @@ -283,12 +283,12 @@ public function providerConstructor() ], [ [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 24, 'prefix' => 'test_', ], [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 24, 'prefix' => 'test_', 'persistent' => false, @@ -297,10 +297,10 @@ public function providerConstructor() ], [ [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), ], [ - 'hosts' => [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)]], + 'hosts' => $this->getHostConfig(), 'lifetime' => 8600, 'prefix' => '', 'persistent' => false, @@ -310,4 +310,9 @@ public function providerConstructor() ]; } + + private function getHostConfig() + { + return [['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => (int)env('TEST_AS_PORT', 3000)]]; + } } diff --git a/tests/aerospike/Cache/Backend/AerospikeTest.php b/tests/aerospike/Cache/Backend/AerospikeTest.php index 2536d9f81..853c5ede6 100644 --- a/tests/aerospike/Cache/Backend/AerospikeTest.php +++ b/tests/aerospike/Cache/Backend/AerospikeTest.php @@ -183,7 +183,7 @@ private function getConfig() { return [ 'hosts' => [ - ['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)] + ['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => (int)env('TEST_AS_PORT', 3000)] ], 'persistent' => false, // important 'namespace' => 'test', diff --git a/tests/aerospike/Session/Adapter/AerospikeTest.php b/tests/aerospike/Session/Adapter/AerospikeTest.php index 4aa9a3e88..af9ca686a 100644 --- a/tests/aerospike/Session/Adapter/AerospikeTest.php +++ b/tests/aerospike/Session/Adapter/AerospikeTest.php @@ -48,7 +48,7 @@ protected function _before() $this->getModule('Aerospike')->_reconfigure([ 'set' => $this->set, 'addr' => env('TEST_AS_HOST', '127.0.0.1'), - 'port' => env('TEST_AS_PORT', 3000) + 'port' => (int)env('TEST_AS_PORT', 3000) ]); } @@ -115,7 +115,7 @@ private function cleanup() $aerospike = new Aerospike( [ 'hosts' => [ - ['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)] + ['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => (int)env('TEST_AS_PORT', 3000)] ] ], false @@ -140,7 +140,7 @@ private function getConfig() { return [ 'hosts' => [ - ['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => env('TEST_AS_PORT', 3000)] + ['addr' => env('TEST_AS_HOST', '127.0.0.1'), 'port' => (int)env('TEST_AS_PORT', 3000)] ], 'persistent' => false, 'namespace' => $this->ns, From 151af08411550431e39b2189796c25db7710f837 Mon Sep 17 00:00:00 2001 From: sergeysviridenko Date: Tue, 26 Sep 2017 15:02:51 +0300 Subject: [PATCH 8/8] Added IBAN validator --- Library/Phalcon/Utils/ArrayUtils.php | 80 +++++ Library/Phalcon/Validation/Validator/Iban.php | 298 ++++++++++++++++++ tests/_fixtures/Utils/array_utils.php | 48 +++ tests/_fixtures/Validation/iban_data.php | 92 ++++++ tests/unit/Utils/ArrayUtilsTest.php | 78 +++++ tests/unit/Validation/Validator/IbanTest.php | 178 +++++++++++ 6 files changed, 774 insertions(+) create mode 100644 Library/Phalcon/Utils/ArrayUtils.php create mode 100644 Library/Phalcon/Validation/Validator/Iban.php create mode 100644 tests/_fixtures/Utils/array_utils.php create mode 100644 tests/_fixtures/Validation/iban_data.php create mode 100644 tests/unit/Utils/ArrayUtilsTest.php create mode 100644 tests/unit/Validation/Validator/IbanTest.php diff --git a/Library/Phalcon/Utils/ArrayUtils.php b/Library/Phalcon/Utils/ArrayUtils.php new file mode 100644 index 000000000..be8f838d5 --- /dev/null +++ b/Library/Phalcon/Utils/ArrayUtils.php @@ -0,0 +1,80 @@ + | + +------------------------------------------------------------------------+ +*/ + +namespace Phalcon\Utils; + +use Traversable; +use InvalidArgumentException; + +/** + * Utility class for manipulation of PHP arrays. + * + * @package Phalcon\Utils + */ +class ArrayUtils +{ + /** + * Convert an iterator to an array. + * + * Converts an iterator to an array. The $recursive flag, on by default, + * hints whether or not you want to do so recursively. + * + * @param array | Traversable $iterator The array or Traversable object to convert + * @param bool $recursive Recursively check all nested structures + * @throws InvalidArgumentException if $iterator is not an array or a Traversable object + * @return array + */ + public function iteratorToArray($iterator, $recursive = true) + { + if (!is_array($iterator) && !$iterator instanceof Traversable) { + throw new InvalidArgumentException(__METHOD__ . ' must be either an array or Traversable'); + } + + if (!$recursive) { + if (is_array($iterator)) { + return $iterator; + } + return iterator_to_array($iterator); + } + + if (method_exists($iterator, 'toArray')) { + return $iterator->toArray(); + } + + $array = []; + foreach ($iterator as $key => $value) { + if (is_scalar($value)) { + $array[$key] = $value; + continue; + } + if ($value instanceof Traversable) { + $array[$key] = $this->iteratorToArray($value, $recursive); + continue; + } + if (is_array($value)) { + $array[$key] = $this->iteratorToArray($value, $recursive); + continue; + } + + $array[$key] = $value; + } + + return $array; + } +} diff --git a/Library/Phalcon/Validation/Validator/Iban.php b/Library/Phalcon/Validation/Validator/Iban.php new file mode 100644 index 000000000..bed6b673d --- /dev/null +++ b/Library/Phalcon/Validation/Validator/Iban.php @@ -0,0 +1,298 @@ + | + +------------------------------------------------------------------------+ +*/ + +namespace Phalcon\Validation\Validator; + +use Phalcon\Validation\Validator; +use Phalcon\Validation; +use Phalcon\Validation\Message; + +/** + * Validates IBAN Numbers (International Bank Account Numbers) + * + * + * use Phalcon\Validation\Validator\Iban; + * + * $validator->add('number', new Iban([ + * 'country_code' => 'AD', // optional + * 'allow_non_sepa' => false, // optional + * 'messageNotSupported' => 'Unknown country within the IBAN', + * 'messageSepaNotSupported' => 'Countries outside the Single Euro Payments Area (SEPA) are not supported', + * 'messageFalseFormat' => 'Field has a false IBAN format', + * 'messageCheckFailed' => 'Field has failed the IBAN check', + * ])); + * + * + * @package Phalcon\Validation\Validator + */ +class Iban extends Validator +{ + /** + * Validation failure message template definitions + * + * @var array + */ + protected $messageTemplates = [ + 'messageNotSupported' => ":field has unknown country within the IBAN", + 'messageSepaNotSupported' => + "Countries outside the Single Euro Payments Area (SEPA) are not supported in :field", + 'messageFalseFormat' => ":field has a false IBAN format", + 'messageCheckFailed' => ":field has failed the IBAN check", + ]; + + /** + * Optional country code by ISO 3166-1 + * + * @var string | null + */ + protected $countryCode; + + /** + * Optionally allow IBAN codes from non-SEPA countries. Default true + * + * @var bool + */ + protected $allowNonSepa = true; + + /** + * The SEPA country codes + * + * @var array + */ + protected $sepaCountries = [ + 'AT', 'BE', 'BG', 'CY', 'CZ', 'DK', 'FO', 'GL', 'EE', 'FI', 'FR', 'DE', + 'GI', 'GR', 'HU', 'IS', 'IE', 'IT', 'LV', 'LI', 'LT', 'LU', 'MT', 'MC', + 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'CH', 'GB' + ]; + + /** + * IBAN regexes by country code + * + * @var array + */ + protected $ibanRegex = [ + 'AD' => 'AD[0-9]{2}[0-9]{4}[0-9]{4}[A-Z0-9]{12}', + 'AE' => 'AE[0-9]{2}[0-9]{3}[0-9]{16}', + 'AL' => 'AL[0-9]{2}[0-9]{8}[A-Z0-9]{16}', + 'AT' => 'AT[0-9]{2}[0-9]{5}[0-9]{11}', + 'AZ' => 'AZ[0-9]{2}[A-Z]{4}[A-Z0-9]{20}', + 'BA' => 'BA[0-9]{2}[0-9]{3}[0-9]{3}[0-9]{8}[0-9]{2}', + 'BE' => 'BE[0-9]{2}[0-9]{3}[0-9]{7}[0-9]{2}', + 'BG' => 'BG[0-9]{2}[A-Z]{4}[0-9]{4}[0-9]{2}[A-Z0-9]{8}', + 'BH' => 'BH[0-9]{2}[A-Z]{4}[A-Z0-9]{14}', + 'BR' => 'BR[0-9]{2}[0-9]{8}[0-9]{5}[0-9]{10}[A-Z][A-Z0-9]', + 'BY' => 'BY[0-9]{2}[A-Z0-9]{4}[0-9]{4}[A-Z0-9]{16}', + 'CH' => 'CH[0-9]{2}[0-9]{5}[A-Z0-9]{12}', + 'CR' => 'CR[0-9]{2}[0-9]{3}[0-9]{14}', + 'CY' => 'CY[0-9]{2}[0-9]{3}[0-9]{5}[A-Z0-9]{16}', + 'CZ' => 'CZ[0-9]{2}[0-9]{20}', + 'DE' => 'DE[0-9]{2}[0-9]{8}[0-9]{10}', + 'DO' => 'DO[0-9]{2}[A-Z0-9]{4}[0-9]{20}', + 'DK' => 'DK[0-9]{2}[0-9]{14}', + 'EE' => 'EE[0-9]{2}[0-9]{2}[0-9]{2}[0-9]{11}[0-9]{1}', + 'ES' => 'ES[0-9]{2}[0-9]{4}[0-9]{4}[0-9]{1}[0-9]{1}[0-9]{10}', + 'FI' => 'FI[0-9]{2}[0-9]{6}[0-9]{7}[0-9]{1}', + 'FO' => 'FO[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}', + 'FR' => 'FR[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}', + 'GB' => 'GB[0-9]{2}[A-Z]{4}[0-9]{6}[0-9]{8}', + 'GE' => 'GE[0-9]{2}[A-Z]{2}[0-9]{16}', + 'GI' => 'GI[0-9]{2}[A-Z]{4}[A-Z0-9]{15}', + 'GL' => 'GL[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}', + 'GR' => 'GR[0-9]{2}[0-9]{3}[0-9]{4}[A-Z0-9]{16}', + 'GT' => 'GT[0-9]{2}[A-Z0-9]{4}[A-Z0-9]{20}', + 'HR' => 'HR[0-9]{2}[0-9]{7}[0-9]{10}', + 'HU' => 'HU[0-9]{2}[0-9]{3}[0-9]{4}[0-9]{1}[0-9]{15}[0-9]{1}', + 'IE' => 'IE[0-9]{2}[A-Z]{4}[0-9]{6}[0-9]{8}', + 'IL' => 'IL[0-9]{2}[0-9]{3}[0-9]{3}[0-9]{13}', + 'IS' => 'IS[0-9]{2}[0-9]{4}[0-9]{2}[0-9]{6}[0-9]{10}', + 'IT' => 'IT[0-9]{2}[A-Z]{1}[0-9]{5}[0-9]{5}[A-Z0-9]{12}', + 'KW' => 'KW[0-9]{2}[A-Z]{4}[0-9]{22}', + 'KZ' => 'KZ[0-9]{2}[0-9]{3}[A-Z0-9]{13}', + 'LB' => 'LB[0-9]{2}[0-9]{4}[A-Z0-9]{20}', + 'LI' => 'LI[0-9]{2}[0-9]{5}[A-Z0-9]{12}', + 'LT' => 'LT[0-9]{2}[0-9]{5}[0-9]{11}', + 'LU' => 'LU[0-9]{2}[0-9]{3}[A-Z0-9]{13}', + 'LV' => 'LV[0-9]{2}[A-Z]{4}[A-Z0-9]{13}', + 'MC' => 'MC[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}', + 'MD' => 'MD[0-9]{2}[A-Z0-9]{20}', + 'ME' => 'ME[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}', + 'MK' => 'MK[0-9]{2}[0-9]{3}[A-Z0-9]{10}[0-9]{2}', + 'MR' => 'MR13[0-9]{5}[0-9]{5}[0-9]{11}[0-9]{2}', + 'MT' => 'MT[0-9]{2}[A-Z]{4}[0-9]{5}[A-Z0-9]{18}', + 'MU' => 'MU[0-9]{2}[A-Z]{4}[0-9]{2}[0-9]{2}[0-9]{12}[0-9]{3}[A-Z]{3}', + 'NL' => 'NL[0-9]{2}[A-Z]{4}[0-9]{10}', + 'NO' => 'NO[0-9]{2}[0-9]{4}[0-9]{6}[0-9]{1}', + 'PK' => 'PK[0-9]{2}[A-Z]{4}[A-Z0-9]{16}', + 'PL' => 'PL[0-9]{2}[0-9]{8}[0-9]{16}', + 'PS' => 'PS[0-9]{2}[A-Z]{4}[A-Z0-9]{21}', + 'PT' => 'PT[0-9]{2}[0-9]{4}[0-9]{4}[0-9]{11}[0-9]{2}', + 'RO' => 'RO[0-9]{2}[A-Z]{4}[A-Z0-9]{16}', + 'RS' => 'RS[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}', + 'SA' => 'SA[0-9]{2}[0-9]{2}[A-Z0-9]{18}', + 'SE' => 'SE[0-9]{2}[0-9]{3}[0-9]{16}[0-9]{1}', + 'SI' => 'SI[0-9]{2}[0-9]{5}[0-9]{8}[0-9]{2}', + 'SK' => 'SK[0-9]{2}[0-9]{4}[0-9]{6}[0-9]{10}', + 'SM' => 'SM[0-9]{2}[A-Z]{1}[0-9]{5}[0-9]{5}[A-Z0-9]{12}', + 'TN' => 'TN59[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}', + 'TR' => 'TR[0-9]{2}[0-9]{5}[A-Z0-9]{1}[A-Z0-9]{16}', + 'VG' => 'VG[0-9]{2}[A-Z]{4}[0-9]{16}', + ]; + + /** + * Sets validator options + * + * @param array $options OPTIONAL + */ + public function __construct(array $options = []) + { + if (isset($options['country_code'])) { + $this->setCountryCode($options['country_code']); + } + + if (isset($options['allow_non_sepa'])) { + $this->allowNonSepa = $options['allow_non_sepa']; + } + + if (!isset($options['messageNotSupported'])) { + $options['messageNotSupported'] = $this->messageTemplates['messageNotSupported']; + } + + if (!isset($options['messageSepaNotSupported'])) { + $options['messageSepaNotSupported'] = $this->messageTemplates['messageSepaNotSupported']; + } + + if (!isset($options['messageFalseFormat'])) { + $options['messageFalseFormat'] = $this->messageTemplates['messageFalseFormat']; + } + + if (!isset($options['messageCheckFailed'])) { + $options['messageCheckFailed'] = $this->messageTemplates['messageCheckFailed']; + } + + parent::__construct($options); + } + + /** + * Sets an optional country code by ISO 3166-1 + * + * @param string | null $countryCode + */ + public function setCountryCode($countryCode = null) + { + if ($countryCode !== null) { + $countryCode = (string) $countryCode; + } + $this->countryCode = $countryCode; + } + + /** + * Sets the optional allow non-sepa countries setting + * + * @param bool $allowNonSepa + */ + public function setAllowNonSepa($allowNonSepa) + { + $this->allowNonSepa = (bool) $allowNonSepa; + } + + /** + * {@inheritdoc} + * + * @param Validation $validation + * @param string $attribute + * + * @return bool + */ + public function validate(Validation $validation, $attribute) + { + $messageCode = $this->getErrorMessageCode($validation, $attribute); + if (!empty($messageCode)) { + $label = $this->prepareLabel($validation, $attribute); + $code = $this->prepareCode($attribute); + $replacePairs = [":field"=> $label]; + + $message = $this->prepareMessage($validation, $attribute, "Iban", $messageCode); + + $validation->appendMessage( + new Message( + strtr($message, $replacePairs), + $attribute, + "Iban", + $code + ) + ); + return false; + } + + return true; + } + + /** + * Validate code and return error message key or empty string + * + * @param Validation $validation + * @param string $attribute + * + * @return string + */ + protected function getErrorMessageCode(Validation $validation, $attribute) + { + $value = $validation->getValue($attribute); + + if ($this->countryCode === null) { + $this->countryCode = substr($value, 0, 2); + } + + if (!array_key_exists($this->countryCode, $this->ibanRegex)) { + return 'messageNotSupported'; + } + + if (!$this->allowNonSepa && !in_array($this->countryCode, $this->sepaCountries)) { + return 'messageSepaNotSupported'; + } + + if (!preg_match('/^' . $this->ibanRegex[$this->countryCode] . '$/', $value)) { + return 'messageFalseFormat'; + } + + $format = substr($value, 4) . substr($value, 0, 4); + $format = str_replace( + ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], + ['10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', + '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35'], + $format + ); + + $temp = intval(substr($format, 0, 1)); + $len = strlen($format); + + for ($x = 1; $x < $len; ++$x) { + $temp *= 10; + $temp += intval(substr($format, $x, 1)); + $temp %= 97; + } + + if ($temp != 1) { + return 'messageCheckFailed'; + } + + return ''; + } +} diff --git a/tests/_fixtures/Utils/array_utils.php b/tests/_fixtures/Utils/array_utils.php new file mode 100644 index 000000000..c70127f49 --- /dev/null +++ b/tests/_fixtures/Utils/array_utils.php @@ -0,0 +1,48 @@ + 'four', + 'bar' => 'five', + 'four' + ], + [ + 'foo' => 'four', + 'bar' => 'five', + 'four' + ] + ], + [ + [ + 'foo' => ['six', 'one'], + 'bar' => 'five', + 'four' + ], + [ + 'foo' => ['six', 'one'], + 'bar' => 'five', + 'four' + ] + ], +]; diff --git a/tests/_fixtures/Validation/iban_data.php b/tests/_fixtures/Validation/iban_data.php new file mode 100644 index 000000000..36d86990b --- /dev/null +++ b/tests/_fixtures/Validation/iban_data.php @@ -0,0 +1,92 @@ + [ + [ + 'TR', + 'TR330006100519786457841326', + ], + [ + 'PT', + 'PT50000201231234567890154', + ], + [ + 'AT', + 'AT611904300234573201', + ], + [ + 'SE', + 'SE4550000000058398257466', + ], + [ + 'CH', + 'CH9300762011623852957', + ], + [ + 'GB', + 'GB29NWBK60161331926819', + ], + [ + 'MT', + 'MT84MALT011000012345MTLCAST001S', + ], + [ + 'MD', + 'MD24AG000225100013104168', + ], + [ + 'HU', + 'HU42117730161111101800000000', + ], + [ + 'GR', + 'GR1601101250000000012300695', + ], + [ + 'DE', + 'DE89370400440532013000', + ], + [ + 'EE', + 'EE382200221020145685', + ], + ], + 'iban-error-code' => [ + [ + 'CY', + '', + 'The input has a false IBAN format', + 'messageFalseFormat', + ], + [ + 'CY', + 'CY170020012800000012005', //not enough symbols in code + 'The input has a false IBAN format', + 'messageFalseFormat', + ], + [ + 'DE', + 'TR330006100519786457841326', + 'The input has a false IBAN format', + 'messageFalseFormat', + ], + [ + 'ZZ', + 'TR330006100519786457841326', + 'Unknown country within the IBAN', + 'messageNotSupported', + ], + [ + 'AD', + 'AD1200012030200359100100', + 'Countries outside the Single Euro Payments Area (SEPA) are not supported', + 'messageSepaNotSupported', + ], + [ + 'AT', + 'AT611904300234573205', //changed last symbol. should be - AT611904300234573201 + 'The input has failed the IBAN check', + 'messageCheckFailed', + ], + ], +]; diff --git a/tests/unit/Utils/ArrayUtilsTest.php b/tests/unit/Utils/ArrayUtilsTest.php new file mode 100644 index 000000000..db617d88a --- /dev/null +++ b/tests/unit/Utils/ArrayUtilsTest.php @@ -0,0 +1,78 @@ + | + +------------------------------------------------------------------------+ +*/ + +namespace Phalcon\Test\Utils; + +use Codeception\TestCase\Test; +use UnitTester; +use Phalcon\Utils\ArrayUtils; +use ArrayIterator; + +class ArrayUtilsTest extends Test +{ + /** + * Tests ArrayUtils::iteratorToArray. Testing array. + * + * @dataProvider providerArray + * @param array $array + * @param array $array + * + * @test + * @author Sergii Svyrydenko + * @since 2017-09-26 + */ + public function shouldReturnArrayFromArray($array, $expected) + { + $utils = new ArrayUtils(); + + $this->assertEquals( + $expected, + $utils->iteratorToArray($array), + 'Arrays are different' + ); + } + + /** + * Tests ArrayUtils::iteratorToArray. Testing iterator. + * + * @dataProvider providerArray + * @param array $array + * @param array $array + * + * @test + * @author Sergii Svyrydenko + * @since 2017-09-26 + */ + public function shouldReturnArrayFromIterator($array, $expected) + { + $utils = new ArrayUtils(); + $iterator = new ArrayIterator($array); + + $this->assertEquals( + $expected, + $utils->iteratorToArray($iterator), + 'Arrays are different' + ); + } + + public function providerArray() + { + return require INCUBATOR_FIXTURES . 'Utils/array_utils.php'; + } +} diff --git a/tests/unit/Validation/Validator/IbanTest.php b/tests/unit/Validation/Validator/IbanTest.php new file mode 100644 index 000000000..2c65ac35b --- /dev/null +++ b/tests/unit/Validation/Validator/IbanTest.php @@ -0,0 +1,178 @@ + | + +------------------------------------------------------------------------+ +*/ + +namespace Phalcon\Test\Validation\Validator; + +use UnitTester; +use Codeception\TestCase\Test; +use Phalcon\Validation\Validator\Iban; +use Phalcon\Validation; + +class IbanTest extends Test +{ + /** + * Tests Iban::validate. When country code set after add to validation. + * + * @dataProvider providerValidCode + * @param string $countryCode + * @param string $code + * + * @test + * @issue 809 + * @author Sergii Svyrydenko + * @since 2017-09-26 + */ + public function shouldValidateIbanCodeWithSetCountryCode($countryCode, $code) + { + $validation = new Validation(); + $validation->add( + 'test', + new Iban() + ); + + $validators = $validation->getValidators(); + $validator = $validators[0]; + $validator = $validator[1]; + + $validator->setCountryCode($countryCode); + + $messages = $validation->validate(['test' => $code]); + + $this->assertCount( + 0, + $messages, + 'The Iban number isn\'t valid' + ); + } + + /** + * Tests Iban::validate. When country code didn't set ever. + * + * @dataProvider providerValidCode + * @param string $countryCode + * @param string $code + * + * @test + * @issue 809 + * @author Sergii Svyrydenko + * @since 2017-09-26 + */ + public function shouldValidateIbanCodeWithoutCountryCode($countryCode, $code) + { + $validation = new Validation(); + $iban = new Iban(); + + $validation->add( + 'test', + $iban + ); + + $messages = $validation->validate(['test' => $code]); + + $this->assertCount( + 0, + $messages, + 'The Iban number isn\'t valid' + ); + } + + /** + * Tests Iban::validate. When country code set in construct. + * + * @dataProvider providerValidCode + * @param string $countryCode + * @param string $code + * + * @test + * @issue 809 + * @author Sergii Svyrydenko + * @since 2017-09-26 + */ + public function shouldValidateIbanCodeWithCountryCode($countryCode, $code) + { + $validation = new Validation(); + $validation->add( + 'test', + new Iban([ + 'country_code' => $countryCode, + ]) + ); + + $messages = $validation->validate(['test' => $code]); + + $this->assertCount( + 0, + $messages, + 'The Iban number isn\'t valid' + ); + } + + /** + * Tests Iban::validate. Generate error message. + * + * @dataProvider providerInvalidCode + * @param string $countryCode + * @param string $code + * @param string $message + * @param string $messageType + * + * @test + * @issue 809 + * @author Sergii Svyrydenko + * @since 2017-09-26 + */ + public function shouldCatchErrorMessage($countryCode, $code, $message, $messageType) + { + $validation = new Validation(); + $iban = new Iban([ + 'country_code' => $countryCode, + $messageType => $message, + 'allow_non_sepa' => false, + ]); + + $validation->add( + 'test', + $iban + ); + + $messages = $validation->validate(['test' => $code]); + + foreach ($messages as $messageReturn) { + $this->assertEquals( + $message, + $messageReturn->getMessage(), + 'Method validate() should return error message' + ); + } + } + + public function providerValidCode() + { + $data = require INCUBATOR_FIXTURES . 'Validation/iban_data.php'; + + return $data['iban-codes']; + } + + public function providerInvalidCode() + { + $data = require INCUBATOR_FIXTURES . 'Validation/iban_data.php'; + + return $data['iban-error-code']; + } +}