Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
54.55% covered (warning)
54.55%
18 / 33
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Html
54.55% covered (warning)
54.55%
18 / 33
33.33% covered (danger)
33.33%
1 / 3
16.61
0.00% covered (danger)
0.00%
0 / 1
 excerpt
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 balanceTags
75.00% covered (warning)
75.00%
12 / 16
0.00% covered (danger)
0.00%
0 / 1
4.25
 sanitize
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Util;
6
7use Symfony\Component\HtmlSanitizer\HtmlSanitizer;
8use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig;
9
10class Html
11{
12    /**
13     * @param int $limit
14     * @param array<string>|string $allowedtags
15     * @param string $ellipsis
16     * @return string
17     */
18    public static function excerpt(
19        string $html,
20        $limit = 30,
21        $allowedtags = '',
22        $ellipsis = '…',
23    ) {
24        $result = strip_tags($html, $allowedtags);
25
26        if (str_word_count($result, 0) > $limit) {
27            $words = str_word_count($result, 2);
28            $pos = array_keys($words);
29            $result = substr($result, 0, $pos[$limit]) . $ellipsis;
30        }
31
32        return self::balanceTags($result);
33    }
34
35    /**
36     * Idea from this answer: https://stackoverflow.com/a/1725345.
37     *
38     * TODO: handle <br> etc.
39     */
40    public static function balanceTags(string $text): string
41    {
42        // Find all opened tags in the front string
43        $tags = [];
44        preg_match_all(
45            '/<\\s*([a-z][\\w]*)(?: +\\w*\\s*=\\s*"[\\s\w?\\/%&=#^$_:()*^-]+")*\\s*>/i',
46            $text,
47            $tags,
48            PREG_OFFSET_CAPTURE,
49        );
50        array_shift($tags); // get rid of the complete match from preg_match_all
51
52        // Check if the opened arrays have been closed in the front string
53        $unclosed = [];
54
55        foreach ($tags[0] as $t) {
56            [$tag, $pos] = $t;
57
58            if (strpos($text, '</' . $tag, $pos) === false) {
59                $unclosed[] = $tag;
60            }
61        }
62
63        foreach (array_reverse($unclosed) as $tag) {
64            $text .= '</' . $tag . '>';
65        }
66
67        return $text;
68    }
69
70    public static function sanitize(
71        string $html,
72        ?HtmlSanitizerConfig $config = null,
73        bool $removeEmptyLines = true,
74    ): string {
75        $config = $config ?: new HtmlSanitizerConfig()
76            // Allow "safe" elements and attributes. All scripts will be removed
77            // as well as other dangerous behaviors like CSS injection
78            ->allowStaticElements()
79            ->allowLinkSchemes(['http', 'https', 'mailto'])
80            ->allowRelativeLinks();
81        $sanitizer = new HtmlSanitizer($config);
82        $result = $sanitizer->sanitize($html);
83
84        // also remove empty lines
85        return $removeEmptyLines
86            ? preg_replace("/(^[\r\n]*|[\r\n]+)[\\s\t]*[\r\n]+/", PHP_EOL, $result) ?? $result
87            : $result;
88    }
89}