Passed
Pull Request — developer (#17179)
by Mariusz
29:27 queued 10:21
created

TextUtils::getTextLength()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 4
nc 4
nop 2
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
		$length = 0;
31
		if ($strict) {
32
			$length = null !== $text ? \strlen($text) : 0;
0 ignored issues
show
introduced by
The condition null !== $text is always true.
Loading history...
33
		} else {
34
			$length = null !== $text ? \mb_strlen($text) : 0;
0 ignored issues
show
introduced by
The condition null !== $text is always true.
Loading history...
35
		}
36
		return $length;
37
	}
38
39
	/**
40
	 * Truncating text.
41
	 *
42
	 * @param string   $text
43
	 * @param bool|int $length
44
	 * @param bool     $addDots
45
	 * @param bool     $strict  Used when a string length in bytes is required
46
	 *
47
	 * @return string
48
	 */
49
	public static function textTruncate($text, $length = false, $addDots = true, bool $strict = false)
50
	{
51
		if (!$length) {
52
			$length = Config::main('listview_max_textlength');
53
		}
54
		$textLength = self::getTextLength($text, $strict);
55
		if ($textLength > $length) {
56
			if ($addDots) {
57
				$length = $length > 3 ? $length - 3 : 0;
58
				$text = $strict ? mb_strcut($text, 0, $length, Config::main('default_charset')) : mb_substr($text, 0, $length, Config::main('default_charset'));
59
				$text .= '...';
60
			} else {
61
				$text = $strict ? mb_strcut($text, 0, $length, Config::main('default_charset')) : mb_substr($text, 0, $length, Config::main('default_charset'));
62
			}
63
		}
64
65
		return $text;
66
	}
67
68
	/**
69
	 * Truncating HTML by words.
70
	 *
71
	 * @param string $html
72
	 * @param int    $length
73
	 * @param string $ending
74
	 *
75
	 * @return string
76
	 */
77
	public static function htmlTruncateByWords(string $html, int $length = 0, string $ending = '...'): string
78
	{
79
		if (!$length) {
80
			$length = Config::main('listview_max_textlength');
81
		}
82
		if (\strlen(strip_tags($html)) <= $length) {
83
			return $html;
84
		}
85
		$totalLength = \strlen($ending);
86
		$openTagsLength = 0;
87
		$openTags = [];
88
		preg_match_all('/(<.+?>)?([^<>]*)/s', $html, $tags, PREG_SET_ORDER);
89
		$html = '';
90
		foreach ($tags as $tag) {
91
			$tagLength = \strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $tag[2]));
92
			if (($totalLength + $tagLength + $openTagsLength) >= $length) {
93
				if (empty($html)) {
94
					preg_match('/^<\s*([^\s>!]+).*?>$/s', $tag[1], $tagName);
95
					$openTags[] = $tagName[1];
96
					$html = $tag[1] . self::textTruncate($tag[2], $length - 3, false);
97
				}
98
				break;
99
			}
100
			if (!empty($tag[1])) {
101
				if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $tag[1])) {
102
					// if tag is a closing tag
103
				} elseif (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $tag[1], $tagName)) {
104
					$pos = array_search(strtolower($tagName[1]), $openTags);
105
					if (false !== $pos) {
106
						unset($openTags[$pos]);
107
						$openTagsLength -= \strlen("</{$tagName[1]}>");
108
					}
109
				} elseif (preg_match('/^<\s*([^\s>!]+).*?>$/s', $tag[1], $tagName)) {
110
					array_unshift($openTags, strtolower($tagName[1]));
111
					$openTagsLength += \strlen("</{$tagName[1]}>");
112
				}
113
			}
114
			$html .= $tag[0];
115
			$totalLength += $tagLength;
116
		}
117
		$html .= $ending;
118
		if ($openTags) {
119
			$html .= '</' . implode('></', $openTags) . '>';
120
		}
121
		return $html;
122
	}
123
124
	/**
125
	 * Truncating HTML.
126
	 *
127
	 * @param string $html
128
	 * @param int    $length
129
	 * @param string $ending
130
	 *
131
	 * @return string
132
	 */
133
	public static function htmlTruncate(string $html, int $length = 255, string $ending = '...'): string
