Passed
Push — feature/6.x ( 53903f...32797a )
by Schlaefer
05:38 queued 02:15
created

Embed::_parse()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 28
nc 3
nop 3
dl 0
loc 50
rs 8.8497
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * Saito - The Threaded Web Forum
6
 *
7
 * @copyright Copyright (c) the Saito Project Developers
8
 * @link https://github.com/Schlaefer/Saito
9
 * @license http://opensource.org/licenses/MIT
10
 */
11
12
namespace BbcodeParser\Lib\jBBCode\Definitions;
13
14
use Cake\Cache\Cache;
15
use BbcodeParser\Lib\Helper\Message;
16
use BbcodeParser\Lib\Helper\UrlParserTrait;
17
use Saito\DomainParser;
18
19
/**
20
 * Class Email handles [email][email protected][/email]
21
 *
22
 * @package Saito\Jbb\CodeDefinition
23
 */
24
//@codingStandardsIgnoreStart
25
class Email extends CodeDefinition
26
//@codingStandardsIgnoreEnd
27
{
28
    use UrlParserTrait;
29
30
    protected $_sParseContent = false;
31
32
    protected $_sTagName = 'email';
33
34
    /**
35
     * {@inheritDoc}
36
     */
37
    protected function _parse($url, $attributes, \JBBCode\ElementNode $node)
38
    {
39
        return $this->_email($url);
40
    }
41
}
42
43
/**
44
 * Class EmailWithAttributes handles [[email protected]]foobar[/email]
45
 *
46
 * @package Saito\Jbb\CodeDefinition
47
 */
