$attrRules) { $validators = []; if (\is_string($attrRules)) { $attrRules = \explode('|', $attrRules); } foreach ($attrRules as $rule) { $validators[] = self::makeValidatorFromRule($rule, $data, $attribute); } //end foreach } //end foreach return function () use ($validators) { foreach ($validators as $validator) { try { if (!$validator['validate']()) { throw new ValidationException($validator['error']); } } catch (SkipValidationException $e) { break; } } }; } /** * Generate a validator * * @param mixed $rule * @param array $data * @param string $attribute * @return array * @throws \ErrorException If a rule is unknown. */ private static function makeValidatorFromRule( mixed $rule, #[\SensitiveParameter] array $data, string $attribute ): array { $rParams = []; $closure = null; if (\is_callable($rule) and !is_string($rule)) { /** @var \Closure $closure */ $closure = $rule; $rule = '#closure#'; } elseif (\strpos($rule, ':') !== false) { $rParams = \explode(':', $rule, 2); $rule = \array_shift($rParams); } switch (true) { case in_array($rule, [ 'nullable', 'sometimes', 'int', 'bool', 'uuid', 'string', 'date', 'email', 'array', 'mixedCase', 'containDigit', 'containSymbol', ]): return self::{sprintf('validation%s', \ucfirst($rule))}( $attribute, $data[$attribute] ?? null ); case in_array($rule, ['size', 'min', 'max']): return self::{sprintf('validation%s', \ucfirst($rule))}( $attribute, $data[$attribute], $rParams ); case in_array($rule, ['required', 'sometimes']): return self::{sprintf('validation%s', \ucfirst($rule))}( $attribute, $data, ); case $rule === 'date_format': return self::validationDateFormat($attribute, $data[$attribute], $rParams); case $rule === '#closure#': /** @var \Closure $rule */ return self::validationClosure($attribute, $data[$attribute], $closure); default: throw new \ErrorException("Unknown validation rule $rule " . implode(': ', $rParams)); } //end switch } // * VALIDATION RULES /** * Make Required validator * * @param string $attribute * @param array $data * @return array */ private static function validationRequired(string $attribute, #[\SensitiveParameter] array $data): array { return [ 'type' => 'required', 'error' => sprintf( '%s est obligatoire', self::translate($attribute) ), 'validate' => function () use ($attribute, $data) { return isset($data[$attribute]); } ]; } /** * Make Nullable validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationNullable(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'nullable', 'error' => sprintf( '%s peut être égale à nul', self::translate($attribute) ), 'validate' => function () use ($value) { if (is_null($value)) { throw new SkipValidationException(); } return true; } ]; } /** * Make Sometimes validator * * @param string $attribute * @param mixed $data * @return array */ private static function validationSometimes(string $attribute, #[\SensitiveParameter] mixed $data): array { return [ 'type' => 'sometimes', 'error' => sprintf( '%s est falcutatif', self::translate($attribute) ), 'validate' => function () use ($data) { if (empty($data)) { throw new SkipValidationException(); } return true; } ]; } /** * Make Int validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationInt(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'int', 'error' => sprintf( '%s doit être un entier', self::translate($attribute) ), 'validate' => function () use ($value) { return \is_int($value); } ]; } /** * Make String validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationString(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'string', 'error' => sprintf( '%s doit être une chaine de charactères', self::translate($attribute) ), 'validate' => function () use ($value) { return \is_string($value); } ]; } /** * Make Bool validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationBool(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'bool', 'error' => sprintf( '%s doit être un booléen', self::translate($attribute) ), 'validate' => function () use ($value) { return \is_bool($value) or $value === 1 or $value === 0; } ]; } /** * Make Size validator * * @param string $attribute * @param mixed $value * @param array $params * @return array */ private static function validationSize(string $attribute, #[\SensitiveParameter] mixed $value, array $params): array { return [ 'type' => 'min', 'error' => sprintf( '%s doit avoir %s caratères', self::translate($attribute), $params[0], $params[0] ), 'validate' => function () use ($value, $params) { if (\is_string($value)) { return \strlen($value) === (int)$params[0]; } return false; } ]; } /** * Make Min validator * * @param string $attribute * @param mixed $value * @param array $params * @return array */ private static function validationMin(string $attribute, #[\SensitiveParameter] mixed $value, array $params): array { return [ 'type' => 'min', 'error' => sprintf( '%s doit avoir au moins %s caratères, ou être plus grand que %s si c\'est un nombre', self::translate($attribute), $params[0], $params[0] ), 'validate' => function () use ($value, $params) { if (\is_string($value)) { return \strlen($value) >= (int)$params[0]; } if (\is_int($value)) { return $value >= (int)$params[0]; } return false; } ]; } /** * Make Max validator * * @param string $attribute * @param mixed $value * @param array $params * @return array */ private static function validationMax(string $attribute, #[\SensitiveParameter] mixed $value, array $params): array { return [ 'type' => 'max', 'error' => sprintf( '%s doit avoir au moins %s caratères, ou être plus grand que %s si c\'est un nombre', self::translate($attribute), $params[0], $params[0] ), 'validate' => function () use ($value, $params) { if (\is_string($value)) { return \strlen($value) <= (int)$params[0]; } if (\is_int($value)) { return $value <= (int)$params[0]; } return false; } ]; } /** * Make Uuid validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationUuid(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'uuid', 'error' => sprintf( '%s doit être un uuid valide', self::translate($attribute) ), 'validate' => function () use ($value) { return Validator::uuid()->validate($value); } ]; } /** * Make MixedCase validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationMixedCase(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'mixedCase', 'error' => sprintf( '%s doit contenir des lettre minuscules et majuscules', self::translate($attribute) ), 'validate' => function () use ($value) { return \preg_match('/[a-z]/', $value) and \preg_match('/[A-Z]/', $value); } ]; } /** * Make ContainDigit validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationContainDigit(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'containDigit', 'error' => sprintf( '%s doit contenir au moins un chiffre', self::translate($attribute) ), 'validate' => function () use ($value) { return \preg_match('/[0-9]/', $value); } ]; } /** * Make ContainSymbol validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationContainSymbol(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'containSymbol', 'error' => sprintf( '%s doit contenir au moins un caractère spécial comme une virgule par exemple', self::translate($attribute) ), 'validate' => function () use ($value) { return \preg_match('/[^a-zA-Z0-9]/', $value); } ]; } /** * Make Date validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationDate(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'date', 'error' => sprintf('%s doit être une date valide', self::translate($attribute)), 'validate' => function () use ($value) { return Validator::dateTime()->validate($value); } ]; } /** * Make DateFormat validator * * @param string $attribute * @param mixed $value * @param array $params * @return array */ private static function validationDateFormat( string $attribute, #[\SensitiveParameter] mixed $value, array $params ): array { return [ 'type' => 'date_format', 'error' => sprintf('%s doit être une date au format %s', self::translate($attribute), $params[0]), 'validate' => function () use ($value, $params) { return Validator::dateTime($params[0])->validate($value); } ]; } /** * Make Email validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationEmail(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'email', 'error' => sprintf('%s doit être un email valide', self::translate($attribute)), 'validate' => function () use ($value) { return Validator::email()->validate($value); } ]; } /** * Make Array validator * * @param string $attribute * @param mixed $value * @return array */ private static function validationArray(string $attribute, #[\SensitiveParameter] mixed $value): array { return [ 'type' => 'array', 'error' => sprintf('%s doit être un tableau valide', self::translate($attribute)), 'validate' => function () use ($value) { return Validator::arrayType()->validate($value); } ]; } /** * Make Closure validator * * @param string $attribute * @param mixed $value * @param \Closure $closure * @return array */ private static function validationClosure( string $attribute, #[\SensitiveParameter] mixed $value, \Closure $closure ): array { return [ 'type' => 'closure', 'error' => sprintf('%s doit être validé', self::translate($attribute)), 'validate' => function () use ($attribute, $value, $closure) { $closure($attribute, $value, function ($message) { throw new ValidationException($message); }); return true; } ]; } // * END VALIDATION RULES /** * This is only used for unit tests. * Get config * * @param string $key * @param string $default * @return string|null */ public static function getConfig(string $key, string $default = null): ?string { $value = null; if (function_exists('config')) { $value = \config($key, $default); } return $value; } /** * Generate a Request from the route name * * @param string $routeName * @param array $query Params to pass on to the route. * @param array $data Http body params. * @return \LDC\LDCCRM\Transport\Request * @throws \ErrorException If the requested route is unknown. * @throws \ErrorException If a required parameter is missing. */ public static function getResquest( string $routeName, array $query = [], #[\SensitiveParameter] array $data = [] ): Request { // Convert bool to integer. $query = \array_map(function ($v) { return is_bool($v) ? (int)$v : $v; }, $query); $data = \array_map(function ($v) { return is_bool($v) ? (int)$v : $v; }, $data); $routes = \json_decode(\file_get_contents(__DIR__ . '/routes.json'), true); if (!isset($routes[$routeName])) { throw new \ErrorException("Unknown route $routeName from the crm"); } $route = $routes[$routeName]; $matches = []; $matchedIndex = []; \preg_match_all('/{([\w-]*)}/', $route['uri'], $matches); if (\count($matches[0])) { foreach ($matches[0] as $k => $match) { if (!isset($query[$matches[1][$k]])) { throw new \ErrorException(sprintf('Missing required parameter %s', $matches[1][$k])); } $route['uri'] = str_replace($match, $query[$matches[1][$k]], $route['uri']); $matchedIndex[$matches[1][$k]] = $query[$matches[1][$k]]; } } foreach ($matchedIndex as $key => $value) { unset($query[$key]); } if (count($query)) { $route['uri'] = sprintf('%s?%s', $route['uri'], \http_build_query($query)); } return new Request( HttpMethods::make(Str::lower($route['methods'][0]), 'name'), $route['uri'], [], $data ); } /** * Get composer.json infos * * @return array */ public static function getPackageInfo(): array { if (empty(static::$packageInfos)) { static::$packageInfos = \json_decode(\file_get_contents(__DIR__ . '/../composer.json'), true); } return static::$packageInfos; } /** * Translation a string using php file lang * * @param string $key * @param string $lang * @return string */ public static function translate(string $key, string $lang = 'fr'): string { try { if (!array_key_exists($lang, self::$translations)) { self::$translations = require __DIR__ . "/../resources/lang/$lang.php"; } } finally { if (!isset(self::$translations['validation'][$key])) { return $key; } else { return self::$translations['validation'][$key]; } } } /** * Write log using syslog * It also write log using \Illuminate\Support\Facades\Log * if it is present * * @param string $message The message is prefixed with "LDC CRM:" . * @param \LDC\LDCCRM\Enums\LogLevel $level The log level to apply. * @return void */ public static function log(string $message, LogLevel $level) { $message = "LDC CRM: $message"; if (\class_exists('\Illuminate\Support\Facades\Log')) { switch ($level->value) { case LogLevel::EMERG->value(): \Illuminate\Support\Facades\Log::emergency($message); break; case LogLevel::ALERT->value(): \Illuminate\Support\Facades\Log::alert($message); break; case LogLevel::CRIT->value(): \Illuminate\Support\Facades\Log::critical($message); break; case LogLevel::ERR->value(): \Illuminate\Support\Facades\Log::error($message); break; case LogLevel::WARNING->value(): \Illuminate\Support\Facades\Log::warning($message); break; case LogLevel::NOTICE->value(): \Illuminate\Support\Facades\Log::notice($message); break; case LogLevel::INFO->value(): \Illuminate\Support\Facades\Log::info($message); break; case LogLevel::DEBUG->value(): \Illuminate\Support\Facades\Log::debug($message); break; } //end switch \Illuminate\Support\Facades\Log::warning($message); } //end if \syslog(\constant($level->value), $message); } /** * Set a property without any validation. * * ! Warning : this can change any property of the object, use with caution. * * @param object $object The object to inspect. * @param string $property The object property to assign to. * @param mixed $value The value to insert. * @return void * @throws \LDC\LDCCRM\Exceptions\AccessError If trying to set a value on a unknown property. */ public static function setRawProperty(object $object, string $property, #[\SensitiveParameter] mixed $value): void { if (is_object($value) and in_array(BaseEnum::class, class_parents($value))) { $value = $value->value; } $objectName = \get_class($object); $refClass = new \ReflectionClass($objectName); try { $refProp = $refClass->getProperty($property); } catch (\ReflectionException $e) { throw new AccessError($objectName, $property); } $refProp->setAccessible(true); $refProp->setValue($object, $value); } /** * Get a property of an object. * * ! Warning : this will return any property of the object, use with caution. * * @param object $object The object to inspect. * @param string $property The object property to inspect. * @return mixed|null Returns null if the property was not found any way. */ public static function getRawProperty(object $object, string $property) { $refClass = new \ReflectionClass(\get_class($object)); try { $refProp = $refClass->getProperty($property); } catch (\ReflectionException $e) { return null; } $refProp->setAccessible(true); return $refProp->getValue($object); } }