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 -->') |
|
|
|
|
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 = '...') |
|
|
|
|
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]; |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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
|
|
|
|
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.