Failed Conditions
Push — master ( 174394...bfbf85 )
by Arnold
14s queued 12s
created

TextExtension::linkify()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 34
ccs 19
cts 19
cp 1
rs 8.4426
c 0
b 0
f 0
cc 7
nc 11
nop 4
crap 7
1
<?php
2
3
namespace Jasny\Twig;
4
5
use Twig\Extension\AbstractExtension;
6
use Twig\TwigFilter;
7
8
/**
9
 * Text functions for Twig.
10
 */
11
class TextExtension extends AbstractExtension
12
{
13
    /**
14
     * Return extension name
15
     *
16
     * @return string
17
     */
18
    public function getName()
19
    {
20
        return 'jasny/text';
21
    }
22
23
    /**
24
     * {@inheritdoc}
25
     */
26 25
    public function getFilters()
27
    {
28
        return [
29 25
            new TwigFilter('paragraph', [$this, 'paragraph'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
30 25
            new TwigFilter('line', [$this, 'line']),
31 25
            new TwigFilter('less', [$this, 'less'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
32 25
            new TwigFilter('truncate', [$this, 'truncate'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
33 25
            new TwigFilter('linkify', [$this, 'linkify'], ['pre_escape' => 'html', 'is_safe' => ['html']])
34
        ];
35
    }
36
37
    /**
38
     * Add paragraph and line breaks to text.
39
     *
40
     * @param string $value
41
     * @return string
42
     */
43 2
    public function paragraph($value)
44
    {
45 2
        if (!isset($value)) {
46 1
            return null;
47
        }
48
49 1
        return '<p>' . preg_replace(['~\n(\s*)\n\s*~', '~(?<!</p>)\n\s*~'], ["</p>\n\$1<p>", "<br>\n"], trim($value)) .
50 1
            '</p>';
51
    }
52
53
    /**
54
     * Get a single line
55
     *
56
     * @param string $value
57
     * @param int    $line   Line number (starts at 1)
58
     * @return string
59
     */
60 4
    public function line($value, $line = 1)
61
    {
62 4
        if (!isset($value)) {
63 1
            return null;
64
        }
65
66 3
        $lines = explode("\n", $value);
67
68 3
        return isset($lines[$line - 1]) ? $lines[$line - 1] : null;
69
    }
70
71
    /**
72
     * Cut of text on a pagebreak.
73
     *
74
     * @param string $value
75
     * @param string $replace
76
     * @param string $break
77
     * @return string
78
     */
79 4 View Code Duplication
    public function less($value, $replace = '...', $break = '<!-- pagebreak -->')
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...
80
    {
81 4
        if (!isset($value)) {
82 1
            return null;
83
        }
84
85 3
        $pos = stripos($value, $break);
86 3
        return $pos === false ? $value : substr($value, 0, $pos) . $replace;
87
    }
88
89
    /**
90
     * Cut of text if it's to long.
91
     *
92
     * @param string $value
93
     * @param int    $length
94
     * @param string $replace
95
     * @return string
96
     */
97 4 View Code Duplication
    public function truncate($value, $length, $replace = '...')
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...
98
    {
99 4
        if (!isset($value)) {
100 1
            return null;
101
        }
102
103 3
        return strlen($value) <= $length ? $value : substr($value, 0, $length - strlen($replace)) . $replace;
104
    }
105
106
    /**
107
     * Linkify a HTTP(S) link.
108
     *
109
     * @param string $protocol  'http' or 'https'
110
     * @param string $text
111
     * @param array  $links     OUTPUT
112
     * @param string $attr
113
     * @param string $mode
114
     * @return string
115
     */
116 6
    protected function linkifyHttp($protocol, $text, array &$links, $attr, $mode)
117
    {
118 6
        $regexp = $mode != 'all'
119 5
            ? '~(?:(https?)://([^\s<>]+)|(?<!\w@)\b(www\.[^\s<>]+?\.[^\s<>]+))(?<![\.,:;\?!\'"\|])~i'
120 6
            : '~(?:(https?)://([^\s<>]+)|(?<!\w@)\b([^\s<>@]+?\.[^\s<>]+)(?<![\.,:]))~i';
121
122 6
        return preg_replace_callback($regexp, function ($match) use ($protocol, &$links, $attr) {
123 5
            if ($match[1]) $protocol = $match[1];
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $protocol, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
124 5
            $link = $match[2] ?: $match[3];
125
126 5
            return '<' . array_push($links, '<a' . $attr . ' href="' . $protocol . '://' . $link . '">'
127 5
                . rtrim($link, '/') . '</a>') . '>';
128 6
        }, $text);
129
    }
130
131
    /**
132
     * Linkify a mail link.
133
     *
134
     * @param string $text
135
     * @param array  $links     OUTPUT
136
     * @param string $attr
137
     * @return string
138
     */
139 5
    protected function linkifyMail($text, array &$links, $attr)
140
    {
141 5
        $regexp = '~([^\s<>]+?@[^\s<>]+?\.[^\s<>]+)(?<![\.,:;\?!\'"\|])~';
142
143 5 View Code Duplication
        return preg_replace_callback($regexp, function ($match) use (&$links, $attr) {
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...
144 4
            return '<' . array_push($links, '<a' . $attr . ' href="mailto:' . $match[1] . '">' . $match[1] . '</a>')
145 4
                . '>';
146 5
        }, $text);
147
    }
148
149
150
    /**
151
     * Linkify a link.
152
     *
153
     * @param string $protocol
154
     * @param string $text
155
     * @param array  $links     OUTPUT
156
     * @param string $attr
157
     * @param string $mode
158
     * @return string
159
     */
160 4
    protected function linkifyOther($protocol, $text, array &$links, $attr, $mode)
161
    {
162 4
        if (strpos($protocol, ':') === false) {
163 4
            $protocol .= in_array($protocol, ['ftp', 'tftp', 'ssh', 'scp']) ? '://' : ':';
164
        }
165
166 4
        $regexp = $mode != 'all'
167 2
            ? '~' . preg_quote($protocol, '~') . '([^\s<>]+)(?<![\.,:;\?!\'"\|])~i'
168 4
            : '~([^\s<>]+)(?<![\.,:])~i';
169
170 4 View Code Duplication
        return preg_replace_callback($regexp, function ($match) use ($protocol, &$links, $attr) {
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...
171 4
            return '<' . array_push($links, '<a' . $attr . ' href="' . $protocol . $match[1] . '">' . $match[1]
172 4
                . '</a>') . '>';
173 4
        }, $text);
174
    }
175
176
    /**
177
     * Turn all URLs in clickable links.
178
     *
179
     * @param string $value
180
     * @param array  $protocols   'http'/'https', 'mail' and also 'ftp', 'scp', 'tel', etc
181
     * @param array  $attributes  HTML attributes for the link
182
     * @param string $mode        normal or all
183
     * @return string
184
     */
185 11
    public function linkify($value, $protocols = ['http', 'mail'], array $attributes = [], $mode = 'normal')
186
    {
187 11
        if (!isset($value)) {
188 1
            return null;
189
        }
190
191
        // Link attributes
192 10
        $attr = '';
193 10
        foreach ($attributes as $key => $val) {
194 1
            $attr .= ' ' . $key . '="' . htmlentities($val) . '"';
195
        }
196
197 10
        $links = [];
198
199
        // Extract existing links and tags
200 10
        $text = preg_replace_callback('~(<a .*?>.*?</a>|<.*?>)~i', function ($match) use (&$links) {
201 1
            return '<' . array_push($links, $match[1]) . '>';
202 10
        }, $value);
203
204
        // Extract text links for each protocol
205 10
        foreach ((array)$protocols as $protocol) {
206 10
            switch ($protocol) {
207 10
                case 'http':
208 10
                case 'https':   $text = $this->linkifyHttp($protocol, $text, $links, $attr, $mode); break;
209 9
                case 'mail':    $text = $this->linkifyMail($text, $links, $attr); break;
210 4
                default:        $text = $this->linkifyOther($protocol, $text, $links, $attr, $mode); break;
211
            }
212
        }
213
214
        // Insert all link
215 10
        return preg_replace_callback('/<(\d+)>/', function ($match) use (&$links) {
216 10
            return $links[$match[1] - 1];
217 10
        }, $text);
218
    }
219
}
220