Skip to:
Content

BuddyPress.org


Ignore:
Timestamp:
08/16/2011 11:33:09 AM (14 years ago)
Author:
boonebgorges
Message:

Modifies bp_create_excerpt() to take account of HTML tag length when truncating long texts. Thanks to CakePHP for the function. Refactors BP use of bp_create_excerpt(), where necessary, to account for new function argument structure. Reworks bp_activity_truncate_entry() so that already-truncated activity items (like blog posts) do not get a Read More link when they contain HTML. Introduces fallback functions for some multibyte string functions, to make bp_create_excerpt() work properly. Thanks to MediaWiki for these fallbacks. Fixes #3452. Props DJPaul for all the help with this patch.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/bp-core/bp-core-template.php

    r4969 r4989  
    347347    }
    348348
    349 /**
    350  * bp_create_excerpt()
    351  *
    352  * Fakes an excerpt on any content. Will not truncate words.
    353  *
    354  * @package BuddyPress Core
    355  * @param $text str The text to create the excerpt from
    356  * @param $excerpt_length Minimum excerpt length, in characters
    357  * @param $filter_shortcodes When true, registered shortcodes (in square brackets) will be stripped
    358  * @param $append_text Be sure to include a leading space
    359  * @return str The excerpt text
    360  */
    361 function bp_create_excerpt( $text, $excerpt_length = 225, $filter_shortcodes = true, $append_text = ' […]' ) { // Fakes an excerpt if needed
     349
     350/**
     351 * Truncates text.
     352 *
     353 * Cuts a string to the length of $length and replaces the last characters
     354 * with the ending if the text is longer than length.
     355 *
     356 * This function is borrowed from CakePHP v2.0, under the MIT license. See
     357 * http://book.cakephp.org/view/1469/Text#truncate-1625
     358 *
     359 * ### Options:
     360 *
     361 * - `ending` Will be used as Ending and appended to the trimmed string
     362 * - `exact` If false, $text will not be cut mid-word
     363 * - `html` If true, HTML tags would be handled correctly
     364 * - `filter_shortcodes` If true, shortcodes will be stripped before truncating
     365 *
     366 * @package BuddyPress
     367 *
     368 * @param string  $text String to truncate.
     369 * @param integer $length Length of returned string, including ellipsis.
     370 * @param array $options An array of html attributes and options.
     371 * @return string Trimmed string.
     372 */
     373function bp_create_excerpt( $text, $length = 225, $options = array() ) {
     374    // Backward compatibility. The third argument used to be a boolean $filter_shortcodes
     375    $filter_shortcodes_default = is_bool( $options ) ? $options : true;
     376
     377    $defaults = array(
     378        'ending'            => __( ' […]', 'buddypress' ),
     379        'exact'             => false,
     380        'html'              => true,
     381        'filter_shortcodes' => $filter_shortcodes_default
     382    );
     383    $r = wp_parse_args( $options, $defaults );
     384    extract( $r );
     385
     386    // Save the original text, to be passed along to the filter
    362387    $original_text = $text;
    363     $text = str_replace( ']]>', ']]>', $text );
    364 
    365     $excerpt_length = apply_filters( 'bp_excerpt_length', $excerpt_length );
    366     $append_text = apply_filters( 'bp_excerpt_append_text', $append_text );
    367 
     388
     389    // Allow plugins to modify these values globally
     390    $length = apply_filters( 'bp_excerpt_length', $length );
     391    $ending = apply_filters( 'bp_excerpt_append_text', $ending );
     392
     393    // Remove shortcodes if necessary
    368394    if ( $filter_shortcodes )
    369395        $text = strip_shortcodes( $text );
    370396
    371     preg_match( "%\s*((?:<[^>]+>)+\S*)\s*|\s+%s", $text, $matches, PREG_OFFSET_CAPTURE, $excerpt_length );
    372 
    373     if ( !empty( $matches ) ) {
    374         $pos = array_pop( array_pop( $matches ) );
    375         $text = substr( $text, 0, $pos ) . $append_text;
    376     }
    377 
    378     return apply_filters( 'bp_create_excerpt', $text, $original_text, $excerpt_length, $filter_shortcodes, $append_text );
     397    // When $html is true, the excerpt should be created without including HTML tags in the
     398    // excerpt length
     399    if ( $html ) {
     400        // The text is short enough. No need to truncate
     401        if ( mb_strlen( preg_replace( '/<.*?>/', '', $text ) ) <= $length ) {
     402            return $text;
     403        }
     404
     405        $totalLength = mb_strlen( strip_tags( $ending ) );
     406        $openTags    = array();
     407        $truncate    = '';
     408
     409        // Find all the tags and put them in a stack for later use
     410        preg_match_all( '/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER );
     411        foreach ( $tags as $tag ) {
     412            // Process tags that need to be closed
     413            if ( !preg_match( '/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s',  $tag[2] ) ) {
     414                if ( preg_match( '/<[\w]+[^>]*>/s', $tag[0] ) ) {
     415                    array_unshift( $openTags, $tag[2] );
     416                } else if ( preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag ) ) {
     417                    $pos = array_search( $closeTag[1], $openTags );
     418                    if ( $pos !== false ) {
     419                        array_splice( $openTags, $pos, 1 );
     420                    }
     421                }
     422            }
     423            $truncate .= $tag[1];
     424
     425            $contentLength = mb_strlen( preg_replace( '/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3] ) );
     426            if ( $contentLength + $totalLength > $length ) {
     427                $left = $length - $totalLength;
     428                $entitiesLength = 0;
     429                if ( preg_match_all( '/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE ) ) {
     430                    foreach ( $entities[0] as $entity ) {
     431                        if ( $entity[1] + 1 - $entitiesLength <= $left ) {
     432                            $left--;
     433                            $entitiesLength += mb_strlen( $entity[0] );
     434                        } else {
     435                            break;
     436                        }
     437                    }
     438                }
     439
     440                $truncate .= mb_substr( $tag[3], 0 , $left + $entitiesLength );
     441                break;
     442            } else {
     443                $truncate .= $tag[3];
     444                $totalLength += $contentLength;
     445            }
     446            if ( $totalLength >= $length ) {
     447                break;
     448            }
     449        }
     450    } else {
     451        if ( mb_strlen( $text ) <= $length ) {
     452            return $text;
     453        } else {
     454            $truncate = mb_substr( $text, 0, $length - mb_strlen( $ending ) );
     455        }
     456    }
     457
     458    // If $exact is false, we can't break on words
     459    if ( !$exact ) {
     460        $spacepos = mb_strrpos( $truncate, ' ' );
     461        if ( isset( $spacepos ) ) {
     462            if ( $html ) {
     463                $bits = mb_substr( $truncate, $spacepos );
     464                preg_match_all( '/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER );
     465                if ( !empty( $droppedTags ) ) {
     466                    foreach ( $droppedTags as $closingTag ) {
     467                        if ( !in_array( $closingTag[1], $openTags ) ) {
     468                            array_unshift( $openTags, $closingTag[1] );
     469                        }
     470                    }
     471                }
     472            }
     473            $truncate = mb_substr( $truncate, 0, $spacepos );
     474        }
     475    }
     476    $truncate .= $ending;
     477
     478    if ( $html ) {
     479        foreach ( $openTags as $tag ) {
     480            $truncate .= '</' . $tag . '>';
     481        }
     482    }
     483
     484    return apply_filters( 'bp_create_excerpt', $truncate, $original_text, $length, $options );
    379485}
    380486add_filter( 'bp_create_excerpt', 'wp_trim_excerpt' );
Note: See TracChangeset for help on using the changeset viewer.