Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.88% covered (warning)
86.88%
278 / 320
66.67% covered (warning)
66.67%
18 / 27
CRAP
0.00% covered (danger)
0.00%
0 / 1
Utils
86.88% covered (warning)
86.88%
278 / 320
66.67% covered (warning)
66.67%
18 / 27
90.41
0.00% covered (danger)
0.00%
0 / 1
 makeValidator
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
 makeValidatorFromRule
97.06% covered (success)
97.06%
33 / 34
0.00% covered (danger)
0.00%
0 / 1
10
 validationRequired
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 validationNullable
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 validationSometimes
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 validationInt
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 validationString
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 validationBool
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 validationSize
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
2.00
 validationMin
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
3.00
 validationMax
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
3.00
 validationUuid
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 validationMixedCase
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 validationContainDigit
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 validationContainSymbol
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 validationDate
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 validationDateFormat
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 validationEmail
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 validationArray
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 validationClosure
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
1.00
 getConfig
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getResquest
89.66% covered (warning)
89.66%
26 / 29
0.00% covered (danger)
0.00%
0 / 1
9.09
 getPackageInfo
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 translate
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 log
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
110
 setRawProperty
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
4.59
 getRawProperty
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2
3namespace LDC\LDCCRM;
4
5use LDC\LDCCRM\Enums\HttpMethods;
6use LDC\LDCCRM\Enums\LogLevel;
7use LDC\LDCCRM\Exceptions\AccessError;
8use LDC\LDCCRM\Exceptions\SkipValidationException;
9use LDC\LDCCRM\Exceptions\ValidationException;
10use LDC\LDCCRM\Transport\Request;
11use Respect\Validation\Validator;
12use Illuminate\Support\Str;
13
14/**
15 * This is the LDCCRM utility class
16 *
17 * @category Utility
18 * @uses \LDC\LDCCRM\Enums\HttpMethods
19 * @uses \LDC\LDCCRM\Transport\Request
20 *
21 * @package LDCCrmClient
22 * @subpackage utilities
23 */
24class Utils
25{
26    /**
27     * Package infos
28     *
29     * @var array
30     */
31    private static array $packageInfos;
32
33    /**
34     * Loaded translations
35     *
36     * @var array
37     */
38    private static array $translations = [];
39
40    /**
41     * Create a new Validator instance.
42     *
43     * @param array $data
44     * @param array $rules
45     * @return closure
46     * @throws \ErrorException If an unknown validation rule is used.
47     */
48    public static function makeValidator(
49        #[\SensitiveParameter] array $data,
50        array $rules
51    ): object {
52        foreach ($rules as $attribute => $attrRules) {
53            $validators = [];
54            if (\is_string($attrRules)) {
55                $attrRules = \explode('|', $attrRules);
56            }
57            foreach ($attrRules as $rule) {
58                $validators[] = self::makeValidatorFromRule($rule, $data, $attribute);
59            } //end foreach
60        } //end foreach
61        return function () use ($validators) {
62            foreach ($validators as $validator) {
63                try {
64                    if (!$validator['validate']()) {
65                        throw new ValidationException($validator['error']);
66                    }
67                } catch (SkipValidationException $e) {
68                    break;
69                }
70            }
71        };
72    }
73
74    /**
75     * Generate a validator
76     *
77     * @param mixed  $rule
78     * @param array  $data
79     * @param string $attribute
80     * @return array
81     * @throws \ErrorException If a rule is unknown.
82     */
83    private static function makeValidatorFromRule(
84        mixed $rule,
85        #[\SensitiveParameter] array $data,
86        string $attribute
87    ): array {
88        $rParams = [];
89        $closure = null;
90        if (\is_callable($rule) and !is_string($rule)) {
91            /** @var \Closure $closure */
92            $closure = $rule;
93            $rule    = '#closure#';
94        } elseif (\strpos($rule, ':') !== false) {
95            $rParams = \explode(':', $rule, 2);
96            $rule    = \array_shift($rParams);
97        }
98        switch (true) {
99            case in_array($rule, [
100                'nullable', 'sometimes',
101                'int', 'bool', 'uuid', 'string',
102                'date', 'email', 'array',
103                'mixedCase', 'containDigit', 'containSymbol',
104            ]):
105                return self::{sprintf('validation%s', \ucfirst($rule))}(
106                    $attribute,
107                    $data[$attribute] ?? null
108                );
109            case in_array($rule, ['size', 'min', 'max']):
110                return self::{sprintf('validation%s', \ucfirst($rule))}(
111                    $attribute,
112                    $data[$attribute],
113                    $rParams
114                );
115            case in_array($rule, ['required', 'sometimes']):
116                return self::{sprintf('validation%s', \ucfirst($rule))}(
117                    $attribute,
118                    $data,
119                );
120            case $rule === 'date_format':
121                return self::validationDateFormat($attribute, $data[$attribute], $rParams);
122            case $rule === '#closure#':
123                /** @var \Closure $rule */
124                return self::validationClosure($attribute, $data[$attribute], $closure);
125            default:
126                throw new \ErrorException("Unknown validation rule $rule " . implode(': ', $rParams));
127        } //end switch
128    }
129
130    // * VALIDATION RULES
131
132    /**
133     * Make Required validator
134     *
135     * @param string $attribute
136     * @param array  $data
137     * @return array
138     */
139    private static function validationRequired(string $attribute, #[\SensitiveParameter] array $data): array
140    {
141        return [
142            'type' => 'required',
143            'error' => sprintf(
144                '%s est obligatoire',
145                self::translate($attribute)
146            ),
147            'validate' => function () use ($attribute, $data) {
148                return isset($data[$attribute]);
149            }
150        ];
151    }
152
153    /**
154     * Make Nullable validator
155     *
156     * @param string $attribute
157     * @param mixed  $value
158     * @return array
159     */
160    private static function validationNullable(string $attribute, #[\SensitiveParameter] mixed $value): array
161    {
162        return [
163            'type' => 'nullable',
164            'error' => sprintf(
165                '%s peut Ãªtre Ã©gale Ã  nul',
166                self::translate($attribute)
167            ),
168            'validate' => function () use ($value) {
169                if (is_null($value)) {
170                    throw new SkipValidationException();
171                }
172                return true;
173            }
174        ];
175    }
176
177    /**
178     * Make Sometimes validator
179     *
180     * @param string $attribute
181     * @param mixed  $data
182     * @return array
183     */
184    private static function validationSometimes(string $attribute, #[\SensitiveParameter] mixed $data): array
185    {
186        return [
187            'type' => 'sometimes',
188            'error' => sprintf(
189                '%s est falcutatif',
190                self::translate($attribute)
191            ),
192            'validate' => function () use ($data) {
193                if (empty($data)) {
194                    throw new SkipValidationException();
195                }
196                return true;
197            }
198        ];
199    }
200
201    /**
202     * Make Int validator
203     *
204     * @param string $attribute
205     * @param mixed  $value
206     * @return array
207     */
208    private static function validationInt(string $attribute, #[\SensitiveParameter] mixed $value): array
209    {
210        return [
211            'type' => 'int',
212            'error' => sprintf(
213                '%s doit Ãªtre un entier',
214                self::translate($attribute)
215            ),
216            'validate' => function () use ($value) {
217                return \is_int($value);
218            }
219        ];
220    }
221
222    /**
223     * Make String validator
224     *
225     * @param string $attribute
226     * @param mixed  $value
227     * @return array
228     */
229    private static function validationString(string $attribute, #[\SensitiveParameter] mixed $value): array
230    {
231        return [
232            'type' => 'string',
233            'error' => sprintf(
234                '%s doit Ãªtre une chaine de charactères',
235                self::translate($attribute)
236            ),
237            'validate' => function () use ($value) {
238                return \is_string($value);
239            }
240        ];
241    }
242
243    /**
244     * Make Bool validator
245     *
246     * @param string $attribute
247     * @param mixed  $value
248     * @return array
249     */
250    private static function validationBool(string $attribute, #[\SensitiveParameter] mixed $value): array
251    {
252        return [
253            'type' => 'bool',
254            'error' => sprintf(
255                '%s doit Ãªtre un booléen',
256                self::translate($attribute)
257            ),
258            'validate' => function () use ($value) {
259                return \is_bool($value) or $value === 1 or $value === 0;
260            }
261        ];
262    }
263
264    /**
265     * Make Size validator
266     *
267     * @param string $attribute
268     * @param mixed  $value
269     * @param array  $params
270     * @return array
271     */
272    private static function validationSize(string $attribute, #[\SensitiveParameter] mixed $value, array $params): array
273    {
274        return [
275            'type' => 'min',
276            'error' => sprintf(
277                '%s doit avoir %s caratères',
278                self::translate($attribute),
279                $params[0],
280                $params[0]
281            ),
282            'validate' => function () use ($value, $params) {
283                if (\is_string($value)) {
284                    return \strlen($value) === (int)$params[0];
285                }
286                return false;
287            }
288        ];
289    }
290
291    /**
292     * Make Min validator
293     *
294     * @param string $attribute
295     * @param mixed  $value
296     * @param array  $params
297     * @return array
298     */
299    private static function validationMin(string $attribute, #[\SensitiveParameter] mixed $value, array $params): array
300    {
301        return [
302            'type' => 'min',
303            'error' => sprintf(
304                '%s doit avoir au moins %s caratères, ou Ãªtre plus grand que %s si c\'est un nombre',
305                self::translate($attribute),
306                $params[0],
307                $params[0]
308            ),
309            'validate' => function () use ($value, $params) {
310                if (\is_string($value)) {
311                    return \strlen($value) >= (int)$params[0];
312                }
313                if (\is_int($value)) {
314                    return $value >= (int)$params[0];
315                }
316                return false;
317            }
318        ];
319    }
320
321    /**
322     * Make Max validator
323     *
324     * @param string $attribute
325     * @param mixed  $value
326     * @param array  $params
327     * @return array
328     */
329    private static function validationMax(string $attribute, #[\SensitiveParameter] mixed $value, array $params): array
330    {
331        return [
332            'type' => 'max',
333            'error' => sprintf(
334                '%s doit avoir au moins %s caratères, ou Ãªtre plus grand que %s si c\'est un nombre',
335                self::translate($attribute),
336                $params[0],
337                $params[0]
338            ),
339            'validate' => function () use ($value, $params) {
340                if (\is_string($value)) {
341                    return \strlen($value) <= (int)$params[0];
342                }
343                if (\is_int($value)) {
344                    return $value <= (int)$params[0];
345                }
346                return false;
347            }
348        ];
349    }
350
351    /**
352     * Make Uuid validator
353     *
354     * @param string $attribute
355     * @param mixed  $value
356     * @return array
357     */
358    private static function validationUuid(string $attribute, #[\SensitiveParameter] mixed $value): array
359    {
360        return [
361            'type' => 'uuid',
362            'error' => sprintf(
363                '%s doit Ãªtre un uuid valide',
364                self::translate($attribute)
365            ),
366            'validate' => function () use ($value) {
367                return Validator::uuid()->validate($value);
368            }
369        ];
370    }
371
372    /**
373     * Make MixedCase validator
374     *
375     * @param string $attribute
376     * @param mixed  $value
377     * @return array
378     */
379    private static function validationMixedCase(string $attribute, #[\SensitiveParameter] mixed $value): array
380    {
381        return [
382            'type' => 'mixedCase',
383            'error' => sprintf(
384                '%s doit contenir des lettre minuscules et majuscules',
385                self::translate($attribute)
386            ),
387            'validate' => function () use ($value) {
388                return \preg_match('/[a-z]/', $value) and \preg_match('/[A-Z]/', $value);
389            }
390        ];
391    }
392
393    /**
394     * Make ContainDigit validator
395     *
396     * @param string $attribute
397     * @param mixed  $value
398     * @return array
399     */
400    private static function validationContainDigit(string $attribute, #[\SensitiveParameter] mixed $value): array
401    {
402        return [
403            'type' => 'containDigit',
404            'error' => sprintf(
405                '%s doit contenir au moins un chiffre',
406                self::translate($attribute)
407            ),
408            'validate' => function () use ($value) {
409                return \preg_match('/[0-9]/', $value);
410            }
411        ];
412    }
413
414    /**
415     * Make ContainSymbol validator
416     *
417     * @param string $attribute
418     * @param mixed  $value
419     * @return array
420     */
421    private static function validationContainSymbol(string $attribute, #[\SensitiveParameter] mixed $value): array
422    {
423        return [
424            'type' => 'containSymbol',
425            'error' => sprintf(
426                '%s doit contenir au moins un caractère spécial comme une virgule par exemple',
427                self::translate($attribute)
428            ),
429            'validate' => function () use ($value) {
430                return \preg_match('/[^a-zA-Z0-9]/', $value);
431            }
432        ];
433    }
434
435    /**
436     * Make Date validator
437     *
438     * @param string $attribute
439     * @param mixed  $value
440     * @return array
441     */
442    private static function validationDate(string $attribute, #[\SensitiveParameter] mixed $value): array
443    {
444        return [
445            'type' => 'date',
446            'error' => sprintf('%s doit Ãªtre une date valide', self::translate($attribute)),
447            'validate' => function () use ($value) {
448                return Validator::dateTime()->validate($value);
449            }
450        ];
451    }
452
453    /**
454     * Make DateFormat validator
455     *
456     * @param string $attribute
457     * @param mixed  $value
458     * @param array  $params
459     * @return array
460     */
461    private static function validationDateFormat(
462        string $attribute,
463        #[\SensitiveParameter] mixed $value,
464        array $params
465    ): array {
466        return [
467            'type' => 'date_format',
468            'error' => sprintf('%s doit Ãªtre une date au format %s', self::translate($attribute), $params[0]),
469            'validate' => function () use ($value, $params) {
470                return Validator::dateTime($params[0])->validate($value);
471            }
472        ];
473    }
474
475    /**
476     * Make Email validator
477     *
478     * @param string $attribute
479     * @param mixed  $value
480     * @return array
481     */
482    private static function validationEmail(string $attribute, #[\SensitiveParameter] mixed $value): array
483    {
484        return [
485            'type' => 'email',
486            'error' => sprintf('%s doit Ãªtre un email valide', self::translate($attribute)),
487            'validate' => function () use ($value) {
488                return Validator::email()->validate($value);
489            }
490        ];
491    }
492
493    /**
494     * Make Array validator
495     *
496     * @param string $attribute
497     * @param mixed  $value
498     * @return array
499     */
500    private static function validationArray(string $attribute, #[\SensitiveParameter] mixed $value): array
501    {
502        return [
503            'type' => 'array',
504            'error' => sprintf('%s doit Ãªtre un tableau valide', self::translate($attribute)),
505            'validate' => function () use ($value) {
506                return Validator::arrayType()->validate($value);
507            }
508        ];
509    }
510
511    /**
512     * Make Closure validator
513     *
514     * @param string   $attribute
515     * @param mixed    $value
516     * @param \Closure $closure
517     * @return array
518     */
519    private static function validationClosure(
520        string $attribute,
521        #[\SensitiveParameter] mixed $value,
522        \Closure $closure
523    ): array {
524        return [
525            'type' => 'closure',
526            'error' => sprintf('%s doit Ãªtre validé', self::translate($attribute)),
527            'validate' => function () use ($attribute, $value, $closure) {
528                $closure($attribute, $value, function ($message) {
529                    throw new ValidationException($message);
530                });
531                return true;
532            }
533        ];
534    }
535
536    // * END VALIDATION RULES
537
538    /**
539     * This is only used for unit tests.
540     * Get config
541     *
542     * @param string $key
543     * @param string $default
544     * @return string|null
545     */
546    public static function getConfig(string $key, string $default = null): ?string
547    {
548        $value = null;
549        if (function_exists('config')) {
550            $value = \config($key, $default);
551        }
552
553        return $value;
554    }
555
556    /**
557     * Generate a Request from the route name
558     *
559     * @param string $routeName
560     * @param array  $query     Params to pass on to the route.
561     * @param array  $data      Http body params.
562     * @return \LDC\LDCCRM\Transport\Request
563     * @throws \ErrorException If the requested route is unknown.
564     * @throws \ErrorException If a required parameter is missing.
565     */
566    public static function getResquest(
567        string $routeName,
568        array $query = [],
569        #[\SensitiveParameter] array $data = []
570    ): Request {
571        // Convert bool to integer.
572        $query = \array_map(function ($v) {
573            return is_bool($v) ? (int)$v : $v;
574        }, $query);
575        $data  = \array_map(function ($v) {
576            return is_bool($v) ? (int)$v : $v;
577        }, $data);
578
579        $routes = \json_decode(\file_get_contents(__DIR__ . '/routes.json'), true);
580        if (!isset($routes[$routeName])) {
581            throw new \ErrorException("Unknown route $routeName from the crm");
582        }
583        $route        = $routes[$routeName];
584        $matches      = [];
585        $matchedIndex = [];
586        \preg_match_all('/{([\w-]*)}/', $route['uri'], $matches);
587        if (\count($matches[0])) {
588            foreach ($matches[0] as $k => $match) {
589                if (!isset($query[$matches[1][$k]])) {
590                    throw new \ErrorException(sprintf('Missing required parameter %s', $matches[1][$k]));
591                }
592                $route['uri']                  = str_replace($match, $query[$matches[1][$k]], $route['uri']);
593                $matchedIndex[$matches[1][$k]] = $query[$matches[1][$k]];
594            }
595        }
596        foreach ($matchedIndex as $key => $value) {
597            unset($query[$key]);
598        }
599        if (count($query)) {
600            $route['uri'] = sprintf('%s?%s', $route['uri'], \http_build_query($query));
601        }
602        return new Request(
603            HttpMethods::make(Str::lower($route['methods'][0]), 'name'),
604            $route['uri'],
605            [],
606            $data
607        );
608    }
609
610    /**
611     * Get composer.json infos
612     *
613     * @return array
614     */
615    public static function getPackageInfo(): array
616    {
617        if (empty(static::$packageInfos)) {
618            static::$packageInfos = \json_decode(\file_get_contents(__DIR__ . '/../composer.json'), true);
619        }
620        return static::$packageInfos;
621    }
622
623    /**
624     * Translation a string using php file lang
625     *
626     * @param string $key
627     * @param string $lang
628     * @return string
629     */
630    public static function translate(string $key, string $lang = 'fr'): string
631    {
632        try {
633            if (!array_key_exists($lang, self::$translations)) {
634                self::$translations = require __DIR__ . "/../resources/lang/$lang.php";
635            }
636        } finally {
637            if (!isset(self::$translations['validation'][$key])) {
638                return $key;
639            } else {
640                return self::$translations['validation'][$key];
641            }
642        }
643    }
644
645    /**
646     * Write log using syslog
647     * It also write log using \Illuminate\Support\Facades\Log
648     * if it is present
649     *
650     * @param string                     $message The message is prefixed with "LDC CRM:" .
651     * @param \LDC\LDCCRM\Enums\LogLevel $level   The log level to apply.
652     * @return void
653     */
654    public static function log(string $message, LogLevel $level)
655    {
656        $message = "LDC CRM: $message";
657        if (\class_exists('\Illuminate\Support\Facades\Log')) {
658            switch ($level->value) {
659                case LogLevel::EMERG->value():
660                    \Illuminate\Support\Facades\Log::emergency($message);
661                    break;
662                case LogLevel::ALERT->value():
663                    \Illuminate\Support\Facades\Log::alert($message);
664                    break;
665                case LogLevel::CRIT->value():
666                    \Illuminate\Support\Facades\Log::critical($message);
667                    break;
668                case LogLevel::ERR->value():
669                    \Illuminate\Support\Facades\Log::error($message);
670                    break;
671                case LogLevel::WARNING->value():
672                    \Illuminate\Support\Facades\Log::warning($message);
673                    break;
674                case LogLevel::NOTICE->value():
675                    \Illuminate\Support\Facades\Log::notice($message);
676                    break;
677                case LogLevel::INFO->value():
678                    \Illuminate\Support\Facades\Log::info($message);
679                    break;
680                case LogLevel::DEBUG->value():
681                    \Illuminate\Support\Facades\Log::debug($message);
682                    break;
683            } //end switch
684            \Illuminate\Support\Facades\Log::warning($message);
685        } //end if
686        \syslog(\constant($level->value), $message);
687    }
688
689    /**
690     * Set a property without any validation.
691     *
692     * ! Warning : this can change any property of the object, use with caution.
693     *
694     * @param object $object   The object to inspect.
695     * @param string $property The object property to assign to.
696     * @param mixed  $value    The value to insert.
697     * @return void
698     * @throws \LDC\LDCCRM\Exceptions\AccessError If trying to set a value on a unknown property.
699     */
700    public static function setRawProperty(object $object, string $property, #[\SensitiveParameter] mixed $value): void
701    {
702        if (is_object($value) and in_array(BaseEnum::class, class_parents($value))) {
703            $value = $value->value;
704        }
705        $objectName = \get_class($object);
706        $refClass   = new \ReflectionClass($objectName);
707        try {
708            $refProp = $refClass->getProperty($property);
709        } catch (\ReflectionException $e) {
710            throw new AccessError($objectName, $property);
711        }
712        $refProp->setAccessible(true);
713        $refProp->setValue($object, $value);
714    }
715
716    /**
717     * Get a property of an object.
718     *
719     * ! Warning : this will return any property of the object, use with caution.
720     *
721     * @param object $object   The object to inspect.
722     * @param string $property The object property to inspect.
723     * @return mixed|null Returns null if the property was not found any way.
724     */
725    public static function getRawProperty(object $object, string $property)
726    {
727        $refClass = new \ReflectionClass(\get_class($object));
728        try {
729            $refProp = $refClass->getProperty($property);
730        } catch (\ReflectionException $e) {
731            return null;
732        }
733        $refProp->setAccessible(true);
734        return $refProp->getValue($object);
735    }
736}