WebViewState::getJsFiles()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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