Passed
Push — developer ( 1116b3...4a3269 )
by Radosław
34:20 queued 19:24
created

TextUtils::htmlTruncate()   B

Complexity

Conditions 11
Paths 19

Size

Total Lines 42
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 31
c 0
b 0
f 0
dl 0
loc 42
rs 7.3166
cc 11
nc 19
nop 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Text utils file.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 5.0 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author    Mariusz Krzaczkowski <[email protected]>
10
 * @author    Radosław Skrzypczak <[email protected]>
11
 */
12
13
namespace App;
14
15
/**
16
 * Text utils class.
17
 */
18
class TextUtils
19
{
20
	/**
21
	 * Get text length.
22
	 *
23
	 * @param string $text
24
	 * @param bool   $strict Text length in bytes
25
	 *
26
	 * @return int
27
	 */
28
	public static function getTextLength($text, bool $strict = false)
29
	{
30
		$lenght = 0;
31
		if ($strict) {
32
			$lenght = null !== $text ? \strlen($text) : 0;
0 ignored issues
show
introduced by
The condition null !== $text is always true.
Loading history...
33
		} else {
34
			$lenght = null !== $text ? mb_strlen($text) : 0;
0 ignored issues
show
introduced by
The condition null !== $text is always true.
Loading history...
35
		}
36
37
		return $lenght;
38
	}
39
40
	/**
41
	 * Truncating text.
42
	 *
43
	 * @param string   $text
44
	 * @param bool|int $length
45
	 * @param bool     $addDots
46
	 * @param bool     $strict  Used when a string length in bytes is required
47
	 *
48
	 * @return string
49
	 */
50
	public static function textTruncate($text, $length = false, $addDots = true, bool $strict = false)
51
	{
52
		if (!$length) {
53
			$length = Config::main('listview_max_textlength');
54
		}
55
		$textLength = self::getTextLength($text, $strict);
56
		if ($textLength > $length) {
57
			if ($addDots) {
58
				$length = $length > 3 ? $length - 3 : 0;
59
				$text = $strict ? mb_strcut($text, 0, $length, Config::main('default_charset')) : mb_substr($text, 0, $length, Config::main('default_charset'));
60
				$text .= '...';
61
			} else {
62
				$text = $strict ? mb_strcut($text, 0, $length, Config::main('default_charset')) : mb_substr($text, 0, $length, Config::main('default_charset'));
63
			}
64
		}
65
66
		return $text;
67
	}
68
69
	/**
70
	 * Truncating HTML by words.
71
	 *
72
	 * @param string $html
73
	 * @param int    $length
74
	 * @param string $ending
75
	 *
76
	 * @return string
77
	 */
78
	public static function htmlTruncateByWords(string $html, int $length = 0, string $ending = '...'): string
79
	{
80
		if (!$length) {
81
			$length = Config::main('listview_max_textlength');
82
		}
83
		if (\strlen(strip_tags($html)) <= $length) {
84
			return $html;
85
		}
86
		$totalLength = \mb_strlen($ending);
87
		$openTagsLength = 0;
88
		$openTags = [];
89
		preg_match_all('/(<.+?>)?([^<>]*)/s', $html, $tags, PREG_SET_ORDER);
90
		$html = '';
91
		foreach ($tags as $tag) {
92
			$tagLength = \mb_strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $tag[2]));
93
			if (($totalLength + $tagLength + $openTagsLength) >= $length) {
94
				if (empty($html)) {
95
					preg_match('/^<\s*([^\s>!]+).*?>$/s', $tag[1], $tagName);
96
					$openTags[] = $tagName[1];
97
					$html = $tag[1] . self::textTruncate($tag[2], $length - 3, false);
98
				}
99
				break;
100
			}
101
			if (!empty($tag[1])) {
102
				if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $tag[1])) {
103
					// if tag is a closing tag
104
				} elseif (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $tag[1], $tagName)) {
105
					$pos = array_search(strtolower($tagName[1]), $openTags);
106
					if (false !== $pos) {
107
						unset($openTags[$pos]);
108
						$openTagsLength -= \mb_strlen("</{$tagName[1]}>");
109
					}
110
				} elseif (preg_match('/^<\s*([^\s>!]+).*?>$/s', $tag[1], $tagName)) {
111
					array_unshift($openTags, strtolower($tagName[1]));
112
					$openTagsLength += \mb_strlen("</{$tagName[1]}>");
113
				}
