Passed
Push — links ( 61dbca )
by Arnaud
05:23
created

Parsedown::inlineLink()   F

Complexity

Conditions 23
Paths 1190

Size

Total Lines 141
Code Lines 86

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 23
eloc 86
c 1
b 0
f 1
nc 1190
nop 1
dl 0
loc 141
rs 0

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
declare(strict_types=1);
4
5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Cecil\Converter;
15
16
use Cecil\Assets\Asset;
17
use Cecil\Assets\Image;
18
use Cecil\Builder;
19
use Cecil\Exception\RuntimeException;
20
use Cecil\Util;
21
use Highlight\Highlighter;
22
23
class Parsedown extends \ParsedownToC
24
{
25
    /** @var Builder */
26
    protected $builder;
27
28
    /** @var \Cecil\Config */
29
    protected $config;
30
31
    /** {@inheritdoc} */
32
    protected $regexAttribute = '(?:[#.][-\w:\\\]+[ ]*|[-\w:\\\]+(?:=(?:["\'][^\n]*?["\']|[^\s]+)?)?[ ]*)';
33
34
    /** Regex who's looking for images */
35
    protected $regexImage = "~^!\[.*?\]\(.*?\)~";
36
37
    /** @var Highlighter */
38
    protected $highlighter;
39
40
    public function __construct(Builder $builder, ?array $options = null)
41
    {
42
        $this->builder = $builder;
43
        $this->config = $builder->getConfig();
44
45
        // "insert" line block: ++text++ -> <ins>text</ins>
46
        $this->InlineTypes['+'][] = 'Insert';
47
        $this->inlineMarkerList = implode('', array_keys($this->InlineTypes));
48
        $this->specialCharacters[] = '+';
49
50
        // Image block (to avoid paragraph)
51
        $this->BlockTypes['!'][] = 'Image';
52
53
        // "notes" block
54
        $this->BlockTypes[':'][] = 'Note';
55
56
        // code highlight
57
        $this->highlighter = new Highlighter();
58
59
        // options
60
        $options = array_merge(['selectors' => (array) $this->config->get('pages.body.toc')], $options ?? []);
61
62
        parent::__construct($options);
63
    }
64
65
    /**
66
     * Insert inline.
67
     * e.g.: ++text++ -> <ins>text</ins>.
68
     */
69
    protected function inlineInsert($Excerpt)
70
    {
71
        if (!isset($Excerpt['text'][1])) {
72
            return;
73
        }
74
75
        if ($Excerpt['text'][1] === '+' && preg_match('/^\+\+(?=\S)(.+?)(?<=\S)\+\+/', $Excerpt['text'], $matches)) {
76
            return [
77
                'extent'  => \strlen($matches[0]),
78
                'element' => [
79
                    'name'    => 'ins',
80
                    'text'    => $matches[1],
81
                    'handler' => 'line',
82
                ],
83
            ];
84
        }
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    protected function inlineLink($Excerpt)
91
    {
92
        $link = parent::inlineLink($Excerpt);
93
94
        if (!isset($link)) {
95
            return null;
96
        }
97
98
        // Link to a page with "page:page_id" as URL
99
        if (Util\Str::startsWith($link['element']['attributes']['href'], 'page:')) {
100
            $link['element']['attributes']['href'] = new \Cecil\Assets\Url($this->builder, substr($link['element']['attributes']['href'], 5, \strlen($link['element']['attributes']['href'])));
101
102
            return $link;
103
        }
104
105
        // External link
106
        if (str_starts_with($link['element']['attributes']['href'], 'http') && !str_starts_with($link['element']['attributes']['href'], (string) $this->config->get('baseurl'))) {
107
            if ($this->config->get('pages.body.links.external.blank') ?? false) {
108
                $link['element']['attributes']['target'] = '_blank';
109
            }
110
            if (!\array_key_exists('rel', $link['element']['attributes'])) {
111
                $link['element']['attributes']['rel'] = '';
112
            }
113
            if ($this->config->get('pages.body.links.external.noopener') ?? true) {
114
                $link['element']['attributes']['rel'] .= ' noopener';
115
            }
116
            if ($this->config->get('pages.body.links.external.noreferrer') ?? true) {
117
                $link['element']['attributes']['rel'] .= ' noreferrer';
118
            }
119
            if ($this->config->get('pages.body.links.external.nofollow') ?? true) {
120
                $link['element']['attributes']['rel'] .= ' nofollow';
121
            }
122
            $link['element']['attributes']['rel'] = trim($link['element']['attributes']['rel']);
123
        }
124
125
        /*
126
         * Embed link?
127
         */
128
        $embed = false;
129
        $embed = (bool) $this->config->get('pages.body.links.embed.enabled') ?? false;
130
        if (isset($link['element']['attributes']['embed'])) {
131
            $embed = true;
132
            if ($link['element']['attributes']['embed'] == 'false') {
133
                $embed = false;
134
            }
135
            unset($link['element']['attributes']['embed']);
136
        }
137
        // video or audio?
138
        $extension = pathinfo($link['element']['attributes']['href'], PATHINFO_EXTENSION);
139
        if (\in_array($extension, (array) $this->config->get('pages.body.links.embed.video.ext'))) {
140
            if (!$embed) {
141
                $link['element']['attributes']['href'] = (string) new Asset($this->builder, $link['element']['attributes']['href'], ['force_slash' => false]);
142
143
                return $link;
144
            }
145
            $video = $this->createMediaFromLink($link, 'video');
146
            if ((bool) $this->config->get('pages.body.images.caption.enabled')) {
147
                return $this->createFigure($video);
148
            }
149
150
            return $video;
151
        }
152
        if (\in_array($extension, (array) $this->config->get('pages.body.links.embed.audio.ext'))) {
153
            if (!$embed) {
154
                $link['element']['attributes']['href'] = (string) new Asset($this->builder, $link['element']['attributes']['href'], ['force_slash' => false]);
155
156
                return $link;
157
            }
158
            $audio = $this->createMediaFromLink($link, 'audio');
159
            if ((bool) $this->config->get('pages.body.images.caption.enabled')) {
160
                return $this->createFigure($audio);
161
            }
162
163
            return $audio;
164
        }
165
        if (!$embed) {
166
            return $link;
167
        }
168
        // GitHub Gist link?
169
        // https://regex101.com/r/QmCiAL/1
170
        $pattern = 'https:\/\/gist\.github.com\/[-a-zA-Z0-9_]+\/[-a-zA-Z0-9_]+';
171
        if (preg_match('/' . $pattern . '/is', (string) $link['element']['attributes']['href'], $matches)) {
172
            $gist = [
173
                'extent'  => $link['extent'],
174
                'element' => [
175
                    'name'       => 'script',
176
                    'text'       => $link['element']['text'],
177
                    'attributes' => [
178
                        'src'   => $matches[0] . '.js',
179
                        'title' => $link['element']['attributes']['title'],
180
                    ],
181
                ],
182
            ];
183
            if ((bool) $this->config->get('pages.body.images.caption.enabled')) {
184
                return $this->createFigure($gist);
185
            }
186
187
            return $gist;
188
        }
189
        // Youtube link?
190
        // https://regex101.com/r/gznM1j/1
191
        $pattern = '(?:https?:\/\/)?(?:www\.)?youtu(?:\.be\/|be.com\/\S*(?:watch|embed)(?:(?:(?=\/[-a-zA-Z0-9_]{11,}(?!\S))\/)|(?:\S*v=|v\/)))([-a-zA-Z0-9_]{11,})';
192
        if (preg_match('/' . $pattern . '/is', (string) $link['element']['attributes']['href'], $matches)) {
193
            $iframe = [
194
                'element' => [
195
                    'name'       => 'iframe',
196
                    'text'       => $link['element']['text'],
197
                    'attributes' => [
198
                        'width'           => '560',
199
                        'height'          => '315',
200
                        'title'           => $link['element']['text'],
201
                        'src'             => 'https://www.youtube.com/embed/' . $matches[1],
202
                        'frameborder'     => '0',
203
                        'allow'           => 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture',
204
                        'allowfullscreen' => '',
205
                        'style'           => 'position:absolute; top:0; left:0; width:100%; height:100%; border:0',
206
                    ],
207
                ],
208
            ];
209
            $youtube = [
210
                'extent'  => $link['extent'],
211
                'element' => [
212
                    'name'    => 'div',
213
                    'handler' => 'elements',
214
                    'text'    => [
215
                        $iframe['element'],
216
                    ],
217
                    'attributes' => [
218
                        'style' => 'position:relative; padding-bottom:56.25%; height:0; overflow:hidden',
219
                        'title' => $link['element']['attributes']['title'],
220
                    ],
221
                ],
222
            ];
223
            if ((bool) $this->config->get('pages.body.images.caption.enabled')) {
224
                return $this->createFigure($youtube);
225
            }
226
227
            return $youtube;
228
        }
229
230
        return $link;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236
    protected function inlineImage($Excerpt)
237
    {
238
        $InlineImage = parent::inlineImage($Excerpt);
239
        if (!isset($InlineImage)) {
240
            return null;
241
        }
242
243
        // normalize path
244
        $InlineImage['element']['attributes']['src'] = $this->normalizePath($InlineImage['element']['attributes']['src']);
245
246
        // should be lazy loaded?
247
        if ((bool) $this->config->get('pages.body.images.lazy.enabled') && !isset($InlineImage['element']['attributes']['loading'])) {
248
            $InlineImage['element']['attributes']['loading'] = 'lazy';
249
        }
250
        // should be decoding async?
251
        if ((bool) $this->config->get('pages.body.images.decoding.enabled') && !isset($InlineImage['element']['attributes']['decoding'])) {
252
            $InlineImage['element']['attributes']['decoding'] = 'async';
253
        }
254
        // add default class?
255
        if ((string) $this->config->get('pages.body.images.class')) {
256
            if (!\array_key_exists('class', $InlineImage['element']['attributes'])) {
257
                $InlineImage['element']['attributes']['class'] = '';
258
            }
259
            $InlineImage['element']['attributes']['class'] .= ' ' . (string) $this->config->get('pages.body.images.class');
260
            $InlineImage['element']['attributes']['class'] = trim($InlineImage['element']['attributes']['class']);
261
        }
262
263
        // disable remote image handling?
264
        if (Util\Url::isUrl($InlineImage['element']['attributes']['src']) && !(bool) $this->config->get('pages.body.images.remote.enabled') ?? true) {
265
            return $InlineImage;
266
        }
267
268
        // create asset
269
        $assetOptions = ['force_slash' => false];
270
        if ((bool) $this->config->get('pages.body.images.remote.fallback.enabled')) {
271
            $assetOptions += ['remote_fallback' => (string) $this->config->get('pages.body.images.remote.fallback.path')];
272
        }
273
        $asset = new Asset($this->builder, $InlineImage['element']['attributes']['src'], $assetOptions);
274
        $InlineImage['element']['attributes']['src'] = $asset;
275
        $width = $asset['width'];
276
277
        /*
278
         * Should be resized?
279
         */
280
        $shouldResize = false;
281
        $assetResized = null;
282
        if (
283
            (bool) $this->config->get('pages.body.images.resize.enabled')
284
            && isset($InlineImage['element']['attributes']['width'])
285
            && (int) $InlineImage['element']['attributes']['width'] < $width
286
        ) {
287
            $shouldResize = true;
288
            $width = (int) $InlineImage['element']['attributes']['width'];
289
        }
290
        if (
291
            !$shouldResize
292
            && (bool) $this->config->get('body.images.responsive.enabled')
293
            && max($this->config->getAssetsImagesWidths()) < $width
294
        ) {
295
            $shouldResize = true;
296
            $width = max($this->config->getAssetsImagesWidths());
297
        }
298
        if ($shouldResize) {
299
            try {
300
                $assetResized = $asset->resize($width);
301
                $InlineImage['element']['attributes']['src'] = $assetResized;
302
            } catch (\Exception $e) {
303
                $this->builder->getLogger()->debug($e->getMessage());
304
305
                return $InlineImage;
306
            }
307
        }
308
309
        // set width
310
        if (!isset($InlineImage['element']['attributes']['width'])) {
311
            $InlineImage['element']['attributes']['width'] = $width;
312
        }
313
        // set height
314
        if (!isset($InlineImage['element']['attributes']['height'])) {
315
            $InlineImage['element']['attributes']['height'] = $assetResized['height'] ?? $asset['height'];
316
        }
317
318
        // placeholder
319
        if (
320
            (!empty($this->config->get('pages.body.images.placeholder')) || isset($InlineImage['element']['attributes']['placeholder']))
321
            && \in_array($InlineImage['element']['attributes']['src']['subtype'], ['image/jpeg', 'image/png', 'image/gif'])
322
        ) {
323
            if (!\array_key_exists('placeholder', $InlineImage['element']['attributes'])) {
324
                $InlineImage['element']['attributes']['placeholder'] = (string) $this->config->get('pages.body.images.placeholder');
325
            }
326
            if (!\array_key_exists('style', $InlineImage['element']['attributes'])) {
327
                $InlineImage['element']['attributes']['style'] = '';
328
            }
329
            $InlineImage['element']['attributes']['style'] = trim($InlineImage['element']['attributes']['style'], ';');
330
            switch ($InlineImage['element']['attributes']['placeholder']) {
331
                case 'color':
332
                    $InlineImage['element']['attributes']['style'] .= sprintf(';max-width:100%%;height:auto;background-color:%s;', Image::getDominantColor($InlineImage['element']['attributes']['src']));
333
                    break;
334
                case 'lqip':
335
                    $InlineImage['element']['attributes']['style'] .= sprintf(';max-width:100%%;height:auto;background-image:url(%s);background-repeat:no-repeat;background-position:center;background-size:cover;', Image::getLqip($InlineImage['element']['attributes']['src']));
336
                    break;
337
            }
338
            unset($InlineImage['element']['attributes']['placeholder']);
339
            $InlineImage['element']['attributes']['style'] = trim($InlineImage['element']['attributes']['style']);
340
        }
341
342
        /*
343
         * Should be responsive?
344
         */
345
        $sizes = '';
346
        if ((bool) $this->config->get('pages.body.images.responsive.enabled')) {
347
            try {
348
                if (
349
                    $srcset = Image::buildSrcset(
350
                        $assetResized ?? $asset,
351
                        $this->config->getAssetsImagesWidths()
352
                    )
353
                ) {
354
                    $InlineImage['element']['attributes']['srcset'] = $srcset;
355
                    $sizes = Image::getSizes($InlineImage['element']['attributes']['class'] ?? '', (array) $this->config->getAssetsImagesSizes());
356
                    $InlineImage['element']['attributes']['sizes'] = $sizes;
357
                }
358
            } catch (\Exception $e) {
359
                $this->builder->getLogger()->debug($e->getMessage());
360
            }
361
        }
362
363
        /*
364
        <!-- if title: a <figure> is required to put in it a <figcaption> -->
365
        <figure>
366
            <!-- if WebP is enabled: a <picture> is required for the WebP <source> -->
367
            <picture>
368
                <source type="image/webp"
369
                    srcset="..."
370
                    sizes="..."
371
                >
372
                <img src="..."
373
                    srcset="..."
374
                    sizes="..."
375
                >
376
            </picture>
377
            <figcaption><!-- title --></figcaption>
378
        </figure>
379
        */
380
381
        $image = $InlineImage;
382
383
        // converts image (JPEG, PNG or GIF) to WebP and put it in picture > source
384
        if (
385
            ((bool) $this->config->get('pages.body.images.webp.enabled') ?? false)
386
            && \in_array($InlineImage['element']['attributes']['src']['subtype'], ['image/jpeg', 'image/png', 'image/gif'])
387
        ) {
388
            try {
389
                // InlineImage src must be an Asset instance
390
                if (!$InlineImage['element']['attributes']['src'] instanceof Asset) {
391
                    throw new RuntimeException(sprintf('Asset "%s" can\'t be converted to WebP.', $InlineImage['element']['attributes']['src']));
392
                }
393
                // abord if InlineImage is an animated GIF
394
                if (Image::isAnimatedGif($InlineImage['element']['attributes']['src'])) {
395
                    throw new RuntimeException(sprintf('Asset "%s" is an animated GIF and can\'t be converted to WebP.', $InlineImage['element']['attributes']['src']));
396
                }
397
                $assetWebp = $InlineImage['element']['attributes']['src']->webp();
398
                $srcset = '';
399
                // build responsives WebP?
400
                if ((bool) $this->config->get('pages.body.images.responsive.enabled')) {
401
                    try {
402
                        $srcset = Image::buildSrcset(
403
                            $assetWebp,
404
                            $this->config->getAssetsImagesWidths()
405
                        );
406
                    } catch (\Exception $e) {
407
                        $this->builder->getLogger()->debug($e->getMessage());
408
                    }
409
                }
410
                // if not, default image as srcset
411
                if (empty($srcset)) {
412
                    $srcset = (string) $assetWebp;
413
                }
414
                $picture = [
415
                    'extent'  => $InlineImage['extent'],
416
                    'element' => [
417
                        'name'       => 'picture',
418
                        'handler'    => 'elements',
419
                        'attributes' => [
420
                            'title' => $image['element']['attributes']['title'],
421
                        ],
422
                    ],
423
                ];
424
                $source = [
425
                    'element' => [
426
                        'name'       => 'source',
427
                        'attributes' => [
428
                            'type'   => 'image/webp',
429
                            'srcset' => $srcset,
430
                            'sizes'  => $sizes,
431
                            'width'  => $InlineImage['element']['attributes']['width'],
432
                            'height' => $InlineImage['element']['attributes']['height'],
433
                        ],
434
                    ],
435
                ];
436
                $picture['element']['text'][] = $source['element'];
437
                unset($image['element']['attributes']['title']);
438
                $picture['element']['text'][] = $image['element'];
439
                $image = $picture;
440
            } catch (\Exception $e) {
441
                $this->builder->getLogger()->debug($e->getMessage());
442
            }
443
        }
444
445
        // if title: put the <img> (or <picture>) in a <figure> and create a <figcaption>
446
        if ((bool) $this->config->get('pages.body.images.caption.enabled')) {
447
            return $this->createFigure($image);
448
        }
449
450
        return $image;
451
    }
452
453
    /**
454
     * Image block.
455
     */
456
    protected function blockImage($Excerpt)
457
    {
458
        if (1 !== preg_match($this->regexImage, $Excerpt['text'])) {
459
            return;
460
        }
461
462
        $InlineImage = $this->inlineImage($Excerpt);
463
        if (!isset($InlineImage)) {
464
            return;
465
        }
466
467
        return $InlineImage;
468
    }
469
470
    /**
471
     * Note block-level markup.
472
     *
473
     * :::tip
474
     * **Tip:** This is an advice.
475
     * :::
476
     *
477
     * Code inspired by https://github.com/sixlive/parsedown-alert from TJ Miller (@sixlive).
478
     */
479
    protected function blockNote($block)
480
    {
481
        if (preg_match('/:::(.*)/', $block['text'], $matches)) {
482
            $block = [
483
                'char'    => ':',
484
                'element' => [
485
                    'name'       => 'aside',
486
                    'text'       => '',
487
                    'attributes' => [
488
                        'class' => 'note',
489
                    ],
490
                ],
491
            ];
492
            if (!empty($matches[1])) {
493
                $block['element']['attributes']['class'] .= " note-{$matches[1]}";
494
            }
495
496
            return $block;
497
        }
498
    }
499
500
    protected function blockNoteContinue($line, $block)
501
    {
502
        if (isset($block['complete'])) {
503
            return;
504
        }
505
        if (preg_match('/:::/', $line['text'])) {
506
            $block['complete'] = true;
507
508
            return $block;
509
        }
510
        $block['element']['text'] .= $line['text'] . "\n";
511
512
        return $block;
513
    }
514
515
    protected function blockNoteComplete($block)
516
    {
517
        $block['element']['rawHtml'] = $this->text($block['element']['text']);
518
        unset($block['element']['text']);
519
520
        return $block;
521
    }
522
523
    /**
524
     * Apply Highlight to code blocks.
525
     */
526
    protected function blockFencedCodeComplete($block)
527
    {
528
        if (!(bool) $this->config->get('pages.body.highlight.enabled')) {
529
            return $block;
530
        }
531
        if (!isset($block['element']['text']['attributes'])) {
532
            return $block;
533
        }
534
535
        try {
536
            $code = $block['element']['text']['text'];
537
            $languageClass = $block['element']['text']['attributes']['class'];
538
            $language = explode('-', $languageClass);
539
            $highlighted = $this->highlighter->highlight($language[1], $code);
540
            $block['element']['text']['attributes']['class'] = vsprintf('%s hljs %s', [
541
                $languageClass,
542
                $highlighted->language,
543
            ]);
544
            $block['element']['text']['rawHtml'] = $highlighted->value;
545
            $block['element']['text']['allowRawHtmlInSafeMode'] = true;
546
            unset($block['element']['text']['text']);
547
        } catch (\Exception $e) {
548
            $this->builder->getLogger()->debug($e->getMessage());
549
        } finally {
550
            return $block;
551
        }
552
    }
553
554
    /**
555
     * {@inheritdoc}
556
     */
557
    protected function parseAttributeData($attributeString)
558
    {
559
        $attributes = preg_split('/[ ]+/', $attributeString, -1, PREG_SPLIT_NO_EMPTY);
560
        $Data = [];
561
        $HtmlAtt = [];
562
563
        foreach ($attributes as $attribute) {
564
            switch ($attribute[0]) {
565
                case '#': // ID
566
                    $Data['id'] = substr($attribute, 1);
567
                    break;
568
                case '.': // Classes
569
                    $classes[] = substr($attribute, 1);
570
                    break;
571
                default:  // Attributes
572
                    parse_str($attribute, $parsed);
573
                    $HtmlAtt = array_merge($HtmlAtt, $parsed);
574
            }
575
        }
576
577
        if (isset($classes)) {
578
            $Data['class'] = implode(' ', $classes);
579
        }
580
        if (!empty($HtmlAtt)) {
581
            foreach ($HtmlAtt as $a => $v) {
582
                $Data[$a] = trim($v, '"');
583
            }
584
        }
585
586
        return $Data;
587
    }
588
589
    /**
590
     * Turns a path relative to static or assets into a website relative path.
591
     *
592
     *   "../../assets/images/img.jpeg"
593
     *   ->
594
     *   "/images/img.jpeg"
595
     */
596
    private function normalizePath(string $path): string
597
    {
598
        // https://regex101.com/r/Rzguzh/1
599
        $pattern = sprintf(
600
            '(\.\.\/)+(\b%s|%s\b)+(\/.*)',
601
            (string) $this->config->get('static.dir'),
602
            (string) $this->config->get('assets.dir')
603
        );
604
        $path = Util::joinPath($path);
605
        if (!preg_match('/' . $pattern . '/is', $path, $matches)) {
606
            return $path;
607
        }
608
609
        return $matches[3];
610
    }
611
612
    /**
613
     * Create a media (video or audio) element from a link.
614
     */
615
    private function createMediaFromLink(array $link, string $type = 'video'): array
616
    {
617
        $block = [
618
            'extent'  => $link['extent'],
619
            'element' => [
620
                'text' => $link['element']['text'],
621
            ],
622
        ];
623
        $block['element']['attributes'] = $link['element']['attributes'];
624
        unset($block['element']['attributes']['href']);
625
        $block['element']['attributes']['src'] = (string) new Asset($this->builder, $link['element']['attributes']['href'], ['force_slash' => false]);
626
        switch ($type) {
627
            case 'video':
628
                $block['element']['name'] = 'video';
629
                if (!isset($block['element']['attributes']['controls'])) {
630
                    $block['element']['attributes']['autoplay'] = '';
631
                    $block['element']['attributes']['loop'] = '';
632
                }
633
                if (isset($block['element']['attributes']['poster'])) {
634
                    $block['element']['attributes']['poster'] = (string) new Asset($this->builder, $block['element']['attributes']['poster'], ['force_slash' => false]);
635
                }
636
637
                return $block;
638
            case 'audio':
639
                $block['element']['name'] = 'audio';
640
641
                return $block;
642
        }
643
644
        throw new \Exception(sprintf('Can\'t create %s from "%s".', $type, $link['element']['attributes']['href']));
645
    }
646
647
    /**
648
     * Create a figure / caption element.
649
     */
650
    private function createFigure(array $inline): array
651
    {
652
        if (empty($inline['element']['attributes']['title'])) {
653
            return $inline;
654
        }
655
656
        $titleRawHtml = $this->line($inline['element']['attributes']['title']);
657
        $inline['element']['attributes']['title'] = strip_tags($titleRawHtml);
658
659
        $figcaption = [
660
            'element' => [
661
                'name'                   => 'figcaption',
662
                'allowRawHtmlInSafeMode' => true,
663
                'rawHtml'                => $titleRawHtml,
664
            ],
665
        ];
666
        $figure = [
667
            'extent'  => $inline['extent'],
668
            'element' => [
669
                'name'    => 'figure',
670
                'handler' => 'elements',
671
                'text'    => [
672
                    $inline['element'],
673
                    $figcaption['element'],
674
                ],
675
            ],
676
        ];
677
678
        return $figure;
679
    }
680
}
681