Passed
Push — webp ( c9ef11 )
by Arnaud
03:49
created

Extension::html()   C

Complexity

Conditions 13
Paths 24

Size

Total Lines 70
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 15.8486

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 13
eloc 48
c 2
b 0
f 0
nc 24
nop 3
dl 0
loc 70
ccs 29
cts 39
cp 0.7436
crap 15.8486
rs 6.6166

How to fix   Long Method    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
 * This file is part of the Cecil/Cecil package.
4
 *
5
 * Copyright (c) Arnaud Ligny <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Cecil\Renderer\Twig;
12
13
use Cecil\Assets\Asset;
14
use Cecil\Assets\Cache;
15
use Cecil\Assets\Image;
16
use Cecil\Assets\Url;
17
use Cecil\Builder;
18
use Cecil\Collection\CollectionInterface;
19
use Cecil\Collection\Page\Collection as PagesCollection;
20
use Cecil\Collection\Page\Page;
21
use Cecil\Config;
22
use Cecil\Converter\Parsedown;
23
use Cecil\Exception\Exception;
24
use Cocur\Slugify\Bridge\Twig\SlugifyExtension;
25
use Cocur\Slugify\Slugify;
26
use MatthiasMullie\Minify;
27
use ScssPhp\ScssPhp\Compiler;
28
29
/**
30
 * Class Twig\Extension.
31
 */
