Completed
Pull Request — master (#56)
by
unknown
02:00
created

WebView::renderHeadHtml()   B

Complexity

Conditions 8
Paths 128

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 8.512

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 24
ccs 12
cts 15
cp 0.8
rs 8.2111
c 0
b 0
f 0
cc 8
nc 128
nop 0
crap 8.512
1
<?php
2
declare(strict_types=1);
3
4
namespace Yiisoft\View;
5
6
use Psr\Container\ContainerInterface;
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use Psr\Log\LoggerInterface;
9
use Yiisoft\Arrays\ArrayHelper;
10
use Yiisoft\Asset\AssetBundle;
11
use Yiisoft\Asset\AssetManager;
12
use Yiisoft\Di\Container;
13
use Yiisoft\Html\Html;
14
use Yiisoft\View\Event\BodyBegin;
15
use Yiisoft\View\Event\BodyEnd;
16
use Yiisoft\View\Event\PageEnd;
17
use Yiisoft\Widget\Widget;
18
19
/**
20
 * View represents a view object in the MVC pattern.
21
 *
22
 * View provides a set of methods (e.g. {@see render()} for rendering purpose.
23
 *
24
 * You can modify its configuration by adding an array to your application config under `components` as it is shown in
25
 * the following example:
26
 *
27
 * ```php
28
 * 'view' => [
29
 *     'theme' => 'app\themes\MyTheme',
30
 *     'renderers' => [
31
 *         // you may add Smarty or Twig renderer here
32
 *     ]
33
 *     // ...
34
 * ]
35
 * ```
36
 *
37
 * For more details and usage information on View, see the [guide article on views](guide:structure-views).
38
 */
39
class WebView extends View
40
{
41
    /**
42
     * The location of registered JavaScript code block or files.
43
     * This means the location is in the head section.
44
     */
45
    public const POS_HEAD = 1;
46
47
    /**
48
     * The location of registered JavaScript code block or files.
49
     * This means the location is at the beginning of the body section.
50
     */
51
    public const POS_BEGIN = 2;
52
53
    /**
54
     * The location of registered JavaScript code block or files.
55
     * This means the location is at the end of the body section.
56
     */
57
    public const POS_END = 3;
58
59
    /**
60
     * The location of registered JavaScript code block.
61
     * This means the JavaScript code block will be executed when HTML document composition is ready.
62
     */
63
    public const POS_READY = 4;
64
65
    /**
66
     * The location of registered JavaScript code block.
67
     * This means the JavaScript code block will be executed when HTML page is completely loaded.
68
     */
69
    public const POS_LOAD = 5;
70
71
    /**
72
     * This is internally used as the placeholder for receiving the content registered for the head section.
73
     */
74
    private const PH_HEAD = '<![CDATA[YII-BLOCK-HEAD]]>';
75
76
    /**
77
     * This is internally used as the placeholder for receiving the content registered for the beginning of the body
78
     * section.
79
     */
80
    private const PH_BODY_BEGIN = '<![CDATA[YII-BLOCK-BODY-BEGIN]]>';
81
82
    /**
83
     * This is internally used as the placeholder for receiving the content registered for the end of the body section.
84
     */
85
    private const PH_BODY_END = '<![CDATA[YII-BLOCK-BODY-END]]>';
86
87
    /**
88
     * @var AssetBundle[] list of the registered asset bundles. The keys are the bundle names, and the values
89
     * are the registered {@see AssetBundle} objects.
90
     *
91
     * {@see registerAssetBundle()}
92
     */
93
    private $assetBundles = [];
94
95
    /**
96
     * @var AssetManager $assetManager
97
     */
98
    private $assetManager;
99
100
    /**
101
     * @var string the page title
102
     */
103
    private $title;
0 ignored issues
show
introduced by
The private property $title is not used, and could be removed.
Loading history...
104
105
    /**
106
     * @var array the registered meta tags.
107
     *
108
     * {@see registerMetaTag()}
109
     */
110
    private $metaTags = [];
111
112
    /**
113
     * @var array the registered link tags.
114
     *
115
     * {@see registerLinkTag()}
116
     */
117
    private $linkTags = [];
118
119
    /**
120
     * @var array the registered CSS code blocks.
121
     *
122
     * {@see registerCss()}
123
     */
124
    private $css = [];
125
126
    /**
127
     * @var array the registered CSS files.
128
     *
129
     * {@see registerCssFile()}
130
     */
131
    private $cssFiles = [];
132
133
    /**
134
     * @var array the registered JS code blocks
135
     *
136
     * {@see registerJs()}
137
     */
138
    private $js = [];
139
140
    /**
141
     * @var array the registered JS files.
142
     *
143
     * {@see registerJsFile()}
144
     */
145
    private $jsFiles = [];
146
147
    /**
148
     * @var ContainerInterface|Container
149
     */
150
    private $container;
151
152 55
    public function __construct(
153
        string $basePath,
154
        Theme $theme,
155
        EventDispatcherInterface $eventDispatcher,
156
        LoggerInterface $logger,
157
        ContainerInterface $container
158
    ) {
159 55
        parent::__construct($basePath, $theme, $eventDispatcher, $logger);
160 55
        $this->container = $container;
161
    }
162
163
    public function widget(string $class, array $constructorArguments = []): Widget
164
    {
165
        if ($this->container->has($class)) {
166
            return $this->container->get($class);
167
        }
168
169
        $this->container->set($class, ['__class' => $class]);
0 ignored issues
show
Bug introduced by
The method set() does not exist on Psr\Container\ContainerInterface. It seems like you code against a sub-type of Psr\Container\ContainerInterface such as Yiisoft\Di\Container or Yiisoft\Factory\Factory. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

169
        $this->container->/** @scrutinizer ignore-call */ 
170
                          set($class, ['__class' => $class]);
Loading history...
170
171
        return $this->container->get($class, ['__construct()' => $constructorArguments]);
0 ignored issues
show
Unused Code introduced by
The call to Psr\Container\ContainerInterface::get() has too many arguments starting with array('__construct()' => $constructorArguments). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

171
        return $this->container->/** @scrutinizer ignore-call */ get($class, ['__construct()' => $constructorArguments]);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
172
    }
173
174
    /**
175
     * Marks the position of an HTML head section.
176
     */
177 31
    public function head(): void
178
    {
179 31
        echo self::PH_HEAD;
180
    }
181
182
    /**
183
     * Marks the beginning of an HTML body section.
184
     */
185 31
    public function beginBody(): void
186
    {
187 31
        echo self::PH_BODY_BEGIN;
188 31
        $this->eventDispatcher->dispatch(new BodyBegin($this->getViewFile()));
0 ignored issues
show
Bug introduced by
It seems like $this->getViewFile() can also be of type boolean; however, parameter $file of Yiisoft\View\Event\BodyBegin::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

188
        $this->eventDispatcher->dispatch(new BodyBegin(/** @scrutinizer ignore-type */ $this->getViewFile()));
Loading history...
189
    }
190
191
    /**
192
     * Marks the ending of an HTML body section.
193
     */
194 31
    public function endBody(): void
195
    {
196 31
        $this->eventDispatcher->dispatch(new BodyEnd($this->getViewFile()));
0 ignored issues
show
Bug introduced by
It seems like $this->getViewFile() can also be of type boolean; however, parameter $file of Yiisoft\View\Event\BodyEnd::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

196
        $this->eventDispatcher->dispatch(new BodyEnd(/** @scrutinizer ignore-type */ $this->getViewFile()));
Loading history...
197 31
        echo self::PH_BODY_END;
198
199 31
        foreach (array_keys($this->assetBundles) as $bundle) {
200 8
            $this->registerAssetFiles($bundle);
201
        }
202
    }
203
204
    /**
205
     * Marks the ending of an HTML page.
206
     *
207
     * @param bool $ajaxMode whether the view is rendering in AJAX mode. If true, the JS scripts registered at
208
     * {@see POS_READY} and {@see POS_LOAD} positions will be rendered at the end of the view like
209
     * normal scripts.
210
     */
211 31
    public function endPage($ajaxMode = false): void
212
    {
213 31
        $this->eventDispatcher->dispatch(new PageEnd($this->getViewFile()));
0 ignored issues
show
Bug introduced by
It seems like $this->getViewFile() can also be of type boolean; however, parameter $file of Yiisoft\View\Event\PageEnd::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
        $this->eventDispatcher->dispatch(new PageEnd(/** @scrutinizer ignore-type */ $this->getViewFile()));
Loading history...
214
215 31
        $content = ob_get_clean();
216
217 31
        echo strtr($content, [
218 31
            self::PH_HEAD => $this->renderHeadHtml(),
219 31
            self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(),
220 31
            self::PH_BODY_END => $this->renderBodyEndHtml($ajaxMode),
221
        ]);
222
223 31
        $this->clear();
224
    }
225
226
    /**
227
     * Renders a view in response to an AJAX request.
228
     *
229
     * This method is similar to {@see render()} except that it will surround the view being rendered with the calls of
230
     * {@see beginPage()}, {@see head()}, {@see beginBody()}, {@see endBody()} and {@see endPage()}. By doing so, the
231
     * method is able to inject into the rendering result with JS/CSS scripts and files that are registered with the
232
     * view.
233
     *
234
     * @param string $view the view name. Please refer to [[render()]] on how to specify this parameter.
235
     * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view
236
     * file.
237
     * @param object $context the context that the view should use for rendering the view. If null, existing [[context]]
238
     * will be used.
239
     *
240
     * @return string the rendering result
241
     *
242
     * {@see render()}
243
     */
244
    public function renderAjax(string $view, array $params = [], $context = null): string
245
    {
246
        $viewFile = $this->findViewFile($view, $context);
0 ignored issues
show
Bug introduced by
The method findViewFile() does not exist on Yiisoft\View\WebView. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

246
        /** @scrutinizer ignore-call */ 
247
        $viewFile = $this->findViewFile($view, $context);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
247
248
        ob_start();
249
        ob_implicit_flush(0);
250
251
        $this->beginPage();
252
        $this->head();
253
        $this->beginBody();
254
        echo $this->renderFile($viewFile, $params, $context);
255
        $this->endBody();
256
        $this->endPage(true);
257
258
        return ob_get_clean();
259
    }
260
261
    /**
262
     * Registers the asset manager being used by this view object.
263
     *
264
     * @return array the asset manager. Defaults to the "assetManager" application component.
265
     */
266 10
    public function getAssetBundles(): array
267
    {
268 10
        return $this->assetBundles;
269
    }
270
271
    /**
272
     * Registers the asset manager being used by this view object.
273
     *
274
     * @return AssetManager the asset manager. Defaults to the "assetManager" application component.
275
     */
276 33
    public function getAssetManager(): AssetManager
277
    {
278 33
        return $this->assetManager;
279
    }
280
281
    /**
282
     * Sets the asset manager.
283
     *
284
     * @param AssetManager $value the asset manager
285
     *
286
     * @return void
287
     */
288 55
    public function setAssetManager(AssetManager $value): void
289
    {
290 55
        $this->assetManager = $value;
291
    }
292
293
    /**
294
     * Clears up the registered meta tags, link tags, css/js scripts and files.
295
     *
296
     * @return void
297
     */
298 31
    public function clear(): void
299
    {
300 31
        $this->metaTags = [];
301 31
        $this->linkTags = [];
302 31
        $this->css = [];
303 31
        $this->cssFiles = [];
304 31
        $this->js = [];
305 31
        $this->jsFiles = [];
306 31
        $this->assetBundles = [];
307
    }
308
309
    /**
310
     * Registers all files provided by an asset bundle including depending bundles files.
311
     *
312
     * Removes a bundle from {@see assetBundles} once files are registered.
313
     *
314
     * @param string $name name of the bundle to register
315
     *
316
     * @return void
317
     */
318 8
    protected function registerAssetFiles(string $name): void
319
    {
320 8
        if (!isset($this->assetBundles[$name])) {
321 7
            return;
322
        }
323
324 8
        $bundle = $this->assetBundles[$name];
325
326 8
        if ($bundle) {
0 ignored issues
show
introduced by
$bundle is of type Yiisoft\Asset\AssetBundle, thus it always evaluated to true.
Loading history...
327 8
            foreach ($bundle->depends as $dep) {
328 7
                $this->registerAssetFiles($dep);
329
            }
330 8
            $bundle->registerAssetFiles($this);
331
        }
332
333 8
        unset($this->assetBundles[$name]);
334
    }
335
336
    /**
337
     * Registers the named asset bundle.
338
     *
339
     * All dependent asset bundles will be registered.
340
     *
341
     * @param string $name the class name of the asset bundle (without the leading backslash)
342
     * @param int|null $position if set, this forces a minimum position for javascript files. This will adjust depending
343
     * assets javascript file position or fail if requirement can not be met. If this is null, asset
344
     * bundles position settings will not be changed.
345
     *
346
     * {@see registerJsFile()} for more details on javascript position.
347
     *
348
     * @throws \RuntimeException if the asset bundle does not exist or a circular dependency is detected
349
     *
350
     * @return AssetBundle the registered asset bundle instance
351
     */
352 12
    public function registerAssetBundle(string $name, ?int $position = null): AssetBundle
353
    {
354 12
        if (!isset($this->assetBundles[$name])) {
355 12
            $bundle = $this->getAssetManager()->getBundle($name);
356
357 12
            $this->assetBundles[$name] = false;
358
359
            // register dependencies
360
361 12
            $pos = $bundle->jsOptions['position'] ?? null;
362
363 12
            foreach ($bundle->depends as $dep) {
364 10
                $this->registerAssetBundle($dep, $pos);
365
            }
366
367 11
            $this->assetBundles[$name] = $bundle;
368 8
        } elseif ($this->assetBundles[$name] === false) {
0 ignored issues
show
introduced by
The condition $this->assetBundles[$name] === false is always false.
Loading history...
369 1
            throw new \RuntimeException("A circular dependency is detected for bundle '$name'.");
370
        } else {
371 7
            $bundle = $this->assetBundles[$name];
372
        }
373
374 11
        if ($position !== null) {
375 8
            $pos = $bundle->jsOptions['position'] ?? null;
376
377 8
            if ($pos === null) {
378 8
                $bundle->jsOptions['position'] = $pos = $position;
379 2
            } elseif ($pos > $position) {
380 2
                throw new \RuntimeException("An asset bundle that depends on '$name' has a higher javascript file position configured than '$name'.");
381
            }
382
383
            // update position for all dependencies
384 8
            foreach ($bundle->depends as $dep) {
385 6
                $this->registerAssetBundle($dep, $pos);
386
            }
387
        }
388
389 11
        return $bundle;
390
    }
391
392
    /**
393
     * Registers a meta tag.
394
     *
395
     * For example, a description meta tag can be added like the following:
396
     *
397
     * ```php
398
     * $view->registerMetaTag([
399
     *     'name' => 'description',
400
     *     'content' => 'This website is about funny raccoons.'
401
     * ]);
402
     * ```
403
     *
404
     * will result in the meta tag `<meta name="description" content="This website is about funny raccoons.">`.
405
     *
406
     * @param array $options the HTML attributes for the meta tag.
407
     * @param string $key the key that identifies the meta tag. If two meta tags are registered with the same key, the
408
     * latter will overwrite the former. If this is null, the new meta tag will be appended to the
409
     * existing ones.
410
     *
411
     * @return void
412
     */
413
    public function registerMetaTag(array $options, string $key = null): void
414
    {
415
        if ($key === null) {
416
            $this->metaTags[] = Html::tag('meta', '', $options);
417
        } else {
418
            $this->metaTags[$key] = Html::tag('meta', '', $options);
419
        }
420
    }
421
422
    /**
423
     * Registers a link tag.
424
     *
425
     * For example, a link tag for a custom [favicon](http://www.w3.org/2005/10/howto-favicon) can be added like the
426
     * following:
427
     *
428
     * ```php
429
     * $view->registerLinkTag(['rel' => 'icon', 'type' => 'image/png', 'href' => '/myicon.png']);
430
     * ```
431
     *
432
     * which will result in the following HTML: `<link rel="icon" type="image/png" href="/myicon.png">`.
433
     *
434
     * **Note:** To register link tags for CSS stylesheets, use [[registerCssFile()]] instead, which has more options
435
     * for this kind of link tag.
436
     *
437
     * @param array $options the HTML attributes for the link tag.
438
     * @param string|null $key the key that identifies the link tag. If two link tags are registered with the same
439
     * key, the latter will overwrite the former. If this is null, the new link tag will be appended
440
     * to the existing ones.
441
     */
442
    public function registerLinkTag(array $options, ?string $key = null): void
443
    {
444
        if ($key === null) {
445
            $this->linkTags[] = Html::tag('link', '', $options);
446
        } else {
447
            $this->linkTags[$key] = Html::tag('link', '', $options);
448
        }
449
    }
450
451
    /**
452
     * Registers CSRF meta tags.
453
     *
454
     * They are rendered dynamically to retrieve a new CSRF token for each request.
455
     *
456
     * ```php
457
     * $view->registerCsrfMetaTags();
458
     * ```
459
     *
460
     * The above code will result in `<meta name="csrf-param" content="[Yiisoft\Web\Request::$csrfParam]">` and
461
     * `<meta name="csrf-token" content="tTNpWKpdy-bx8ZmIq9R72...K1y8IP3XGkzZA==">` added to the page.
462
     *
463
     * Note: Hidden CSRF input of ActiveForm will be automatically refreshed by calling `window.yii.refreshCsrfToken()`
464
     * from `yii.js`.
465
     */
466
    public function registerCsrfMetaTags(): void
467
    {
468
        $this->metaTags['csrf_meta_tags'] = $this->renderDynamic('return Yiisoft\Html\Html::csrfMetaTags();');
469
    }
470
471
    /**
472
     * Registers a CSS code block.
473
     *
474
     * @param string $css the content of the CSS code block to be registered
475
     * @param array $options the HTML attributes for the `<style>`-tag.
476
     * @param string $key the key that identifies the CSS code block. If null, it will use $css as the key. If two CSS
477
     * code blocks are registered with the same key, the latter will overwrite the former.
478
     */
479
    public function registerCss(string $css, array $options = [], string $key = null): void
480
    {
481
        $key = $key ?: md5($css);
482
        $this->css[$key] = Html::style($css, $options);
483
    }
484
485
    /**
486
     * Registers a CSS file.
487
     *
488
     * This method should be used for simple registration of CSS files. If you want to use features of
489
     * {@see AssetManager} like appending timestamps to the URL and file publishing options, use {@see AssetBundle}
490
     * and {@see registerAssetBundle()} instead.
491
     *
492
     * @param string $url the CSS file to be registered.
493
     * @param array $options the HTML attributes for the link tag. Please refer to {@see \Yiisoft\Html\Html::cssFile()}
494
     * for the supported options. The following options are specially handled and are not treated as HTML
495
     * attributes:
496
     *
497
     *   - `depends`: array, specifies the names of the asset bundles that this CSS file depends on.
498
     *
499
     * @param string $key the key that identifies the CSS script file. If null, it will use $url as the key. If two CSS
500
     * files are registered with the same key, the latter will overwrite the former.
501
     *
502
     * @return void
503
     */
504 18
    public function registerCssFile(string $url, array $options = [], string $key = null): void
505
    {
506 18
        $key = $key ?: $url;
507
508 18
        $depends = ArrayHelper::remove($options, 'depends', []);
509
510 18
        if (empty($depends)) {
511 18
            $this->cssFiles[$key] = Html::cssFile($url, $options);
512
        } else {
513
            $bundle = $this->createBundle([
514
                'baseUrl' => '',
515
                'css' => [strncmp($url, '//', 2) === 0 ? $url : ltrim($url, '/')],
516
                'cssOptions' => $options,
517
                'depends' => (array) $depends,
518
            ]);
519
            $bundles = [$key => $bundle];
0 ignored issues
show
Unused Code introduced by
The assignment to $bundles is dead and can be removed.
Loading history...
520
521
            $this->registerAssetBundle($key);
522
        }
523
    }
524
525
    /**
526
     * Registers a JS code block.
527
     *
528
     * @param string $js the JS code block to be registered
529
     * @param int $position the position at which the JS script tag should be inserted in a page.
530
     *
531
     * The possible values are:
532
     *
533
     * - [[POS_HEAD]]: in the head section
534
     * - [[POS_BEGIN]]: at the beginning of the body section
535
     * - [[POS_END]]: at the end of the body section. This is the default value.
536
     * - [[POS_LOAD]]: executed when HTML page is completely loaded.
537
     * - [[POS_READY]]: executed when HTML document composition is ready.
538
     *
539
     * @param string $key the key that identifies the JS code block. If null, it will use $js as the key. If two JS code
540
     * blocks are registered with the same key, the latter will overwrite the former.
541
     *
542
     * @return void
543
     */
544 1
    public function registerJs(string $js, int $position = self::POS_END, string $key = null): void
545
    {
546 1
        $key = $key ?: md5($js);
547 1
        $this->js[$position][$key] = $js;
548
    }
549
550
    /**
551
     * Registers a JS file.
552
     *
553
     * This method should be used for simple registration of JS files. If you want to use features of
554
     * {@see AssetManager} like appending timestamps to the URL and file publishing options, use {@see AssetBundle}
555
     * and {@see registerAssetBundle()} instead.
556
     *
557
     * @param string $url the JS file to be registered.
558
     * @param array $options the HTML attributes for the script tag. The following options are specially handled and
559
     * are not treated as HTML attributes:
560
     *
561
     * - `depends`: array, specifies the names of the asset bundles that this JS file depends on.
562
     * - `position`: specifies where the JS script tag should be inserted in a page. The possible values are:
563
     *     * [[POS_HEAD]]: in the head section
564
     *     * [[POS_BEGIN]]: at the beginning of the body section
565
     *     * [[POS_END]]: at the end of the body section. This is the default value.
566
     *
567
     * Please refer to {@see \Yiisoft\Html\Html::jsFile()} for other supported options.
568
     *
569
     * @param string $key the key that identifies the JS script file. If null, it will use $url as the key. If two JS
570
     * files are registered with the same key at the same position, the latter will overwrite the former.
571
     * Note that position option takes precedence, thus files registered with the same key, but different
572
     * position option will not override each other.
573
     *
574
     * @return void
575
     */
576 19
    public function registerJsFile(string $url, array $options = [], string $key = null): void
577
    {
578 19
        $key = $key ?: $url;
579
580 19
        $depends = ArrayHelper::remove($options, 'depends', []);
581
582 19
        if (empty($depends)) {
583 19
            $position = ArrayHelper::remove($options, 'position', self::POS_END);
584 19
            $this->jsFiles[$position][$key] = Html::jsFile($url, $options);
585
        } else {
586
            $bundle = $this->createBundle([
587
                'baseUrl' => '',
588
                'js' => [strncmp($url, '//', 2) === 0 ? $url : ltrim($url, '/')],
589
                'jsOptions' => $options,
590
                'depends' => (array) $depends,
591
            ]);
592
            $bundles = [$key => $bundle];
0 ignored issues
show
Unused Code introduced by
The assignment to $bundles is dead and can be removed.
Loading history...
593
            $this->registerAssetBundle($key);
594
        }
595
    }
596
597
    /**
598
     * Registers a JS code block defining a variable. The name of variable will be used as key, preventing duplicated
599
     * variable names.
600
     *
601
     * @param string $name Name of the variable
602
     * @param array|string $value Value of the variable
603
     * @param int $position the position in a page at which the JavaScript variable should be inserted.
604
     *
605
     * The possible values are:
606
     *
607
     * - [[POS_HEAD]]: in the head section. This is the default value.
608
     * - [[POS_BEGIN]]: at the beginning of the body section.
609
     * - [[POS_END]]: at the end of the body section.
610
     * - [[POS_LOAD]]: enclosed within jQuery(window).load().
611
     *   Note that by using this position, the method will automatically register the jQuery js file.
612
     * - [[POS_READY]]: enclosed within jQuery(document).ready().
613
     *   Note that by using this position, the method will automatically register the jQuery js file.
614
     */
615 1
    public function registerJsVar(string $name, $value, int $position = self::POS_HEAD): void
616
    {
617 1
        $js = sprintf('var %s = %s;', $name, \Yiisoft\Json\Json::htmlEncode($value));
618 1
        $this->registerJs($js, $position, $name);
619
    }
620
621
    /**
622
     * Renders the content to be inserted in the head section.
623
     *
624
     * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files.
625
     *
626
     * @return string the rendered content
627
     */
628 31
    protected function renderHeadHtml(): string
629
    {
630 31
        $lines = [];
631 31
        if (!empty($this->metaTags)) {
632
            $lines[] = implode("\n", $this->metaTags);
633
        }
634
635 31
        if (!empty($this->linkTags)) {
636
            $lines[] = implode("\n", $this->linkTags);
637
        }
638 31
        if (!empty($this->cssFiles)) {
639 18
            $lines[] = implode("\n", $this->cssFiles);
640
        }
641 31
        if (!empty($this->css)) {
642
            $lines[] = implode("\n", $this->css);
643
        }
644 31
        if (!empty($this->jsFiles[self::POS_HEAD])) {
645 3
            $lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]);
646
        }
647 31
        if (!empty($this->js[self::POS_HEAD])) {
648 1
            $lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]));
