Spoiler::_mbStrpad()   B
last analyzed

Complexity

Conditions 10
Paths 10

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 23
nc 10
nop 4
dl 0
loc 36
rs 7.6666
c 1
b 0
f 0

How to fix   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
 * Saito - The Threaded Web Forum
7
 *
8
 * @copyright Copyright (c) the Saito Project Developers
9
 * @link https://github.com/Schlaefer/Saito
10
 * @license http://opensource.org/licenses/MIT
11
 */
12
13
namespace Plugin\BbcodeParser\src\Lib\jBBCode\Definitions;
14
15
use Cake\Cache\Cache;
16
use Cake\Core\Configure;
17
use Plugin\BbcodeParser\src\Lib\Helper\Message;
18
use Plugin\BbcodeParser\src\Lib\Helper\UrlParserTrait;
19
use Saito\DomainParser;
20
21
/**
22
 * Class Email handles [email][email protected][/email]
23
 *
24
 * @package Saito\Jbb\CodeDefinition
25
 */
26
class Email extends CodeDefinition
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']);
0 ignored issues
show
Bug Best Practice introduced by
The property Html does not exist on Plugin\BbcodeParser\src\...BCode\Definitions\Embed. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug introduced by
The method link() does not exist on null. ( Ignorable by Annotation )

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

81
            return $this->Html->/** @scrutinizer ignore-call */ link($url, $url, ['target' => '_blank']);

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...
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) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
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)]);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->_sHelper->Html->d...=> json_encode($info))) targeting Cake\View\Helper::__call() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
The method div() does not exist on null. ( Ignorable by Annotation )

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

123
        return $this->_sHelper->Html->/** @scrutinizer ignore-call */ div('js-embed', '', ['id' => $uid, 'data-embed' => json_encode($info)]);

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...
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 = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="' . $width . '" height="' . $height . '">
285
									<param name="movie" value="' . $url . '"></param>
286
									<embed src="' . $url . '" width="' . $width . '" height="' . $height . '" type="application/x-shockwave-flash" wmode="opaque" style="width:' . $width . 'px; height:' . $height . 'px;" id="VideoPlayback" flashvars=""> </embed> </object>';
287
288
        return $out;
289
    }
290
}
291
292
//@codingStandardsIgnoreStart
293
class FileWithAttributes extends CodeDefinition
294
//@codingStandardsIgnoreEnd
295
{
296
    use UrlParserTrait;
297
298
    protected $_sTagName = 'file';
299
300
    protected $_sParseContent = false;
301
302
    protected $_sUseOptions = true;
303
304
    /**
305
     * {@inheritDoc}
306
     */
307
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
308
    {
309
        if (empty($attributes['src']) || $attributes['src'] !== 'upload') {
310
            $message = sprintf(__('File not allowed.'));
311
312
            return Message::format($message);
313
        }
314
315
        $url = $this->_linkToUploadedFile($content);
316
317
        return $this->_sHelper->Html->link($content, $url, ['target' => '_blank']);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->_sHelper->Html->l...('target' => '_blank')) targeting Cake\View\Helper::__call() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
318
    }
319
}
320
321
//@codingStandardsIgnoreStart
322
class Image extends CodeDefinition
323
//@codingStandardsIgnoreEnd
324
{
325
    use UrlParserTrait;
326
327
    protected $_sTagName = 'img';
328
329
    protected $_sParseContent = false;
330
331
    /**
332
     * {@inheritDoc}
333
     */
334
    protected function _parse($url, $attributes, \JBBCode\ElementNode $node)
335
    {
336
        // image is internaly uploaded
337
        if (!empty($attributes['src']) && $attributes['src'] === 'upload') {
338
            $url = $this->_linkToUploadedFile($url);
339
        }
340
341
        // process [img=(parameters)]
342
        $options = [];
343
        if (!empty($attributes['img'])) {
344
            $default = trim($attributes['img']);
345
            switch ($default) {
346
                default:
347
                    preg_match(
348
                        '/(\d{0,3})(?:x(\d{0,3}))?/i',
349
                        $default,
350
                        $dimension
351
                    );
352
                    // $dimension for [img=50] or [img=50x100]
353
                    // [0] (50) or (50x100)
354
                    // [1] (50)
355
                    // [2] (100)
356
                    if (!empty($dimension[1])) {
357
                        $options['width'] = $dimension[1];
358
                        if (!empty($dimension[2])) {
359
                            $options['height'] = $dimension[2];
360
                        }
361
                    }
362
            }
363
        }
364
365
        $url = $this->_urlToHttps($url);
366
        $image = $this->Html->image($url, $options);
0 ignored issues
show
Bug Best Practice introduced by
The property Html does not exist on Plugin\BbcodeParser\src\...BCode\Definitions\Image. Since you implemented __get, consider adding a @property annotation.
Loading history...
367
368
        if ($node->getParent()->getTagName() === 'Document') {
0 ignored issues
show
Bug introduced by
The method getTagName() does not exist on JBBCode\Node. It seems like you code against a sub-type of JBBCode\Node such as JBBCode\ElementNode. ( Ignorable by Annotation )

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

368
        if ($node->getParent()->/** @scrutinizer ignore-call */ getTagName() === 'Document') {
Loading history...
369
            $image = $this->_sHelper->Html->link(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $image is correct as $this->_sHelper->Html->l... 'target' => '_blank')) targeting Cake\View\Helper::__call() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
370
                $image,
371
                $url,
372
                ['escape' => false, 'target' => '_blank']
373
            );
374
        }
375
376
        return $image;
377
    }
