ldc-crm-client/src/Utils.php

737 lines
22 KiB
PHP
Raw Normal View History

<?php
namespace LDC\LDCCRM;
use LDC\LDCCRM\Enums\HttpMethods;
use LDC\LDCCRM\Enums\LogLevel;
use LDC\LDCCRM\Exceptions\AccessError;
use LDC\LDCCRM\Exceptions\SkipValidationException;
use LDC\LDCCRM\Exceptions\ValidationException;
use LDC\LDCCRM\Transport\Request;
use Respect\Validation\Validator;
2023-09-05 16:40:45 +02:00
use Illuminate\Support\Str;
/**
* This is the LDCCRM utility class
*
* @category Utility
* @uses \LDC\LDCCRM\Enums\HttpMethods
* @uses \LDC\LDCCRM\Transport\Request
*
* @package LDCCrmClient
* @subpackage utilities
*/
class Utils
{
/**
* Package infos
*
* @var array
*/
2023-09-15 12:11:28 +02:00
private static array $packageInfos;
/**
* Loaded translations
*
* @var array
*/
2023-09-15 12:11:28 +02:00
private static array $translations = [];
/**
* Create a new Validator instance.
*
* @param array $data
* @param array $rules
* @return closure
* @throws \ErrorException If an unknown validation rule is used.
*/
public static function makeValidator(
2023-10-05 12:16:43 +02:00
#[\SensitiveParameter] array $data,
array $rules
): object {
foreach ($rules as $attribute => $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(
2023-09-05 16:40:45 +02:00
mixed $rule,
2023-10-05 12:16:43 +02:00
#[\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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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
*/
2023-10-05 12:16:43 +02:00
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.
*/
2023-10-05 12:16:43 +02:00
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);
2021-12-06 00:16:13 +01:00
$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");
}
2021-12-06 00:16:13 +01:00
$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]));
}
2021-12-06 00:16:13 +01:00
$route['uri'] = str_replace($match, $query[$matches[1][$k]], $route['uri']);
$matchedIndex[$matches[1][$k]] = $query[$matches[1][$k]];
}
}
2021-10-18 09:01:05 +02:00
foreach ($matchedIndex as $key => $value) {
unset($query[$key]);
}
if (count($query)) {
$route['uri'] = sprintf('%s?%s', $route['uri'], \http_build_query($query));
}
2023-09-05 16:40:45 +02:00
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
{
2023-10-05 12:16:43 +02:00
if (empty(static::$packageInfos)) {
static::$packageInfos = \json_decode(\file_get_contents(__DIR__ . '/../composer.json'), true);
}
return static::$packageInfos;
}
2021-09-14 14:23:18 +02:00
/**
* 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];
}
}
}
/**
2021-09-14 14:23:18 +02:00
* 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')) {
2023-09-05 16:40:45 +02:00
switch ($level->value) {
2023-09-15 12:11:28 +02:00
case LogLevel::EMERG->value():
\Illuminate\Support\Facades\Log::emergency($message);
break;
2023-09-15 12:11:28 +02:00
case LogLevel::ALERT->value():
\Illuminate\Support\Facades\Log::alert($message);
break;
2023-09-15 12:11:28 +02:00
case LogLevel::CRIT->value():
\Illuminate\Support\Facades\Log::critical($message);
break;
2023-09-15 12:11:28 +02:00
case LogLevel::ERR->value():
\Illuminate\Support\Facades\Log::error($message);
break;
2023-09-15 12:11:28 +02:00
case LogLevel::WARNING->value():
\Illuminate\Support\Facades\Log::warning($message);
break;
2023-09-15 12:11:28 +02:00
case LogLevel::NOTICE->value():
\Illuminate\Support\Facades\Log::notice($message);
break;
2023-09-15 12:11:28 +02:00
case LogLevel::INFO->value():
\Illuminate\Support\Facades\Log::info($message);
break;
2023-09-15 12:11:28 +02:00
case LogLevel::DEBUG->value():
\Illuminate\Support\Facades\Log::debug($message);
break;
2023-10-05 12:16:43 +02:00
} //end switch
\Illuminate\Support\Facades\Log::warning($message);
2023-09-05 16:40:45 +02:00
} //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
2023-10-05 12:16:43 +02:00
* @throws \LDC\LDCCRM\Exceptions\AccessError If trying to set a value on a unknown property.
*/
2023-10-05 12:16:43 +02:00
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);
}
}