WebView::head()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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