Completed
Push — master ( 3d03c6...f25c62 )
by Mikael
02:52
created

CTextFilter::syntaxHighlightGeSHi()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 22
ccs 12
cts 12
cp 1
rs 8.9197
cc 4
eloc 11
nc 8
nop 2
crap 4
1
<?php
2
3
namespace Mos\TextFilter;
4
5
/**
6
 * Filter and format content.
7
 *
8
 */
9
class CTextFilter
10
{
11
    use TTextUtilities,
12
        TShortcode;
13
14
15
16
    /**
17
     * Supported filters.
18
     */
19
    private $filters = [
20
        "jsonfrontmatter",
21
        "yamlfrontmatter",
22
        "bbcode",
23
        "clickable",
24
        "shortcode",
25
        "markdown",
26
        "geshi",
27
        "nl2br",
28
        "purify",
29
        "titlefromh1",
30
        "titlefromheader",
31
        "anchor4Header",
32
     ];
33
34
35
36
     /**
37
      * Current document parsed.
38
      */
39
    private $current;
40
41
42
43
    /**
44
     * Hold meta information for filters to use.
45
     */
46
    private $meta = [];
47
48
49
50
    /**
51
     * Call each filter.
52
     *
53
     * @deprecated deprecated since version 1.2 in favour of parse().
54
     *
55
     * @param string       $text    the text to filter.
56
     * @param string|array $filters as comma separated list of filter,
57
     *                              or filters sent in as array.
58
     *
59
     * @return string the formatted text.
60
     */
61
    public function doFilter($text, $filters)
62
    {
63
        // Define all valid filters with their callback function.
64
        $callbacks = [
65
            'bbcode'    => 'bbcode2html',
66
            'clickable' => 'makeClickable',
67
            'shortcode' => 'shortCode',
68
            'markdown'  => 'markdown',
69
            'nl2br'     => 'nl2br',
70
            'purify'    => 'purify',
71
        ];
72
73
        // Make an array of the comma separated string $filters
74
        if (is_array($filters)) {
75
            $filter = $filters;
76
        } else {
77
            $filters = strtolower($filters);
78
            $filter = preg_replace('/\s/', '', explode(',', $filters));
79
        }
80
81
        // For each filter, call its function with the $text as parameter.
82
        foreach ($filter as $key) {
83
            if (!isset($callbacks[$key])) {
84
                throw new Exception("The filter '$filters' is not a valid filter string due to '$key'.");
85
            }
86
            $text = call_user_func_array([$this, $callbacks[$key]], [$text]);
87
        }
88
89
        return $text;
90
    }
91
92
93
94
    /**
95
     * Set meta information that some filters can use.
96
     *
97
     * @param array $meta values for filters to use.
98
     *
99
     * @return void
100
     */
101
    public function setMeta($meta)
102
    {
103
        return $this->meta = $meta;
104
    }
105
106
107
108
    /**
109
     * Return an array of all filters supported.
110
     *
111
     * @return array with strings of filters supported.
112
     */
113 1
    public function getFilters()
114
    {
115 1
        return $this->filters;
116
    }
117
118
119
120
    /**
121
     * Check if filter is supported.
122
     *
123
     * @param string $filter to use.
124
     *
125
     * @throws mos/TextFilter/Exception  when filter does not exists.
126
     *
127
     * @return boolean true if filter exists, false othwerwise.
128
     */
129 2
    public function hasFilter($filter)
130
    {
131 2
        return in_array($filter, $this->filters);
132
    }
133
134
135
136
    /**
137
     * Add array items to frontmatter.
138
     *
139
     * @param array|null $matter key value array with items to add
140
     *                           or null if empty.
141
     *
142
     * @return $this
143
     */
144 3
    private function addToFrontmatter($matter)
145
    {
146 3
        if (empty($matter)) {
147 2
            return $this;
148
        }
149
150 2
        if (is_null($this->current->frontmatter)) {
151
            $this->current->frontmatter = [];
152
        }
153
154 2
        $this->current->frontmatter = array_merge($this->current->frontmatter, $matter);
155 3
        return $this;
156 1
    }
157
158
159
160
    /**
161
     * Call a specific filter and store its details.
162
     *
163
     * @param string $filter to use.
164
     *
165
     * @throws mos/TextFilter/Exception when filter does not exists.
166
     *
167
     * @return string the formatted text.
168
     */
169 6
    private function parseFactory($filter)
170
    {
171
        // Define single tasks filter with a callback.
172
        $callbacks = [
173 6
            "bbcode"    => "bbcode2html",
174 6
            "clickable" => "makeClickable",
175 6
            "shortcode" => "shortCode",
176 6
            "markdown"  => "markdown",
177 6
            "geshi"     => "syntaxHighlightGeSHi",
178 6
            "nl2br"     => "nl2br",
179 6
            "purify"    => "purify",
180 6
            'anchor4Header' => 'createAnchor4Header',
181 6
        ];
182
183
        // Do the specific filter
184 6
        $text = $this->current->text;
185
        switch ($filter) {
186 6
            case "jsonfrontmatter":
187 3
                $res = $this->jsonFrontMatter($text);
188 3
                $this->current->text = $res["text"];
189 3
                $this->addToFrontmatter($res["frontmatter"]);
190 3
                break;
191
192 4
            case "yamlfrontmatter":
193
                $res = $this->yamlFrontMatter($text);
194
                $this->current->text = $res["text"];
195
                $this->addToFrontmatter($res["frontmatter"]);
196
                break;
197
198 4 View Code Duplication
            case "titlefromh1":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
199 1
                $title = $this->getTitleFromFirstH1($text);
200 1
                $this->current->text = $text;
201 1
                if (!isset($this->current->frontmatter["title"])) {
202 1
                    $this->addToFrontmatter(["title" => $title]);
203 1
                }
204 1
                break;
205
206 4 View Code Duplication
            case "titlefromheader":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
207
                $title = $this->getTitleFromFirstHeader($text);
208
                $this->current->text = $text;
209
                if (!isset($this->current->frontmatter["title"])) {
210
                    $this->addToFrontmatter(["title" => $title]);
211
                }
212
                break;
213
214 4
            case "bbcode":
215 4
            case "clickable":
216 4
            case "shortcode":
217 4
            case "markdown":
218 4
            case "geshi":
219 4
            case "nl2br":
220 4
            case "purify":
221 4
            case "anchor4Header":
222 4
                $this->current->text = call_user_func_array(
223 4
                    [$this, $callbacks[$filter]],
224 4
                    [$text]
225 4
                );
226 4
                break;
227
228
            default:
229
                throw new Exception("The filter '$filter' is not a valid filter     string.");
230
        }
231 6
    }
232
233
234
235
    /**
236
     * Call each filter and return array with details of the formatted content.
237
     *
238
     * @param string $text   the text to filter.
239
     * @param array  $filter array of filters to use.
240
     *
241
     * @throws mos/TextFilter/Exception  when filterd does not exists.
242
     *
243
     * @return array with the formatted text and additional details.
244
     */
245 8
    public function parse($text, $filter)
246
    {
247 8
        $this->current = new \stdClass();
248 8
        $this->current->frontmatter = [];
249 8
        $this->current->text = $text;
250
251 8
        foreach ($filter as $key) {
252 6
            $this->parseFactory($key);
253 8
        }
254
255 8
        $this->current->text = $this->getUntilStop($this->current->text);
256
257 8
        return $this->current;
258
    }
259
260
261
262
    /**
263
     * Add excerpt as short version of text if available.
264
     *
265
     * @param object &$current same structure as returned by parse().
266
     *
267
     * @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...
268
     */
269 2
    public function addExcerpt($current)
270
    {
271 2
        list($excerpt, $hasMore) = $this->getUntilMore($current->text);
272 2
        $current->excerpt = $excerpt;
273 2
        $current->hasMore = $hasMore;
274 2
    }
275
276
277
278
    /**
279
     * Extract front matter from text.
280
     *
281
     * @param string $text       the text to be parsed.
282
     * @param string $startToken the start token.
283
     * @param string $stopToken  the stop token.
284
     *
285
     * @return array with the formatted text and the front matter.
286
     */
287 3
    private function extractFrontMatter($text, $startToken, $stopToken)
288
    {
289 3
        $tokenLength = strlen($startToken);
290
291 3
        $start = strpos($text, $startToken);
292
        // Is a valid start?
293 3
        if ($start !== false && $start !== 0) {
294
            if ($text[$start - 1] !== "\n") {
295
                $start = false;
296
            }
297
        }
298
299 3
        $frontmatter = null;
300 3
        if ($start !== false) {
301 3
            $stop = strpos($text, $stopToken, $tokenLength - 1);
302
303 3
            if ($stop !== false && $text[$stop - 1] === "\n") {
304 2
                $length = $stop - ($start + $tokenLength);
305
306 2
                $frontmatter = substr($text, $start + $tokenLength, $length);
307 2
                $textStart = substr($text, 0, $start);
308 2
                $text = $textStart . substr($text, $stop + $tokenLength);
309 2
            }
310 3
        }
311
312 3
        return [$text, $frontmatter];
313
    }
314
315
316
317
    /**
318
     * Extract JSON front matter from text.
319
     *
320
     * @param string $text the text to be parsed.
321
     *
322
     * @return array with the formatted text and the front matter.
323
     */
324 3 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...
325
    {
326 3
        list($text, $frontmatter) = $this->extractFrontMatter($text, "{{{\n", "}}}\n");
327
328 3
        if (!empty($frontmatter)) {
329 2
            $frontmatter = json_decode($frontmatter, true);
330
331 2
            if (is_null($frontmatter)) {
332
                throw new Exception("Failed parsing JSON frontmatter.");
333
            }
334 2
        }
335
336
        return [
337 3
            "text" => $text,
338
            "frontmatter" => $frontmatter
339 3
        ];
340
    }
341
342
343
344
    /**
345
     * Extract YAML front matter from text.
346
     *
347
     * @param string $text the text to be parsed.
348
     *
349
     * @return array with the formatted text and the front matter.
350
     */
351 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...
352
    {
353
        list($text, $frontmatter) = $this->extractFrontMatter($text, "---\n", "...\n");
354
355
        if (function_exists("yaml_parse") && !empty($frontmatter)) {
356
            $frontmatter = yaml_parse("---\n$frontmatter...\n");
357
358
            if ($frontmatter === false) {
359
                throw new Exception("Failed parsing YAML frontmatter.");
360
            }
361
        }
362
363
        return [
364
            "text" => $text,
365
            "frontmatter" => $frontmatter
366
        ];
367
    }
368
369
370
371
    /**
372
     * Get the title from the first H1.
373
     *
374
     * @param string $text the text to be parsed.
375
     *
376
     * @return string|null with the title, if its found.
377
     */
378 1 View Code Duplication
    public function getTitleFromFirstH1($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...
379
    {
380 1
        $matches = [];
381 1
        $title = null;
382
383 1
        if (preg_match("#<h1.*?>(.*)</h1>#", $text, $matches)) {
384 1
            $title = strip_tags($matches[1]);
385 1
        }
386
387 1
        return $title;
388
    }
389
390
391
392
    /**
393
     * Get the title from the first header.
394
     *
395
     * @param string $text the text to be parsed.
396
     *
397
     * @return string|null with the title, if its found.
398
     */
399 View Code Duplication
    public function getTitleFromFirstHeader($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...
400
    {
401
        $matches = [];
402
        $title = null;
403
404
        if (preg_match("#<h[1-6].*?>(.*)</h[1-6]>#", $text, $matches)) {
405
            $title = strip_tags($matches[1]);
406
        }
407
408
        return $title;
409
    }
410
411
412
413
    /**
414
     * Helper, BBCode formatting converting to HTML.
415
     *
416
     * @param string $text The text to be converted.
417
     *
418
     * @return string the formatted text.
419
     *
420
     * @link http://dbwebb.se/coachen/reguljara-uttryck-i-php-ger-bbcode-formattering
421
     */
422 3
    public function bbcode2html($text)
423
    {
424
        $search = [
425 3
            '/\[b\](.*?)\[\/b\]/is',
426 3
            '/\[i\](.*?)\[\/i\]/is',
427 3
            '/\[u\](.*?)\[\/u\]/is',
428 3
            '/\[img\](https?.*?)\[\/img\]/is',
429 3
            '/\[url\](https?.*?)\[\/url\]/is',
430
            '/\[url=(https?.*?)\](.*?)\[\/url\]/is'
431 3
        ];
432
433
        $replace = [
434 3
            '<strong>$1</strong>',
435 3
            '<em>$1</em>',
436 3
            '<u>$1</u>',
437 3
            '<img src="$1" />',
438 3
            '<a href="$1">$1</a>',
439
            '<a href="$1">$2</a>'
440 3
        ];
441
442 3
        return preg_replace($search, $replace, $text);
443
    }
444
445
446
447
    /**
448
     * Make clickable links from URLs in text.
449
     *
450
     * @param string $text the text that should be formatted.
451
     *
452
     * @return string with formatted anchors.
453
     *
454
     * @link http://dbwebb.se/coachen/lat-php-funktion-make-clickable-automatiskt-skapa-klickbara-lankar
455
     */
456 1
    public function makeClickable($text)
457
    {
458 1
        return preg_replace_callback(
459 1
            '#\b(?<![href|src]=[\'"])https?://[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/))#',
460 1
            function ($matches) {
461 1
                return "<a href='{$matches[0]}'>{$matches[0]}</a>";
462 1
            },
463
            $text
464 1
        );
465
    }
466
467
468
469
    /**
470
     * Syntax highlighter using GeSHi http://qbnz.com/highlighter/.
471
     *
472
     * @param string $text     text to be converted.
473
     * @param string $language which language to use for highlighting syntax.
474
     *
475
     * @return string the formatted text.
476
     */
477 2
    public function syntaxHighlightGeSHi($text, $language = "text")
478
    {
479 2
        $language = $language ?: "text";
480 2
        $language = ($language === 'html') ? 'html4strict' : $language;
481
482 2
        $geshi = new \GeSHi($text, $language);
483 2
        $geshi->set_overall_class('geshi');
484 2
        $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...
485
        //$geshi->set_header_type(GESHI_HEADER_PRE_VALID);
486
        //$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
487
        //echo "<pre>", $geshi->get_stylesheet(false) , "</pre>"; exit;
488
489 2
        $code = $geshi->parse_code();
490
491
        // Replace last &nbsp;</pre>, -strlen("&nbsp;</pre>") == 12
492 2
        $length = strlen("&nbsp;</pre>");
493 2
        if (substr($code, -$length) == "&nbsp;</pre>") {
494 2
            $code = substr_replace($code, "</pre>", -$length);
495 2
        }
496
497 2
        return $code;
498
    }
499
500
501
502
    /**
503
     * Format text according to HTML Purifier.
504
     *
505
     * @param string $text that should be formatted.
506
     *
507
     * @return string as the formatted html-text.
508
     */
509 1
    public function purify($text)
510
    {
511 1
        $config   = \HTMLPurifier_Config::createDefault();
512 1
        $config->set("Cache.DefinitionImpl", null);
513
        //$config->set('Cache.SerializerPath', '/home/user/absolute/path');
514
515 1
        $purifier = new \HTMLPurifier($config);
516
    
517 1
        return $purifier->purify($text);
518
    }
519
520
521
522
    /**
523
     * Format text according to Markdown syntax.
524
     *
525
     * @param string $text the text that should be formatted.
526
     *
527
     * @return string as the formatted html-text.
528
     */
529 7
    public function markdown($text)
530
    {
531 7
        return \Michelf\MarkdownExtra::defaultTransform($text);
532
    }
533
534
535
536
    /**
537
     * For convenience access to nl2br
538
     *
539
     * @param string $text text to be converted.
540
     *
541
     * @return string the formatted text.
542
     */
543 1
    public function nl2br($text)
544
    {
545 1
        return nl2br($text);
546
    }
547
548
549
550
    /**
551
     * Support SmartyPants for better typography.
552
     *
553
     * @param string text text to be converted.
554
     * @return string the formatted text.
555
     */
556
/*     public static function SmartyPants($text) {   
557
      require_once(__DIR__.'/php_smartypants_1.5.1e/smartypants.php');
558
      return SmartyPants($text);
559
    }
560
*/
561
562
563
    /**
564
     * Support enhanced SmartyPants/Typographer for better typography.
565
     *
566
     * @param string text text to be converted.
567
     * @return string the formatted text.
568
     */
569
/*     public static function Typographer($text) {   
570
      require_once(__DIR__.'/php_smartypants_typographer_1.0/smartypants.php');
571
      $ret = SmartyPants($text);
572
      return $ret;
573
    }
574
*/
575
}
576