649
        }
650
651 31
        return empty($lines) ? '' : implode("\n", $lines);
652
    }
653
654
    /**
655
     * Renders the content to be inserted at the beginning of the body section.
656
     *
657
     * The content is rendered using the registered JS code blocks and files.
658
     *
659
     * @return string the rendered content
660
     */
661 31
    protected function renderBodyBeginHtml(): string
662
    {
663 31
        $lines = [];
664 31
        if (!empty($this->jsFiles[self::POS_BEGIN])) {
665 3
            $lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]);
666
        }
667 31
        if (!empty($this->js[self::POS_BEGIN])) {
668
            $lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]));
669
        }
670
671 31
        return empty($lines) ? '' : implode("\n", $lines);
672
    }
673
674
    /**
675
     * Renders the content to be inserted at the end of the body section.
676
     *
677
     * The content is rendered using the registered JS code blocks and files.
678
     *
679
     * @param bool $ajaxMode whether the view is rendering in AJAX mode. If true, the JS scripts registered at
680
     * [[POS_READY]] and [[POS_LOAD]] positions will be rendered at the end of the view like normal scripts.
681
     *
682
     * @return string the rendered content
683
     */
684 31
    protected function renderBodyEndHtml(bool $ajaxMode): string
685
    {
686 31
        $lines = [];
687
688 31
        if (!empty($this->jsFiles[self::POS_END])) {
689 15
            $lines[] = implode("\n", $this->jsFiles[self::POS_END]);
690
        }
691
692 31
        if ($ajaxMode) {
693
            $scripts = [];
694
            if (!empty($this->js[self::POS_END])) {
695
                $scripts[] = implode("\n", $this->js[self::POS_END]);
696
            }
697
            if (!empty($this->js[self::POS_READY])) {
698
                $scripts[] = implode("\n", $this->js[self::POS_READY]);
699
            }
700
            if (!empty($this->js[self::POS_LOAD])) {
701
                $scripts[] = implode("\n", $this->js[self::POS_LOAD]);
702
            }
703
            if (!empty($scripts)) {
704
                $lines[] = Html::script(implode("\n", $scripts));
705
            }
706
        } else {
707 31
            if (!empty($this->js[self::POS_END])) {
708
                $lines[] = Html::script(implode("\n", $this->js[self::POS_END]));
709
            }
710 31
            if (!empty($this->js[self::POS_READY])) {
711
                $js = "document.addEventListener('DOMContentLoaded', function(event) {\n" . implode("\n", $this->js[self::POS_READY]) . "\n});";
712
                $lines[] = Html::script($js, ['type' => 'text/javascript']);
713
            }
714 31
            if (!empty($this->js[self::POS_LOAD])) {
715
                $js = "window.addEventListener('load', function (event) {\n" . implode("\n", $this->js[self::POS_LOAD]) . "\n});";
716
                $lines[] = Html::script($js, ['type' => 'text/javascript']);
717
            }
718
        }
719
720 31
        return empty($lines) ? '' : implode("\n", $lines);
721
    }
722
723
    /**
724
     * @param array $options
725
     *
726
     * @return AssetBundle
727
     */
728
    private function createBundle(array $options): AssetBundle
729
    {
730
        $bundle = new AssetBundle();
731
        $bundle->baseUrl = $options['baseUrl'];
732
        $bundle->js = $options['js'];
733
        $bundle->jsOptions = $options['jsOptions'];
734
        $bundle->depends = $options['depends'];
735
736
        return $bundle;
737
    }
738
}
739