Passed
Push — main ( 09fd06...633b92 )
by Dimitri
03:33
created

Helpers::env()   C

Complexity

Conditions 17
Paths 75

Size

Total Lines 47
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 28
nc 75
nop 2
dl 0
loc 47
rs 5.2166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Utilities;
13
14
use Exception;
15
use HTMLPurifier;
0 ignored issues
show
Bug introduced by
The type HTMLPurifier was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use HTMLPurifier_Config;
0 ignored issues
show
Bug introduced by
The type HTMLPurifier_Config was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use InvalidArgumentException;
18
19
class Helpers
20
{
21
    /**
22
     * Détermine si la version actuelle de PHP est égale ou supérieure à la valeur fournie
23
     */
24
    public static function isPhp(string $version): bool
25
    {
26
        static $_is_php;
27
28
        if (! isset($_is_php[$version])) {
29
            $_is_php[$version] = version_compare(PHP_VERSION, $version, '>=');
30
        }
31
32
        return $_is_php[$version];
33
    }
34
35
    /**
36
     * Tester si une application s'exécute en local ou en ligne
37
     */
38
    public static function isOnline(): bool
39
    {
40
        $host = explode(':', $_SERVER['HTTP_HOST'] ?? '')[0];
41
42
        return
43
            ! empty($host) // Si c'est vide, ca veut certainement dire qu'on est en CLI, or le CLI << n'est pas >> utilisé en ligne
44
            && ! in_array($host, ['localhost', '127.0.0.1'], true)
45
            && ! preg_match('#\.dev$#', $host)
46
            && ! preg_match('#\.test$#', $host)
47
            && ! preg_match('#\.lab$#', $host)
48
            && ! preg_match('#\.loc(al)?$#', $host)
49
            && ! preg_match('#^192\.168#', $host);
50
    }
51
52
    /**
53
     * Tests d'inscriptibilité des fichiers
54
     *
55
     * is_writable() renvoie TRUE sur les serveurs Windows lorsque vous ne pouvez vraiment pas écrire
56
     * le fichier, basé sur l'attribut en lecture seule. is_writable() n'est pas non plus fiable
57
     * sur les serveurs Unix si safe_mode est activé.
58
     *
59
     * @see https://bugs.php.net/bug.php?id=54709
60
     *
61
     * @throws Exception
62
     * @codeCoverageIgnore Pas pratique à tester, car travis fonctionne sous linux
63
     */
64
    public static function isReallyWritable(string $file): bool
65
    {
66
        // If we're on a Unix server with safe_mode off we call is_writable
67
        if (DIRECTORY_SEPARATOR === '/' || ! ini_get('safe_mode')) {
68
            return is_writable($file);
69
        }
70
71
        /* Pour les serveurs Windows et les installations safe_mode "on", nous allons en fait
72
         * écrire un fichier puis le lire. Bah...
73
         */
74
        if (is_dir($file)) {
75
            $file = rtrim($file, '/') . '/' . bin2hex(random_bytes(16));
76
            if (($fp = @fopen($file, 'ab')) === false) {
77
                return false;
78
            }
79
80
            fclose($fp);
81
            @chmod($file, 0777);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

81
            /** @scrutinizer ignore-unhandled */ @chmod($file, 0777);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
82
            @unlink($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

82
            /** @scrutinizer ignore-unhandled */ @unlink($file);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
83
84
            return true;
85
        }
86
        if (! is_file($file) || ($fp = @fopen($file, 'ab')) === false) {
87
            return false;
88
        }
89
90
        fclose($fp);
91
92
        return true;
93
    }
94
95
    public static function cleanUrl(string $url): string
96
    {
97
        $path  = parse_url($url);
98
        $query = '';
99
100
        if (! empty($path['host'])) {
101
            $r = $path['scheme'] . '://';
102
            if (! empty($path['user'])) {
103
                $r .= $path['user'];
104
                if (! empty($path['pass'])) {
105
                    $r .= ':' . $path['pass'] . '@';
106
                }
107
                $r .= '@';
108
            }
109
            if (! empty($path['host'])) {
110
                $r .= $path['host'];
111
            }
112
            if (! empty($path['port'])) {
113
                $r .= ':' . $path['port'];
114
            }
115
            $url = $r . $path['path'];
116
            if (! empty($path['query'])) {
117
                $query = '?' . $path['query'];
118
            }
119
        }
120
        $url = str_replace('/./', '/', $url);
121
122
        while (substr_count($url, '../')) {
123
            $url = preg_replace('!/([\\w\\d]+/\\.\\.)!', '', $url);
124
        }
125
126
        return $url . $query;
127
    }
128
129
    /**
130
     * Supprimer les caractères invisibles
131
     *
132
     * Cela empêche de prendre en sandwich des caractères nuls
133
     * entre les caractères ascii, comme Java\0script.
134
     */
135
    public static function removeInvisibleCharacters(string $str, bool $url_encoded = true): string
136
    {
137
        $non_displayables = [];
138
139
        if ($url_encoded) {
140
            $non_displayables[] = '/%0[0-8bcef]/i';	// url encoded 00-08, 11, 12, 14, 15
141
            $non_displayables[] = '/%1[0-9a-f]/i';	// url encoded 16-31
142
            $non_displayables[] = '/%7f/i';	// url encoded 127
143
        }
144
145
        $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';	// 00-08, 11, 12, 14-31, 127
146
147
        do {
148
            $str = preg_replace($non_displayables, '', $str, -1, $count);
149
        } while ($count);
150
151
        return $str;
152
    }
153
154
    /**
155
     * Effectue un simple échappement automatique des données pour des raisons de sécurité.
156
     * Pourrait envisager de rendre cela plus complexe à une date ultérieure.
157
     *
158
     * Si $data est une chaîne, il suffit alors de l'échapper et de la renvoyer.
159
     * Si $data est un tableau, alors il boucle dessus, s'échappant de chaque
160
     * 'valeur' des paires clé/valeur.
161
     *
162
     * Valeurs de contexte valides : html, js, css, url, attr, raw, null
163
     *
164
     * @param array|string $data
165
     *
166
     * @throws InvalidArgumentException
167
     *
168
     * @return array|string
169
     */
170
    public static function esc($data, ?string $context = 'html', ?string $encoding = null)
171
    {
172
        if (is_array($data)) {
173
            foreach ($data as $key => &$value) {
174
                $value = self::esc($value, $context);
175
            }
176
        }
177
178
        if (is_string($data)) {
179
            $context = strtolower($context);
0 ignored issues
show
Bug introduced by
It seems like $context can also be of type null; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

179
            $context = strtolower(/** @scrutinizer ignore-type */ $context);
Loading history...
180
181
            // Fournit un moyen de NE PAS échapper aux données depuis
182
            // cela pourrait être appelé automatiquement par
183
            // la bibliothèque View.
184
            if (empty($context) || $context === 'raw') {
185
                return $data;
186
            }
187
188
            if (! in_array($context, ['html', 'js', 'css', 'url', 'attr'], true)) {
189
                throw new InvalidArgumentException('Invalid escape context provided.');
190
            }
191
192
            if ($context === 'attr') {
193
                $method = 'escapeHtmlAttr';
194
            } else {
195
                $method = 'escape' . ucfirst($context);
196
            }
197
198
            static $escaper;
199
            if (! $escaper) {
200
                $escaper = new \Laminas\Escaper\Escaper($encoding);
0 ignored issues
show
Bug introduced by
The type Laminas\Escaper\Escaper was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
201
            }
202
203
            if ($encoding && $escaper->getEncoding() !== $encoding) {
204
                $escaper = new \Laminas\Escaper\Escaper($encoding);
205
            }
206
207
            $data = $escaper->{$method}($data);
208
        }
209
210
        return $data;
211
    }
212
213
    /**
214
     * Méthode pratique pour htmlspecialchars.
215
     *
216
     * @param mixed       $text    Texte à envelopper dans htmlspecialchars. Fonctionne également avec des tableaux et des objets.
217
     *                             Les tableaux seront mappés et tous leurs éléments seront échappés. Les objets seront transtypés s'ils
218
     *                             implémenter une méthode `__toString`. Sinon, le nom de la classe sera utilisé.
219
     *                             Les autres types de scalaires seront renvoyés tels quels.
220
     * @param bool        $double  Encodez les entités html existantes.
221
     * @param string|null $charset Jeu de caractères à utiliser lors de l'échappement. La valeur par défaut est la valeur de configuration dans `mb_internal_encoding()` ou 'UTF-8'.
222
     *
223
     * @return mixed Texte enveloppé.
224
     * @credit CackePHP (https://cakephp.org)
225
     */
226
    public static function h($text, bool $double = true, ?string $charset = null)
227
    {
228
        if (is_string($text)) {
229
            // optimize for strings
230
        } elseif (is_array($text)) {
231
            $texts = [];
232
233
            foreach ($text as $k => $t) {
234
                $texts[$k] = self::h($t, $double, $charset);
235
            }
236
237
            return $texts;
238
        } elseif (is_object($text)) {
239
            if (method_exists($text, '__toString')) {
240
                $text = (string) $text;
241
            } else {
242
                $text = '(object)' . get_class($text);
243
            }
244
        } elseif ($text === null || is_scalar($text)) {
245
            return $text;
246
        }
247
248
        static $defaultCharset = false;
249
        if ($defaultCharset === false) {
250
            $defaultCharset = mb_internal_encoding();
251
            if ($defaultCharset === null) {
252
                $defaultCharset = 'UTF-8';
253
            }
254
        }
255
        if (is_string($double)) {
0 ignored issues
show
introduced by
The condition is_string($double) is always false.
Loading history...
256
            self::deprecationWarning(
257
                'Passing charset string for 2nd argument is deprecated. ' .
258
                'Use the 3rd argument instead.'
259
            );
260
            $charset = $double;
261
            $double  = true;
262
        }
263
264
        return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, $charset ?: $defaultCharset, $double);
265
    }
266
267
    /**
268
     * Garantit qu'une extension se trouve à la fin d'un nom de fichier
269
     */
270
    public static function ensureExt(string $path, string $ext = 'php'): string
271
    {
272
        if ($ext) {
273
            $ext = '.' . preg_replace('#^\.#', '', $ext);
274
275
            if (substr($path, -strlen($ext)) !== $ext) {
276
                $path .= $ext;
277
            }
278
        }
279
280
        return trim($path);
281
    }
282
283
    /**
284
     * Purifiez l'entrée à l'aide de la classe autonome HTMLPurifier.
285
     * Utilisez facilement plusieurs configurations de purificateur.
286
     *
287
     * @param string|string[] $dirty_html
288
     * @param false|string    $config
289
     *
290
     * @return string|string[]
291
     */
292
    public static function purify($dirty_html, $config = false, string $charset = 'UTF-8')
293
    {
294
        if (is_array($dirty_html)) {
295
            foreach ($dirty_html as $key => $val) {
296
                $clean_html[$key] = self::purify($val, $config);
297
            }
298
        } else {
299
            switch ($config) {
300
301
                case 'comment':
302
                    $config = HTMLPurifier_Config::createDefault();
303
                    $config->set('Core.Encoding', $charset);
304
                    $config->set('HTML.Doctype', 'XHTML 1.0 Strict');
305
                    $config->set('HTML.Allowed', 'p,a[href|title],abbr[title],acronym[title],b,strong,blockquote[cite],code,em,i,strike');
306
                    $config->set('AutoFormat.AutoParagraph', true);
307
                    $config->set('AutoFormat.Linkify', true);
308
                    $config->set('AutoFormat.RemoveEmpty', true);
309
                    break;
310
311
                case false:
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $config of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
312
                    $config = HTMLPurifier_Config::createDefault();
313
                    $config->set('Core.Encoding', $charset);
314
                    $config->set('HTML.Doctype', 'XHTML 1.0 Strict');
315
                    break;
316
317
                default:
318
                    throw new InvalidArgumentException('The HTMLPurifier configuration labeled "' . htmlspecialchars($config, ENT_QUOTES, $charset) . '" could not be found.');
319
            }
320
321
            $purifier   = new HTMLPurifier($config);
322
            $clean_html = $purifier->purify($dirty_html);
323
        }
324
325
        return $clean_html;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $clean_html does not seem to be defined for all execution paths leading up to this point.
Loading history...
326
    }
327
328
    /**
329
     * Chaîner les attributs à utiliser dans les balises HTML.
330
     *
331
     * Fonction d'assistance utilisée pour convertir une chaîne, un tableau ou un objet
332
     * d'attributs à une chaîne.
333
     *
334
     * @param array|object|string $attributes
335
     */
336
    public static function stringifyAttributes($attributes, bool $js = false): string
337
    {
338
        $atts = '';
339
340
        if (empty($attributes)) {
341
            return $atts;
342
        }
343
344
        if (is_string($attributes)) {
345
            return ' ' . $attributes;
346
        }
347
348
        $attributes = (array) $attributes;
349
350
        foreach ($attributes as $key => $val) {
351
            $atts .= ($js) ? $key . '=' . self::esc($val, 'js') . ',' : ' ' . $key . '="' . self::esc($val, 'attr') . '"';
0 ignored issues
show
Bug introduced by
Are you sure self::esc($val, 'js') of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

351
            $atts .= ($js) ? $key . '=' . /** @scrutinizer ignore-type */ self::esc($val, 'js') . ',' : ' ' . $key . '="' . self::esc($val, 'attr') . '"';
Loading history...
Bug introduced by
Are you sure self::esc($val, 'attr') of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

351
            $atts .= ($js) ? $key . '=' . self::esc($val, 'js') . ',' : ' ' . $key . '="' . /** @scrutinizer ignore-type */ self::esc($val, 'attr') . '"';
Loading history...
352
        }
353
354
        return rtrim($atts, ',');
355
    }
356
357
    /**
358
     * Obtient une variable d'environnement à partir des sources disponibles et fournit une émulation
359
     * pour les variables d'environnement non prises en charge ou incohérentes (c'est-à-dire DOCUMENT_ROOT sur
360
     * IIS, ou SCRIPT_NAME en mode CGI). Expose également quelques coutumes supplémentaires
361
     * informations sur l'environnement.
362
     *
363
     * @param string     $key     Nom de la variable d'environnement
364
     * @param mixed|null $default Spécifiez une valeur par défaut au cas où la variable d'environnement n'est pas définie.
365
     *
366
     * @return string Paramétrage des variables d'environnement.
367
     * @credit CakePHP - http://book.cakephp.org/4.0/en/core-libraries/global-constants-and-functions.html#env
368
     */
369
    public static function env(string $key, $default = null)
370
    {
371
        if ($key === 'HTTPS') {
372
            if (isset($_SERVER['HTTPS'])) {
373
                return !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
0 ignored issues
show
Bug Best Practice introduced by
The expression return ! empty($_SERVER[...RVER['HTTPS'] !== 'off' returns the type boolean which is incompatible with the documented return type string.
Loading history...
374
            }
375
376
            return strpos((string) self::env('SCRIPT_URI'), 'https://') === 0;
0 ignored issues
show
Bug Best Practice introduced by
The expression return strpos((string)se...RI'), 'https://') === 0 returns the type boolean which is incompatible with the documented return type string.
Loading history...
377
        }
378
379
        if ($key === 'SCRIPT_NAME' && self::env('CGI_MODE') && isset($_ENV['SCRIPT_URL'])) {
380
            $key = 'SCRIPT_URL';
381
        }
382
383
        $val = $_SERVER[$key] ?? $_ENV[$key] ?? null;
384
        if ($val == null && getenv($key) !== false) {
385
            $val = getenv($key);
386
        }
387
388
        if ($key === 'REMOTE_ADDR' && $val === self::env('SERVER_ADDR')) {
389
            $addr = self::env('HTTP_PC_REMOTE_ADDR');
390
            if ($addr !== null) {
391
                $val = $addr;
392
            }
393
        }
394
395
        if ($val !== null) {
396
            return $val;
397
        }
398
399
        switch ($key) {
400
            case 'DOCUMENT_ROOT':
401
                $name = (string) self::env('SCRIPT_NAME');
402
                $filename = (string) self::env('SCRIPT_FILENAME');
403
                $offset = 0;
404
                if (!strpos($name, '.php')) {
405
                    $offset = 4;
406
                }
407
408
                return substr($filename, 0, -(strlen($name) + $offset));
409
            case 'PHP_SELF':
410
                return str_replace((string) self::env('DOCUMENT_ROOT'), '', (string) self::env('SCRIPT_FILENAME'));
411
            case 'CGI_MODE':
412
                return PHP_SAPI === 'cgi';
0 ignored issues
show
Bug Best Practice introduced by
The expression return BlitzPHP\Utilities\PHP_SAPI === 'cgi' returns the type boolean which is incompatible with the documented return type string.
Loading history...
413
        }
414
415
        return $default;
416
    }
417
418
    /**
419
     * Recherche l'URL de base de l'application independamment de la configuration de l'utilisateur
420
     */
421
    public static function findBaseUrl(): string
422
    {
423
        if (isset($_SERVER['SERVER_ADDR'])) {
424
            $server_addr = $_SERVER['HTTP_HOST'] ?? ((strpos($_SERVER['SERVER_ADDR'], ':') !== false) ? '[' . $_SERVER['SERVER_ADDR'] . ']' : $_SERVER['SERVER_ADDR']);
425
426
            if (isset($_SERVER['SERVER_PORT'])) {
427
                $server_addr .= ':' . ((! preg_match('#:' . $_SERVER['SERVER_PORT'] . '$#', $server_addr)) ? $_SERVER['SERVER_PORT'] : '80');
428
            }
429
430
            if (
431
                (! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off')
432
                || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https')
433
                || (! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off')
434
            ) {
435
                $base_url = 'https';
436
            } else {
437
                $base_url = 'http';
438
            }
439
440
            $base_url .= '://' . $server_addr . dirname(substr($_SERVER['SCRIPT_NAME'], 0, strpos($_SERVER['SCRIPT_NAME'], basename($_SERVER['SCRIPT_FILENAME']))));
441
        } else {
442
            $base_url = 'http://localhost:' . ($_SERVER['SERVER_PORT'] ?? '80');
443
        }
444
445
        return $base_url;
446
    }
447
448
    /**
449
     * Jolie fonction de commodité d'impression JSON.
450
     *
451
     * Dans les terminaux, cela agira de la même manière que json_encode() avec JSON_PRETTY_PRINT directement, lorsqu'il n'est pas exécuté sur cli
452
     * enveloppera également les balises <pre> autour de la sortie de la variable donnée. Similaire à pr().
453
     *
454
     * Cette fonction renvoie la même variable qui a été transmise.
455
     *
456
     * @param mixed $var Variable à imprimer.
457
     *
458
     * @return mixed le même $var qui a été passé à cette fonction
459
     *
460
     * @see pr()
461
     */
462
    public static function pj($var)
463
    {
464
        $template = (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') ? '<pre class="pj">%s</pre>' : "\n%s\n\n";
465
        printf($template, trim(json_encode($var, JSON_PRETTY_PRINT)));
466
467
        return $var;
468
    }
469
470
    /**
471
     * Méthode d'assistance pour générer des avertissements d'obsolescence
472
     *
473
     * @param string $message    Le message à afficher comme avertissement d'obsolescence.
474
     * @param int    $stackFrame Le cadre de pile à inclure dans l'erreur. Par défaut à 1
475
     *                           car cela devrait pointer vers le code de l'application/du plugin.
476
     *
477
     * @return void
478
     */
479
    public static function deprecationWarning(string $message, int $stackFrame = 1)
480
    {
481
        if (! (error_reporting() & E_USER_DEPRECATED)) {
482
            return;
483
        }
484
485
        $trace = debug_backtrace();
486
        if (isset($trace[$stackFrame])) {
487
            $frame = $trace[$stackFrame];
488
            $frame += ['file' => '[internal]', 'line' => '??'];
489
490
            $message = sprintf(
491
                '%s - %s, line: %s' . "\n" .
492
                ' You can disable deprecation warnings by setting `Error.errorLevel` to' .
493
                ' `E_ALL & ~E_USER_DEPRECATED` in your config/app.php.',
494
                $message,
495
                $frame['file'],
496
                $frame['line']
497
            );
498
        }
499
500
        @trigger_error($message, E_USER_DEPRECATED);
501
    }
502
503
    /**
504
     * Déclenche un E_USER_WARNING.
505
     */
506
    public static function triggerWarning(string $message)
507
    {
508
        $stackFrame = 1;
509
        $trace      = debug_backtrace();
510
        if (isset($trace[$stackFrame])) {
511
            $frame = $trace[$stackFrame];
512
            $frame += ['file' => '[internal]', 'line' => '??'];
513
            $message = sprintf(
514
                '%s - %s, line: %s',
515
                $message,
516
                $frame['file'],
517
                $frame['line']
518
            );
519
        }
520
        trigger_error($message, E_USER_WARNING);
521
    }
522
523
    /**
524
     * Divise un nom de plugin de syntaxe à points en son plugin et son nom de classe.
525
     * Si $name n'a pas de point, alors l'index 0 sera nul.
526
     *
527
     * Couramment utilisé comme
528
     * ```
529
     * list($plugin, $name) = Helpers::pluginSplit($name);
530
     * ```
531
     *
532
     * @param string      $name      Le nom que vous voulez diviser en plugin.
533
     * @param bool        $dotAppend Définir sur true si vous voulez que le plugin ait un '.' qui y est annexé.
534
     * @param string|null $plugin    Plugin optionnel par défaut à utiliser si aucun plugin n'est trouvé. La valeur par défaut est nulle.
535
     *
536
     * @return array Tableau avec 2 index. 0 => nom du plugin, 1 => nom de la classe.
537
     * @credit <a href="https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pluginSplit">CakePHP</a>
538
     * @psalm-return array{string|null, string}
539
     */
540
    public static function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array
541
    {
542
        if (strpos($name, '.') !== false) {
543
            $parts = explode('.', $name, 2);
544
            if ($dotAppend) {
545
                $parts[0] .= '.';
546
            }
547
548
            /** @psalm-var array{string, string}*/
549
            return $parts;
550
        }
551
552
        return [$plugin, $name];
553
    }
554
555
    /**
556
     * Séparez l'espace de noms du nom de classe.
557
     *
558
     * Couramment utilisé comme `list($namespace, $className) = Helpers::namespaceSplit($class);`.
559
     *
560
     * @param string $class Le nom complet de la classe, ie `BlitzPHP\Core\App`.
561
     *
562
     * @return array<string> Tableau avec 2 index. 0 => namespace, 1 => nom de la classe.
563
     */
564
    public static function namespaceSplit(string $class): array
565
    {
566
        $pos = strrpos($class, '\\');
567
        if ($pos === false) {
568
            return ['', $class];
569
        }
570
571
        return [substr($class, 0, $pos), substr($class, $pos + 1)];
572
    }
573
}
574