Skip to:
Content

BuddyPress.org

Changeset 4989


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.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/bp-activity/bp-activity-filters.php

    r4961 r4989  
    189189    $append_text    = apply_filters( 'bp_activity_excerpt_append_text', __( '[Read more]', 'buddypress' ) );
    190190    $excerpt_length = apply_filters( 'bp_activity_excerpt_length', 358 );
    191     $excerpt        = $text;
    192 
    193     $id = !empty( $activities_template->activity->current_comment->id ) ? 'acomment-read-more-' . $activities_template->activity->current_comment->id : 'activity-read-more-' . bp_get_activity_id();
    194 
    195     if ( strlen( $excerpt ) > $excerpt_length )
    196         $excerpt = sprintf( '%1$s<span class="activity-read-more" id="%2$s"><a href="%3$s" rel="nofollow">%4$s</a></span>', bp_create_excerpt( $excerpt, $excerpt_length, true, '&hellip;' ), $id, bp_get_activity_thread_permalink(), $append_text );
     191
     192    // Run the text through the excerpt function. If it's too short, the original text will be
     193    // returned.
     194    $excerpt        = bp_create_excerpt( $text, $excerpt_length, array( 'ending' => __( '&hellip;', 'buddypress' ) ) );
     195
     196    // If the text returned by bp_create_excerpt() is different from the original text (ie it's
     197    // been truncated), add the "Read More" link.
     198    if ( $excerpt != $text ) {
     199        $id = !empty( $activities_template->activity->current_comment->id ) ? 'acomment-read-more-' . $activities_template->activity->current_comment->id : 'activity-read-more-' . bp_get_activity_id();
     200
     201        $excerpt = sprintf( '%1$s<span class="activity-read-more" id="%2$s"><a href="%3$s" rel="nofollow">%4$s</a></span>', $excerpt, $id, bp_get_activity_thread_permalink(), $append_text );
     202    }
    197203
    198204    return apply_filters( 'bp_activity_truncate_entry', $excerpt, $text, $append_text );
  • trunk/bp-activity/bp-activity-template.php

    r4961 r4989  
    15371537
    15381538        if ( 'activity_update' == $activities_template->activity->type )
    1539             $title .= ': ' . strip_tags( ent2ncr( trim( convert_chars( bp_create_excerpt( $activities_template->activity->content, 70, true, " [&#133;]" ) ) ) ) );
     1539            $title .= ': ' . strip_tags( ent2ncr( trim( convert_chars( bp_create_excerpt( $activities_template->activity->content, 70, array( 'ending' => " [&#133;]" ) ) ) ) ) );
    15401540
    15411541        return apply_filters( 'bp_get_activity_feed_item_title', $title );
  • 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 = ' [&hellip;]' ) { // 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'            => __( ' [&hellip;]', '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( ']]>', ']]&gt;', $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' );
  • trunk/bp-core/bp-core-wpabstraction.php

    r4820 r4989  
    2727        }
    2828    }
    29    
     29
    3030    if ( !function_exists( 'update_blog_option' ) ) {
    3131        function update_blog_option( $blog_id, $option_name, $value ) {
     
    7474        return "{$prefix}spam = 0 AND {$prefix}deleted = 0 AND {$prefix}user_status = 0";
    7575}
     76
     77/**
     78 * Multibyte encoding fallback functions
     79 *
     80 * The PHP multibyte encoding extension is not enabled by default. In cases where it is not enabled,
     81 * these functions provide a fallback.
     82 *
     83 * Borrowed from MediaWiki, under the GPLv2. Thanks!
     84 */
     85if ( !function_exists( 'mb_strlen' ) ) {
     86    /**
     87     * Fallback implementation of mb_strlen, hardcoded to UTF-8.
     88     * @param string $str
     89     * @param string $enc optional encoding; ignored
     90     * @return int
     91     */
     92    function mb_strlen( $str, $enc = '' ) {
     93        $counts = count_chars( $str );
     94        $total = 0;
     95
     96        // Count ASCII bytes
     97        for( $i = 0; $i < 0x80; $i++ ) {
     98            $total += $counts[$i];
     99        }
     100
     101        // Count multibyte sequence heads
     102        for( $i = 0xc0; $i < 0xff; $i++ ) {
     103            $total += $counts[$i];
     104        }
     105        return $total;
     106    }
     107}
     108
     109if ( !function_exists( 'mb_strpos' ) ) {
     110    /**
     111     * Fallback implementation of mb_strpos, hardcoded to UTF-8.
     112     * @param $haystack String
     113     * @param $needle String
     114     * @param $offset String: optional start position
     115     * @param $encoding String: optional encoding; ignored
     116     * @return int
     117     */
     118    function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
     119        $needle = preg_quote( $needle, '/' );
     120
     121        $ar = array();
     122        preg_match( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
     123
     124        if( isset( $ar[0][1] ) ) {
     125            return $ar[0][1];
     126        } else {
     127            return false;
     128        }
     129    }
     130}
     131
     132if ( !function_exists( 'mb_strrpos' ) ) {
     133    /**
     134     * Fallback implementation of mb_strrpos, hardcoded to UTF-8.
     135     * @param $haystack String
     136     * @param $needle String
     137     * @param $offset String: optional start position
     138     * @param $encoding String: optional encoding; ignored
     139     * @return int
     140     */
     141    function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
     142        $needle = preg_quote( $needle, '/' );
     143
     144        $ar = array();
     145        preg_match_all( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
     146
     147        if( isset( $ar[0] ) && count( $ar[0] ) > 0 &&
     148            isset( $ar[0][count( $ar[0] ) - 1][1] ) ) {
     149            return $ar[0][count( $ar[0] ) - 1][1];
     150        } else {
     151            return false;
     152        }
     153    }
     154}
     155
    76156?>
Note: See TracChangeset for help on using the changeset viewer.