48
//@codingStandardsIgnoreStart
49
class EmailWithAttributes extends Email
50
//@codingStandardsIgnoreEnd
51
{
52
    protected $_sUseOptions = true;
53
54
    /**
55
     * {@inheritDoc}
56
     */
57
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
58
    {
59
        return $this->_email($attributes['email'], $content);
60
    }
61
}
62
63
//@codingStandardsIgnoreStart
64
class Embed extends CodeDefinition
65
//@codingStandardsIgnoreEnd
66
{
67
    protected $_sTagName = 'embed';
68
69
    protected $_sParseContent = false;
70
71
    /**
72
     * {@inheritDoc}
73
     */
74
    protected function _parse($url, $attributes, \JBBCode\ElementNode $node)
75
    {
76
        if (!$this->_sOptions->get('content_embed_active')) {
77
            if (!$this->_sOptions->get('autolink')) {
78
                return $url;
79
            }
80
81
            return $this->Html->link($url, $url, ['target' => '_blank']);
82
        }
83
84
        $loader = function () use ($url) {
85
            $embed = ['url' => $url];
86
87
            try {
88
                $info = \Embed\Embed::create(
89
                    $url,
90
                    [
91
                    'min_image_width' => 100,
92
                    'min_image_height' => 100,
93
                    ]
94
                );
95
96
                $embed = [
97
                    'html' => $info->code,
98
                    'providerIcon' => $info->providerIcon,
99
                    'providerName' => $info->providerName,
100
                    'providerUrl' => $info->providerUrl,
101
                    'title' => $info->title,
102
                    'url' => $info->url ?? $url,
103
                ];
104
105
                if ($this->_sOptions->get('content_embed_text')) {
106
                    $embed['description'] = $info->description;
107
                }
108
109
                if ($this->_sOptions->get('content_embed_media')) {
110
                    $embed['image'] = $info->image;
111
                }
112
            } catch (\Throwable $e) {
113
            }
114
115
            return $embed;
116
        };
117
118
        $callable = \Closure::fromCallable($loader);
119
120
        $uid = 'embed-' . md5($url);
121
        $info = Cache::remember($uid, $callable, 'bbcodeParserEmbed');
122
123
        return $this->_sHelper->Html->div('js-embed', '', ['id' => $uid, 'data-embed' => json_encode($info)]);
124
    }
125
}
126
127
//@codingStandardsIgnoreStart
128
class Iframe extends CodeDefinition
129
//@codingStandardsIgnoreEnd
130
{
131
    protected $_sTagName = 'iframe';
132
133
    protected $_sParseContent = false;
134
135
    protected $_sUseOptions = true;
136
137
    /**
138
     * Array with domains from which embedding video is allowed
139
     *
140
     * array(
141
     *  'youtube' => 1,
142
     *  'vimeo' => 1,
143
     * );
144
     *
145
     * array('*' => 1) means every domain allowed
146
     *
147
     * @var array
148
     */
149
    protected $_allowedVideoDomains = null;
150
151
    /**
152
     * {@inheritDoc}
153
     */
154
    protected function _parse($url, $attributes, \JBBCode\ElementNode $node)
155
    {
156
        if (empty($attributes['src'])) {
157
            return false;
158
        }
159
160
        unset($attributes['iframe']);
161
162
        $allowed = $this->_checkHostAllowed($attributes['src']);
163
        if ($allowed !== true) {
164
            return $allowed;
165
        }
166
167
        if (strpos($attributes['src'], '?') === false) {
168
            $attributes['src'] .= '?';
169
        }
170
        $attributes['src'] .= '&amp;wmode=Opaque';
171
172
        $atrStr = '';
173
        foreach ($attributes as $attributeName => $attributeValue) {
174
            $atrStr .= "$attributeName=\"$attributeValue\" ";
175
        }
176
        $atrStr = rtrim($atrStr);
177
178
        $html = <<<eof
179
<div class="embed-responsive embed-responsive-16by9">
180
    <iframe class="embed-responsive-item" {$atrStr}></iframe>
181
</div>
182
eof;
183
184
        return $html;
185
    }
186
187
    /**
188
     * get allowed domains
189
     *
190
     * @return array
191
     */
192
    protected function _allowedDomains()
193
    {
194
        if ($this->_allowedVideoDomains !== null) {
195
            return $this->_allowedVideoDomains;
196
        }
197
198
        $ad = explode('|', $this->_sOptions->get('video_domains_allowed'));
199
        $trim = function ($v) {
200
            return trim($v);
201
        };
202
        $this->_allowedVideoDomains = array_fill_keys(array_map($trim, $ad), 1);
203
204
        return $this->_allowedVideoDomains;
205
    }
206
207
    /**
208
     * Check host allowed
209
     *
210
     * @param string $url url
211
     *
212
     * @return bool|string
213
     */
214
    protected function _checkHostAllowed($url)
215
    {
216
        $allowedDomains = $this->_allowedDomains();
217
        if (empty($allowedDomains)) {
218
            return false;
219
        }
220
221
        if ($allowedDomains === ['*' => 1]) {
222
            return true;
223
        }
224
225
        $host = DomainParser::domain($url);
226
        if ($host && isset($allowedDomains[$host])) {
227
            return true;
228
        }
229
230
        $message = sprintf(
231
            __('Domain <strong>%s</strong> not allowed for embedding video.'),
232
            $host
233
        );
234
235
        return Message::format($message);
236
    }
237
}
238
239
//@codingStandardsIgnoreStart
240
class Flash extends Iframe
241
//@codingStandardsIgnoreEnd
242
{
243
    protected $_sTagName = 'flash_video';
244
245
    protected $_sParseContent = false;
246
247
    protected $_sUseOptions = false;
248
249
    protected static $_flashVideoDomainsWithHttps = [
250
        'vimeo' => 1,
251
        'youtube' => 1,
252
    ];
253
254
    /**
255
     * {@inheritDoc}
256
     */
257
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
258
    {
259
        $match = preg_match(
260
            "#(?P<url>.+?)\|(?P<width>.+?)\|(?<height>\d+)#is",
261
            $content,
262
            $matches
263
        );
264
        if (!$match) {
265
            return Message::format(__('No Flash detected.'));
266
        }
267
268
        $height = $matches['height'];
269
        $url = $matches['url'];
270
        $width = $matches['width'];
271
272
        $allowed = $this->_checkHostAllowed($url);
273
        if ($allowed !== true) {
274
            return $allowed;
275
        }
276
277
        if (env('HTTPS')) {
278
            $host = DomainParser::domain($url);
279
            if (isset(self::$_flashVideoDomainsWithHttps[$host])) {
280
                $url = str_ireplace('http://', 'https://', $url);
281
            }
282
        }
283
284
        $out = <<<EOF
285
<object
286
    classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
287
    width="$width"
288
    height="$height">
289
        <param name="movie" value="$url"></param>
290
        <embed src="$url"
291
            width="$width"
292
            height="$height"
293
            type="application/x-shockwave-flash"
294
            wmode="opaque"
295
            style="width:${width}px; height:${height}px;"
296
            id="VideoPlayback"
297
            flashvars="">
298
        </embed>
299
</object>
300
EOF;
301
302
        return $out;
303
    }
304
}
305
306
//@codingStandardsIgnoreStart
307
class FileWithAttributes extends CodeDefinition
308
//@codingStandardsIgnoreEnd
309
{
310
    use UrlParserTrait;
311
312
    protected $_sTagName = 'file';
313
314
    protected $_sParseContent = false;
315
316
    protected $_sUseOptions = true;
317
318
    /**
319
     * {@inheritDoc}
320
     */
321
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
322
    {
323
        if (empty($attributes['src']) || $attributes['src'] !== 'upload') {
324
            $message = sprintf(__('File not allowed.'));
325
326
            return Message::format($message);
327
        }
328
329
        $url = $this->_linkToUploadedFile($content);
330
331
        return $this->_sHelper->Html->link($content, $url, ['target' => '_blank']);
332
    }
333
}
334
335
//@codingStandardsIgnoreStart
336
class Image extends CodeDefinition
337
//@codingStandardsIgnoreEnd
338
{
339
    use UrlParserTrait;
340
341
    protected $_sTagName = 'img';
342
343
    protected $_sParseContent = false;
344
345
    /**
346
     * {@inheritDoc}
347
     */
348
    protected function _parse($url, $attributes, \JBBCode\ElementNode $node)
349
    {
350
        // image is internaly uploaded
351
        if (!empty($attributes['src']) && $attributes['src'] === 'upload') {
352
            $url = $this->_linkToUploadedFile($url);
353
        }
354
355
        // process [img=(parameters)]
356
        $options = [];
357
        if (!empty($attributes['img'])) {
358
            $default = trim($attributes['img']);
359
            switch ($default) {
360
                default:
361
                    preg_match(
362
                        '/(\d{0,3})(?:x(\d{0,3}))?/i',
363
                        $default,
364
                        $dimension
365
                    );
366
                    // $dimension for [img=50] or [img=50x100]
367
                    // [0] (50) or (50x100)
368
                    // [1] (50)
369
                    // [2] (100)
370
                    if (!empty($dimension[1])) {
371
                        $options['width'] = $dimension[1];
372
                        if (!empty($dimension[2])) {
373
                            $options['height'] = $dimension[2];
374
                        }
375
                    }
376
            }
377
        }
378
379
        $url = $this->_urlToHttps($url);
380
        $image = $this->Html->image($url, $options);
381
382
        if ($node->getParent()->getTagName() === 'Document') {
383
            $image = $this->_sHelper->Html->link(
384
                $image,
385
                $url,
386
                ['escape' => false, 'target' => '_blank']
387
            );
388
        }
389
390
        return $image;
391
    }
392
}
393
394
//@codingStandardsIgnoreStart
395
class ImageWithAttributes extends Image
396
//@codingStandardsIgnoreEnd
397
{
398
    protected $_sUseOptions = true;
399
}
400
401
/**
402
 * Class UlList handles [list][*]…[/list]
403
 *
404
 * @see https://gist.github.com/jbowens/5646994
405
 * @package Saito\Jbb\CodeDefinition
406
 */
