@@ -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,89 +154,82 @@ public static function truncate($text, $length = 0, $noSplit = true, $allowHtml
154154 *
155155 * @since 3.1
156156 */
157- public static function truncateComplex ($ html , $ maxLength = 0 , $ noSplit = true )
157+ public static function truncateComplex (string $ html , $ maxLength = 0 , bool $ noSplit = true ): string
158158 {
159- // Start with some basic rules.
160159 $ baseLength = \strlen ($ html );
161160
162- // If the original HTML string is shorter than the $maxLength do nothing and return that.
163- if ($ baseLength <= $ maxLength || $ maxLength === 0 ) {
161+ // Early return for trivial cases
162+ if ($ maxLength === 0 || $ baseLength <= $ maxLength ) {
164163 return $ html ;
165164 }
166165
167- // Take care of short simple cases .
168- if ($ maxLength <= 3 && $ html [0 ] !== '< ' && !str_contains (substr ($ html , 0 , $ maxLength - 1 ), '< ' ) && $ baseLength > $ maxLength ) {
166+ // Special case: very short cutoff, plain text .
167+ if ($ maxLength <= 3 && $ html [0 ] !== '< ' && !str_contains (substr ($ html , 0 , max ( 0 , $ maxLength - 1 )) , '< ' )) {
169168 return '... ' ;
170169 }
171170
172- // Deal with maximum length of 1 where the string starts with a tag.
171+ // Special case: string starts with a tag and maxLength is 1
173172 if ($ maxLength === 1 && $ html [0 ] === '< ' ) {
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 . '... ' ;
173+ $ endTagPos = strpos ($ html , '> ' );
174+ if ($ endTagPos === false ) {
175+ return '... ' ;
181176 }
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 . '... ' ;
177+ $ tag = substr ($ html , 1 , $ endTagPos - 1 );
178+ return substr ($ html , 0 , $ endTagPos + 1 ) . "</ $ tag>... " ;
187179 }
188180
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 );
181+ // Get a plain text truncated string
182+ $ ptString = HTMLHelper::_ ('string.truncate ' , $ html , $ maxLength , $ noSplit , false );
191183
192- // It's all HTML, just return it.
193184 if ($ ptString === '' ) {
194185 return $ html ;
195186 }
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.
199187 if (!str_ends_with ($ ptString , '... ' )) {
200188 return $ html ;
201189 }
202-
203- // Regular truncate gives us the ellipsis but we want to go back for text and tags.
204190 if ($ ptString === '... ' ) {
205191 $ stripped = substr (strip_tags ($ html ), 0 , $ maxLength );
206- $ ptString = HTMLHelper::_ ('string.truncate ' , $ stripped , $ maxLength , $ noSplit , $ allowHtml = false );
192+ $ ptString = HTMLHelper::_ ('string.truncate ' , $ stripped , $ maxLength , $ noSplit , false );
207193 }
208-
209- // We need to trim the ellipsis that truncate adds.
210194 $ ptString = rtrim ($ ptString , '. ' );
211195
212- // Now deal with more complex truncation.
213196 while ($ maxLength <= $ baseLength ) {
214- // Get the truncated string assuming HTML is allowed.
215- $ htmlString = HTMLHelper::_ ('string.truncate ' , $ html , $ maxLength , $ noSplit , $ allowHtml = true );
197+ $ htmlString = HTMLHelper::_ ('string.truncate ' , $ html , $ maxLength , $ noSplit , true );
216198
217199 if ($ htmlString === '... ' && \strlen ($ ptString ) + 3 > $ maxLength ) {
218- return $ htmlString ;
200+ return ' ... ' ;
219201 }
220202
221203 $ htmlString = rtrim ($ htmlString , '. ' );
222204
223- // Now get the plain text from the HTML string and trim it.
224- $ htmlStringToPtString = HTMLHelper::_ ('string.truncate ' , $ htmlString , $ maxLength , $ noSplit , $ allowHtml = false );
205+ // Get the plain text version of the truncated HTML string
206+ $ htmlStringToPtString = HTMLHelper::_ ('string.truncate ' , $ htmlString , $ maxLength , $ noSplit , false );
225207 $ htmlStringToPtString = rtrim ($ htmlStringToPtString , '. ' );
226208
227- // If the new plain text string matches the original plain text string we are done.
209+ // If plain text matches, we're done
228210 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+ }
229218 return $ htmlString . '... ' ;
230219 }
231220
232- // Get the number of HTML tag characters in the first $maxLength characters
221+ // Adjust length for HTML tags
233222 $ diffLength = \strlen ($ ptString ) - \strlen ($ htmlStringToPtString );
234-
235223 if ($ diffLength <= 0 ) {
224+ // Remove whitespace, non-breaking spaces, and trailing tags before the ellipsis
225+ $ htmlString = preg_replace ('/( |\s)+(<\/[^>]+>)?$/u ' , '' , $ htmlString );
226+
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+ }
236231 return $ htmlString . '... ' ;
237232 }
238-
239- // Set new $maxlength that adjusts for the HTML tags
240233 $ maxLength += $ diffLength ;
241234 }
242235
0 commit comments