Passed
Push — master ( 66bc9c...3db065 )
by Alexander
03:13 queued 38s
created

WebViewState::addJsVars()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 10
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\View\State;
6
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Yiisoft\Html\Html;
10
use Yiisoft\Html\Tag\Link;
11
use Yiisoft\Html\Tag\Meta;
12
use Yiisoft\Html\Tag\Script;
13
use Yiisoft\Html\Tag\Style;
14
use Yiisoft\Json\Json;
15
use Yiisoft\View\WebView;
16
17
use function array_key_exists;
18
use function get_class;
19
use function gettype;
20
use function in_array;
21
use function is_array;
22
use function is_object;
23
use function is_string;
24
25
/**
26
 * @internal
27
 */
28
final class WebViewState
29
{
30
    use StateTrait;
31
32
    /**
33
     * @var string The page title
34
     */
35
    private string $title = '';
36
37
    /**
38
     * @var Meta[] The registered meta tags.
39
     *
40
     * @see registerMeta()
41
     * @see registerMetaTag()
42
     */
43
    private array $metaTags = [];
44
45
    /**
46
     * @var array The registered link tags.
47
     * @psalm-var array<int, Link[]>
48
     *
49
     * @see registerLink()
50
     * @see registerLinkTag()
51
     */
52
    private array $linkTags = [];
53
54
    /**
55
     * @var array The registered CSS code blocks.
56
     * @psalm-var array<int, string[]|Style[]>
57
     *
58
     * {@see registerCss()}
59
     */
60
    private array $css = [];
61
62
    /**
63
     * @var array The registered CSS files.
64
     * @psalm-var array<int, string[]>
65
     *
66
     * {@see registerCssFile()}
67
     */
68
    private array $cssFiles = [];
69
70
    /**
71
     * @var array The registered JS code blocks
72
     * @psalm-var array<int, string[]|Script[]>
73
     *
74
     * {@see registerJs()}
75
     */
76
    private array $js = [];
77
78
    /**
79
     * @var array The registered JS files.
80
     * @psalm-var array<int, string[]>
81
     *
82
     * {@see registerJsFile()}
83
     */
84
    private array $jsFiles = [];
85
86
    /**
87
     * Get title in views.
88
     *
89
     * @return string
90
     */
91 3
    public function getTitle(): string
92
    {
93 3
        return $this->title;
94
    }
95
96
    /**
97
     * @return Meta[] The registered meta tags.
98
     */
99 48
    public function getMetaTags(): array
100
    {
101 48
        return $this->metaTags;
102
    }
103
104
    /**
105
     * @return array The registered link tags.
106
     * @psalm-return array<int, Link[]>
107
     */
108 48
    public function getLinkTags(): array
109
    {
110 48
        return $this->linkTags;
111
    }
112
113
    /**
114
     * @return array The registered CSS code blocks.
115
     * @psalm-return array<int, string[]|Style[]>
116
     */
117 48
    public function getCss(): array
118
    {
119 48
        return $this->css;
120
    }
121
122
    /**
123
     * @return array The registered CSS files.
124
     * @psalm-return array<int, string[]>
125
     */
126 48
    public function getCssFiles(): array
127
    {
128 48
        return $this->cssFiles;
129
    }
130
131
    /**
132
     * @return array The registered JS code blocks
133
     * @psalm-return array<int, string[]|Script[]>
134
     */
135 48
    public function getJs(): array
136
    {
137 48
        return $this->js;
138
    }
139
140
    /**
141
     * @return array The registered JS files.
142
     * @psalm-return array<int, string[]>
143
     */
144 48
    public function getJsFiles(): array
145
    {
146 48
        return $this->jsFiles;
147
    }
148
149
    /**
150
     * Set title in views.
151
     *
152
     * {@see getTitle()}
153
     *
154
     * @param string $value
155
     *
156
     * @return static
157
     */
158 4
    public function setTitle(string $value): self
159
    {
160 4
        $this->title = $value;
161 4
        return $this;
162
    }
163
164
    /**
165
     * Registers a meta tag.
166
     *
167
     * For example, a description meta tag can be added like the following:
168
     *
169
     * ```php
170
     * $state->registerMeta([
171
     *     'name' => 'description',
172
     *     'content' => 'This website is about funny raccoons.'
173
     * ]);
174
     * ```
175
     *
176
     * @param array $attributes The HTML attributes for the meta tag.
177
     * @param string|null $key The key that identifies the meta tag. If two meta tags are registered with the same key,
178
     * the latter will overwrite the former. If this is null, the new meta tag will be appended to the existing ones.
179
     */
180 2
    public function registerMeta(array $attributes, ?string $key = null): void
181
    {
182 2
        $this->registerMetaTag(Html::meta($attributes), $key);
183 2
    }
184
185
    /**
186
     * Registers a {@see Meta} tag.
187
     *
188
     * @see registerMeta()
189
     */
190 6
    public function registerMetaTag(Meta $meta, ?string $key = null): void
191
    {
192 6
        $key === null
193 5
            ? $this->metaTags[] = $meta
194 1
            : $this->metaTags[$key] = $meta;
195 6
    }
196
197
    /**
198
     * Registers a link tag.
199
     *
200
     * For example, a link tag for a custom [favicon](http://www.w3.org/2005/10/howto-favicon) can be added like the
201
     * following:
202
     *
203
     * ```php
204
     * $view->registerLink(['rel' => 'icon', 'type' => 'image/png', 'href' => '/myicon.png']);
205
     * ```
206
     *
207
     * **Note:** To register link tags for CSS stylesheets, use {@see registerCssFile()]} instead, which has more
208
     * options for this kind of link tag.
209
     *
210
     * @param array $attributes The HTML attributes for the link tag.
211
     * @param int $position The position at which the link tag should be inserted in a page.
212
     * @param string|null $key The key that identifies the link tag. If two link tags are registered with the same key,
213
     * the latter will overwrite the former. If this is null, the new link tag will be appended to the existing ones.
214
     */
215 4
    public function registerLink(array $attributes, int $position = WebView::POSITION_HEAD, ?string $key = null): void
216
    {
217 4
        $this->registerLinkTag(Html::link()->attributes($attributes), $position, $key);
218 4
    }
219
220
    /**
221
     * Registers a {@see Link} tag.
222
     *
223
     * @see registerLink()
224
     */
225 10
    public function registerLinkTag(Link $link, int $position = WebView::POSITION_HEAD, ?string $key = null): void
226
    {
227 10
        $key === null
228 9
            ? $this->linkTags[$position][] = $link
229 1
            : $this->linkTags[$position][$key] = $link;
230 10
    }
231
232
    /**
233
     * Registers a CSS code block.
234
     *
235
     * @param string $css The content of the CSS code block to be registered.
236
     * @param string|null $key The key that identifies the CSS code block. If null, it will use $css as the key.
237
     * If two CSS code blocks are registered with the same key, the latter will overwrite the former.
238
     * @param array $attributes The HTML attributes for the {@see Style} tag.
239
     */
240 9
    public function registerCss(
241
        string $css,
242
        int $position = WebView::POSITION_HEAD,
243
        array $attributes = [],
244
        ?string $key = null
245
    ): void {
246 9
        $key = $key ?: md5($css);
247 9
        $this->css[$position][$key] = $attributes === [] ? $css : Html::style($css, $attributes);
248 9
    }
249
250
    /**
251
     * Registers a CSS code block from file.
252
     *
253
     * @param string $path The path or URL to CSS file.
254
     *
255
     * @see registerCss()
256
     */
257 3
    public function registerCssFromFile(
258
        string $path,
259
        int $position = WebView::POSITION_HEAD,
260
        array $attributes = [],
261
        ?string $key = null
262
    ): void {
263 3
        $css = file_get_contents($path);
264 3
        if ($css === false) {
265 1
            throw new RuntimeException(sprintf('File %s could not be read.', $path));
266
        }
267
268 2
        $this->registerCss($css, $position, $attributes, $key);
269 2
    }
270
271
    /**
272
     * Register a {@see Style} tag.
273
     *
274
     * @see registerJs()
275
     */
276 3
    public function registerStyleTag(Style $style, int $position = WebView::POSITION_HEAD, ?string $key = null): void
277
    {
278 3
        $key = $key ?: md5($style->render());
279 3
        $this->css[$position][$key] = $style;
280 3
    }
281
282
    /**
283
     * Registers a CSS file.
284
     *
285
     * This method should be used for simple registration of CSS files. If you want to use features of
286
     * {@see \Yiisoft\Assets\AssetManager} like appending timestamps to the URL and file publishing options, use
287
     * {@see \Yiisoft\Assets\AssetBundle}.
288
     *
289
     * @param string $url The CSS file to be registered.
290
     * @param array $options the HTML attributes for the link tag. Please refer to {@see \Yiisoft\Html\Html::cssFile()}
291
     * for the supported options.
292
     * @param string|null $key The key that identifies the CSS script file. If null, it will use $url as the key.
293
     * If two CSS files are registered with the same key, the latter will overwrite the former.
294
     */
295 15
    public function registerCssFile(
296
        string $url,
297
        int $position = WebView::POSITION_HEAD,
298
        array $options = [],
299
        string $key = null
300
    ): void {
301 15
        if (!$this->isValidCssPosition($position)) {
302 2
            throw new InvalidArgumentException('Invalid position of CSS file.');
303
        }
304
305 13
        $this->cssFiles[$position][$key ?: $url] = Html::cssFile($url, $options)->render();
306 13
    }
307
308
    /**
309
     * It processes the CSS configuration generated by the asset manager and converts it into HTML code.
310
     *
311
     * @param array $cssFiles
312
     */
313 5
    public function addCssFiles(array $cssFiles): void
314
    {
315
        /** @var mixed $value */
316 5
        foreach ($cssFiles as $key => $value) {
317 5
            $this->registerCssFileByConfig(
318 5
                is_string($key) ? $key : null,
319 5
                is_array($value) ? $value : [$value],
320
            );
321
        }
322 2
    }
323
324
    /**
325
     * It processes the CSS strings generated by the asset manager.
326
     *
327
     * @param array $cssStrings
328
     */
329 8
    public function addCssStrings(array $cssStrings): void
330
    {
331
        /** @var mixed $value */
332 8
        foreach ($cssStrings as $key => $value) {
333 8
            $this->registerCssStringByConfig(
334 8
                is_string($key) ? $key : null,
335 8
                is_array($value) ? $value : [$value, WebView::POSITION_HEAD],
336
            );
337
        }
338 2
    }
339
340
    /**
341
     * Registers a JS code block.
342
     *
343
     * @param string $js the JS code block to be registered
344
     * @param int $position the position at which the JS script tag should be inserted in a page.
345
     *
346
     * The possible values are:
347
     *
348
     * - {@see WebView::POSITION_HEAD}: in the head section
349
     * - {@see WebView::POSITION_BEGIN}: at the beginning of the body section
350
     * - {@see WebView::POSITION_END}: at the end of the body section. This is the default value.
351
     * - {@see WebView::POSITION_LOAD}: executed when HTML page is completely loaded.
352
     * - {@see WebView::POSITION_READY}: executed when HTML document composition is ready.
353
     * @param string|null $key The key that identifies the JS code block. If null, it will use $js as the key.
354
     * If two JS code blocks are registered with the same key, the latter will overwrite the former.
355
     */
356 9
    public function registerJs(string $js, int $position = WebView::POSITION_END, ?string $key = null): void
357
    {
358 9
        $key = $key ?: md5($js);
359 9
        $this->js[$position][$key] = $js;
360 9
    }
361
362
    /**
363
     * Register a `script` tag
364
     *
365
     * @see registerJs()
366
     */
367 5
    public function registerScriptTag(Script $script, int $position = WebView::POSITION_END, ?string $key = null): void
368
    {
369 5
        $key = $key ?: md5($script->render());
370 5
        $this->js[$position][$key] = $script;
371 5
    }
372
373
    /**
374
     * Registers a JS file.
375
     *
376
     * This method should be used for simple registration of JS files. If you want to use features of
377
     * {@see \Yiisoft\Assets\AssetManager} like appending timestamps to the URL and file publishing options, use
378
     * {@see \Yiisoft\Assets\AssetBundle}.
379
     *
380
     * @param string $url The JS file to be registered.
381
     * @param array $options The HTML attributes for the script tag. The following options are specially handled and
382
     * are not treated as HTML attributes:
383
     *
384
     * - `position`: specifies where the JS script tag should be inserted in a page. The possible values are:
385
     *     * {@see WebView::POSITION_HEAD}: in the head section
386
     *     * {@see WebView::POSITION_BEGIN}: at the beginning of the body section
387
     *     * {@see WebView::POSITION_END}: at the end of the body section. This is the default value.
388
     *
389
     * Please refer to {@see \Yiisoft\Html\Html::javaScriptFile()} for other supported options.
390
     * @param string|null $key The key that identifies the JS script file. If null, it will use $url as the key.
391
     * If two JS files are registered with the same key at the same position, the latter will overwrite the former.
392
     * Note that position option takes precedence, thus files registered with the same key, but different
393
     * position option will not override each other.
394
     */
395 15
    public function registerJsFile(
396
        string $url,
397
        int $position = WebView::POSITION_END,
398
        array $options = [],
399
        string $key = null
400
    ): void {
401 15
        if (!$this->isValidJsPosition($position)) {
402 2
            throw new InvalidArgumentException('Invalid position of JS file.');
403
        }
404
405 13
        $this->jsFiles[$position][$key ?: $url] = Html::javaScriptFile($url, $options)->render();
406 13
    }
407
408
    /**
409
     * Registers a JS code block defining a variable. The name of variable will be used as key, preventing duplicated
410
     * variable names.
411
     *
412
     * @param string $name Name of the variable
413
     * @param mixed $value Value of the variable
414
     * @param int $position The position in a page at which the JavaScript variable should be inserted.
415
     *
416
     * The possible values are:
417
     *
418
     * - {@see WebView::POSITION_HEAD}: in the head section. This is the default value.
419
     * - {@see WebView::POSITION_BEGIN}: at the beginning of the body section.
420
     * - {@see WebView::POSITION_END}: at the end of the body section.
421
     * - {@see WebView::POSITION_LOAD}: enclosed within jQuery(window).load().
422
     *   Note that by using this position, the method will automatically register the jQuery js file.
423
     * - {@see POSITION_READY}: enclosed within jQuery(document).ready().
424
     *   Note that by using this position, the method will automatically register the jQuery js file.
425
     */
426 4
    public function registerJsVar(string $name, $value, int $position = WebView::POSITION_HEAD): void
427
    {
428 4
        $js = sprintf('var %s = %s;', $name, Json::htmlEncode($value));
429 4
        $this->registerJs($js, $position, $name);
430 4
    }
431
432
    /**
433
     * It processes the JS configuration generated by the asset manager and converts it into HTML code.
434
     *
435
     * @param array $jsFiles
436
     */
437 5
    public function addJsFiles(array $jsFiles): void
438
    {
439
        /** @var mixed $value */
440 5
        foreach ($jsFiles as $key => $value) {
441 5
            $this->registerJsFileByConfig(
442 5
                is_string($key) ? $key : null,
443 5
                is_array($value) ? $value : [$value],
444
            );
445
        }
446 2
    }
447
448
    /**
449
     * It processes the JS strings generated by the asset manager.
450
     *
451
     * @param array $jsStrings
452
     *
453
     * @throws InvalidArgumentException
454
     */
455 8
    public function addJsStrings(array $jsStrings): void
456
    {
457
        /** @var mixed $value */
458 8
        foreach ($jsStrings as $key => $value) {
459 8
            $this->registerJsStringByConfig(
460 8
                is_string($key) ? $key : null,
461 8
                is_array($value) ? $value : [$value, WebView::POSITION_END]
462
            );
463
        }
464 2
    }
465
466
    /**
467
     * It processes the JS variables generated by the asset manager and converts it into JS code.
468
     *
469
     * @param array $jsVars
470
     *
471
     * @throws InvalidArgumentException
472
     */
473 6
    public function addJsVars(array $jsVars): void
474
    {
475
        /** @var mixed $value */
476 6
        foreach ($jsVars as $key => $value) {
477 6
            if (is_string($key)) {
478 2
                $this->registerJsVar($key, $value, WebView::POSITION_HEAD);
479
            } else {
480 5
                $this->registerJsVarByConfig((array) $value);
481
            }
482
        }
483 2
    }
484
485
    /**
486
     * Clears the data for working with the event loop:
487
     *  - the added parameters and blocks;
488
     *  - the registered meta tags, link tags, css/js scripts, files and title.
489
     */
490 48
    public function clear(): void
491
    {
492 48
        $this->parameters = [];
493 48
        $this->blocks = [];
494 48
        $this->title = '';
495 48
        $this->metaTags = [];
496 48
        $this->linkTags = [];
497 48
        $this->css = [];
498 48
        $this->cssFiles = [];
499 48
        $this->js = [];
500 48
        $this->jsFiles = [];
501 48
    }
502
503
    /**
504
     * @throws InvalidArgumentException
505
     */
506 5
    private function registerCssFileByConfig(?string $key, array $config): void
507
    {
508 5
        if (!array_key_exists(0, $config)) {
509 1
            throw new InvalidArgumentException('Do not set CSS file.');
510
        }
511 4
        $file = $config[0];
512
513 4
        if (!is_string($file)) {
514 1
            throw new InvalidArgumentException(
515 1
                sprintf(
516 1
                    'CSS file should be string. Got %s.',
517 1
                    $this->getType($file),
518
                )
519
            );
520
        }
521
522 3
        $position = (int) ($config[1] ?? WebView::POSITION_HEAD);
523
524 3
        unset($config[0], $config[1]);
525 3
        $this->registerCssFile($file, $position, $config, $key);
526 2
    }
527
528
    /**
529
     * @throws InvalidArgumentException
530
     */
531 8
    private function registerCssStringByConfig(?string $key, array $config): void
532
    {
533 8
        if (!array_key_exists(0, $config)) {
534 2
            throw new InvalidArgumentException('Do not set CSS string.');
535
        }
536 6
        $css = $config[0];
537
538 6
        if (!is_string($css) && !($css instanceof Style)) {
539 2
            throw new InvalidArgumentException(
540 2
                sprintf(
541 2
                    'CSS string should be string or instance of \\' . Style::class . '. Got %s.',
542 2
                    $this->getType($css),
543
                )
544
            );
545
        }
546
547 4
        $position = $config[1] ?? WebView::POSITION_HEAD;
548 4
        if (!$this->isValidCssPosition($position)) {
549 2
            throw new InvalidArgumentException('Invalid position of CSS strings.');
550
        }
551
552 2
        unset($config[0], $config[1]);
553 2
        if ($config !== []) {
554 1
            $css = ($css instanceof Style ? $css : Html::style($css))->attributes($config);
555
        }
556
557 2
        is_string($css)
558 2
            ? $this->registerCss($css, $position, [], $key)
559 1
            : $this->registerStyleTag($css, $position, $key);
560 2
    }
561
562
    /**
563
     * @param mixed $position
564
     *
565
     * @psalm-assert =int $position
566
     */
567 18
    private function isValidCssPosition($position): bool
568
    {
569
        return in_array(
570 18
            $position,
571
            [
572
                WebView::POSITION_HEAD,
573
                WebView::POSITION_BEGIN,
574
                WebView::POSITION_END,
575
            ],
576
            true,
577
        );
578
    }
579
580
    /**
581
     * @throws InvalidArgumentException
582
     */
583 5
    private function registerJsFileByConfig(?string $key, array $config): void
584
    {
585 5
        if (!array_key_exists(0, $config)) {
586 1
            throw new InvalidArgumentException('Do not set JS file.');
587
        }
588 4
        $file = $config[0];
589
590 4
        if (!is_string($file)) {
591 1
            throw new InvalidArgumentException(
592 1
                sprintf(
593 1
                    'JS file should be string. Got %s.',
594 1
                    $this->getType($file),
595
                )
596
            );
597
        }
598
599 3
        $position = (int) ($config[1] ?? WebView::POSITION_END);
600
601 3
        unset($config[0], $config[1]);
602 3
        $this->registerJsFile($file, $position, $config, $key);
603 2
    }
604
605
    /**
606
     * @throws InvalidArgumentException
607
     */
608 8
    private function registerJsStringByConfig(?string $key, array $config): void
609
    {
610 8
        if (!array_key_exists(0, $config)) {
611 2
            throw new InvalidArgumentException('Do not set JS string.');
612
        }
613 6
        $js = $config[0];
614
615 6
        if (!is_string($js) && !($js instanceof Script)) {
616 2
            throw new InvalidArgumentException(
617 2
                sprintf(
618 2
                    'JS string should be string or instance of \\' . Script::class . '. Got %s.',
619 2
                    $this->getType($js),
620
                )
621
            );
622
        }
623
624 4
        $position = $config[1] ?? WebView::POSITION_END;
625 4
        if (!$this->isValidJsPosition($position)) {
626 2
            throw new InvalidArgumentException('Invalid position of JS strings.');
627
        }
628
629 2
        unset($config[0], $config[1]);
630 2
        if ($config !== []) {
631 1
            $js = ($js instanceof Script ? $js : Html::script($js))->attributes($config);
632
        }
633
634 2
        is_string($js)
635 2
            ? $this->registerJs($js, $position, $key)
636 1
            : $this->registerScriptTag($js, $position, $key);
637 2
    }
638
639
    /**
640
     * @throws InvalidArgumentException
641
     */
642 5
    private function registerJsVarByConfig(array $config): void
643
    {
644 5
        if (!array_key_exists(0, $config)) {
645 1
            throw new InvalidArgumentException('Do not set JS variable name.');
646
        }
647 4
        $key = $config[0];
648
649 4
        if (!is_string($key)) {
650 1
            throw new InvalidArgumentException(
651 1
                sprintf(
652 1
                    'JS variable name should be string. Got %s.',
653 1
                    $this->getType($key),
654
                )
655
            );
656
        }
657
658 3
        if (!array_key_exists(1, $config)) {
659 1
            throw new InvalidArgumentException('Do not set JS variable value.');
660
        }
661
        /** @var mixed */
662 2
        $value = $config[1];
663
664 2
        $position = $config[2] ?? WebView::POSITION_HEAD;
665 2
        if (!$this->isValidJsPosition($position)) {
666 1
            throw new InvalidArgumentException('Invalid position of JS variable.');
667
        }
668
669 1
        $this->registerJsVar($key, $value, $position);
670 1
    }
671
672
    /**
673
     * @param mixed $position
674
     *
675
     * @psalm-assert =int $position
676
     */
677 20
    private function isValidJsPosition($position): bool
678
    {
679
        return in_array(
680 20
            $position,
681
            [
682
                WebView::POSITION_HEAD,
683
                WebView::POSITION_BEGIN,
684
                WebView::POSITION_END,
685
                WebView::POSITION_READY,
686
                WebView::POSITION_LOAD,
687
            ],
688
            true,
689
        );
690
    }
691
692
    /**
693
     * @param mixed $value
694
     */
695 7
    private function getType($value): string
696
    {
697 7
        return is_object($value) ? get_class($value) : gettype($value);
698
    }
699
}
700