378
}
379
380
//@codingStandardsIgnoreStart
381
class ImageWithAttributes extends Image
382
//@codingStandardsIgnoreEnd
383
{
384
    protected $_sUseOptions = true;
385
}
386
387
/**
388
 * Class UlList handles [list][*]…[/list]
389
 *
390
 * @see https://gist.github.com/jbowens/5646994
391
 * @package Saito\Jbb\CodeDefinition
392
 */
393
//@codingStandardsIgnoreStart
394
class UlList extends CodeDefinition
395
//@codingStandardsIgnoreEnd
396
{
397
    protected $_sTagName = 'list';
398
399
    /**
400
     * {@inheritDoc}
401
     */
402
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
403
    {
404
        $listPieces = explode('[*]', $content);
405
        unset($listPieces[0]);
406
        $listPieceProcessor = function ($li) {
407
            return '<li>' . $li . '</li>' . "\n";
408
        };
409
        $listPieces = array_map($listPieceProcessor, $listPieces);
410
411
        return '<ul>' . implode('', $listPieces) . '</ul>';
412
    }
413
}
414
415
//@codingStandardsIgnoreStart
416
class Spoiler extends CodeDefinition
417
//@codingStandardsIgnoreEnd
418
{
419
    protected $_sTagName = 'spoiler';
420
421
    /**
422
     * {@inheritDoc}
423
     */
424
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
425
    {
426
        $length = mb_strlen(strip_tags($content));
427
        $minLenght = mb_strlen(__('Spoiler')) + 4;
428
        if ($length < $minLenght) {
429
            $length = $minLenght;
430
        }
431
432
        $title = $this->_mbStrpad(
433
            ' ' . __('Spoiler') . ' ',
434
            $length,
435
            '▇',
436
            STR_PAD_BOTH
437
        );
438
439
        // Escape HTML-special chars to prevent injection
440
        $spoilerContent = htmlentities($content);
441
        // Encode content for JS usage
442
        $spoilerContent = json_encode($spoilerContent);
443
444
        $out = <<<EOF
445
<div class="richtext-spoiler" style="display: inline;">
446
	<a href="#" class="richtext-spoiler-link"
447
		onclick='this.parentNode.innerHTML = $spoilerContent; return false;'
448
		>
449
		$title
450
	</a>
451
</div>
452
EOF;
453
454
        return $out;
455
    }
456
457
    /**
458
     * Strpad
459
     *
460
     * @see http://www.php.net/manual/en/function.str-pad.php#111147
461
     *
462
     * @param string $str string
463
     * @param int $padLen length
464
     * @param string $padStr padding
465
     * @param int $dir direction
466
     *
467
     * @return null|string
468
     */
469
    protected function _mbStrpad(
470
        $str,
471
        $padLen,
472
        $padStr = ' ',
473
        $dir = STR_PAD_RIGHT
474
    ) {
475
        $strLen = mb_strlen($str);
476
        $padStrLen = mb_strlen($padStr);
477
        if (!$strLen && ($dir == STR_PAD_RIGHT || $dir == STR_PAD_LEFT)) {
478
            $strLen = 1; // @debug
479
        }
480
        if (!$padLen || !$padStrLen || $padLen <= $strLen) {
481
            return $str;
482
        }
483
484
        $result = null;
485
        $repeat = (int)ceil($strLen - $padStrLen + $padLen);
486
        if ($dir == STR_PAD_RIGHT) {
487
            $result = $str . str_repeat($padStr, $repeat);
488
            $result = mb_substr($result, 0, $padLen);
489
        } else {
490
            if ($dir == STR_PAD_LEFT) {
491
                $result = str_repeat($padStr, $repeat) . $str;
492
                $result = mb_substr($result, -$padLen);
493
            } else {
494
                if ($dir == STR_PAD_BOTH) {
495
                    $length = ($padLen - $strLen) / 2;
496
                    $repeat = (int)ceil($length / $padStrLen);
497
                    $result = mb_substr(str_repeat($padStr, $repeat), 0, (int)floor($length)) .
498
                        $str .
499
                        mb_substr(str_repeat($padStr, $repeat), 0, (int)ceil($length));
500
                }
501
            }
502
        }
503
504
        return $result;
505
    }
506
}
507
508
/**
509
 * Hanldes [upload]<image>[/upload]
510
 *
511
 * @deprecated since Saito 5.2; kept for backwards compatability
512
 */