407
//@codingStandardsIgnoreStart
408
class UlList extends CodeDefinition
409
//@codingStandardsIgnoreEnd
410
{
411
    protected $_sTagName = 'list';
412
413
    /**
414
     * {@inheritDoc}
415
     */
416
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
417
    {
418
        $listPieces = explode('[*]', $content);
419
        unset($listPieces[0]);
420
        $listPieceProcessor = function ($li) {
421
            return '<li>' . $li . '</li>' . "\n";
422
        };
423
        $listPieces = array_map($listPieceProcessor, $listPieces);
424
425
        return '<ul>' . implode('', $listPieces) . '</ul>';
426
    }
427
}
428
429
//@codingStandardsIgnoreStart
430
class Spoiler extends CodeDefinition
431
//@codingStandardsIgnoreEnd
432
{
433
    protected $_sTagName = 'spoiler';
434
435
    /**
436
     * {@inheritDoc}
437
     */
438
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
439
    {
440
        $length = mb_strlen(strip_tags($content));
441
        $minLenght = mb_strlen(__('Spoiler')) + 4;
442
        if ($length < $minLenght) {
443
            $length = $minLenght;
444
        }
445
446
        $title = $this->_mbStrpad(
447
            ' ' . __('Spoiler') . ' ',
448
            $length,
449
            '▇',
450
            STR_PAD_BOTH
451
        );
452
453
        $json = json_encode(['string' => $content]);
454
        $id = 'spoiler_' . rand(0, 9999999999999);
455
456
        $out = <<<EOF
457
<div class="richtext-spoiler" style="display: inline;">
458
	<script>
459
		window.$id = $json;
460
	</script>
461
	<a href="#" class="richtext-spoiler-link"
462
		onclick='this.parentNode.innerHTML = window.$id.string; delete window.$id; return false;'
463
		>
464
		$title
465
	</a>
466
</div>
467
EOF;
468
469
        return $out;
470
    }
471
472
    /**
473
     * Strpad
474
     *
475
     * @see http://www.php.net/manual/en/function.str-pad.php#111147
476
     *
477
     * @param string $str string
478
     * @param int $padLen length
479
     * @param string $padStr padding
480
     * @param int $dir direction
481
     *
482
     * @return null|string
483
     */
484
    protected function _mbStrpad(
485
        $str,
486
        $padLen,
487
        $padStr = ' ',
488
        $dir = STR_PAD_RIGHT
489
    ) {
490
        $strLen = mb_strlen($str);
491
        $padStrLen = mb_strlen($padStr);
492
        if (!$strLen && ($dir == STR_PAD_RIGHT || $dir == STR_PAD_LEFT)) {
493
            $strLen = 1; // @debug
494
        }
495
        if (!$padLen || !$padStrLen || $padLen <= $strLen) {
496
            return $str;
497
        }
498
499
        $result = null;
500
        $repeat = (int)ceil($strLen - $padStrLen + $padLen);
501
        if ($dir == STR_PAD_RIGHT) {
502
            $result = $str . str_repeat($padStr, $repeat);
503
            $result = mb_substr($result, 0, $padLen);
504
        } else {
505
            if ($dir == STR_PAD_LEFT) {
506
                $result = str_repeat($padStr, $repeat) . $str;
507
                $result = mb_substr($result, -$padLen);
508
            } else {
509
                if ($dir == STR_PAD_BOTH) {
510
                    $length = ($padLen - $strLen) / 2;
511
                    $repeat = (int)ceil($length / $padStrLen);
512
                    $result = mb_substr(str_repeat($padStr, $repeat), 0, (int)floor($length)) .
513
                        $str .
514
                        mb_substr(str_repeat($padStr, $repeat), 0, (int)ceil($length));
515
                }
516
            }
517
        }
518
519
        return $result;
520
    }
521
}
522
523
/**
524
 * Hanldes [upload]<image>[/upload]
525
 *
526
 * @deprecated since Saito 5.2; kept for backwards compatability
527
 */
