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

WebView::clear()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

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