Completed
Push — master ( 53fe75...4dba6c )
by Mikael
02:20
created

CTextFilter   F

Complexity

Total Complexity 70

Size/Duplication

Total Lines 875
Duplicated Lines 3.89 %

Coupling/Cohesion

Components 4
Dependencies 6

Test Coverage

Coverage 87.96%

Importance

Changes 18
Bugs 0 Features 4
Metric Value
wmc 70
c 18
b 0
f 4
lcom 4
cbo 6
dl 34
loc 875
ccs 190
cts 216
cp 0.8796
rs 2.1818

22 Methods

Rating   Name   Duplication   Size   Complexity  
B doFilter() 0 31 4
A setMeta() 0 4 1
A getFilters() 0 4 1
A hasFilter() 0 4 1
A addToFrontmatter() 0 13 3
C parseFactory() 0 55 13
A parse() 0 14 2
A addExcerpt() 0 6 1
C extractFrontMatter() 0 27 7
A jsonFrontMatter() 17 17 3
A yamlFrontMatter() 17 17 4
A getTitleFromFirstH1() 0 11 2
A bbcode2html() 0 22 1
A makeClickable() 0 10 1
A syntaxHighlightGeSHi() 0 17 3
A purify() 0 10 1
A markdown() 0 4 1
A nl2br() 0 4 1
B shortCode() 0 45 4
A shortCodeInit() 0 18 3
C shortCodeFigure() 0 47 9
B ShortCodeAsciinema() 0 27 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CTextFilter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CTextFilter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Mos\TextFilter;
4
5
/**
6
 * Filter and format content.
7
 *
8
 */