528
//@codingStandardsIgnoreStart
529
class Upload extends Image
530
//@codingStandardsIgnoreEnd
531
{
532
    protected $_sTagName = 'upload';
533
534
    /**
535
     * {@inheritDoc}
536
     */
537
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
538
    {
539
        $attributes['src'] = 'upload';
540
        if (!empty($attributes['width'])) {
541
            $attributes['img'] = $attributes['width'];
542
        }
543
        if (!empty($attributes['height'])) {
544
            $attributes['img'] .= 'x' . $attributes['height'];
545
        }
546
547
        return parent::_parse($content, $attributes, $node);
548
    }
549
}
550
551
/**
552
 * Hanldes [upload width=<width> height=<height>]<image>[/upload]
553
 *
554
 * @deprecated since Saito 5.2; kept for backwards compatability
555
 */
556
//@codingStandardsIgnoreStart
557
class UploadWithAttributes extends Upload
558
//@codingStandardsIgnoreEnd
559
{
560
    protected $_sUseOptions = true;
561
}
562
563
/**
564
 * Class Url handles [url]http://example.com[/url]
565
 *
566
 * @package Saito\Jbb\CodeDefinition
567
 */
568
//@codingStandardsIgnoreStart
569
class Url extends CodeDefinition
570
//@codingStandardsIgnoreEnd
571
{
572
    use UrlParserTrait;
573
574
    protected $_sParseContent = false;
575
576
    protected $_sTagName = 'url';
577
578
    /**
579
     * {@inheritDoc}
580
     */
581
    protected function _parse($url, $attributes, \JBBCode\ElementNode $node)
582
    {
583
        $defaults = ['label' => true];
584
        // parser may return $attributes = null
585
        if (empty($attributes)) {
586
            $attributes = [];
587
        }
588
        $attributes = $attributes + $defaults;
589
590
        return $this->_getUrl($url, $attributes);
591
    }
592
593
    /**
594
     * {@inheritDoc}
595
     */
596
    protected function _getUrl($content, $attributes)
597
    {
598
        $shortTag = true;
599
600
        return $this->_url($content, $content, $attributes['label'], $shortTag);
601
    }
602
}
603
604
/**
605
 * Class Link handles [link]http://example.com[/link]
606
 *
607
 * @package Saito\Jbb\CodeDefinition
608
 */