114
			}
115
			$html .= $tag[0];
116
			$totalLength += $tagLength;
117
		}
118
		$html .= $ending;
119
		if ($openTags) {
120
			$html .= '</' . implode('></', $openTags) . '>';
121
		}
122
		return $html;
123
	}
124
125
	/**
126
	 * Truncating HTML.
127
	 *
128
	 * @param string $html
129
	 * @param int    $length
130
	 * @param string $ending
131
	 *
132
	 * @return string
133
	 */
134
	public static function htmlTruncate(string $html, int $length = 255, string $ending = '...'): string
135
	{
136
		if (\strlen($html) <= $length) {
137
			return $html;
138
		}
139
		$totalLength = \mb_strlen($ending);
140
		$openTagsLength = 0;
141
		$openTags = [];
142
		preg_match_all('/(<.+?>)?([^<>]*)/s', $html, $tags, PREG_SET_ORDER);
143
		$html = '';
144
		foreach ($tags as $tag) {
145
			$tagLength = \mb_strlen($tag[0]);
146
			if (($totalLength + $tagLength + $openTagsLength) >= $length) {
147
				if (empty($html)) {
148
					preg_match('/^<\s*([^\s>!]+).*?>$/s', $tag[1], $tagName);
149
					$openTags[] = $tagName[1];
150
					$html = $tag[1] . self::textTruncate($tag[2], $length - 3, false);
151
				}
152
				break;
153
			}
154
			if (!empty($tag[1])) {
155
				if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $tag[1])) {
156
					// if tag is a closing tag
157
				} elseif (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $tag[1], $tagName)) {
158
					$pos = array_search(strtolower($tagName[1]), $openTags);
159
					if (false !== $pos) {
160
						unset($openTags[$pos]);
161
						$openTagsLength -= \mb_strlen("</{$tagName[1]}>");
162
					}
163
				} elseif (preg_match('/^<\s*([^\s>!]+).*?>$/s', $tag[1], $tagName)) {
164
					array_unshift($openTags, strtolower($tagName[1]));
165
					$openTagsLength += \mb_strlen("</{$tagName[1]}>");
166
				}
167
			}
168
			$html .= $tag[0];
169
			$totalLength += $tagLength;
170
		}
171
		$html .= $ending;
172
		if ($openTags) {
173
			$html .= '</' . implode('></', $openTags) . '>';
174
		}
175
		return $html;
176
	}
177
178
	/**
179
	 * Get all attributes of a tag.
180
	 *
181
	 * @param string $tag
182
	 *
183
	 * @return string[]
184
	 */
185
	public static function getTagAttributes(string $tag): array
186
	{
187
		$dom = new \DOMDocument('1.0', 'UTF-8');
188
		$previousValue = libxml_use_internal_errors(true);
189
		$dom->loadHTML('<?xml encoding="utf-8"?>' . $tag);
190
		libxml_clear_errors();
191
		libxml_use_internal_errors($previousValue);
192
		$tag = $dom->getElementsByTagName('*')->item(2);
193
		$attributes = [];
194
		if ($tag->hasAttributes()) {
195
			foreach ($tag->attributes as $attr) {
196
				$attributes[$attr->name] = $attr->value;
197
			}
198
		}
199
		return $attributes;
200
	}
201
202
	/**
203
	 * Truncating text.
204
	 *
205
	 * @param string   $text
206
	 * @param bool|int $length
207
	 * @param bool     $addDots
208
	 * @param bool     $strict  Used when a string length in bytes is required
209
	 *
210
	 * @return string
211
	 */
212
	public static function textTruncateWithTooltip($text, $length = false, $addDots = true, bool $strict = false)
213
	{
214
		$truncateText = self::textTruncate($text, $length, $addDots, $strict);
215
		if (!$length) {
216
			$length = Config::main('listview_max_textlength');
217
		}
218
		if (\strlen($text) > $length) {
219
			$truncateText .= '<span class="js-popover-tooltip ml-1 d-inline my-auto u-h-fit u-cursor-pointer" data-placement="top" data-content="' . $text . '">
220
			<span class="fas fa-info-circle"></span>
221
			</span>';
222
		}
223
		return $truncateText;
224
	}
225
}
226