9
class CTextFilter
10
{
11
    use TTextUtilities;
12
13
14
15
    /**
16
     * Supported filters.
17
     */
18
    private $filters = [
19
        "jsonfrontmatter",
20
        "yamlfrontmatter",
21
        "bbcode",
22
        "clickable",
23
        "shortcode",
24
        "markdown",
25
        "geshi",
26
        "nl2br",
27
        "purify",
28
        "titlefromh1",
29
        "anchor4Header",
30
     ];
31
32
33
34
     /**
35
      * Current document parsed.
36
      */
37
    private $current;
38
39
40
41
    /**
42
     * Hold meta information for filters to use.
43
     */
44
    private $meta = [];
45
46
47
48
    /**
49
     * Call each filter.
50
     *
51
     * @deprecated deprecated since version 1.2 in favour of parse().
52
     *
53
     * @param string       $text    the text to filter.
54
     * @param string|array $filters as comma separated list of filter,
55
     *                              or filters sent in as array.
56
     *
57
     * @return string the formatted text.
58
     */
59
    public function doFilter($text, $filters)
60
    {
61
        // Define all valid filters with their callback function.
62
        $callbacks = [
63
            'bbcode'    => 'bbcode2html',
64
            'clickable' => 'makeClickable',
65
            'shortcode' => 'shortCode',
66
            'markdown'  => 'markdown',
67
            'nl2br'     => 'nl2br',
68
            'purify'    => 'purify',
69
        ];
70
71
        // Make an array of the comma separated string $filters
72
        if (is_array($filters)) {
73
            $filter = $filters;
74
        } else {
75
            $filters = strtolower($filters);
76
            $filter = preg_replace('/\s/', '', explode(',', $filters));
77
        }
78
79
        // For each filter, call its function with the $text as parameter.
80
        foreach ($filter as $key) {
81
82
            if (!isset($callbacks[$key])) {
83
                throw new Exception("The filter '$filters' is not a valid filter string due to '$key'.");
84
            }
85
            $text = call_user_func_array([$this, $callbacks[$key]], [$text]);
86
        }
87
88
        return $text;
89
    }
90
91 1
92
93 1
    /**
94
     * Set meta information that some filters can use.
95
     *
96
     * @param array $meta values for filters to use.
97
     *
98
     * @return void
99
     */
100
    public function setMeta($meta)
101
    {
102
        return $this->meta = $meta;
103
    }
104
105
106
107 2
    /**
108
     * Return an array of all filters supported.
109 2
     *
110
     * @return array with strings of filters supported.
111
     */
112
    public function getFilters()
113
    {
114
        return $this->filters;
115
    }
116
117
118
119
    /**
120
     * Check if filter is supported.
121
     *
122 3
     * @param string $filter to use.
123
     *
124 3
     * @throws mos/TextFilter/Exception  when filter does not exists.
125 2
     *
126
     * @return boolean true if filter exists, false othwerwise.
127
     */
128 2
    public function hasFilter($filter)
129 2
    {
130 2
        return in_array($filter, $this->filters);
131
    }
132 2
133 2
134
135
    /**
136
     * Add array items to frontmatter.
137
     *
138
     * @param array|null $matter key value array with items to add
139
     *                           or null if empty.
140
     *
141
     * @return $this
142
     */
143
    private function addToFrontmatter($matter)
144
    {
145
        if (empty($matter)) {
146
            return $this;
147 6
        }
148
149
        if (is_null($this->current->frontmatter)) {
150
            $this->current->frontmatter = [];
151 6
        }
152 6
153 6
        $this->current->frontmatter = array_merge($this->current->frontmatter, $matter);
154 6
        return $this;
155 6
    }
156 6
157 6
158 6
159 6
    /**
160
     * Call a specific filter and store its details.
161
     *
162 6
     * @param string $filter to use.
163
     *
164 6
     * @throws mos/TextFilter/Exception when filter does not exists.
165 3
     *
166 3
     * @return string the formatted text.
167 3
     */
168 3
    private function parseFactory($filter)
169
    {
170 4
        // Define single tasks filter with a callback.
171
        $callbacks = [
172
            "bbcode"    => "bbcode2html",
173
            "clickable" => "makeClickable",
174 1
            "shortcode" => "shortCode",
175
            "markdown"  => "markdown",
176 4
            "geshi"     => "syntaxHighlightGeSHi",
177 2
            "nl2br"     => "nl2br",
178 1
            "purify"    => "purify",
179 1
            'anchor4Header' => 'createAnchor4Header',
180 1
        ];
181 1
182 1
        // Do the specific filter
183
        $text = $this->current->text;
184 4
        switch ($filter) {
185 4
            case "jsonfrontmatter":
186 4
                $res = $this->jsonFrontMatter($text);
187 4
                $this->current->text = $res["text"];
188 4
                $this->addToFrontmatter($res["frontmatter"]);
189 4
                break;
190 4
191 4
            case "yamlfrontmatter":
192 4
                $res = $this->yamlFrontMatter($text);
193 4
                $this->current->text = $res["text"];
194 4
                $this->addToFrontmatter($res["frontmatter"]);
195 4
                break;
196 4
197
            case "titlefromh1":
198
                $title = $this->getTitleFromFirstH1($text);
199
                $this->current->text = $text;
200
                if (!isset($this->current->frontmatter["title"])) {
201 6
                    $this->addToFrontmatter(["title" => $title]);
202
                }
203
                break;
204
205
            case "bbcode":
206
            case "clickable":
207
            case "shortcode":
208
            case "markdown":
209
            case "geshi":
210
            case "nl2br":
211
            case "purify":
212
            case "anchor4Header":
213
                $this->current->text = call_user_func_array(
214
                    [$this, $callbacks[$filter]],
215 8
                    [$text]
216
                );
217 8
                break;
218 8
219 8
            default:
220
                throw new Exception("The filter '$filter' is not a valid filter     string.");
221 8
        }
222 6
    }
223 8
224
225 8
226 8
    /**
227 8
     * Call each filter and return array with details of the formatted content.
228 8
     *
229
     * @param string $text   the text to filter.
230 8
     * @param array  $filter array of filters to use.
231
     *
232
     * @throws mos/TextFilter/Exception  when filterd does not exists.
233
     *
234
     * @return array with the formatted text and additional details.
235
     */
236
    public function parse($text, $filter)
237
    {
238
        $this->current = new \stdClass();
239
        $this->current->frontmatter = null;
240
        $this->current->text = $text;
241
242
        foreach ($filter as $key) {
243
            $this->parseFactory($key);
244 3
        }
245
246 3
        $this->current->text = $this->getUntilStop($this->current->text);
247
248 3
        return $this->current;
249
    }
250 3
251
252
253
    /**
254
     * Add excerpt as short version of text if available.
255
     *
256 3
     * @param object &$current same structure as returned by parse().
257 3
     *
258 3
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
259
     */
260 3
    public function addExcerpt($current)
261 2
    {
262
        list($excerpt, $hasMore) = $this->getUntilMore($current->text);
263 2
        $current->excerpt = $excerpt;
264 2
        $current->hasMore = $hasMore;
265 2
    }
266 2
267 3
268
269 3
    /**
270
     * Extract front matter from text.
271
     *
272
     * @param string $text       the text to be parsed.
273
     * @param string $startToken the start token.
274
     * @param string $stopToken  the stop token.
275
     *
276
     * @return array with the formatted text and the front matter.
277
     */
278
    private function extractFrontMatter($text, $startToken, $stopToken)
279
    {
280
        $tokenLength = strlen($startToken);
281 3
282
        $start = strpos($text, $startToken);
283 3
        // Is a valid start?
284
        if ($start !== false && $start !== 0) {
285 3
            if ($text[$start - 1] !== "\n") {
286 2
                $start = false;
287
            }
288 2
        }
289
290
        $frontmatter = null;
291 2
        if ($start !== false) {
292
            $stop = strpos($text, $stopToken, $tokenLength - 1);
293
294 3
            if ($stop !== false && $text[$stop - 1] === "\n") {
295
                $length = $stop - ($start + $tokenLength);
296 3
297
                $frontmatter = substr($text, $start + $tokenLength, $length);
298
                $textStart = substr($text, 0, $start);
299
                $text = $textStart . substr($text, $stop + $tokenLength);
300
            }
301
        }
302
303
        return [$text, $frontmatter];
304
    }
305
306
307
308
    /**
309
     * Extract JSON front matter from text.
310
     *
311
     * @param string $text the text to be parsed.
312
     *
313
     * @return array with the formatted text and the front matter.
314
     */
315 View Code Duplication
    public function jsonFrontMatter($text)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
316
    {
317
        list($text, $frontmatter) = $this->extractFrontMatter($text, "{{{\n", "}}}\n");
318
319
        if (!empty($frontmatter)) {
320
            $frontmatter = json_decode($frontmatter, true);
321
322
            if (is_null($frontmatter)) {
323
                throw new Exception("Failed parsing JSON frontmatter.");
324
            }
325
        }
326
327
        return [
328
            "text" => $text,
329
            "frontmatter" => $frontmatter
330
        ];
331
    }
332
333
334
335
    /**
336 1
     * Extract YAML front matter from text.
337
     *
338 1
     * @param string $text the text to be parsed.
339 1
     *
340
     * @return array with the formatted text and the front matter.
341 1
     */
342 1 View Code Duplication
    public function yamlFrontMatter($text)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
343 1
    {
344
        list($text, $frontmatter) = $this->extractFrontMatter($text, "---\n", "...\n");
345 1
346
        if (function_exists("yaml_parse") && !empty($frontmatter)) {
347
            $frontmatter = yaml_parse("---\n$frontmatter...\n");
348
349
            if ($frontmatter === false) {
350
                throw new Exception("Failed parsing YAML frontmatter.");
351
            }
352
        }
353
354
        return [
355
            "text" => $text,
356
            "frontmatter" => $frontmatter
357
        ];
358
    }
359 3
360
361
362 3
    /**
363 3
     * Get the title from the first H1.
364 3
     *
365 3
     * @param string $text the text to be parsed.
366 3
     *
367
     * @return string|null with the title, if its found.
368 3
     */
369
    public function getTitleFromFirstH1($text)
370
    {
371 3
        $matches = [];
372 3
        $title = null;
373 3
374 3
        if (preg_match("#<h1.*?>(.*)</h1>#", $text, $matches)) {
375 3
            $title = strip_tags($matches[1]);
376
        }
377 3
378
        return $title;
379 3
    }
380
381
382
383
    /**
384
     * Helper, BBCode formatting converting to HTML.
385
     *
386
     * @param string $text The text to be converted.
387
     *
388
     * @return string the formatted text.
389
     *
390
     * @link http://dbwebb.se/coachen/reguljara-uttryck-i-php-ger-bbcode-formattering
391
     */
392
    public function bbcode2html($text)
393 1
    {
394
        $search = [
395 1
            '/\[b\](.*?)\[\/b\]/is',
396 1
            '/\[i\](.*?)\[\/i\]/is',
397
            '/\[u\](.*?)\[\/u\]/is',
398 1
            '/\[img\](https?.*?)\[\/img\]/is',
399 1
            '/\[url\](https?.*?)\[\/url\]/is',
400
            '/\[url=(https?.*?)\](.*?)\[\/url\]/is'
401 1
        ];
402
403
        $replace = [
404
            '<strong>$1</strong>',
405
            '<em>$1</em>',
406
            '<u>$1</u>',
407
            '<img src="$1" />',
408
            '<a href="$1">$1</a>',
409
            '<a href="$1">$2</a>'
410
        ];
411
412
        return preg_replace($search, $replace, $text);
413
    }
414 2
415
416 2
417 2
    /**
418 2
     * Make clickable links from URLs in text.
419 2
     *
420 2
     * @param string $text the text that should be formatted.
421
     *
422
     * @return string with formatted anchors.
423
     *
424
     * @link http://dbwebb.se/coachen/lat-php-funktion-make-clickable-automatiskt-skapa-klickbara-lankar
425 2
     */
426
    public function makeClickable($text)
427
    {
428
        return preg_replace_callback(
429
            '#\b(?<![href|src]=[\'"])https?://[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/))#',
430
            function ($matches) {
431
                return "<a href='{$matches[0]}'>{$matches[0]}</a>";
432
            },
433
            $text
434
        );
435
    }
436
437 1
438
439 1
    /**
440 1
     * Syntax highlighter using GeSHi http://qbnz.com/highlighter/.
441
     *
442
     * @param string $text     text to be converted.
443 1
     * @param string $language which language to use for highlighting syntax.
444
     *
445 1
     * @return string the formatted text.
446
     */
447
    public function syntaxHighlightGeSHi($text, $language = "text")
448
    {
449
        $language = $language ?: "text";
450
        $language = ($language === 'html') ? 'html4strict' : $language;
451
        $geshi = new \GeSHi($text, $language);
452
        $geshi->set_overall_class('geshi');
453
        $geshi->enable_classes('geshi');
0 ignored issues
show
Documentation introduced by
'geshi' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
454
        //$geshi->set_header_type(GESHI_HEADER_PRE_VALID);
455
        //$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
456
        //echo "<pre>", $geshi->get_stylesheet(false) , "</pre>"; exit;
457 7
458
        $code = $geshi->parse_code();
459 7
460
        // Replace last &nbsp;</pre>, -strlen("&nbsp;</pre>") == 12
461
        $code = substr_replace($code, "</pre>", -12);
462
        return $code;
463
    }
464
465
466
467
    /**
468
     * Format text according to HTML Purifier.
469
     *
470
     * @param string $text that should be formatted.
471 1
     *
472
     * @return string as the formatted html-text.
473 1
     */
474
    public function purify($text)
475
    {
476
        $config   = \HTMLPurifier_Config::createDefault();
477
        $config->set("Cache.DefinitionImpl", null);
478
        //$config->set('Cache.SerializerPath', '/home/user/absolute/path');
479
480
        $purifier = new \HTMLPurifier($config);
481
    
482
        return $purifier->purify($text);
483
    }
484
485 2
486
487
    /**
488
     * Format text according to Markdown syntax.
489
     *
490
     * @param string $text the text that should be formatted.
491
     *
492
     * @return string as the formatted html-text.
493
     */
494
    public function markdown($text)
495
    {
496
        return \Michelf\MarkdownExtra::defaultTransform($text);
497
    }
498
499
500
501 2
    /**
502 2
     * For convenience access to nl2br
503 2
     *
504
     * @param string $text text to be converted.
505 2
     *
506 2
     * @return string the formatted text.
507 2
     */
508 2
    public function nl2br($text)
509
    {
510 2
        return nl2br($text);
511 1
    }
512
513
514 1
515 1
    /**
516
     * Shortcode to to quicker format text as HTML.
517
     *
518
     * @param string $text text to be converted.
519
     *
520
     * @return string the formatted text.
521 2
     */
522
    public function shortCode($text)
523 2
    {
524
        /* Needs PHP 7
525
        $patternsAndCallbacks = [
526
            "/\[(FIGURE)[\s+](.+)\]/" => function ($match) {
527
                return self::ShortCodeFigure($matches[2]);
528
            },
529
            "/(```([\w]*))\n([^`]*)```[\n]{1}/s" => function ($match) {
530
                return $this->syntaxHighlightGeSHi($matches[3], $matches[2]);
531
            },
532
        ];
533
534
        return preg_replace_callback_array($patternsAndCallbacks, $text);
535 1
        */
536
537 1
        $patterns = [
538
            "/\[(FIGURE)[\s+](.+)\]/",
539 1
            "/(```)([\w]*)\n([^`]*)```[\n]{1}/s",
540 1
            "/\[(ASCIINEMA)[\s+](.+)\]/",
541 1
        ];
542 1
543
        return preg_replace_callback(
544
            $patterns,
545 1
            function ($matches) {
546 1
                switch ($matches[1]) {
547 1
548
                    case "FIGURE":
549 1
                        return self::ShortCodeFigure($matches[2]);
550
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
551 1
552
                    case "ASCIINEMA":
553
                        return self::ShortCodeAsciinema($matches[2]);
554
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
555
556
                    case "```":
557
                        return $this->syntaxHighlightGeSHi($matches[3], $matches[2]);
558
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
559
560
                    default:
561
                        return "{$matches[1]} is unknown shortcode.";
562
                }
563
            },
564
            $text
565 1
        );
566
    }
567
568 1
569
570 1
    /**
571 1
     * Init shortcode handling by preparing the option list to an array, for those using arguments.
572 1
     *
573 1
     * @param string $options for the shortcode.
574 1
     *
575 1
     * @return array with all the options.
576 1
     */
577 1
    public static function shortCodeInit($options)
578 1
    {
579 1
        preg_match_all('/[a-zA-Z0-9]+="[^"]+"|\S+/', $options, $matches);
580 1
581 1
        $res = array();
582
        foreach ($matches[0] as $match) {
583 1
            $pos = strpos($match, '=');
584 1
            if ($pos === false) {
585 1
                $res[$match] = true;
586
            } else {
587 1
                $key = substr($match, 0, $pos);
588 1
                $val = trim(substr($match, $pos+1), '"');
589 1
                $res[$key] = $val;
590
            }
591 1
        }
592 1
593 1
        return $res;
594 1
    }
595
596 1
597 1
598 1
    /**
599 1
     * Shortcode for <figure>.
600 1
     *
601 1
     * Usage example: [FIGURE src="img/home/me.jpg" caption="Me" alt="Bild på mig" nolink="nolink"]
602
     *
603
     * @param string $options for the shortcode.
604 1
     *
605 1
     * @return array with all the options.
606 1
     */
607 1
    public static function shortCodeFigure($options)
608 1
    {
609
        // Merge incoming options with default and expose as variables
610 1
        $options= array_merge(
611
            [
612
                'id' => null,
613
                'class' => null,
614
                'src' => null,
615
                'title' => null,
616
                'alt' => null,
617
                'caption' => null,
618
                'href' => null,
619
                'nolink' => false,
620
            ],
621
            self::ShortCodeInit($options)
622
        );
623
        extract($options, EXTR_SKIP);
624
625
        $id = $id ? " id='$id'" : null;
626
        $class = $class ? " class='figure $class'" : " class='figure'";
627
        $title = $title ? " title='$title'" : null;
628
629
        if (!$alt && $caption) {
630
            $alt = $caption;
631
        }
632
633
        if (!$href) {
634
            $pos = strpos($src, '?');
635
            $href = $pos ? substr($src, 0, $pos) : $src;
636
        }
637
638
        $start = null;
639
        $end = null;
640
        if (!$nolink) {
641
            $start = "<a href='{$href}'>";
642
            $end = "</a>";
643
        }
644
645
        $html = <<<EOD
646
<figure{$id}{$class}>
647
{$start}<img src='{$src}' alt='{$alt}'{$title}/>{$end}
648
<figcaption markdown=1>{$caption}</figcaption>
649
</figure>
650
EOD;
651
652
        return $html;
653
    }
654
655
656
657
    /**
658
     * Shortcode for [asciinema].
659
     *
660
     * @param string $code the code to process.
0 ignored issues
show
Bug introduced by
There is no parameter named $code. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
661
     * @param string $options for the shortcode.
662
     * @return array with all the options.
663
     */
664
    public static function ShortCodeAsciinema($options) {
665
        // Merge incoming options with default and expose as variables
666
        $options= array_merge(
667
            [
668
                'id' => null,
669
                'class' => null,
670
                'src' => null,
671
                'title' => null,
672
                'caption' => null,
673
            ],
674
            self::ShortCodeInit($options)
675
        );
676
        extract($options, EXTR_SKIP);
677
678
        $id = $id ? " id=\"$id\"" : null;
679
        $class = $class ? " class=\"figure asciinema $class\"" : " class=\"figure asciinema\"";
680
        $title = $title ? " title=\"$title\"" : null;
681
682
        $html = <<<EOD
683
<figure{$id}{$class}$title>
684
<script type="text/javascript" src="https://asciinema.org/a/{$src}.js" id="asciicast-{$src}" async></script>
685
<figcaption markdown=1>{$caption}</figcaption>
686
</figure>
687
EOD;
688
689
        return $html;
690
    }
691
692
693
694
/**
695
 * Shortcode for including a SVG-image inside a <figure>.
696
 *
697
 * @param string $code the code to process.
698
 * @param string $options for the shortcode.
699
 * @return array with all the options.
700
 */
701
/*public static function ShortCodeSVGFigure($options) {
702
  extract(array_merge(array(
703
    'id' => null,
704
    'class' => null,
705
    'src' => null,
706
    'path' => null,
707
    'title' => null,
708
    'alt' => null,
709
    'caption' => null,
710
    'href' => null,
711
    'nolink' => false,
712
  ), CTextFilter::ShortCodeInit($options)), EXTR_SKIP);
713
714
  $id = $id ? " id='$id'" : null;
715
  //$class = $class ? " class='$class'" : null;
716
  $class = $class ? " class='figure $class'" : " class='figure'";
717
  $title = $title ? " title='$title'" : null;
718
  
719
  if(!$alt && $caption) {
720
    $alt = $caption;
721
  }
722
723
  if(!$href) {
724
    $pos = strpos($src, '?');
725
    $href = $pos ? substr($src, 0, $pos) : $src;
726
  }
727
728
  if(!$nolink) {
729
    $a_start = "<a href='{$href}'>";
730
    $a_end = "</a>";
731
  }
732
733
  // Import the file containing the svg-image
734
  $svg = null;
735
  
736
  if($path[0] != '/') {
737
    $path = self::$dir . '/' . $path;
738
  }
739
740
  if(is_file($path)) {
741
    $svg = file_get_contents($path);
742
  }
743
  else {
744
    $svg = "No such file: $path";
745
  }
746
  $html = <<<EOD
747
<figure{$id}{$class}>
748
{$svg}
749
<figcaption markdown=1>{$caption}</figcaption>
750
</figure>
751
EOD;
752
753
  return $html;
754
}
755
756
*/
757
758
759
760
/**
761
 * Shorttags to to quicker format text as HTML.
762
 *
763
 * @param string text text to be converted.
764
 * @return string the formatted text.
765
 */
766
/*public static function ShortTags($text) {
767
  $callback = function($matches) {
768
    switch($matches[1]) {
769
      case 'IMG':
770
        $caption = t('Image: ');
771
        $pos = strpos($matches[2], '?');
772
        $href = $pos ? substr($matches[2], 0, $pos) : $matches[2];
773
        $src = htmlspecialchars($matches[2]);
774
        return <<<EOD
775
<figure>
776
<a href='{$href}'><img src='{$src}' alt='{$matches[3]}' /></a>
777
<figcaption markdown=1>{$caption}{$matches[3]}</figcaption>
778
</figure>
779
EOD;
780
781
      case 'IMG2':
782
        $caption = null; //t('Image: ');
783
        $pos = strpos($matches[2], '?');
784
        $href = $pos ? substr($matches[2], 0, $pos) : $matches[2];
785
        $src = htmlspecialchars($matches[2]);
786
        return <<<EOD
787
<figure class="{$matches[4]}">
788
<a href='{$href}'><img src='{$src}' alt='{$matches[3]}' /></a>
789
<figcaption markdown=1>{$caption}{$matches[3]}</figcaption>
790
</figure>
791
EOD;
792
      case 'BOOK':
793
        $isbn = $matches[2];
794
        $stores = array(
795
          'BTH' => "http://bth.summon.serialssolutions.com/?#!/search?ho=t&amp;q={$isbn}",
796
          'Libris' => "http://libris.kb.se/hitlist?q={$isbn}",
797
          'Google Books' => "http://books.google.com/books?q={$isbn}",
798
          'Bokus' => "http://www.bokus.com/bok/{$isbn}",
799
          'Adlibris' => "http://www.adlibris.com/se/product.aspx?isbn={$isbn}",
800
          'Amazon' => "http://www.amazon.com/s/ref=nb_ss?url=field-keywords={$isbn}",
801
          'Barnes&Noble' => "http://search.barnesandnoble.com/booksearch/ISBNInquiry.asp?r=1&IF=N&EAN={$isbn}",
802
        );
803
        $html = null;
804
        foreach($stores as $key => $val) {
805
          $html .= "<a href='$val'>$key</a> &bull; ";
806
        }
807
        return substr($html, 0, -8);
808
      break;
809
810
      case 'YOUTUBE':
811
        $caption = t('Figure: ');
812
        $height = ceil($matches[3] / (16/9));
813
        return <<<EOD
814
<figure>
815
<iframe width='{$matches[3]}' height='{$height}' src="http://www.youtube.com/embed/{$matches[2]}" frameborder="0"
816
allowfullscreen></iframe>
817
<figcaption>{$caption}{$matches[4]}</figcaption>
818
</figure>
819
EOD;
820
      break;
821
      
822
      case 'syntax=': return CTextFilter::SyntaxHighlightGeSHi($matches[3], $matches[2]); break;
823
      case '```': return CTextFilter::SyntaxHighlightGeSHi($matches[3], $matches[2]); break;
824
      //case 'syntax=': return "<pre>" . highlight_string($matches[3], true) . "</pre>"; break;
825
      //case 'INCL':  include($matches[2]); break;
826
      case 'INFO':  return "<div class='info' markdown=1>"; break;
827
      case '/INFO': return "</div>"; break;
828
      case 'BASEURL': return CLydia::Instance()->request->base_url; break;
829
      case 'FIGURE': return CTextFilter::ShortCodeFigure($matches[2]); break;
830
      case 'FIGURE-SVG': return CTextFilter::ShortCodeSVGFigure($matches[2]); break;
831
      case 'ASCIINEMA': return CTextFilter::ShortCodeAsciinema($matches[2]); break;
832
      default: return "{$matches[1]} IS UNKNOWN SHORTTAG."; break;
833
    }
834
  };
835
  $patterns = array(
836
    '#\[(BASEURL)\]#',
837
    //'/\[(AUTHOR) name=(.+) email=(.+) url=(.+)\]/',
838
    '/\[(FIGURE)[\s+](.+)\]/',
839
    '/\[(FIGURE-SVG)[\s+](.+)\]/',
840
    '/\[(ASCIINEMA)[\s+](.+)\]/',
841
    '/\[(IMG) src=(.+) alt=(.+)\]/',
842
    '/\[(IMG2) src=(.+) alt="(.+)" class="(.+)"\]/',
843
    '/\[(BOOK) isbn=(.+)\]/',
844
    '/\[(YOUTUBE) src=(.+) width=(.+) caption=(.+)\]/',
845
    '/~~~(syntax=)(php|html|html5|css|sql|javascript|bash)\n([^~]+)\n~~~/s',
846
    '/(```)(php|html|html5|css|sql|javascript|bash|text|txt|python)\n([^`]+)\n```/s',
847
    //'/\[(INCL)/s*([^\]+)/',
848
    '#\[(INFO)\]#', '#\[(/INFO)\]#',
849
  );
850
851
  $ret = preg_replace_callback($patterns, $callback, $text);
852
  return $ret;
853
}
854
*/
855
856
857
858
    /**
859
     * Support SmartyPants for better typography.
860
     *
861
     * @param string text text to be converted.
862
     * @return string the formatted text.
863
     */
864
/*     public static function SmartyPants($text) {   
865
      require_once(__DIR__.'/php_smartypants_1.5.1e/smartypants.php');
866
      return SmartyPants($text);
867
    }
868
*/
869
870
871
    /**
872
     * Support enhanced SmartyPants/Typographer for better typography.
873
     *
874
     * @param string text text to be converted.
875
     * @return string the formatted text.
876
     */
877
/*     public static function Typographer($text) {   
878
      require_once(__DIR__.'/php_smartypants_typographer_1.0/smartypants.php');
879
      $ret = SmartyPants($text);
880
      return $ret;
881
    }
882
*/
883
}
884