513
//@codingStandardsIgnoreStart
514
class Upload extends Image
515
//@codingStandardsIgnoreEnd
516
{
517
    protected $_sTagName = 'upload';
518
519
    /**
520
     * {@inheritDoc}
521
     */
522
    protected function _parse($content, $attributes, \JBBCode\ElementNode $node)
523
    {
524
        $attributes['src'] = 'upload';
525
        if (!empty($attributes['width'])) {
526
            $attributes['img'] = $attributes['width'];
527
        }
528
        if (!empty($attributes['height'])) {
529
            $attributes['img'] .= 'x' . $attributes['height'];
530
        }
531
532
        return parent::_parse($content, $attributes, $node);
533
    }
534
}
535
536
/**
537
 * Hanldes [upload width=<width> height=<height>]<image>[/upload]
538
 *
539
 * @deprecated since Saito 5.2; kept for backwards compatability
540
 */
541
//@codingStandardsIgnoreStart
542
class UploadWithAttributes extends Upload
0 ignored issues
show
Deprecated Code introduced by
The class Plugin\BbcodeParser\src\...Code\Definitions\Upload has been deprecated: since Saito 5.2; kept for backwards compatability ( Ignorable by Annotation )

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

542
class UploadWithAttributes extends /** @scrutinizer ignore-deprecated */ Upload
Loading history...
543
//@codingStandardsIgnoreEnd
544
{
545
    protected $_sUseOptions = true;
546
}
547
548
/**
549
 * Class Url handles [url]http://example.com[/url]
550
 *
551
 * @package Saito\Jbb\CodeDefinition
552
 */
553
//@codingStandardsIgnoreStart
554
class Url extends CodeDefinition
555
//@codingStandardsIgnoreEnd
556
{
557
    use UrlParserTrait;
558
559
    protected $_sParseContent = false;
560
561
    protected $_sTagName = 'url';
562
563
    /**
564
     * {@inheritDoc}
565
     */
566
    protected function _parse($url, $attributes, \JBBCode\ElementNode $node)
567
    {
568
        $defaults = ['label' => true];
569
        // parser may return $attributes = null
570
        if (empty($attributes)) {
571
            $attributes = [];
572
        }
573
        $attributes = $attributes + $defaults;
574
575
        return $this->_getUrl($url, $attributes);
576
    }
577
578
    /**
579
     * {@inheritDoc}
580
     */
581
    protected function _getUrl($content, $attributes)
582
    {
583
        $shortTag = true;
584
585
        return $this->_url($content, $content, $attributes['label'], $shortTag);
586
    }
587
}
588
589
/**
590
 * Class Link handles [link]http://example.com[/link]
591
 *
592
 * @package Saito\Jbb\CodeDefinition
593
 */
594
//@codingStandardsIgnoreStart
595
class Link extends Url
596
//@codingStandardsIgnoreEnd
597
{
598
    protected $_sTagName = 'link';
599
}
600
601
/**
602
 * Class UrlWithAttributes handles [url=http://example.com]foo[/url]
603
 *
604
 * @package Saito\Jbb\CodeDefinition
605
 */
606
//@codingStandardsIgnoreStart
607
class UrlWithAttributes extends Url
608
//@codingStandardsIgnoreEnd
609
{
610
    protected $_sParseContent = true;
611
612
    protected $_sUseOptions = true;
613
614
    /**
615
     * {@inheritDoc}
616
     */
617
    protected function _getUrl($content, $attributes)
618
    {
619
        $shortTag = false;
620
        $url = $attributes[$this->_sTagName];
621
622
        return $this->_url($url, $content, $attributes['label'], $shortTag);
623
    }
624
}
625
626
/**
627
 * Class LinkWithAttributes handles [link=http://example.com]foo[/link]
628
 *
629
 * @package Saito\Jbb\CodeDefinition
630
 */
631
//@codingStandardsIgnoreStart
632
class LinkWithAttributes extends UrlWithAttributes
633
//@codingStandardsIgnoreEnd
634
{
635
    protected $_sTagName = 'link';
636
}
637