Test Failed
Push — main ( e1affe...2e6d72 )
by Dimitri
14:06
created

NativeAdapter::addCss()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 4
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 9
ccs 0
cts 2
cp 0
crap 12
rs 10
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\View\Adapters;
13
14
use BlitzPHP\Debug\Toolbar\Collectors\ViewsCollector;
15
use BlitzPHP\Utilities\Helpers;
16
use RuntimeException;
17
18
/**
19
 * Class View
20
 */
21
class NativeAdapter extends AbstractAdapter
22
{
23
    /**
24
     * {@inheritDoc}
25
     */
26
    protected string $ext = 'php';
27
28
    /**
29
     * Fusionner les données enregistrées et les données utilisateur
30
     */
31
    protected $tempData;
32
33
    /**
34
     * Indique si les données doivent être enregistrées entre les rendus.
35
     *
36
     * @var bool
37
     */
38
    protected $saveData;
39
40
    /**
41
     * Nombre de vues chargées
42
     */
43
    protected int $viewsCount = 0;
44
45
    /**
46
     * Contient les sections et leurs données.
47
     */
48
    protected array $sections = [];
49
50
    /**
51
     * Le nom de la section actuelle en cours de rendu, le cas échéant.
52
     *
53
     * @var string[]
54
     */
55
    protected array $sectionStack = [];
56
57
    /**
58
     * Contient les css charges a partie de la section actuelle en cours de rendu
59
     */
60
    protected array $_styles = [];
61
62
    /**
63
     * Contient les librairies css charges a partie de la section actuelle en cours de rendu
64
     */
65
    protected array $_lib_styles = [];
66
67
    /**
68
     * Contient les scripts js charges a partie de la section actuelle en cours de rendu
69
     */
70
    protected array $_scripts = [];
71
72
    /**
73
     * Contient les scripts js des librairies charges a partie de la section actuelle en cours de rendu
74
     */
75
    protected array $_lib_scripts = [];
76
77
    /**
78
     * {@inheritDoc}
79
     */
80
    public function __construct(protected array $config, $viewPathLocator = null, protected bool $debug = BLITZ_DEBUG)
81
    {
82 4
        parent::__construct($config, $viewPathLocator, $debug);
83
84 4
        $this->saveData = (bool) ($config['save_data'] ?? true);
85
    }
86
87
    /**
88
     * {@inheritDoc}
89
     */
90
    public function render(string $view, ?array $options = null, ?bool $saveData = null): string
91
    {
92 2
        $this->renderVars['start']   = microtime(true);
93 2
        $this->renderVars['view']    = $view;
94 2
        $this->renderVars['options'] = $options ?? [];
95
96
        // Stocke les résultats ici donc même si
97
        // plusieurs vues sont appelées dans une vue, ce ne sera pas le cas
98
        // nettoyez-le sauf si nous le voulons.
99 2
        $saveData ??= $this->saveData;
100
101
        // A-t-il été mis en cache ?
102
        if (isset($this->renderVars['options']['cache'])) {
103 2
            $cacheName = $this->renderVars['options']['cache_name'] ?? str_replace('.' . $this->ext, '', $this->renderVars['view']);
104
            $cacheName = str_replace(['\\', '/'], '', $cacheName);
105
106
            $this->renderVars['cacheName'] = $cacheName;
107
108
            if ($output = cache($this->renderVars['cacheName'])) {
109
                $this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
0 ignored issues
show
Bug introduced by
It seems like $this->renderVars['start'] can also be of type string; however, parameter $start of BlitzPHP\View\Adapters\A...apter::logPerformance() does only seem to accept double, 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

109
                $this->logPerformance(/** @scrutinizer ignore-type */ $this->renderVars['start'], microtime(true), $this->renderVars['view']);
Loading history...
Bug introduced by
It seems like microtime(true) can also be of type string; however, parameter $end of BlitzPHP\View\Adapters\A...apter::logPerformance() does only seem to accept double, 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

109
                $this->logPerformance($this->renderVars['start'], /** @scrutinizer ignore-type */ microtime(true), $this->renderVars['view']);
Loading history...
110
111
                return $output;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $output returns the type BlitzPHP\Cache\Cache|true which is incompatible with the type-hinted return string.
Loading history...
112
            }
113
        }
114
115 2
        $this->renderVars['file'] = $this->getRenderedFile($this->renderVars['options'], $this->renderVars['view'], $this->ext);
116
117
        // Rendre nos données de vue disponibles pour la vue.
118 2
        $this->prepareTemplateData($saveData);
119
120
        // Enregistrer les variables actuelles
121 2
        $renderVars = $this->renderVars;
122
123
        $output = (function (): string {
124 2
            extract($this->tempData);
125 2
            ob_start();
126 2
            include $this->renderVars['file'];
127
128 2
            return ob_get_clean() ?: '';
129 2
        })();
130
131
        // Lors de l'utilisation de mises en page, les données ont déjà été stockées
132
        // dans $this->sections, et aucune autre sortie valide
133
        // est autorisé dans $output donc nous allons l'écraser.
134
        if ($this->layout !== null && $this->sectionStack === []) {
135
            $layoutView   = $this->layout;
136
            $this->layout = null;
137
            // Enregistrer les variables actuelles
138
            $renderVars = $this->renderVars;
139
            $output     = $this->render($layoutView, array_merge($options ?? [], ['viewPath' => LAYOUT_PATH]), $saveData);
140
        }
141
142 2
        $this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
143
144
        if (($this->debug && (! isset($options['debug']) || $options['debug'] === true))) {
145
            if (in_array(ViewsCollector::class, config('toolbar.collectors'), true)) {
0 ignored issues
show
Bug introduced by
config('toolbar.collectors') of type BlitzPHP\Config\Config|null is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

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

145
            if (in_array(ViewsCollector::class, /** @scrutinizer ignore-type */ config('toolbar.collectors'), true)) {
Loading history...
146
                // Nettoyer nos noms de chemins pour les rendre un peu plus propres
147
                $this->renderVars['file'] = clean_path($this->renderVars['file']);
148
                $this->renderVars['file'] = ++$this->viewsCount . ' ' . $this->renderVars['file'];
149
150
                $output = '<!-- DEBUG-VIEW START ' . $this->renderVars['file'] . ' -->' . PHP_EOL
151
                    . $output . PHP_EOL
152
                    . '<!-- DEBUG-VIEW ENDED ' . $this->renderVars['file'] . ' -->' . PHP_EOL;
153
            }
154
        }
155
156 2
        $output = $this->decorate($output);
157
158
        // Faut-il mettre en cache ?
159
        if (isset($this->renderVars['options']['cache'])) {
160 2
            cache()->write($this->renderVars['cacheName'], $output, (int) $this->renderVars['options']['cache']);
161
        }
162
163 2
        $this->tempData = null;
164
165
        // Récupère les variables actuelles
166 2
        $this->renderVars = $renderVars;
167
168 2
        return $output;
169
    }
170
171
    /**
172
     * {@inheritDoc}
173
     */
174
    public function renderString(string $view, ?array $options = null, ?bool $saveData = null): string
175
    {
176
        $start = microtime(true);
177
        $saveData ??= $this->saveData;
178
        $this->prepareTemplateData($saveData);
179
180
        $output = (function (string $view): string {
181
            extract($this->tempData);
182
            ob_start();
183
            eval('?>' . $view);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
184
185
            return ob_get_clean() ?: '';
186
        })($view);
187
188
        $this->logPerformance($start, microtime(true), $this->excerpt($view));
0 ignored issues
show
Bug introduced by
It seems like $start can also be of type string; however, parameter $start of BlitzPHP\View\Adapters\A...apter::logPerformance() does only seem to accept double, 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

188
        $this->logPerformance(/** @scrutinizer ignore-type */ $start, microtime(true), $this->excerpt($view));
Loading history...
Bug introduced by
It seems like microtime(true) can also be of type string; however, parameter $end of BlitzPHP\View\Adapters\A...apter::logPerformance() does only seem to accept double, 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

188
        $this->logPerformance($start, /** @scrutinizer ignore-type */ microtime(true), $this->excerpt($view));
Loading history...
189
        $this->tempData = null;
190
191
        return $output;
192
    }
193
194
    /**
195
     * Extraire le premier bit d'une longue chaîne et ajouter des points de suspension
196
     */
197
    public function excerpt(string $string, int $length = 20): string
198
    {
199
        return (strlen($string) > $length) ? substr($string, 0, $length - 3) . '...' : $string;
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     */
205
    public function setData(array $data = [], ?string $context = null): self
206
    {
207
        if ($context) {
208
            $data = esc($data, $context);
209
        }
210
211
        $this->tempData = $data;
212
213
        return $this;
214
    }
215
216
    /**
217
     * {@inheritDoc}
218
     */
219
    public function addData(array $data = [], ?string $context = null): self
220
    {
221
        if ($context) {
222
            $data = esc($data, $context);
223
        }
224
225
        $this->tempData ??= $this->data;
226
        $this->tempData = array_merge($this->tempData, $data);
227
228
        return $this;
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234
    public function setVar(string $name, $value = null, ?string $context = null): self
235
    {
236
        if ($context) {
237 2
            $value = esc($value, $context);
238
        }
239
240 2
        $this->tempData ??= $this->data;
241 2
        $this->tempData[$name] = $value;
242
243 2
        return $this;
244
    }
245
246
    /**
247
     * {@inheritDoc}
248
     */
249
    public function getData(): array
250
    {
251
        return $this->tempData ?? $this->data;
252
    }
253
254
    /**
255
     * {@inheritDoc}
256
     */
257
    public function resetData(): self
258
    {
259
        $this->data     = [];
260
        $this->tempData = [];
261
262
        return $this;
263
    }
264
265
    /**
266
     * Spécifie que la vue actuelle doit étendre une mise en page existante.
267
     */
268
    public function extend(string $layout)
269
    {
270
        $this->layout = $layout;
271
    }
272
273
    /**
274
     * Commence le contenu d'une section dans la mise en page.
275
     */
276
    public function start(string $name, ?string $content = null)
277
    {
278
        if (null === $content) {
279
            $this->sectionStack[] = $name;
280
281
            ob_start();
282
        } else {
283
            $this->sections[$name] = [$content];
284
        }
285
    }
286
287
    /**
288
     * Commence le contenu d'une section dans la mise en page.
289
     *
290
     * @alias self::start()
291
     */
292
    public function section(string $name, ?string $content = null): void
293
    {
294
        $this->start($name, $content);
295
    }
296
297
    /**
298
     * Commence le contenu d'une section dans la mise en page.
299
     *
300
     * @alias self::start()
301
     */
302
    public function begin(string $name, ?string $content = null): void
303
    {
304
        $this->start($name, $content);
305
    }
306
307
    /**
308
     * Capture la dernière section
309
     *
310
     * @throws RuntimeException
311
     */
312
    public function stop()
313
    {
314
        $contents = ob_get_clean();
315
316
        if ($this->sectionStack === []) {
317
            throw new RuntimeException('View themes, no current section.');
318
        }
319
320
        $section = array_pop($this->sectionStack);
321
322
        // Assurez-vous qu'un tableau existe afin que nous puissions stocker plusieurs entrées pour cela.
323
        if (! array_key_exists($section, $this->sections)) {
324
            $this->sections[$section] = [];
325
        }
326
327
        $this->sections[$section][] = $contents;
328
    }
329
330
    /**
331
     * Capture la dernière section
332
     *
333
     * @throws RuntimeException
334
     *
335
     * @alias self::stop()
336
     */
337
    public function endSection(): void
338
    {
339
        $this->stop();
340
    }
341
342
    /**
343
     * Capture la dernière section
344
     *
345
     * @throws RuntimeException
346
     *
347
     * @alias self::stop()
348
     */
349
    public function end(): void
350
    {
351
        $this->stop();
352
    }
353
354
    /**
355
     * Restitue le contenu d'une section.
356
     */
357
    public function show(string $sectionName, bool $preserve = false)
358
    {
359
        if (! isset($this->sections[$sectionName])) {
360
            echo '';
361
362
            return;
363
        }
364
365
        $start = $end = '';
366
        if ($sectionName === 'css') {
367
            $start = "<style type=\"text/css\">\n";
368
            $end   = "</style>\n";
369
        }
370
        if ($sectionName === 'js') {
371
            $start = "<script type=\"text/javascript\">\n";
372
            $end   = "</script>\n";
373
        }
374
375
        echo $start;
376
377
        foreach ($this->sections[$sectionName] as $key => $contents) {
378
            echo $contents;
379
380
            if (false === $preserve) {
381
                unset($this->sections[$sectionName][$key]);
382
            }
383
        }
384
385
        echo $end;
386
    }
387
388
    /**
389
     * Affichage rapide du contenu principal
390
     */
391
    public function renderView(): void
392
    {
393
        $this->show('content');
394
    }
395
396
    /**
397
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires.
398
     *
399
     * @param mixed $saveData
400
     */
401
    public function insert(string $view, ?array $data = [], ?array $options = null, $saveData = true): string
402
    {
403
        $view = Helpers::ensureExt($view, $this->ext);
404
        $view = str_replace(' ', '', $view);
405
406
        if ($view[0] !== '/') {
407
            $view = $this->retrievePartialPath($view);
408
        }
409
410
        return $this->addData($data)->render($view, $options, $saveData);
411
    }
412
413
    /**
414
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires.
415
     *
416
     * @alias self::insert()
417
     *
418
     * @param mixed $saveData
419
     */
420
    public function include(string $view, ?array $data = [], ?array $options = null, $saveData = true): string
421
    {
422
        return $this->insert($view, $data, $options, $saveData);
423
    }
424
425
    /**
426
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires lorsqu'une condition est remplie.
427
     *
428
     * @param mixed $saveData
429
     */
430
    public function insertWhen(bool|callable $condition, string $view, ?array $data = [], ?array $options = null, $saveData = true): string
431
    {
432
        if (is_callable($condition)) {
433
            $condition = $condition();
434
        }
435
436
        if (true === $condition) {
437
            return $this->insert($view, $data, $options, $saveData);
438
        }
439
440
        return '';
441
    }
442
443
    /**
444
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires lorsqu'une condition est remplie.
445
     *
446
     * @alias self::insertWhen()
447
     *
448
     * @param mixed $saveData
449
     */
450
    public function includeWhen(bool|callable $condition, string $view, ?array $data = [], ?array $options = null, $saveData = true): string
451
    {
452
        return $this->insertWhen($condition, $view, $data, $options, $saveData);
453
    }
454
455
    /**
456
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires lorsqu'une condition n'est pas remplie.
457
     *
458
     * @param mixed $saveData
459
     */
460
    public function insertUnless(bool|callable $condition, string $view, ?array $data = [], ?array $options = null, $saveData = true): string
461
    {
462
        if (is_callable($condition)) {
463
            $condition = $condition();
464
        }
465
466
        return $this->insertWhen(false === $condition, $view, $data, $options, $saveData);
467
    }
468
469
    /**
470
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires lorsqu'une condition n'est pas remplie.
471
     *
472
     * @alias self::insertUnless()
473
     *
474
     * @param mixed $saveData
475
     */
476
    public function includeUnless(bool|callable $condition, string $view, ?array $data = [], ?array $options = null, $saveData = true): string
477
    {
478
        return $this->insertUnless($condition, $view, $data, $options, $saveData);
479
    }
480
481
    /**
482
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires si elle existe.
483
     *
484
     * @alias self::insertIf()
485
     *
486
     * @param mixed $saveData
487
     */
488
    public function includeIf(string $view, ?array $data = [], ?array $options = null, $saveData = true): string
489
    {
490
        return $this->insertIf($view, $data, $options, $saveData);
491
    }
492
493
    /**
494
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires si elle existe.
495
     *
496
     * @param mixed $saveData
497
     */
498
    public function insertIf(string $view, ?array $data = [], ?array $options = null, $saveData = true): string
499
    {
500
        $view = Helpers::ensureExt($view, $this->ext);
501
        $view = str_replace(' ', '', $view);
502
503
        if ($view[0] !== '/') {
504
            $view = $this->retrievePartialPath($view);
505
        }
506
507
        if (is_file($view)) {
508
            return $this->addData($data)->render($view, $options, $saveData);
509
        }
510
511
        return '';
512
    }
513
514
    /**
515
     * Ajoute un fichier css de librairie a la vue
516
     */
517
    public function addLibCss(string ...$src): self
518
    {
519
        foreach ($src as $var) {
520
            if (! in_array($var, $this->_lib_styles, true)) {
521
                $this->_lib_styles[] = $var;
522
            }
523
        }
524
525
        return $this;
526
    }
527
528
    /**
529
     * Ajoute un fichier css a la vue
530
     */
531
    public function addCss(string ...$src): self
532
    {
533
        foreach ($src as $var) {
534
            if (! in_array($var, $this->_styles, true)) {
535
                $this->_styles[] = $var;
536
            }
537
        }
538
539
        return $this;
540
    }
541
542
    /**
543
     * Compile les fichiers de style de l'instance et genere les link:href vers ceux-ci
544
     */
545
    public function stylesBundle(string ...$groups): void
546
    {
547
        $groups     = (array) (empty($groups) ? $this->layout ?? 'default' : $groups);
548
        $lib_styles = $styles = [];
549
550
        foreach ($groups as $group) {
551
            $lib_styles = array_merge(
552
                $lib_styles,
553
                // (array) config('layout.'.$group.'.lib_styles'),
554
                $this->_lib_styles
555
            );
556
            $styles = array_merge(
557
                $styles,
558
                // (array) config('layout.'.$group.'.styles'),
559
                $this->_styles
560
            );
561
        }
562
563
        if (! empty($lib_styles)) {
564
            lib_styles(array_unique($lib_styles));
565
        }
566
        if (! empty($styles)) {
567
            styles(array_unique($styles));
568
        }
569
570
        $this->show('css');
571
    }
572
573
    /**
574
     * Ajoute un fichier js de librairie a la vue
575
     */
576
    public function addLibJs(string ...$src): self
577
    {
578
        foreach ($src as $var) {
579
            if (! in_array($var, $this->_lib_scripts, true)) {
580
                $this->_lib_scripts[] = $var;
581
            }
582
        }
583
584
        return $this;
585
    }
586
587
    /**
588
     * Ajoute un fichier js a la vue
589
     */
590
    public function addJs(string ...$src): self
591
    {
592
        foreach ($src as $var) {
593
            if (! in_array($var, $this->_scripts, true)) {
594
                $this->_scripts[] = $var;
595
            }
596
        }
597
598
        return $this;
599
    }
600
601
    /**
602
     * Compile les fichiers de script de l'instance et genere les link:href vers ceux-ci
603
     */
604
    public function scriptsBundle(string ...$groups): void
605
    {
606
        $groups      = (array) (empty($groups) ? $this->layout ?? 'default' : $groups);
607
        $lib_scripts = $scripts = [];
608
609
        foreach ($groups as $group) {
610
            $lib_scripts = array_merge(
611
                $lib_scripts,
612
                // (array) config('layout.'.$group.'.lib_scripts'),
613
                $this->_lib_scripts
614
            );
615
            $scripts = array_merge(
616
                $scripts,
617
                // (array) config('layout.'.$group.'.scripts'),
618
                $this->_scripts
619
            );
620
        }
621
622
        if (! empty($lib_scripts)) {
623
            lib_scripts(array_unique($lib_scripts));
624
        }
625
        if (! empty($scripts)) {
626
            scripts(array_unique($scripts));
627
        }
628
629
        $this->show('js');
630
    }
631
632
    protected function prepareTemplateData(bool $saveData): void
633
    {
634 2
        $this->tempData ??= $this->data;
635
636
        if ($saveData) {
637 2
            $this->data = $this->tempData;
638
        }
639
    }
640
641
    private function retrievePartialPath(string $view): string
642
    {
643
        $current_dir = pathinfo($this->renderVars['file'] ?? '', PATHINFO_DIRNAME);
644
        if (file_exists(rtrim($current_dir, DS) . DS . $view)) {
0 ignored issues
show
Bug introduced by
It seems like $current_dir can also be of type array; however, parameter $string of rtrim() 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

644
        if (file_exists(rtrim(/** @scrutinizer ignore-type */ $current_dir, DS) . DS . $view)) {
Loading history...
645
            $view = rtrim($current_dir, DS) . DS . $view;
646
        } elseif (file_exists(rtrim($current_dir, DS) . DS . 'partials' . DS . $view)) {
647
            $view = rtrim($current_dir, DS) . DS . 'partials' . DS . $view;
648
        } elseif (file_exists($this->viewPath . 'partials' . DS . $view)) {
649
            $view = $this->viewPath . 'partials' . DS . $view;
650
        } elseif (file_exists($this->viewPath . trim(dirname($current_dir), '/\\') . DS . $view)) {
0 ignored issues
show
Bug introduced by
It seems like $current_dir can also be of type array; however, parameter $path of dirname() 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

650
        } elseif (file_exists($this->viewPath . trim(dirname(/** @scrutinizer ignore-type */ $current_dir), '/\\') . DS . $view)) {
Loading history...
651
            $view = $this->viewPath . trim(dirname($current_dir), '/\\') . DS . $view;
652
        } elseif (file_exists(VIEW_PATH . 'partials' . DS . $view)) {
653
            $view = VIEW_PATH . 'partials' . DS . $view;
654
        } elseif (file_exists(VIEW_PATH . trim(dirname($current_dir), '/\\') . DS . $view)) {
655
            $view = VIEW_PATH . trim(dirname($current_dir), '/\\') . DS . $view;
656
        }
657
658
        return $view;
659
    }
660
}
661