From 85788e86b57b70dda3f6163632ae800c712e007d Mon Sep 17 00:00:00 2001 From: Maxime Renou Date: Wed, 7 Jul 2021 10:26:43 +0200 Subject: [PATCH] version 1.1 --- .gitignore | 2 + ValidatorBundle/.gitignore | 2 + .../DependencyInjection/Configuration.php | 24 + .../DependencyInjection/ValidatorExtension.php | 34 ++ ValidatorBundle/LICENSE | 21 + ValidatorBundle/Ressources/config/services.yaml | 7 + ValidatorBundle/Validator.php | 505 +++++++++++++++++++++ ValidatorBundle/ValidatorBundle.php | 22 + composer.json | 23 + composer.lock | 220 +++++++++ 10 files changed, 860 insertions(+) create mode 100644 .gitignore create mode 100644 ValidatorBundle/.gitignore create mode 100644 ValidatorBundle/DependencyInjection/Configuration.php create mode 100644 ValidatorBundle/DependencyInjection/ValidatorExtension.php create mode 100644 ValidatorBundle/LICENSE create mode 100644 ValidatorBundle/Ressources/config/services.yaml create mode 100644 ValidatorBundle/Validator.php create mode 100644 ValidatorBundle/ValidatorBundle.php create mode 100644 composer.json create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ce5adb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +vendor diff --git a/ValidatorBundle/.gitignore b/ValidatorBundle/.gitignore new file mode 100644 index 0000000..ecdf2d7 --- /dev/null +++ b/ValidatorBundle/.gitignore @@ -0,0 +1,2 @@ +vendor +.idea diff --git a/ValidatorBundle/DependencyInjection/Configuration.php b/ValidatorBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000..1b31c83 --- /dev/null +++ b/ValidatorBundle/DependencyInjection/Configuration.php @@ -0,0 +1,24 @@ +root('validator'); + return ($treeBuilder); + } +} diff --git a/ValidatorBundle/DependencyInjection/ValidatorExtension.php b/ValidatorBundle/DependencyInjection/ValidatorExtension.php new file mode 100644 index 0000000..d17a1f2 --- /dev/null +++ b/ValidatorBundle/DependencyInjection/ValidatorExtension.php @@ -0,0 +1,34 @@ +load('services.yaml'); + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + return $config; + } + + public function getAlias() + { + return parent::getAlias(); // TODO: Change the autogenerated stub + } +} diff --git a/ValidatorBundle/LICENSE b/ValidatorBundle/LICENSE new file mode 100644 index 0000000..192b1c3 --- /dev/null +++ b/ValidatorBundle/LICENSE @@ -0,0 +1,21 @@ +DON'T BE A DICK PUBLIC LICENSE + +Version 1, December 2009 + +Copyright (C) 2009 Philip Sturgeon email@philsturgeon.co.uk + +Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. + +DON'T BE A DICK PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +Do whatever you like with the original work, just don't be a dick. + +Being a dick includes - but is not limited to - the following instances: + +1a. Outright copyright infringement - Don't just copy this and change the name. +1b. Selling the unmodified original with no work done what-so-ever, that's REALLY being a dick. +1c. Modifying the original work to contain hidden harmful content. That would make you a PROPER dick. + +If you become rich through modifications, related works/services, or supporting the original work, share the love. Only a dick would make loads off this work and not buy the original works creator(s) a pint. + +Code is provided with no warranty. Using somebody else's code and bitching when it goes wrong makes you a DONKEY dick. Fix the problem yourself. A non-dick would submit the fix back. \ No newline at end of file diff --git a/ValidatorBundle/Ressources/config/services.yaml b/ValidatorBundle/Ressources/config/services.yaml new file mode 100644 index 0000000..1340455 --- /dev/null +++ b/ValidatorBundle/Ressources/config/services.yaml @@ -0,0 +1,7 @@ +services: + bluesquare.validator: + class: Bluesquare\ValidatorBundle\Validator + autowire: true + public: true + arguments: ['@request_stack', '@translator'] + Bluesquare\ValidatorBundle\Validator: '@bluesquare.validator' diff --git a/ValidatorBundle/Validator.php b/ValidatorBundle/Validator.php new file mode 100644 index 0000000..b15a428 --- /dev/null +++ b/ValidatorBundle/Validator.php @@ -0,0 +1,505 @@ +values = []; + $this->errors = []; + $this->rules = []; + $this->validated = false; + + $this->translator = $translator; + $this->request = $requestStack->getCurrentRequest(); + + $session = $this->request->getSession()->getFlashBag()->get("Bluesquare:ValidatorBundle"); + + if(is_array($session) && isset($session[0])) { + if(isset($session[0]['context'])) $this->context = $session[0]['context']; + if(isset($session[0]['errors']) && is_array($session[0]['errors'])) $this->errors = $session[0]['errors']; + if(isset($session[0]['values']) && is_array($session[0]['values'])) $this->values = $session[0]['values']; + $this->validated = true; + } + + if ($this->post()) + { + $values = []; + foreach (array_merge($_GET, $_POST) as $field => $value) $values[$field] = $value; + $json = @json_decode(file_get_contents('php://input'), true); + if (is_array($json)) $values = array_merge($values, $json); + $this->values = array_merge($this->values, $values); + } + } + + // + + public function context($context) + { + $this->context = $context; + return $this; + } + + public function entity($entity) + { + $this->entity = $entity; + return $this; + } + + public function set($key, $value) + { + $this->values[$key] = $value; + return $this; + } + + public function error($field, $error) + { + if (!isset($this->errors[$field])) + $this->errors[$field] = []; + + $this->errors[$field][] = $error; + + return $this; + } + + // + + public function json($code = 400, $data = []) + { + $errors = []; + + foreach ($this->errors as $field => $field_errors) + { + $errors[$field] = [ + 'error' => "validator.".$field_errors[0].':'.$field.(!is_null($this->context) ? ':'.$this->context : '') + ]; + } + + $data = array_merge([ + 'message' => 'Please check your input', + 'error' => "validator.form-error".(!is_null($this->context) ? ':'.$this->context : ''), + 'errors' => $errors + ], $data); + + return new JsonResponse($data, $code); + } + + public function keep() + { + $data = [ + 'errors' => $this->errors, + 'values' => $this->values, + 'context' => $this->context + ]; + $this->request->getSession()->getFlashBag()->add('Bluesquare:ValidatorBundle', $data); + return $this; + } + + public function validated() + { + return $this->validated; + } + + public function failed() + { + return (count($this->errors) > 0); + } + + public function errors() + { + $translator = $this->translator; + $context = $this->context; + + return array_map(function ($errors) use ($translator, $context) { + $error = $errors[0]; + $message = null; + if (!is_null($context)) { + $message = $translator->trans("$context.$error", [], "validator"); + } + if (is_null($message) || $message == "$context.$error") { + $message = $translator->trans("$error", [], "validator"); + } + return $message; + }, $this->errors); + } + + public function message() + { + $result = $this->failed() ? 'form_error' : 'form_success'; + $message = null; + if (!is_null($this->context)) { + $message = $this->translator->trans("$this->context.$result", [], "validator"); + } + if (is_null($message) || $message == "$this->context.$result") { + $message = $this->translator->trans("$result", [], "validator"); + } + return $message; + } + + public function has($field) + { + return !is_null($this->get($field)); + } + + public function value($field, $default = null) + { + $value = isset($this->values[$field]) ? trim($this->values[$field]) : $default; + if (empty($value)) $value = $default; + + if (is_null($value) && !is_null($this->entity)) { + $method = "get".$this->camelize($field); + if (method_exists($this->entity, $method)) + $value = $this->entity->$method(); + } + + return $value; + } + + public function get($field, $default = null) + { + $value = isset($this->values[$field]) ? trim($this->values[$field]) : $default; + if (strlen($value) == 0) $value = $default; + return $value; + } + + public function checked($field) + { + return $this->post() ? ( + !is_null($this->get($field)) + ) : ( + !is_null($this->value($field)) && $this->value($field) != 0 && $this->value($field) != false + ); + } + + public function isChecked($field) + { + return $this->has($field); + } + + public function getFile($name) + { + return $this->request->files->get($name); + } + + public function hasFile($name) + { + $file = $this->getFile($name); + return !is_null($file) && !(is_array($file) || $file instanceof Traversable); + } + + public function hasFiles($name) + { + $files = $this->getFile($name); + return !is_null($files) && (is_array($files) || $files instanceof Traversable); + } + + public function inject() + { + $args = func_get_args(); + if (count($args) === 0) return false; + if (is_object($args[0])) $entity = array_shift($args); + else $entity = $this->entity; + if (is_null($entity)) return false; + + foreach ($args as $field) { + $method = "set".$this->camelize($field); + if (method_exists($entity, $method)) $entity->$method($this->get($field)); + } + + return true; + } + + public function injectFile() + { + $args = func_get_args(); + if (count($args) === 0) return false; + if (is_object($args[0])) $entity = array_shift($args); + else $entity = $this->entity; + + if (is_null($entity) || count($args) != 2) return false; + $attribute = $args[0]; + $storage = $args[1]; + + if (!$this->hasFile($attribute)) + { + $this->error($attribute, 'required'); + return false; + } + + if ($storage instanceof Storage) + { + try { + return $storage->store($entity, $attribute, $this->getFile($attribute)); + } + catch(MimeTypeException $exception) { + $this->error($attribute, 'file_mime_type'); + } + } + + return false; + } + + // + + public function post() + { + return in_array(strtolower($this->request->getMethod()), ['delete', 'put', 'post', 'patch']); + } + + public function check() + { + return $this->validate(); + } + + // + + protected function validate() + { + foreach ($this->rules as $field => $rules) + { + $rules_names = array_map(function($rule) { return $rule['rule']; }, $rules); + $nullable = !in_array('required', $rules_names) && !in_array('required_file', $rules_names) && !in_array('required_files', $rules_names); + foreach ($rules as $rule) $this->test($field, $rule, $nullable); + } + + $this->validated = true; + + return (count($this->errors) == 0); + } + + public function test($field, $rule, $nullable = false) + { + $name = $rule['rule']; + $data = $rule['data']; + $value = $this->get($field); + $success = true; + + switch ($name) + { + case 'required_file': + $success = $this->hasFile($field); + break; + case 'required_files': + $success = $this->hasFiles($field); + break; + case 'required': + $success = !(is_null($value)); + break; + case 'integer': + $success = (filter_var($value, FILTER_VALIDATE_INT) !== false); + break; + case 'float': + $success = (filter_var($value, FILTER_VALIDATE_FLOAT) !== false); + break; + case 'boolean': + $success = (filter_var($value, FILTER_VALIDATE_BOOLEAN) !== false); + break; + case 'email': + $success = (filter_var($value, FILTER_VALIDATE_EMAIL) !== false); + break; + case 'phone': + $_pattern = "/^\+?\d{7,15}$/"; + $success = (!(strlen($value) == 10 && ctype_digit($value)) && !preg_match($_pattern, $value)) ? false : true; + break; + case 'zipcode': + $success = (!(strlen($value) == 5 && ctype_digit($value))) ? false : true; + break; + case 'alphanumeric': + $success = ctype_alnum($value); + break; + case 'date': + $success = preg_match('/^([0-9]{2}\/[0-9]{2}\/[0-9]{4})|([0-9]{4}-[0-9]{2}-[0-9]{2})$/', $value); + break; + case 'datetime': + $_pattern = "[0-9]{4}\-[0-9]{2}\-[0-9]{2}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}"; + $success = (preg_match($_pattern, $value)) ? true : false; + break; + case 'url': + $_pattern = "%^((?:(?:https?|ftp)://))?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$%iuS"; + $success = preg_match($_pattern, $value); + break; + case 'in_array': + $success = in_array($value, $data['values']); + break; + case 'min': + $success = min($value, $data['length']) == $value ? true : false; + break; + case 'max': + $success = max($value, $data['length']) == $value ? true : false; + break; + case 'min_length': + $success = strlen($value) >= $data['length'] ? true : false; + break; + case 'max_length': + $success = strlen($value) <= $data['length'] ? true : false; + break; + case 'identical': + $success = $value == $this->get($data['target']); + break; + // (éwé c'est un switch) + } + + if (!$success) + { + if ($nullable && empty($value)) + return true; + + $this->error($field, $name); + } + + return $success; + } + + public function rule($field, $rule, $data = []) + { + if (!isset($this->rules[$field])) + $this->rules[$field] = []; + + $this->rules[$field][] = ['rule' => $rule, 'data' => $data]; + + return $this; + } + + // Rules: multiple + + public function required() + { + foreach (func_get_args() as $field) $this->rule($field, 'required'); + return $this; + } + + public function integer() + { + foreach (func_get_args() as $field) $this->rule($field, 'integer'); + return $this; + } + + public function float() + { + foreach (func_get_args() as $field) $this->rule($field, 'float'); + return $this; + } + + public function boolean() + { + foreach (func_get_args() as $field) $this->rule($field, 'boolean'); + return $this; + } + + public function email() + { + foreach (func_get_args() as $field) $this->rule($field, 'email'); + return $this; + } + + public function phone() + { + foreach (func_get_args() as $field) $this->rule($field, 'phone'); + return $this; + } + + public function zipcode() + { + foreach (func_get_args() as $field) $this->rule($field, 'zipcode'); + return $this; + } + + public function alphanumeric() + { + foreach (func_get_args() as $field) $this->rule($field, 'alphanumeric'); + return $this; + } + + public function date() + { + foreach (func_get_args() as $field) $this->rule($field, 'date'); + return $this; + } + + public function datetime() + { + foreach (func_get_args() as $field) $this->rule($field, 'datetime'); + return $this; + } + + public function url() + { + foreach (func_get_args() as $field) $this->rule($field, 'url'); + return $this; + } + + // Rules: single + + public function requiredFile($name) + { + $this->rule($name, 'required_file'); + return $this; + } + + public function requiredFiles($name) + { + $this->rule($name, 'required_files'); + return $this; + } + + public function min($field, $length) + { + $this->rule($field, 'min', ['length' => $length]); + return $this; + } + + public function max($field, $length) + { + $this->rule($field, 'max', ['length' => $length]); + return $this; + } + + public function minLength($field, $length) + { + $this->rule($field, 'min_length', ['length' => $length]); + return $this; + } + + public function maxLength($field, $length) + { + $this->rule($field, 'max_length', ['length' => $length]); + return $this; + } + + public function inArray($field, $values) + { + $this->rule($field, 'in_array', ['values' => $values]); + return $this; + } + + public function identical($field, $field_confirm) + { + $this->rule($field_confirm, 'identical', ['target' => $field]); + return $this; + } + + // Helpers + + protected function camelize($string) + { + $string = implode('_', explode('-', $string)); + $words = array_map('ucfirst', explode('_', $string)); + return implode('', $words); + } +} diff --git a/ValidatorBundle/ValidatorBundle.php b/ValidatorBundle/ValidatorBundle.php new file mode 100644 index 0000000..08195a2 --- /dev/null +++ b/ValidatorBundle/ValidatorBundle.php @@ -0,0 +1,22 @@ +extension) + $this->extension = new ValidatorExtension(); + return $this->extension; + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..0c51c2b --- /dev/null +++ b/composer.json @@ -0,0 +1,23 @@ +{ + "name": "bluesquare-packages/symfony-validator", + "description": "Validator created by Bluesquare Computing", + "keywords": ["template", "composer", "package"], + "license": "proprietary", + "authors": [ + { + "name": "RENOU Maxime", + "email": "maxime@bluesquare.io" + } + ], + "type": "symfony-bundle", + "require": { + "php": ">=7.1", + "symfony/translation": "^4.2", + "bluesquare-packages/symfony-storage": "dev-master" + }, + "autoload": { + "psr-4": { + "Bluesquare\\ValidatorBundle\\": "ValidatorBundle/" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..374a81a --- /dev/null +++ b/composer.lock @@ -0,0 +1,220 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2c11f55a0316234fd9227351ee9d9a67", + "packages": [ + { + "name": "symfony/contracts", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2018-12-05T08:06:11+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-09-21T13:07:52+00:00" + }, + { + "name": "symfony/translation", + "version": "v4.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "23fd7aac70d99a17a8e6473a41fec8fab3331050" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/23fd7aac70d99a17a8e6473a41fec8fab3331050", + "reference": "23fd7aac70d99a17a8e6473a41fec8fab3331050", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<3.4", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" + }, + "provide": { + "symfony/translation-contracts-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/intl": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2019-01-27T23:11:39+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1" + }, + "platform-dev": [] +}