+ # Consider truncation once the display length has reached the maximim.
+ # Double-check that we're not in the middle of a bracket/entity...
+ if ( $displayLen >= $length && $bracketState == 0 && $entityState == 0 ) {
+ if ( !$testingEllipsis ) {
+ $testingEllipsis = true;
+ # Save where we are; we will truncate here unless
+ # the ellipsis actually makes the string longer.
+ $pOpenTags = $openTags; // save state
+ $pRet = $ret; // save state
+ } elseif ( $displayLen > ( $length + strlen( $ellipsis ) ) ) {
+ # Ellipsis won't make string longer/equal, the truncation point was OK.
+ $openTags = $pOpenTags; // reload state
+ $ret = $this->removeBadCharLast( $pRet ); // reload state, multi-byte char fix
+ $ret .= $ellipsis; // add ellipsis
+ break;
+ }
+ }
+ }
+ if ( $displayLen == 0 ) {
+ return ''; // no text shown, nothing to format
+ }
+ // Close the last tag if left unclosed by bad HTML
+ $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
+ while ( count( $openTags ) > 0 ) {
+ $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
+ }
+ return $ret;
+ }
+
+ // truncateHtml() helper function
+ // like strcspn() but adds the skipped chars to $ret
+ private function truncate_skip( &$ret, $text, $search, $start, $len = -1 ) {
+ $skipCount = 0;
+ if ( $start < strlen( $text ) ) {
+ $skipCount = strcspn( $text, $search, $start, $len );
+ $ret .= substr( $text, $start, $skipCount );
+ }
+ return $skipCount;
+ }
+
+ /*
+ * truncateHtml() helper function
+ * (a) push or pop $tag from $openTags as needed
+ * (b) clear $tag value
+ * @param String &$tag Current HTML tag name we are looking at
+ * @param int $tagType (0-open tag, 1-close tag)
+ * @param char $lastCh Character before the '>' that ended this tag
+ * @param array &$openTags Open tag stack (not accounting for $tag)
+ */
+ private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
+ $tag = ltrim( $tag );
+ if ( $tag != '' ) {
+ if ( $tagType == 0 && $lastCh != '/' ) {
+ $openTags[] = $tag; // tag opened (didn't close itself)
+ } else if ( $tagType == 1 ) {
+ if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
+ array_pop( $openTags ); // tag closed
+ }