Completed
Push — master ( bb3a50...4c5b2b )
by Mikael
02:52
created

src/TextFilter/CTextFilter.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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