Passed
Pull Request — master (#211)
by Alexander
05:07 queued 02:35
created

WebViewState::getType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

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