Completed
Push — develop ( 043012...3d7377 )
by Vladimir
12s queued 10s
created

AnchorsFilter::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @copyright 2018 Vladimir Jimenez
5
 * @license   https://github.com/stakx-io/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx\Templating\Twig\Extension;
9
10
use allejo\stakx\Utilities\HtmlUtils;
11
use Twig\TwigFilter;
12
13
class AnchorsFilter extends AbstractTwigExtension implements TwigFilterInterface
14
{
15
    /**
16
     * @param string $html          The HTML we'll be processing
17
     * @param bool   $beforeHeading Set to true if the anchor should be placed before the heading's content
18
     * @param array  $anchorAttrs   Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use
19
     *                              `href`, `class`, or `title`
20
     * @param string $anchorBody    The content that will be placed inside the anchor; the `{heading}` placeholder is
21
     *                              available
22
     * @param string $anchorClass   The class(es) that will be used for each anchor. Separate multiple classes with a
23
     *                              space
24
     * @param string $anchorTitle   The title attribute that will be used for anchors; the `{heading}` placeholder is
25
     *                              available
26
     * @param int    $hMin          The minimum header level to build an anchor for; any header lower than this value
27
     *                              will be ignored
28
     * @param int    $hMax          The maximum header level to build an anchor for; any header greater than this value
29
     *                              will be ignored
30
     *
31
     * @return string
32
     */
33 9
    public static function filter($html, $beforeHeading = false, $anchorAttrs = [], $anchorBody = '', $anchorClass = '', $anchorTitle = '', $hMin = 1, $hMax = 6)
34
    {
35 9
        if (!function_exists('simplexml_load_string'))
36 9
        {
37
            trigger_error('XML support is not available with the current PHP installation.', E_USER_WARNING);
38
39
            return $html;
40
        }
41
42
        if ($anchorClass)
43 9
        {
44 2
            $anchorAttrs['class'] = $anchorClass;
45 2
        }
46
47 9
        $dom = new \DOMDocument();
48 9
        $currLvl = 0;
49 9
        $headings = HtmlUtils::htmlXPath($dom, $html, '//h1|//h2|//h3|//h4|//h5|//h6');
50
51
        /** @var \DOMElement $heading */
52 9
        foreach ($headings as $heading)
53
        {
54 9
            $headingID = $heading->attributes->getNamedItem('id');
55
56 9
            if ($headingID === null)
57 9
            {
58
                continue;
59
            }
60
61 9
            sscanf($heading->tagName, 'h%u', $currLvl);
62
63 9
            if (!($hMin <= $currLvl && $currLvl <= $hMax))
64 9
            {
65 1
                continue;
66
            }
67
68 9
            $anchor = $dom->createElement('a');
69 9
            $anchor->setAttribute('href', '#' . $headingID->nodeValue);
70
71 9
            $body = strtr($anchorBody, [
72 9
                '{heading}' => $heading->textContent,
73 9
            ]);
74
75 9
            if (substr($body, 0, 1) === '<')
76 9
            {
77 2
                $domAnchorBody = new \DOMDocument();
78 2
                $loaded = @$domAnchorBody->loadHTML($body, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
79
80
                if ($loaded)
81 2
                {
82
                    /** @var \DOMElement $childNode */
83 2
                    foreach ($domAnchorBody->childNodes as $childNode)
84
                    {
85 2
                        $node = $anchor->ownerDocument->importNode($childNode->cloneNode(true), true);
86 2
                        $anchor->appendChild($node);
87 2
                    }
88 2
                }
89 2
            }
90
            else
91
            {
92 7
                $anchor->nodeValue = $body;
93
            }
94
95
            if ($anchorTitle)
96 9
            {
97 1
                $anchorAttrs['title'] = strtr($anchorTitle, [
98 1
                    '{heading}' => $heading->textContent,
99 1
                ]);
100 1
            }
101
102 9
            foreach ($anchorAttrs as $attrName => $attrValue)
103
            {
104 3
                $anchor->setAttribute($attrName, $attrValue);
105 9
            }
106
107
            if ($beforeHeading)
108 9
            {
109 3
                $heading->insertBefore($dom->createTextNode(' '), $heading->childNodes[0]);
110 3
                $heading->insertBefore($anchor, $heading->childNodes[0]);
111 3
            }
112
            else
113
            {
114 6
                $heading->appendChild($dom->createTextNode(' '));
115 6
                $heading->appendChild($anchor);
116
            }
117 9
        }
118
119 9
        return preg_replace('/<\\/?body>/', '', $dom->saveHTML());
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public static function get()
126
    {
127
        return new TwigFilter('anchors', __CLASS__ . '::filter');
128
    }
129
}
130