609
//@codingStandardsIgnoreStart
610
class Link extends Url
611
//@codingStandardsIgnoreEnd
612
{
613
    protected $_sTagName = 'link';
614
}
615
616
/**
617
 * Class UrlWithAttributes handles [url=http://example.com]foo[/url]
618
 *
619
 * @package Saito\Jbb\CodeDefinition
620
 */
621
//@codingStandardsIgnoreStart
622
class UrlWithAttributes extends Url
623
//@codingStandardsIgnoreEnd
624
{
625
    protected $_sParseContent = true;
626
627
    protected $_sUseOptions = true;
628
629
    /**
630
     * {@inheritDoc}
631
     */
632
    protected function _getUrl($content, $attributes)
633
    {
634
        $shortTag = false;
635
        $url = $attributes[$this->_sTagName];
636
637
        return $this->_url($url, $content, $attributes['label'], $shortTag);
638
    }
639
}
640
641
/**
642
 * Class LinkWithAttributes handles [link=http://example.com]foo[/link]
643
 *
644
 * @package Saito\Jbb\CodeDefinition
645
 */
646
//@codingStandardsIgnoreStart
647
class LinkWithAttributes extends UrlWithAttributes
648
//@codingStandardsIgnoreEnd
649
{
650
    protected $_sTagName = 'link';
651
}
652