ExternalLinkProcessor   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 103
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 22
eloc 48
c 2
b 0
f 0
dl 0
loc 103
ccs 49
cts 49
cp 1
rs 10

5 Methods

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