@@ -40,7 +40,7 @@ abstract class StringHelper
4040 */
4141 public static function truncate ($ text , $ length = 0 , $ noSplit = true , $ allowHtml = true )
4242 {
43- // Assume a lone open tag is invalid HTML
43+ // Assume a lone open tag is invalid HTML.
4444 if ($ length === 1 && $ text [0 ] === '< ' ) {
4545 return '... ' ;
4646 }
@@ -154,82 +154,89 @@ public static function truncate($text, $length = 0, $noSplit = true, $allowHtml
154154 *
155155 * @since 3.1
156156 */
157- public static function truncateComplex (string $ html , $ maxLength = 0 , bool $ noSplit = true ): string
157+ public static function truncateComplex ($ html , $ maxLength = 0 , $ noSplit = true )
158158 {
159+ // Start with some basic rules.
159160 $ baseLength = \strlen ($ html );
160161
161- // Early return for trivial cases
162- if ($ maxLength === 0 || $ baseLength <= $ maxLength ) {
162+ // If the original HTML string is shorter than the $maxLength do nothing and return that.
163+ if ($ baseLength <= $ maxLength || $ maxLength === 0 ) {
163164 return $ html ;
164165 }
165166
166- // Special case: very short cutoff, plain text .
167- if ($ maxLength <= 3 && $ html [0 ] !== '< ' && !str_contains (substr ($ html , 0 , max ( 0 , $ maxLength - 1 )) , '< ' )) {
167+ // Take care of short simple cases .
168+ if ($ maxLength <= 3 && $ html [0 ] !== '< ' && !str_contains (substr ($ html , 0 , $ maxLength - 1 ), '< ' ) && $ baseLength > $ maxLength ) {
168169 return '... ' ;
169170 }
170171
171- // Special case: string starts with a tag and maxLength is 1
172+ // Deal with maximum length of 1 where the string starts with a tag.
172173 if ($ maxLength === 1 && $ html [0 ] === '< ' ) {
173- $ endTagPos = strpos ($ html , '> ' );
174- if ($ endTagPos === false ) {
175- return '... ' ;
174+ $ endTagPos = \strlen (strstr ($ html , '> ' , true ));
175+ $ tag = substr ($ html , 1 , $ endTagPos );
176+
177+ $ l = $ endTagPos + 1 ;
178+
179+ if ($ noSplit ) {
180+ return substr ($ html , 0 , $ l ) . '</ ' . $ tag . '... ' ;
176181 }
177- $ tag = substr ($ html , 1 , $ endTagPos - 1 );
178- return substr ($ html , 0 , $ endTagPos + 1 ) . "</ $ tag>... " ;
182+
183+ // @todo: $character doesn't seem to be used...
184+ $ character = substr (strip_tags ($ html ), 0 , 1 );
185+
186+ return substr ($ html , 0 , $ l ) . '</ ' . $ tag . '... ' ;
179187 }
180188
181- // Get a plain text truncated string
182- $ ptString = HTMLHelper::_ ('string.truncate ' , $ html , $ maxLength , $ noSplit , false );
189+ // First get the truncated plain text string. This is the rendered text we want to end up with.
190+ $ ptString = HTMLHelper::_ ('string.truncate ' , $ html , $ maxLength , $ noSplit , $ allowHtml = false );
183191
192+ // It's all HTML, just return it.
184193 if ($ ptString === '' ) {
185194 return $ html ;
186195 }
196+
197+ // If the plain text is shorter than the max length the variable will not end in ...
198+ // In that case we use the whole string.
187199 if (!str_ends_with ($ ptString , '... ' )) {
188200 return $ html ;
189201 }
202+
203+ // Regular truncate gives us the ellipsis but we want to go back for text and tags.
190204 if ($ ptString === '... ' ) {
191205 $ stripped = substr (strip_tags ($ html ), 0 , $ maxLength );
192- $ ptString = HTMLHelper::_ ('string.truncate ' , $ stripped , $ maxLength , $ noSplit , false );
206+ $ ptString = HTMLHelper::_ ('string.truncate ' , $ stripped , $ maxLength , $ noSplit , $ allowHtml = false );
193207 }
208+
209+ // We need to trim the ellipsis that truncate adds.
194210 $ ptString = rtrim ($ ptString , '. ' );
195211
212+ // Now deal with more complex truncation.
196213 while ($ maxLength <= $ baseLength ) {
197- $ htmlString = HTMLHelper::_ ('string.truncate ' , $ html , $ maxLength , $ noSplit , true );
214+ // Get the truncated string assuming HTML is allowed.
215+ $ htmlString = HTMLHelper::_ ('string.truncate ' , $ html , $ maxLength , $ noSplit , $ allowHtml = true );
198216
199217 if ($ htmlString === '... ' && \strlen ($ ptString ) + 3 > $ maxLength ) {
200- return ' ... ' ;
218+ return $ htmlString ;
201219 }
202220
203221 $ htmlString = rtrim ($ htmlString , '. ' );
204222
205- // Get the plain text version of the truncated HTML string
206- $ htmlStringToPtString = HTMLHelper::_ ('string.truncate ' , $ htmlString , $ maxLength , $ noSplit , false );
223+ // Now get the plain text from the HTML string and trim it.
224+ $ htmlStringToPtString = HTMLHelper::_ ('string.truncate ' , $ htmlString , $ maxLength , $ noSplit , $ allowHtml = false );
207225 $ htmlStringToPtString = rtrim ($ htmlStringToPtString , '. ' );
208226
209- // If plain text matches, we're done
227+ // If the new plain text string matches the original plain text string we are done.
210228 if ($ ptString === $ htmlStringToPtString ) {
211- // Remove whitespace, non-breaking spaces, and trailing tags before the ellipsis
212- $ htmlString = preg_replace ('/( |\s)+(<\/[^>]+>)?$/u ' , '' , $ htmlString );
213-
214- // If it ends with a closing tag, try to inject the ellipsis before the last closing tag
215- if (preg_match ('/(<\/[^>]+>)$/ ' , $ htmlString , $ matches )) {
216- return preg_replace ('/(<\/[^>]+>)$/ ' , '...$1 ' , $ htmlString );
217- }
218229 return $ htmlString . '... ' ;
219230 }
220231
221- // Adjust length for HTML tags
232+ // Get the number of HTML tag characters in the first $maxLength characters
222233 $ diffLength = \strlen ($ ptString ) - \strlen ($ htmlStringToPtString );
223- if ($ diffLength <= 0 ) {
224- // Remove whitespace, non-breaking spaces, and trailing tags before the ellipsis
225- $ htmlString = preg_replace ('/( |\s)+(<\/[^>]+>)?$/u ' , '' , $ htmlString );
226234
227- // If it ends with a closing tag, inject the ellipsis before the last closing tag
228- if (preg_match ('/(<\/[^>]+>)$/ ' , $ htmlString , $ matches )) {
229- return preg_replace ('/(<\/[^>]+>)$/ ' , '...$1 ' , $ htmlString );
230- }
235+ if ($ diffLength <= 0 ) {
231236 return $ htmlString . '... ' ;
232237 }
238+
239+ // Set new $maxlength that adjusts for the HTML tags
233240 $ maxLength += $ diffLength ;
234241 }
235242
0 commit comments