Passed
Pull Request — master (#141)
by Alexander
02:18
created

WebView::renderBodyEndHtml()   B

Complexity

Conditions 10
Paths 160

Size

Total Lines 42
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 10.3638

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 28
c 1
b 0
f 0
dl 0
loc 42
ccs 22
cts 26
cp 0.8462
rs 7.1666
cc 10
nc 160
nop 1
crap 10.3638

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\View;
6
7
use InvalidArgumentException;
8
use Yiisoft\Html\Html;
9
use Yiisoft\Html\Tag\Script;
10
use Yiisoft\Html\Tag\Style;
11
use Yiisoft\Json\Json;
12
use Yiisoft\View\Event\BodyBegin;
13
use Yiisoft\View\Event\BodyEnd;
14
use Yiisoft\View\Event\PageBegin;
15
use Yiisoft\View\Event\PageEnd;
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
 * View represents a view object in the MVC pattern.
27
 *
28
 * View provides a set of methods (e.g. {@see render()} for rendering purpose.
29
 *
30
 * You can modify its configuration by adding an array to your application config under `components` as it is shown in
31
 * the following example:
32
 *
33
 * ```php
34
 * 'view' => [
35
 *     'theme' => 'app\themes\MyTheme',
36
 *     'renderers' => [
37
 *         // you may add Smarty or Twig renderer here
38
 *     ]
39
 *     // ...
40
 * ]
41
 * ```
42
 *
43
 * For more details and usage information on View, see the [guide article on views](guide:structure-views).
44
 */
45
final class WebView extends BaseView
46
{
47
    /**
48
     * The location of registered JavaScript code block or files.
49
     * This means the location is in the head section.
50
     */
51
    public const POSITION_HEAD = 1;
52
53
    /**
54
     * The location of registered JavaScript code block or files.
55
     * This means the location is at the beginning of the body section.
56
     */
57
    public const POSITION_BEGIN = 2;
58
59
    /**
60
     * The location of registered JavaScript code block or files.
61
     * This means the location is at the end of the body section.
62
     */
63
    public const POSITION_END = 3;
64
65
    /**
66
     * The location of registered JavaScript code block.
67
     * This means the JavaScript code block will be executed when HTML document composition is ready.
68
     */
69
    public const POSITION_READY = 4;
70
71
    /**
72
     * The location of registered JavaScript code block.
73
     * This means the JavaScript code block will be executed when HTML page is completely loaded.
74
     */
75
    public const POSITION_LOAD = 5;
76
77
    private const DEFAULT_POSITION_CSS_FILE = self::POSITION_HEAD;
78
    private const DEFAULT_POSITION_CSS_STRING = self::POSITION_HEAD;
79
    private const DEFAULT_POSITION_JS_FILE = self::POSITION_END;
80
    private const DEFAULT_POSITION_JS_VARIABLE = self::POSITION_HEAD;
81
    private const DEFAULT_POSITION_JS_STRING = self::POSITION_END;
82
83
    /**
84
     * This is internally used as the placeholder for receiving the content registered for the head section.
85
     */
86
    private const PLACEHOLDER_HEAD = '<![CDATA[YII-BLOCK-HEAD-%s]]>';
87
88
    /**
89
     * This is internally used as the placeholder for receiving the content registered for the beginning of the body
90
     * section.
91
     */
92
    private const PLACEHOLDER_BODY_BEGIN = '<![CDATA[YII-BLOCK-BODY-BEGIN-%s]]>';
93
94
    /**
95
     * This is internally used as the placeholder for receiving the content registered for the end of the body section.
96
     */
97
    private const PLACEHOLDER_BODY_END = '<![CDATA[YII-BLOCK-BODY-END-%s]]>';
98
99
    /**
100
     * @var string the page title
101
     */
102
    private string $title = '';
103
104
    /**
105
     * @var array the registered meta tags.
106
     *
107
     * {@see registerMetaTag()}
108
     */
109
    private array $metaTags = [];
110
111
    /**
112
     * @var array the registered link tags.
113
     *
114
     * {@see registerLinkTag()}
115
     */
116
    private array $linkTags = [];
117
118
    /**
119
     * @var array the registered CSS code blocks.
120
     *
121
     * {@see registerCss()}
122
     */
123
    private array $css = [];
124
125
    /**
126
     * @var array the registered CSS files.
127
     *
128
     * {@see registerCssFile()}
129
     */
130
    private array $cssFiles = [];
131
132
    /**
133
     * @var array the registered JS code blocks
134
     * @psalm-var array<int, string[]|Script[]>
135
     *
136
     * {@see registerJs()}
137
     */
138
    private array $js = [];
139
140
    /**
141
     * @var array the registered JS files.
142
     *
143
     * {@see registerJsFile()}
144
     */
145
    private array $jsFiles = [];
146
147
    /**
148
     * Marks the position of an HTML head section.
149
     */
150 17
    public function head(): void
151
    {
152 17
        echo sprintf(self::PLACEHOLDER_HEAD, $this->getPlaceholderSignature());
153 17
    }
154
155
    /**
156
     * Marks the beginning of an HTML body section.
157
     */
158 17
    public function beginBody(): void
159
    {
160 17
        echo sprintf(self::PLACEHOLDER_BODY_BEGIN, $this->getPlaceholderSignature());
161 17
        $this->eventDispatcher->dispatch(new BodyBegin($this->getViewFile()));
162 17
    }
163
164
    /**
165
     * Marks the ending of an HTML body section.
166
     */
167 17
    public function endBody(): void
168
    {
169 17
        $this->eventDispatcher->dispatch(new BodyEnd($this->getViewFile()));
170 17
        echo sprintf(self::PLACEHOLDER_BODY_END, $this->getPlaceholderSignature());
171 17
    }
172
173
    /**
174
     * Marks the beginning of a page.
175
     */
176 17
    public function beginPage(): void
177
    {
178 17
        ob_start();
179 17
        PHP_VERSION_ID >= 80000 ? ob_implicit_flush(false) : ob_implicit_flush(0);
180
181 17
        $this->eventDispatcher->dispatch(new PageBegin($this->getViewFile()));
182 17
    }
183
184
    /**
185
     * Marks the ending of an HTML page.
186
     *
187
     * @param bool $ajaxMode whether the view is rendering in AJAX mode. If true, the JS scripts registered at
188
     * {@see POSITION_READY} and {@see POSITION_LOAD} positions will be rendered at the end of the view like
189
     * normal scripts.
190
     */
191 17
    public function endPage($ajaxMode = false): void
192
    {
193 17
        $this->eventDispatcher->dispatch(new PageEnd($this->getViewFile()));
194
195 17
        $content = ob_get_clean();
196
197 17
        echo strtr($content, [
198 17
            sprintf(self::PLACEHOLDER_HEAD, $this->getPlaceholderSignature()) => $this->renderHeadHtml(),
199 17
            sprintf(self::PLACEHOLDER_BODY_BEGIN, $this->getPlaceholderSignature()) => $this->renderBodyBeginHtml(),
200 17
            sprintf(self::PLACEHOLDER_BODY_END, $this->getPlaceholderSignature()) => $this->renderBodyEndHtml($ajaxMode),
201
        ]);
202
203 17
        $this->clear();
204 17
    }
205
206
    /**
207
     * Renders a view in response to an AJAX request.
208
     *
209
     * This method is similar to {@see render()} except that it will surround the view being rendered with the calls of
210
     * {@see beginPage()}, {@see head()}, {@see beginBody()}, {@see endBody()} and {@see endPage()}. By doing so, the
211
     * method is able to inject into the rendering result with JS/CSS scripts and files that are registered with the
212
     * view.
213
     *
214
     * @param string $view the view name. Please refer to {@see render()} on how to specify this parameter.
215
     * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view
216
     * file.
217
     *
218
     * @return string the rendering result
219
     *
220
     * {@see render()}
221
     */
222 2
    public function renderAjax(string $view, array $params = []): string
223
    {
224 2
        $viewFile = $this->findTemplateFile($view);
225
226 2
        ob_start();
227 2
        PHP_VERSION_ID >= 80000 ? ob_implicit_flush(false) : ob_implicit_flush(0);
228
229 2
        $this->beginPage();
230 2
        $this->head();
231 2
        $this->beginBody();
232 2
        echo $this->renderFile($viewFile, $params);
233 2
        $this->endBody();
234 2
        $this->endPage(true);
235
236 2
        return ob_get_clean();
237
    }
238
239
    /**
240
     * Renders a string in response to an AJAX request.
241
     *
242
     * @param string $string The string.
243
     *
244
     * @return string The rendering result.
245
     */
246 1
    public function renderAjaxString(string $string): string
247
    {
248 1
        ob_start();
249 1
        PHP_VERSION_ID >= 80000 ? ob_implicit_flush(false) : ob_implicit_flush(0);
250
251 1
        $this->beginPage();
252 1
        $this->head();
253 1
        $this->beginBody();
254 1
        echo $string;
255 1
        $this->endBody();
256 1
        $this->endPage(true);
257
258 1
        return ob_get_clean();
259
    }
260
261
    /**
262
     * Clears up the registered meta tags, link tags, css/js scripts and files.
263
     */
264 17
    public function clear(): void
265
    {
266 17
        $this->metaTags = [];
267 17
        $this->linkTags = [];
268 17
        $this->css = [];
269 17
        $this->cssFiles = [];
270 17
        $this->js = [];
271 17
        $this->jsFiles = [];
272 17
    }
273
274
    /**
275
     * Registers a meta tag.
276
     *
277
     * For example, a description meta tag can be added like the following:
278
     *
279
     * ```php
280
     * $view->registerMetaTag([
281
     *     'name' => 'description',
282
     *     'content' => 'This website is about funny raccoons.'
283
     * ]);
284
     * ```
285
     *
286
     * will result in the meta tag `<meta name="description" content="This website is about funny raccoons.">`.
287
     *
288
     * @param array $options the HTML attributes for the meta tag.
289
     * @param string $key the key that identifies the meta tag. If two meta tags are registered with the same key, the
290
     * latter will overwrite the former. If this is null, the new meta tag will be appended to the
291
     * existing ones.
292
     */
293 1
    public function registerMetaTag(array $options, string $key = null): void
294
    {
295 1
        if ($key === null) {
296 1
            $this->metaTags[] = Html::meta()->attributes($options)->render();
297
        } else {
298
            $this->metaTags[$key] = Html::meta()->attributes($options)->render();
299
        }
300 1
    }
301
302
    /**
303
     * Registers a link tag.
304
     *
305
     * For example, a link tag for a custom [favicon](http://www.w3.org/2005/10/howto-favicon) can be added like the
306
     * following:
307
     *
308
     * ```php
309
     * $view->registerLinkTag(['rel' => 'icon', 'type' => 'image/png', 'href' => '/myicon.png']);
310
     * ```
311
     *
312
     * which will result in the following HTML: `<link rel="icon" type="image/png" href="/myicon.png">`.
313
     *
314
     * **Note:** To register link tags for CSS stylesheets, use {@see registerCssFile()]} instead, which has more
315
     * options for this kind of link tag.
316
     *
317
     * @param array $options the HTML attributes for the link tag.
318
     * @param string|null $key the key that identifies the link tag. If two link tags are registered with the same
319
     * key, the latter will overwrite the former. If this is null, the new link tag will be appended
320
     * to the existing ones.
321
     */
322 1
    public function registerLinkTag(array $options, ?string $key = null): void
323
    {
324 1
        if ($key === null) {
325 1
            $this->linkTags[] = Html::link()->attributes($options)->render();
326
        } else {
327
            $this->linkTags[$key] = Html::link()->attributes($options)->render();
328
        }
329 1
    }
330
331
    /**
332
     * Registers a CSS code block.
333
     *
334
     * @param string $css the content of the CSS code block to be registered
335
     * @param string|null $key the key that identifies the CSS code block. If null, it will use $css as the key. If two CSS
336
     * code blocks are registered with the same key, the latter will overwrite the former.
337
     */
338 4
    public function registerCss(string $css, int $position = self::DEFAULT_POSITION_CSS_STRING, ?string $key = null): void
339
    {
340 4
        $key = $key ?: md5($css);
341 4
        $this->css[$position][$key] = $css;
342 4
    }
343
344
    /**
345
     * Register a `style` tag.
346
     *
347
     * @see registerJs()
348
     */
349 1
    public function registerStyleTag(Style $style, int $position = self::DEFAULT_POSITION_CSS_STRING, ?string $key = null): void
350
    {
351 1
        $key = $key ?: md5($style->render());
352 1
        $this->css[$position][$key] = $style;
353 1
    }
354
355
    /**
356
     * Registers a CSS file.
357
     *
358
     * This method should be used for simple registration of CSS files. If you want to use features of
359
     * {@see \Yiisoft\Assets\AssetManager} like appending timestamps to the URL and file publishing options, use
360
     * {@see \Yiisoft\Assets\AssetBundle}.
361
     *
362
     * @param string $url the CSS file to be registered.
363
     * @param array $options the HTML attributes for the link tag. Please refer to {@see \Yiisoft\Html\Html::cssFile()}
364
     * for the supported options.
365
     * @param string $key the key that identifies the CSS script file. If null, it will use $url as the key. If two CSS
366
     * files are registered with the same key, the latter will overwrite the former.
367
     */
368 1
    public function registerCssFile(string $url, int $position = self::DEFAULT_POSITION_CSS_FILE, array $options = [], string $key = null): void
369
    {
370 1
        if (!$this->isValidCssPosition($position)) {
371
            throw new InvalidArgumentException('Invalid position of CSS file.');
372
        }
373
374 1
        $this->cssFiles[$position][$key ?: $url] = Html::cssFile($url, $options)->render();
375 1
    }
376
377
    /**
378
     * Registers a JS code block.
379
     *
380
     * @param string $js the JS code block to be registered
381
     * @param int $position the position at which the JS script tag should be inserted in a page.
382
     *
383
     * The possible values are:
384
     *
385
     * - {@see POSITION_HEAD}: in the head section
386
     * - {@see POSITION_BEGIN}: at the beginning of the body section
387
     * - {@see POSITION_END}: at the end of the body section. This is the default value.
388
     * - {@see POSITION_LOAD}: executed when HTML page is completely loaded.
389
     * - {@see POSITION_READY}: executed when HTML document composition is ready.
390
     * @param string $key the key that identifies the JS code block. If null, it will use $js as the key. If two JS code
391
     * blocks are registered with the same key, the latter will overwrite the former.
392
     */
393 5
    public function registerJs(string $js, int $position = self::DEFAULT_POSITION_JS_FILE, ?string $key = null): void
394
    {
395 5
        $key = $key ?: md5($js);
396 5
        $this->js[$position][$key] = $js;
397 5
    }
398
399
    /**
400
     * Register a `script` tag
401
     *
402
     * @see registerJs()
403
     */
404 4
    public function registerScriptTag(Script $script, int $position = self::DEFAULT_POSITION_JS_STRING, ?string $key = null): void
405
    {
406 4
        $key = $key ?: md5($script->render());
407 4
        $this->js[$position][$key] = $script;
408 4
    }
409
410
    /**
411
     * Registers a JS file.
412
     *
413
     * This method should be used for simple registration of JS files. If you want to use features of
414
     * {@see \Yiisoft\Assets\AssetManager} like appending timestamps to the URL and file publishing options, use
415
     * {@see \Yiisoft\Assets\AssetBundle}.
416
     *
417
     * @param string $url the JS file to be registered.
418
     * @param array $options the HTML attributes for the script tag. The following options are specially handled and
419
     * are not treated as HTML attributes:
420
     *
421
     * - `position`: specifies where the JS script tag should be inserted in a page. The possible values are:
422
     *     * {@see POSITION_HEAD}: in the head section
423
     *     * {@see POSITION_BEGIN}: at the beginning of the body section
424
     *     * {@see POSITION_END}: at the end of the body section. This is the default value.
425
     *
426
     * Please refer to {@see \Yiisoft\Html\Html::javaScriptFile()} for other supported options.
427
     * @param string $key the key that identifies the JS script file. If null, it will use $url as the key. If two JS
428
     * files are registered with the same key at the same position, the latter will overwrite the former.
429
     * Note that position option takes precedence, thus files registered with the same key, but different
430
     * position option will not override each other.
431
     */
432 1
    public function registerJsFile(string $url, int $position = self::DEFAULT_POSITION_JS_FILE, array $options = [], string $key = null): void
433
    {
434 1
        if (!$this->isValidJsPosition($position)) {
435
            throw new InvalidArgumentException('Invalid position of JS file.');
436
        }
437
438 1
        $this->jsFiles[$position][$key ?: $url] = Html::javaScriptFile($url, $options)->render();
439 1
    }
440
441
    /**
442
     * Registers a JS code block defining a variable. The name of variable will be used as key, preventing duplicated
443
     * variable names.
444
     *
445
     * @param string $name Name of the variable
446
     * @param array|string $value Value of the variable
447
     * @param int $position the position in a page at which the JavaScript variable should be inserted.
448
     *
449
     * The possible values are:
450
     *
451
     * - {@see POSITION_HEAD}: in the head section. This is the default value.
452
     * - {@see POSITION_BEGIN}: at the beginning of the body section.
453
     * - {@see POSITION_END}: at the end of the body section.
454
     * - {@see POSITION_LOAD}: enclosed within jQuery(window).load().
455
     *   Note that by using this position, the method will automatically register the jQuery js file.
456
     * - {@see POSITION_READY}: enclosed within jQuery(document).ready().
457
     *   Note that by using this position, the method will automatically register the jQuery js file.
458
     */
459 2
    public function registerJsVar(string $name, $value, int $position = self::DEFAULT_POSITION_JS_VARIABLE): void
460
    {
461 2
        $js = sprintf('var %s = %s;', $name, Json::htmlEncode($value));
462 2
        $this->registerJs($js, $position, $name);
463 2
    }
464
465
    /**
466
     * Renders the content to be inserted in the head section.
467
     *
468
     * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files.
469
     *
470
     * @return string the rendered content
471
     */
472 17
    protected function renderHeadHtml(): string
473
    {
474 17
        $lines = [];
475 17
        if (!empty($this->metaTags)) {
476 1
            $lines[] = implode("\n", $this->metaTags);
477
        }
478
479 17
        if (!empty($this->linkTags)) {
480 1
            $lines[] = implode("\n", $this->linkTags);
481
        }
482 17
        if (!empty($this->cssFiles[self::POSITION_HEAD])) {
483 1
            $lines[] = implode("\n", $this->cssFiles[self::POSITION_HEAD]);
484
        }
485 17
        if (!empty($this->css[self::POSITION_HEAD])) {
486 2
            $lines[] = $this->generateCss($this->css[self::POSITION_HEAD]);
487
        }
488 17
        if (!empty($this->jsFiles[self::POSITION_HEAD])) {
489 1
            $lines[] = implode("\n", $this->jsFiles[self::POSITION_HEAD]);
490
        }
491 17
        if (!empty($this->js[self::POSITION_HEAD])) {
492 3
            $lines[] = $this->generateJs($this->js[self::POSITION_HEAD]);
493
        }
494
495 17
        return empty($lines) ? '' : implode("\n", $lines);
496
    }
497
498
    /**
499
     * Renders the content to be inserted at the beginning of the body section.
500
     *
501
     * The content is rendered using the registered JS code blocks and files.
502
     *
503
     * @return string the rendered content
504
     */
505 17
    protected function renderBodyBeginHtml(): string
506
    {
507 17
        $lines = [];
508 17
        if (!empty($this->cssFiles[self::POSITION_BEGIN])) {
509
            $lines[] = implode("\n", $this->cssFiles[self::POSITION_BEGIN]);
510
        }
511 17
        if (!empty($this->css[self::POSITION_BEGIN])) {
512 2
            $lines[] = $this->generateCss($this->css[self::POSITION_BEGIN]);
513
        }
514 17
        if (!empty($this->jsFiles[self::POSITION_BEGIN])) {
515 1
            $lines[] = implode("\n", $this->jsFiles[self::POSITION_BEGIN]);
516
        }
517 17
        if (!empty($this->js[self::POSITION_BEGIN])) {
518 1
            $lines[] = $this->generateJs($this->js[self::POSITION_BEGIN]);
519
        }
520
521 17
        return empty($lines) ? '' : implode("\n", $lines);
522
    }
523
524
    /**
525
     * Renders the content to be inserted at the end of the body section.
526
     *
527
     * The content is rendered using the registered JS code blocks and files.
528
     *
529
     * @param bool $ajaxMode whether the view is rendering in AJAX mode. If true, the JS scripts registered at
530
     * {@see POSITION_READY} and {@see POSITION_LOAD} positions will be rendered at the end of the view like normal
531
     * scripts.
532
     *
533
     * @return string the rendered content
534
     */
535 17
    protected function renderBodyEndHtml(bool $ajaxMode): string
536
    {
537 17
        $lines = [];
538
539 17
        if (!empty($this->cssFiles[self::POSITION_END])) {
540
            $lines[] = implode("\n", $this->cssFiles[self::POSITION_END]);
541
        }
542 17
        if (!empty($this->css[self::POSITION_END])) {
543 2
            $lines[] = $this->generateCss($this->css[self::POSITION_END]);
544
        }
545 17
        if (!empty($this->jsFiles[self::POSITION_END])) {
546 1
            $lines[] = implode("\n", $this->jsFiles[self::POSITION_END]);
547
        }
548
549 17
        if ($ajaxMode) {
550 3
            $scripts = array_merge(
551 3
                $this->js[self::POSITION_END] ?? [],
552 3
                $this->js[self::POSITION_READY] ?? [],
553 3
                $this->js[self::POSITION_LOAD] ?? [],
554
            );
555 3
            if (!empty($scripts)) {
556 3
                $lines[] = $this->generateJs($scripts);
557
            }
558
        } else {
559 14
            if (!empty($this->js[self::POSITION_END])) {
560 4
                $lines[] = $this->generateJs($this->js[self::POSITION_END]);
561
            }
562 14
            if (!empty($this->js[self::POSITION_READY])) {
563
                $js = "document.addEventListener('DOMContentLoaded', function(event) {\n" .
564 1
                    $this->generateJsWithoutTag($this->js[self::POSITION_READY]) .
565 1
                    "\n});";
566 1
                $lines[] = Html::script($js)->render();
567
            }
568 14
            if (!empty($this->js[self::POSITION_LOAD])) {
569
                $js = "window.addEventListener('load', function (event) {\n" .
570
                    $this->generateJsWithoutTag($this->js[self::POSITION_LOAD]) .
571
                    "\n});";
572
                $lines[] = Html::script($js)->render();
573
            }
574
        }
575
576 17
        return empty($lines) ? '' : implode("\n", $lines);
577
    }
578
579
    /**
580
     * Get title in views.
581
     *
582
     * in Layout:
583
     *
584
     * ```php
585
     * <title><?= Html::encode($this->getTitle()) ?></title>
586
     * ```
587
     *
588
     * in Views:
589
     *
590
     * ```php
591
     * $this->setTitle('Web Application - Yii 3.0.');
592
     * ```
593
     *
594
     * @return string
595
     */
596
    public function getTitle(): string
597
    {
598
        return $this->title;
599
    }
600
601
    /**
602
     * It processes the CSS configuration generated by the asset manager and converts it into HTML code.
603
     *
604
     * @param array $cssFiles
605
     */
606
    public function setCssFiles(array $cssFiles): void
607
    {
608
        foreach ($cssFiles as $key => $value) {
609
            $this->registerCssFileByConfig(
610
                is_string($key) ? $key : null,
611
                is_array($value) ? $value : [$value],
612
            );
613
        }
614
    }
615
616
    /**
617
     * @param array $cssStrings
618
     */
619 1
    public function setCssStrings(array $cssStrings): void
620
    {
621
        /** @var mixed $value */
622 1
        foreach ($cssStrings as $key => $value) {
623 1
            $this->registerCssStringByConfig(
624 1
                is_string($key) ? $key : null,
625 1
                is_array($value) ? $value : [$value, self::DEFAULT_POSITION_CSS_STRING]
626
            );
627
        }
628 1
    }
629
630
    /**
631
     * It processes the JS configuration generated by the asset manager and converts it into HTML code.
632
     *
633
     * @param array $jsFiles
634
     */
635
    public function setJsFiles(array $jsFiles): void
636
    {
637
        foreach ($jsFiles as $key => $value) {
638
            $this->registerJsFileByConfig(
639
                is_string($key) ? $key : null,
640
                is_array($value) ? $value : [$value],
641
            );
642
        }
643
    }
644
645
    /**
646
     * It processes the JS strings generated by the asset manager.
647
     *
648
     * @param array $jsStrings
649
     *
650
     * @throws InvalidArgumentException
651
     */
652 7
    public function setJsStrings(array $jsStrings): void
653
    {
654
        /** @var mixed $value */
655 7
        foreach ($jsStrings as $key => $value) {
656 7
            $this->registerJsStringByConfig(
657 7
                is_string($key) ? $key : null,
658 7
                is_array($value) ? $value : [$value, self::DEFAULT_POSITION_JS_STRING]
659
            );
660
        }
661 1
    }
662
663
    /**
664
     * It processes the JS variables generated by the asset manager and converts it into JS code.
665
     *
666
     * @param array $jsVars
667
     *
668
     * @throws InvalidArgumentException
669
     */
670 5
    public function setJsVars(array $jsVars): void
671
    {
672 5
        foreach ($jsVars as $key => $value) {
673 5
            if (is_string($key)) {
674 1
                $this->registerJsVar($key, $value, self::DEFAULT_POSITION_JS_VARIABLE);
675
            } else {
676 5
                $this->registerJsVarByConfig($value);
677
            }
678
        }
679 1
    }
680
681
    /**
682
     * Set title in views.
683
     *
684
     * {@see getTitle()}
685
     *
686
     * @param string $value
687
     */
688
    public function setTitle(string $value): void
689
    {
690
        $this->title = $value;
691
    }
692
693
    /**
694
     * @throws InvalidArgumentException
695
     */
696
    private function registerCssFileByConfig(?string $key, array $config): void
697
    {
698
        if (!array_key_exists(0, $config)) {
699
            throw new InvalidArgumentException('Do not set CSS file.');
700
        }
701
        $file = $config[0];
702
703
        if (!is_string($file)) {
704
            throw new InvalidArgumentException(
705
                sprintf(
706
                    'CSS file should be string. Got %s.',
707
                    $this->getType($file),
708
                )
709
            );
710
        }
711
712
        $position = $config[1] ?? self::DEFAULT_POSITION_CSS_FILE;
713
714
        unset($config[0], $config[1]);
715
        $this->registerCssFile($file, $position, $config, $key);
716
    }
717
718
    /**
719
     * @throws InvalidArgumentException
720
     */
721 1
    private function registerCssStringByConfig(?string $key, array $config): void
722
    {
723 1
        if (!array_key_exists(0, $config)) {
724
            throw new InvalidArgumentException('Do not set CSS string.');
725
        }
726 1
        $css = $config[0];
727
728 1
        if (!is_string($css) && !($css instanceof Style)) {
729
            throw new InvalidArgumentException(
730
                sprintf(
731
                    'CSS string should be string or instance of \\' . Style::class . '. Got %s.',
732
                    $this->getType($css),
733
                )
734
            );
735
        }
736
737 1
        $position = $config[1] ?? self::DEFAULT_POSITION_CSS_STRING;
738 1
        if (!$this->isValidCssPosition($position)) {
739
            throw new InvalidArgumentException('Invalid position of CSS strings.');
740
        }
741
742 1
        unset($config[0], $config[1]);
743 1
        if ($config !== []) {
744 1
            $css = ($css instanceof Style ? $css : Html::style($css))->attributes($config);
745
        }
746
747 1
        is_string($css)
748 1
            ? $this->registerCss($css, $position, $key)
749 1
            : $this->registerStyleTag($css, $position, $key);
750 1
    }
751
752
    /**
753
     * @throws InvalidArgumentException
754
     */
755
    private function registerJsFileByConfig(?string $key, array $config): void
756
    {
757
        if (!array_key_exists(0, $config)) {
758
            throw new InvalidArgumentException('Do not set JS file.');
759
        }
760
        $file = $config[0];
761
762
        if (!is_string($file)) {
763
            throw new InvalidArgumentException(
764
                sprintf(
765
                    'JS file should be string. Got %s.',
766
                    $this->getType($file),
767
                )
768
            );
769
        }
770
771
        $position = $config[1] ?? self::DEFAULT_POSITION_JS_FILE;
772
773
        unset($config[0], $config[1]);
774
        $this->registerJsFile($file, $position, $config, $key);
775
    }
776
777
    /**
778
     * @throws InvalidArgumentException
779
     */
780 7
    private function registerJsStringByConfig(?string $key, array $config): void
781
    {
782 7
        if (!array_key_exists(0, $config)) {
783 2
            throw new InvalidArgumentException('Do not set JS string.');
784
        }
785 5
        $js = $config[0];
786
787 5
        if (!is_string($js) && !($js instanceof Script)) {
788 2
            throw new InvalidArgumentException(
789 2
                sprintf(
790 2
                    'JS string should be string or instance of \\' . Script::class . '. Got %s.',
791 2
                    $this->getType($js),
792
                )
793
            );
794
        }
795
796 3
        $position = $config[1] ?? self::DEFAULT_POSITION_JS_STRING;
797 3
        if (!$this->isValidJsPosition($position)) {
798 2
            throw new InvalidArgumentException('Invalid position of JS strings.');
799
        }
800
801 1
        unset($config[0], $config[1]);
802 1
        if ($config !== []) {
803
            $js = ($js instanceof Script ? $js : Html::script($js))->attributes($config);
804
        }
805
806 1
        is_string($js)
807 1
            ? $this->registerJs($js, $position, $key)
808 1
            : $this->registerScriptTag($js, $position, $key);
809 1
    }
810
811
    /**
812
     * @throws InvalidArgumentException
813
     */
814 5
    private function registerJsVarByConfig(array $config): void
815
    {
816 5
        if (!array_key_exists(0, $config)) {
817 1
            throw new InvalidArgumentException('Do not set JS variable name.');
818
        }
819 4
        $key = $config[0];
820
821 4
        if (!is_string($key)) {
822 1
            throw new InvalidArgumentException(
823 1
                sprintf(
824 1
                    'JS variable name should be string. Got %s.',
825 1
                    $this->getType($key),
826
                )
827
            );
828
        }
829
830 3
        if (!array_key_exists(1, $config)) {
831 1
            throw new InvalidArgumentException('Do not set JS variable value.');
832
        }
833
        /** @var mixed */
834 2
        $value = $config[1];
835
836 2
        $position = $config[2] ?? self::DEFAULT_POSITION_JS_VARIABLE;
837 2
        if (!$this->isValidJsPosition($position)) {
838 1
            throw new InvalidArgumentException('Invalid position of JS variable.');
839
        }
840
841 1
        $this->registerJsVar($key, $value, $position);
842 1
    }
843
844
    /**
845
     * @param string[]|Style[] $items
846
     */
847 4
    private function generateCss(array $items): string
848
    {
849 4
        $lines = [];
850
851 4
        $css = [];
852 4
        foreach ($items as $item) {
853 4
            if ($item instanceof Style) {
854 1
                if ($css !== []) {
855 1
                    $lines[] = Html::style(implode("\n", $css))->render();
856 1
                    $css = [];
857
                }
858 1
                $lines[] = $item->render();
859
            } else {
860 4
                $css[] = $item;
861
            }
862
        }
863 4
        if ($css !== []) {
864 4
            $lines[] = Html::style(implode("\n", $css))->render();
865
        }
866
867 4
        return implode("\n", $lines);
868
    }
869
870
    /**
871
     * @param Script[]|string[] $items
872
     */
873 6
    private function generateJs(array $items): string
874
    {
875 6
        $lines = [];
876
877 6
        $js = [];
878 6
        foreach ($items as $item) {
879 6
            if ($item instanceof Script) {
880 4
                if ($js !== []) {
881 3
                    $lines[] = Html::script(implode("\n", $js))->render();
882 3
                    $js = [];
883
                }
884 4
                $lines[] = $item->render();
885
            } else {
886 5
                $js[] = $item;
887
            }
888
        }
889 6
        if ($js !== []) {
890 4
            $lines[] = Html::script(implode("\n", $js))->render();
891
        }
892
893 6
        return implode("\n", $lines);
894
    }
895
896
    /**
897
     * @param Script[]|string[] $items
898
     */
899 1
    private function generateJsWithoutTag(array $items): string
900
    {
901 1
        $js = [];
902 1
        foreach ($items as $item) {
903 1
            $js[] = $item instanceof Script ? $item->getContent() : $item;
904
        }
905 1
        return implode("\n", $js);
906
    }
907
908
    /**
909
     * @param mixed $position
910
     *
911
     * @psalm-assert =int $position
912
     */
913 2
    private function isValidCssPosition($position): bool
914
    {
915 2
        return in_array(
916 2
            $position,
917
            [
918 2
                self::POSITION_HEAD,
919 2
                self::POSITION_BEGIN,
920 2
                self::POSITION_END,
921
            ],
922 2
            true,
923
        );
924
    }
925
926
    /**
927
     * @param mixed $position
928
     *
929
     * @psalm-assert =int $position
930
     */
931 6
    private function isValidJsPosition($position): bool
932
    {
933 6
        return in_array(
934 6
            $position,
935
            [
936 6
                self::POSITION_HEAD,
937 6
                self::POSITION_BEGIN,
938 6
                self::POSITION_END,
939 6
                self::POSITION_READY,
940 6
                self::POSITION_LOAD,
941
            ],
942 6
            true,
943
        );
944
    }
945
946
    /**
947
     * @param mixed $value
948
     */
949 3
    private function getType($value): string
950
    {
951 3
        return is_object($value) ? get_class($value) : gettype($value);
952
    }
953
}
954