Completed
Push — 1.5 ( fe2226...1e8722 )
by Colin
02:27
created

ExternalLinkProcessor::applyRelAttribute()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 12
cts 12
cp 1
rs 8.4444
c 0
b 0
f 0
cc 8
nc 10
nop 2
crap 8
1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace League\CommonMark\Extension\ExternalLink;
13
14
use League\CommonMark\EnvironmentInterface;
15
use League\CommonMark\Event\DocumentParsedEvent;
16
use League\CommonMark\Inline\Element\Link;
17
18
final class ExternalLinkProcessor
19
{
20
    public const APPLY_NONE = '';
21
    public const APPLY_ALL = 'all';
22
    public const APPLY_EXTERNAL = 'external';
23
    public const APPLY_INTERNAL = 'internal';
24
25
    /** @var EnvironmentInterface */
26
    private $environment;
27
28 201
    public function __construct(EnvironmentInterface $environment)
29
    {
30 201
        $this->environment = $environment;
31 201
    }
32
33
    /**
34
     * @param DocumentParsedEvent $e
35
     *
36
     * @return void
37
     */
38 201
    public function __invoke(DocumentParsedEvent $e)
39
    {
40 201
        $internalHosts = $this->environment->getConfig('external_link/internal_hosts', []);
41 201
        $openInNewWindow = $this->environment->getConfig('external_link/open_in_new_window', false);
42 201
        $classes = $this->environment->getConfig('external_link/html_class', '');
43
44 201
        $walker = $e->getDocument()->walker();
45 201
        while ($event = $walker->next()) {
46 201
            if ($event->isEntering() && $event->getNode() instanceof Link) {
47
                /** @var Link $link */
48 201
                $link = $event->getNode();
49
50 201
                $host = parse_url($link->getUrl(), PHP_URL_HOST);
51 201
                if (empty($host)) {
52
                    // Something is terribly wrong with this URL
53 3
                    continue;
54
                }
55
56 198
                if (self::hostMatches($host, $internalHosts)) {
57 195
                    $link->data['external'] = false;
58 195
                    $this->applyRelAttribute($link, false);
59 195
                    continue;
60
                }
61
62
                // Host does not match our list
63 198
                $this->markLinkAsExternal($link, $openInNewWindow, $classes);
64
            }
65
        }
66 201
    }
67
68 198
    private function markLinkAsExternal(Link $link, bool $openInNewWindow, string $classes): void
69
    {
70 198
        $link->data['external'] = true;
71 198
        $link->data['attributes'] = $link->getData('attributes', []);
72 198
        $this->applyRelAttribute($link, true);
73
74 198
        if ($openInNewWindow) {
75 3
            $link->data['attributes']['target'] = '_blank';
76
        }
77
78 198
        if (!empty($classes)) {
79 3
            $link->data['attributes']['class'] = trim(($link->data['attributes']['class'] ?? '') . ' ' . $classes);
80
        }
81 198
    }
82
83 198
    private function applyRelAttribute(Link $link, bool $isExternal): void
84
    {
85 198
        $rel = [];
86
87 198
        foreach (['nofollow', 'noopener', 'noreferrer'] as $type) {
88 198
            $option = $this->environment->getConfig('external_link/' . $type, self::APPLY_EXTERNAL);
89
            switch (true) {
90 198
                case $option === self::APPLY_ALL:
91 195
                case $isExternal && $option === self::APPLY_EXTERNAL:
92 192
                case !$isExternal && $option === self::APPLY_INTERNAL:
93 195
                    $rel[] = $type;
94
            }
95
        }
96
97 198
        if ($rel === []) {
98 48
            return;
99
        }
100
101 195
        $link->data['attributes']['rel'] = \implode(' ', $rel);
102 195
    }
103
104
    /**
105
     * @param string $host
106
     * @param mixed  $compareTo
107
     *
108
     * @return bool
109
     *
110
     * @internal This method is only public so we can easily test it. DO NOT USE THIS OUTSIDE OF THIS EXTENSION!
111
     */
112 219
    public static function hostMatches(string $host, $compareTo)
113
    {
114 219
        foreach ((array) $compareTo as $c) {
115 216
            if (strpos($c, '/') === 0) {
116 6
                if (preg_match($c, $host)) {
117 6
                    return true;
118
                }
119 210
            } elseif ($c === $host) {
120 201
                return true;
121
            }
122
        }
123
124 207
        return false;
125
    }
126
}
127