134
	{
135
		if (\strlen($html) <= $length) {
136
			return $html;
137
		}
138
		$totalLength = \strlen($ending);
139
		$openTagsLength = 0;
140
		$openTags = [];
141
		preg_match_all('/(<.+?>)?([^<>]*)/s', $html, $tags, PREG_SET_ORDER);
142
		$html = '';
143
		foreach ($tags as $tag) {
144
			$tagLength = \strlen($tag[0]);
145
			if (($totalLength + $tagLength + $openTagsLength) >= $length) {
146
				if (empty($html)) {
147
					preg_match('/^<\s*([^\s>!]+).*?>$/s', $tag[1], $tagName);
148
					$openTags[] = $tagName[1];
149
					$html = $tag[1] . self::textTruncate($tag[2], $length - 3, false);
150
				}
151
				break;
152
			}
153
			if (!empty($tag[1])) {
154
				if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $tag[1])) {
155
					// if tag is a closing tag
156
				} elseif (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $tag[1], $tagName)) {
157
					$pos = array_search(strtolower($tagName[1]), $openTags);
158
					if (false !== $pos) {
159
						unset($openTags[$pos]);
160
						$openTagsLength -= \strlen("</{$tagName[1]}>");
161
					}
162
				} elseif (preg_match('/^<\s*([^\s>!]+).*?>$/s', $tag[1], $tagName)) {
163
					array_unshift($openTags, strtolower($tagName[1]));
164
					$openTagsLength += \strlen("</{$tagName[1]}>");
165
				}
166
			}
167
			$html .= $tag[0];
168
			$totalLength += $tagLength;
169
		}
170
		$html .= $ending;
171
		if ($openTags) {
172
			$html .= '</' . implode('></', $openTags) . '>';
173
		}
174
		return $html;
175
	}
176
177
	/**
178
	 * Get all attributes of a tag.
179
	 *
180
	 * @param string $tag
181
	 *
182
	 * @return string[]
183
	 */
184
	public static function getTagAttributes(string $tag): array
185
	{
186
		$dom = new \DOMDocument('1.0', 'UTF-8');
187
		$previousValue = libxml_use_internal_errors(true);
188
		$dom->loadHTML('<?xml encoding="utf-8"?>' . $tag);
189
		libxml_clear_errors();
190
		libxml_use_internal_errors($previousValue);
191
		$tag = $dom->getElementsByTagName('*')->item(2);
192
		$attributes = [];
193
		if ($tag->hasAttributes()) {
194
			foreach ($tag->attributes as $attr) {
195
				$attributes[$attr->name] = $attr->value;
196
			}
197
		}
198
		return $attributes;
199
	}
200
201
	/**
202
	 * Truncating text.
203
	 *
204
	 * @param string   $text
205
	 * @param bool|int $length
206
	 * @param bool     $addDots
207
	 * @param bool     $strict  Used when a string length in bytes is required
208
	 *
209
	 * @return string
210
	 */
211
	public static function textTruncateWithTooltip($text, $length = false, $addDots = true, bool $strict = false)
212
	{
213
		$truncateText = self::textTruncate($text, $length, $addDots, $strict);
214
		if (!$length) {
215
			$length = Config::main('listview_max_textlength');
216
		}
217
		if (\strlen($text) > $length) {
218
			$truncateText .= '<span class="js-popover-tooltip ml-1 d-inline my-auto u-h-fit u-cursor-pointer" data-placement="top" data-content="' . \App\Purifier::encodeHtml($text) . '">
219
			<span class="fas fa-info-circle"></span>
220
			</span>';
221
		}
222
		return $truncateText;
223
	}
224
225
	/**
226
	 * Building html for a table from an array of data.
227
	 *
228
	 * @param array $rows   Entries to display
229
	 * @param array $column List of labeled columns to display
230
	 *
231
	 * @return string
232
	 */
233
	public static function getHtmlTable(array $rows, array $column): string
234
	{
235
		if (empty($rows)) {
236
			return '';
237
		}
238
		$html = '<table width="100%" border="1" cellpadding="3" style="border-collapse: collapse;"><thead><tr>';
239
		foreach ($column as $key => $label) {
240
			$html .= "<th>{$label}</th>";
241
		}
242
		$html .= '</tr></thead><tbody>';
243
		foreach ($rows as $row) {
244
			$html .= '<tr>';
245
			foreach ($column as $key => $label) {
246
				$html .= "<td>{$row[$key]}</td>";
247
			}
248
			$html .= '</tr>';
249
		}
250
		$html .= '</tbody></table>';
251
		return $html;
252
	}
253
}
254