Completed
Push — master ( 8df78a...d8ceec )
by Mikael
02:48
created

CTextFilter::getTitleFromFirstHeader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 11
Ratio 100 %

Code Coverage

Tests 1
CRAP Score 2

Importance

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