32
class Extension extends SlugifyExtension
33
{
34
    /** @var Builder */
35
    protected $builder;
36
37
    /** @var Config */
38
    protected $config;
39
40
    /** @var Slugify */
41
    private static $slugifier;
42
43 1
    public function __construct(Builder $builder)
44
    {
45 1
        if (!self::$slugifier instanceof Slugify) {
46 1
            self::$slugifier = Slugify::create(['regexp' => Page::SLUGIFY_PATTERN]);
47
        }
48
49 1
        parent::__construct(self::$slugifier);
50
51 1
        $this->builder = $builder;
52 1
        $this->config = $this->builder->getConfig();
53 1
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58
    public function getName()
59
    {
60
        return 'cecil';
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 1
    public function getFilters()
67
    {
68
        return [
69 1
            new \Twig\TwigFilter('filter_by', [$this, 'filterBy']),
70
            // sort
71 1
            new \Twig\TwigFilter('sort_by_title', [$this, 'sortByTitle']),
72 1
            new \Twig\TwigFilter('sort_by_weight', [$this, 'sortByWeight']),
73 1
            new \Twig\TwigFilter('sort_by_date', [$this, 'sortByDate']),
74
            // assets
75 1
            new \Twig\TwigFilter('url', [$this, 'url']),
76 1
            new \Twig\TwigFilter('html', [$this, 'html']),
77 1
            new \Twig\TwigFilter('inline', [$this, 'inline']),
78 1
            new \Twig\TwigFilter('fingerprint', [$this, 'fingerprint']),
79 1
            new \Twig\TwigFilter('to_css', [$this, 'toCss']),
80 1
            new \Twig\TwigFilter('minify', [$this, 'minify']),
81 1
            new \Twig\TwigFilter('minify_css', [$this, 'minifyCss']),
82 1
            new \Twig\TwigFilter('minify_js', [$this, 'minifyJs']),
83 1
            new \Twig\TwigFilter('scss_to_css', [$this, 'scssToCss']),
84 1
            new \Twig\TwigFilter('sass_to_css', [$this, 'scssToCss']),
85 1
            new \Twig\TwigFilter('resize', [$this, 'resize']),
86 1
            new \Twig\TwigFilter('dataurl', [$this, 'dataurl']),
87
            // content
88 1
            new \Twig\TwigFilter('slugify', [$this, 'slugifyFilter']),
89 1
            new \Twig\TwigFilter('excerpt', [$this, 'excerpt']),
90 1
            new \Twig\TwigFilter('excerpt_html', [$this, 'excerptHtml']),
91 1
            new \Twig\TwigFilter('markdown_to_html', [$this, 'markdownToHtml']),
92 1
            new \Twig\TwigFilter('json_decode', [$this, 'jsonDecode']),
93 1
            new \Twig\TwigFilter('preg_split', [$this, 'pregSplit']),
94 1
            new \Twig\TwigFilter('preg_match_all', [$this, 'pregMatchAll']),
95
            // deprecated
96 1
            new \Twig\TwigFilter(
97 1
                'filterBySection',
98 1
                [$this, 'filterBySection'],
99 1
                ['deprecated' => true, 'alternative' => 'filter_by']
100
            ),
101 1
            new \Twig\TwigFilter(
102 1
                'filterBy',
103 1
                [$this, 'filterBy'],
104 1
                ['deprecated' => true, 'alternative' => 'filter_by']
105
            ),
106 1
            new \Twig\TwigFilter(
107 1
                'sortByTitle',
108 1
                [$this, 'sortByTitle'],
109 1
                ['deprecated' => true, 'alternative' => 'sort_by_title']
110
            ),
111 1
            new \Twig\TwigFilter(
112 1
                'sortByWeight',
113 1
                [$this, 'sortByWeight'],
114 1
                ['deprecated' => true, 'alternative' => 'sort_by_weight']
115
            ),
116 1
            new \Twig\TwigFilter(
117 1
                'sortByDate',
118 1
                [$this, 'sortByDate'],
119 1
                ['deprecated' => true, 'alternative' => 'sort_by_date']
120
            ),
121 1
            new \Twig\TwigFilter(
122 1
                'minifyCSS',
123 1
                [$this, 'minifyCss'],
124 1
                ['deprecated' => true, 'alternative' => 'minifyCss']
125
            ),
126 1
            new \Twig\TwigFilter(
127 1
                'minifyJS',
128 1
                [$this, 'minifyJs'],
129 1
                ['deprecated' => true, 'alternative' => 'minifyJs']
130
            ),
131 1
            new \Twig\TwigFilter(
132 1
                'SCSStoCSS',
133 1
                [$this, 'scssToCss'],
134 1
                ['deprecated' => true, 'alternative' => 'scss_to_css']
135
            ),
136 1
            new \Twig\TwigFilter(
137 1
                'excerptHtml',
138 1
                [$this, 'excerptHtml'],
139 1
                ['deprecated' => true, 'alternative' => 'excerpt_html']
140
            ),
141 1
            new \Twig\TwigFilter(
142 1
                'urlize',
143 1
                [$this, 'slugifyFilter'],
144 1
                ['deprecated' => true, 'alternative' => 'slugify']
145
            ),
146
        ];
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152 1
    public function getFunctions()
153
    {
154
        return [
155
            // assets
156 1
            new \Twig\TwigFunction('url', [$this, 'url']),
157 1
            new \Twig\TwigFunction('asset', [$this, 'asset']),
158 1
            new \Twig\TwigFunction('integrity', [$this, 'integrity']),
159
            // content
160 1
            new \Twig\TwigFunction('readtime', [$this, 'readtime']),
161
            // others
162 1
            new \Twig\TwigFunction('getenv', [$this, 'getEnv']),
163
            // deprecated
164 1
            new \Twig\TwigFunction(
165 1
                'minify',
166 1
                [$this, 'minify'],
167 1
                ['deprecated' => true, 'alternative' => 'minify filter']
168
            ),
169 1
            new \Twig\TwigFunction(
170 1
                'toCSS',
171 1
                [$this, 'toCss'],
172 1
                ['deprecated' => true, 'alternative' => 'to_css filter']
173
            ),
174 1
            new \Twig\TwigFunction(
175 1
                'hash',
176 1
                [$this, 'integrity'],
177 1
                ['deprecated' => true, 'alternative' => 'integrity']
178
            ),
179
        ];
180
    }
181
182
    /**
183
     * Filters by Section.
184
     * Alias of `filterBy('section', $value)`.
185
     */
186
    public function filterBySection(PagesCollection $pages, string $section): CollectionInterface
187
    {
188
        return $this->filterBy($pages, 'section', $section);
189
    }
190
191
    /**
192
     * Filters by variable's name/value.
193
     */
194 1
    public function filterBy(PagesCollection $pages, string $variable, string $value): CollectionInterface
195
    {
196
        $filteredPages = $pages->filter(function (Page $page) use ($variable, $value) {
197 1
            $notVirtual = false;
198 1
            if (!$page->isVirtual()) {
199 1
                $notVirtual = true;
200
            }
201
            // is a dedicated getter exists?
202 1
            $method = 'get'.ucfirst($variable);
203 1
            if (method_exists($page, $method) && $page->$method() == $value) {
204
                return $notVirtual && true;
205
            }
206 1
            if ($page->getVariable($variable) == $value) {
207 1
                return $notVirtual && true;
208
            }
209 1
        });
210
211 1
        return $filteredPages;
212
    }
213
214
    /**
215
     * Sorts by title.
216
     */
217 1
    public function sortByTitle(\Traversable $collection): array
218
    {
219 1
        $collection = iterator_to_array($collection);
220 1
        array_multisort(array_keys($collection), SORT_NATURAL | SORT_FLAG_CASE, $collection);
0 ignored issues
show
Bug introduced by
Cecil\Renderer\Twig\SORT...rer\Twig\SORT_FLAG_CASE cannot be passed to array_multisort() as the parameter $rest expects a reference. ( Ignorable by Annotation )

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

220
        array_multisort(array_keys($collection), /** @scrutinizer ignore-type */ SORT_NATURAL | SORT_FLAG_CASE, $collection);
Loading history...
Bug introduced by
array_keys($collection) cannot be passed to array_multisort() as the parameter $array expects a reference. ( Ignorable by Annotation )

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

220
        array_multisort(/** @scrutinizer ignore-type */ array_keys($collection), SORT_NATURAL | SORT_FLAG_CASE, $collection);
Loading history...
221
222 1
        return $collection;
223
    }
224
225
    /**
226
     * Sorts by weight.
227
     */
228 1
    public function sortByWeight(\Traversable $collection): array
229
    {
230
        $callback = function ($a, $b) {
231 1
            if (!isset($a['weight'])) {
232 1
                $a['weight'] = 0;
233
            }
234 1
            if (!isset($b['weight'])) {
235 1
                $a['weight'] = 0;
236
            }
237 1
            if ($a['weight'] == $b['weight']) {
238 1
                return 0;
239
            }
240
241 1
            return ($a['weight'] < $b['weight']) ? -1 : 1;
242 1
        };
243
244 1
        $collection = iterator_to_array($collection);
245 1
        usort($collection, $callback);
246
247 1
        return $collection;
248
    }
249
250
    /**
251
     * Sorts by date: the most recent first.
252
     */
253 1
    public function sortByDate(\Traversable $collection): array
254
    {
255
        $callback = function ($a, $b) {
256 1
            if ($a['date'] == $b['date']) {
257 1
                return 0;
258
            }
259
260 1
            return ($a['date'] > $b['date']) ? -1 : 1;
261 1
        };
262
263 1
        $collection = iterator_to_array($collection);
264 1
        usort($collection, $callback);
265
266 1
        return $collection;
267
    }
268
269
    /**
270
     * Creates an URL.
271
     *
272
     * $options[
273
     *     'canonical' => true,
274
     *     'addhash'   => false,
275
     *     'format'    => 'json',
276
     * ];
277
     *
278
     * @param Page|Asset|string|null $value
279
     * @param array|null             $options
280
     *
281
     * @return mixed
282
     */
283 1
    public function url($value = null, array $options = null)
284
    {
285 1
        return new Url($this->builder, $value, $options);
286
    }
287
288
    /**
289
     * Creates an asset (CSS, JS, images, etc.).
290
     *
291
     * @param string|array $path    File path (relative from static/ dir).
292
     * @param array|null   $options
293
     *
294
     * @return Asset
295
     */
296 1
    public function asset($path, array $options = null): Asset
297
    {
298 1
        return new Asset($this->builder, $path, $options);
299
    }
300
301
    /**
302
     * Compiles a SCSS asset.
303
     *
304
     * @param string|Asset $asset
305
     *
306
     * @return Asset
307
     */
308 1
    public function toCss($asset): Asset
309
    {
310 1
        if (!$asset instanceof Asset) {
311
            $asset = new Asset($this->builder, $asset);
312
        }
313
314 1
        return $asset->compile();
315
    }
316
317
    /**
318
     * Minifying an asset (CSS or JS).
319
     *
320
     * @param string|Asset $asset
321
     *
322
     * @return Asset
323
     */
324 1
    public function minify($asset): Asset
325
    {
326 1
        if (!$asset instanceof Asset) {
327
            $asset = new Asset($this->builder, $asset);
328
        }
329
330 1
        return $asset->minify();
331
    }
332
333
    /**
334
     * Fingerprinting an asset.
335
     *
336
     * @param string|Asset $asset
337
     *
338
     * @return Asset
339
     */
340 1
    public function fingerprint($asset): Asset
341
    {
342 1
        if (!$asset instanceof Asset) {
343
            $asset = new Asset($this->builder, $asset);
344
        }
345
346 1
        return $asset->fingerprint();
347
    }
348
349
    /**
350
     * Resizes an image.
351
     *
352
     * @param string|Asset $asset
353
     *
354
     * @return Asset
355
     */
356
    public function resize($asset, int $size): Asset
357
    {
358
        if (!$asset instanceof Asset) {
359
            $asset = new Asset($this->builder, $asset);
360
        }
361
362
        return $asset->resize($size);
363
    }
364
365
    /**
366
     * Returns the data URL of an image.
367
     *
368
     * @param string|Asset $asset
369
     *
370
     * @return string
371
     */
372
    public function dataurl($asset): string
373
    {
374
        if (!$asset instanceof Asset) {
375
            $asset = new Asset($this->builder, $asset);
376
        }
377
378
        return $asset->dataurl();
379
    }
380
381
    /**
382
     * Hashing an asset with algo (sha384 by default).
383
     *
384
     * @param string|Asset $path
385
     * @param string       $algo
386
     *
387
     * @return string
388
     */
389 1
    public function integrity($asset, string $algo = 'sha384'): string
390
    {
391 1
        if (!$asset instanceof Asset) {
392 1
            $asset = new Asset($this->builder, $asset);
393
        }
394
395 1
        return $asset->getIntegrity($algo);
396
    }
397
398
    /**
399
     * Minifying a CSS string.
400
     */
401 1
    public function minifyCss(string $value): string
402
    {
403 1
        if ($this->builder->isDebug()) {
404 1
            return $value;
405
        }
406
407
        $cache = new Cache($this->builder);
408
        $cacheKey = $cache->createKeyFromString($value);
409
        if (!$cache->has($cacheKey)) {
410
            $minifier = new Minify\CSS($value);
411
            $value = $minifier->minify();
412
            $cache->set($cacheKey, $value);
413
        }
414
415
        return $cache->get($cacheKey, $value);
416
    }
417
418
    /**
419
     * Minifying a JavaScript string.
420
     */
421 1
    public function minifyJs(string $value): string
422
    {
423 1
        if ($this->builder->isDebug()) {
424 1
            return $value;
425
        }
426
427
        $cache = new Cache($this->builder);
428
        $cacheKey = $cache->createKeyFromString($value);
429
        if (!$cache->has($cacheKey)) {
430
            $minifier = new Minify\JS($value);
431
            $value = $minifier->minify();
432
            $cache->set($cacheKey, $value);
433
        }
434
435
        return $cache->get($cacheKey, $value);
436
    }
437
438
    /**
439
     * Compiles a SCSS string.
440
     */
441 1
    public function scssToCss(string $value): string
442
    {
443 1
        $cache = new Cache($this->builder);
444 1
        $cacheKey = $cache->createKeyFromString($value);
445 1
        if (!$cache->has($cacheKey)) {
446 1
            $scssPhp = new Compiler();
447 1
            $outputStyles = ['expanded', 'compressed'];
448 1
            $outputStyle = strtolower((string) $this->config->get('assets.compile.style'));
449 1
            if (!in_array($outputStyle, $outputStyles)) {
450
                throw new Exception(\sprintf('Scss output style "%s" doesn\'t exists.', $outputStyle));
451
            }
452 1
            $scssPhp->setOutputStyle($outputStyle);
453 1
            $variables = $this->config->get('assets.compile.variables') ?? [];
454 1
            if (!empty($variables)) {
455 1
                $variables = array_map('ScssPhp\ScssPhp\ValueConverter::parseValue', $variables);
456 1
                $scssPhp->replaceVariables($variables);
457
            }
458 1
            $value = $scssPhp->compileString($value)->getCss();
459 1
            $cache->set($cacheKey, $value);
460
        }
461
462 1
        return $cache->get($cacheKey, $value);
463
    }
464
465
    /**
466
     * Returns the HTML version of an asset.
467
     *
468
     * $options[
469
     *     'preload'    => false,
470
     *     'responsive' => false,
471
     * ];
472
     */
473 1
    public function html(Asset $asset, array $attributes = [], array $options = []): string
474
    {
475 1
        $htmlAttributes = '';
476 1
        $preload = false;
477 1
        $responsive = $this->config->get('assets.images.responsive.enabled') ?? false;
478 1
        $webp = $this->config->get('assets.images.webp.enabled') ?? false;
479
        extract($options, EXTR_IF_EXISTS);
480 1
481 1
        foreach ($attributes as $name => $value) {
482 1
            $attribute = \sprintf(' %s="%s"', $name, $value);
483 1
            if (empty($value)) {
484
                $attribute = \sprintf(' %s', $name);
485 1
            }
486
            $htmlAttributes .= $attribute;
487
        }
488 1
489 1
        switch ($asset['ext']) {
490 1
            case 'css':
491 1
                if ($preload) {
492 1
                    return \sprintf(
493
                        '<link href="%s" rel="preload" as="style" onload="this.onload=null;this.rel=\'stylesheet\'"%s>
494 1
                         <noscript><link rel="stylesheet" href="%1$s"%2$s></noscript>',
495 1
                        $this->url($asset['path'], $options),
496
                        $htmlAttributes
497
                    );
498
                }
499 1
500 1
                return \sprintf('<link rel="stylesheet" href="%s"%s>', $this->url($asset['path'], $options), $htmlAttributes);
501
            case 'js':
502
                return \sprintf('<script src="%s"%s></script>', $this->url($asset['path'], $options), $htmlAttributes);
503
        }
504 1
505 1
        if ($asset['type'] == 'image') {
506
            if ($responsive && $srcset = Image::getSrcset(
507
                $asset,
508
                $this->config->get('assets.images.responsive.width.steps') ?? 5,
509 1
                $this->config->get('assets.images.responsive.width.min') ?? 320,
510
                $this->config->get('assets.images.responsive.width.max') ?? 1280
511
            )) {
512
                $htmlAttributes .= \sprintf(' srcset="%s"', $srcset);
513
                $htmlAttributes .= \sprintf(' sizes="%s"', $this->config->get('assets.images.responsive.sizes.default') ?? '100vw');
514
            }
515 1
516 1
            $img = \sprintf(
517 1
                '<img src="%s" width="'.($asset->getWidth() ?: 0).'" height="'.($asset->getHeight() ?: 0).'"%s>',
518 1
                $this->url($asset['path'], $options),
519
                $htmlAttributes
520
            );
521
522
            if ($webp) {
523
                $assetWebp = Image::convertTopWebp($asset, $this->config->get('assets.images.quality') ?? 85);
524
                $srcset = Image::getSrcset(
525
                    $assetWebp,
526
                    $this->config->get('assets.images.responsive.width.steps') ?? 5,
527
                    $this->config->get('assets.images.responsive.width.min') ?? 320,
528 1
                    $this->config->get('assets.images.responsive.width.max') ?? 1280
529
                ) ?: (string) $assetWebp;
530 1
                $source = \sprintf(
531
                    '<source type="image/webp" srcset="%s" sizes="%s">',
532
                    $srcset,
533
                    $this->config->get('assets.images.responsive.sizes.default') ?? '100vw'
534 1
                );
535
536
                return \sprintf("<picture>\n  %s\n  %s\n</picture>", $source, $img);
537
            }
538
539
            return $img;
540
        }
541
542
        throw new Exception(\sprintf('%s is available with CSS, JS and images files only.', '"html" filter'));
543
    }
544
545
    /**
546
     * Returns the content of an asset.
547
     */
548
    public function inline(Asset $asset): string
549
    {
550
        if (is_null($asset['content'])) {
551
            throw new Exception(\sprintf('%s is available with CSS et JS files only.', '"inline" filter'));
552
        }
553
554
        return $asset['content'];
555 1
    }
556
557
    /**
558 1
     * Reads $length first characters of a string and adds a suffix.
559 1
     */
560 1
    public function excerpt(string $string = null, int $length = 450, string $suffix = ' …'): ?string
561 1
    {
562
        $string = str_replace('</p>', '<br /><br />', $string);
563
        $string = trim(strip_tags($string, '<br>'), '<br />');
564 1
        if (mb_strlen($string) > $length) {
565
            $string = mb_substr($string, 0, $length);
566
            $string .= $suffix;
567
        }
568
569
        return $string;
570 1
    }
571
572
    /**
573 1
     * Reads characters before '<!-- excerpt|break -->'.
574 1
     */
575
    public function excerptHtml(string $string): string
576
    {
577
        // https://regex101.com/r/Xl7d5I/3
578
        $pattern = '(.*)(<!--[[:blank:]]?(excerpt|break)[[:blank:]]?-->)(.*)';
579 1
        preg_match('/'.$pattern.'/is', $string, $matches);
580
        if (empty($matches)) {
581
            return $string;
582
        }
583
584
        return trim($matches[1]);
585
    }
586
587
    /**
588
     * Converts a Markdown string to HTML.
589
     */
590
    public function markdownToHtml(string $markdown): ?string
591
    {
592
        try {
593
            $parsedown = new Parsedown($this->builder);
594
            $html = $parsedown->text($markdown);
595
        } catch (\Exception $e) {
596
            throw new Exception('"markdown_to_html" filter can not convert supplied Markdown.');
597
        }
598
599
        return $html;
600
    }
601
602
    /**
603
     * Converts a JSON string to an array.
604
     */
605
    public function jsonDecode(string $json): ?array
606
    {
607
        try {
608
            $array = json_decode($json, true);
609
            if ($array === null && json_last_error() !== JSON_ERROR_NONE) {
610
                throw new \Exception('Error');
611
            }
612
        } catch (\Exception $e) {
613
            throw new Exception('"json_decode" filter can not parse supplied JSON.');
614
        }
615
616
        return $array;
617
    }
618
619
    /**
620
     * Split a string into an array using a regular expression.
621
     */
622
    public function pregSplit(string $value, string $pattern, int $limit = 0): ?array
623
    {
624
        try {
625
            $array = preg_split($pattern, $value, $limit);
626
            if ($array === false) {
627
                throw new \Exception('Error');
628
            }
629
        } catch (\Exception $e) {
630
            throw new Exception('"preg_split" filter can not split supplied string.');
631
        }
632
633
        return $array;
634
    }
635
636
    /**
637
     * Perform a regular expression match and return the group for all matches.
638
     */
639
    public function pregMatchAll(string $value, string $pattern, int $group = 0): ?array
640
    {
641
        try {
642
            $array = preg_match_all($pattern, $value, $matches, PREG_PATTERN_ORDER);
643
            if ($array === false) {
644
                throw new \Exception('Error');
645
            }
646
        } catch (\Exception $e) {
647
            throw new Exception('"preg_match_all" filter can not match in supplied string.');
648
        }
649
650 1
        return $matches[$group];
651
    }
652 1
653
    /**
654
     * Calculates estimated time to read a text.
655
     */
656
    public function readtime(string $text): string
657
    {
658
        $words = str_word_count(strip_tags($text));
659
        $min = floor($words / 200);
660
        if ($min === 0) {
661
            return '1';
662
        }
663
664
        return (string) $min;
665
    }
666
667
    /**
668
     * Gets the value of an environment variable.
669
     */
670
    public function getEnv(string $var): ?string
671
    {
672
        return getenv($var) ?: null;
673
    }
674
}
675