16

Here is the excerpt of the post on my home page. I want to end the excerpt with a sentence. In this case it is ' opportunities. '. I know how to modify the length but it won't do what i want.

enter image description here

Here is excerpt from other website. Here you see excerpt ending with a proper sentence unlike the above which ends with the first word of next sentence 'Proactively'.

enter image description here

After Implementing the solution by G.M. :-

enter image description here

6
  • Please explain your issue better than that. From the answers and comments on them, it's hard to understand what you want to achieve.
    – kaiser
    Commented Aug 3, 2013 at 14:03
  • @kaiser I have updated the details.
    – Mohit
    Commented Aug 3, 2013 at 14:20
  • @Mohit have you tryed my code?
    – gmazzap
    Commented Aug 3, 2013 at 14:25
  • Yes, excerpt ends with sentence but 'Read More' is gone.
    – Mohit
    Commented Aug 3, 2013 at 14:31
  • @G.M. Alright i have to add the read more in the return.
    – Mohit
    Commented Aug 3, 2013 at 14:34

5 Answers 5

14

This requires PHP 5.3+ (WP requires PHP 5.2.4+)

add_filter('get_the_excerpt', 'end_with_sentence');

function end_with_sentence($excerpt) {
  $allowed_end = array('.', '!', '?', '...');
  $exc = explode( ' ', $excerpt );
  $found = false;
  $last = '';
  while ( ! $found && ! empty($exc) ) { 
    $last = array_pop($exc);
    $end = strrev( $last );
    $found = in_array( $end{0}, $allowed_end );
  }
  return (! empty($exc)) ? $excerpt : rtrim(implode(' ', $exc) . ' ' .$last);
}

Edit

After @kaiser comment I try to run this on save/update to prevent page slow down on showing a lot of posts. This should be tested

add_filter('wp_insert_post_data', 'end_with_sentence_on_save', 20, 2);

function end_with_sentence_on_save($data, $postarr) {
  if ( ! empty( $data['post_content'] ) && $data['post_status'] != 'inherit' && $data['post_status'] != 'trash' ) {
    $text = strip_shortcodes( $data['post_content'] );
    $text = apply_filters('the_content', $text );
    $text = str_replace(']]>', ']]>', $text );
    $excerpt_length = apply_filters('excerpt_length', 55);
    $data['post_excerpt'] = wp_trim_words($text, $excerpt_length, '');
  } else {
    return $data;
  }
  $allowed_end = array('.', '!', '?', '...');
  $exc = explode(' ', $data['post_excerpt']);
  $found = false;
  $last = '';
  while ( ! $found && ! empty($exc) ) { 
    $last = array_pop($exc);
    $end = strrev( $last );
    $found = in_array( $end{0}, $allowed_end );
  }
  if (! empty($exc)) $data['post_excerpt'] = rtrim(implode(' ', $exc) . ' ' .$last);
  return $data; 
}
12
  • 1
    sure about that: $end{0}? Btw, you could just return or break when something is found. Additional note: It might better to that when saving the post, so it's not regenerated during runtime, which can get quite slow if you're displaying lots of posts.
    – kaiser
    Commented Aug 3, 2013 at 14:02
  • @kaiser cite: Strings may also be accessed using braces, as in $str{42}, for the same purpose. php.net docs To find something I had to find last char of last string. Using $found I break the cycle and check last string, so no need to break.
    – gmazzap
    Commented Aug 3, 2013 at 14:10
  • Completely missed that it's a while checking for false. +1
    – kaiser
    Commented Aug 3, 2013 at 14:16
  • @kaiser +1 for the second part of your comment, now I edit the code to run on save
    – gmazzap
    Commented Aug 3, 2013 at 14:31
  • 1
    Sure @jessica was a bad edit (in the original version it was ok). Thanks, fixed.
    – gmazzap
    Commented Apr 12, 2014 at 10:05
3

Here is my version, keeping all html tags and also trimming the content after the last word in of a sentence

if ( ! function_exists( 'pietergoosen_custom_wp_trim_excerpt' ) ) : 

    function pietergoosen_custom_wp_trim_excerpt($pietergoosen_excerpt) {
    global $post;
    $raw_excerpt = $pietergoosen_excerpt;
        if ( '' == $pietergoosen_excerpt ) {

            $pietergoosen_excerpt = get_the_content('');
            $pietergoosen_excerpt = strip_shortcodes( $pietergoosen_excerpt );
            $pietergoosen_excerpt = apply_filters('the_content', $pietergoosen_excerpt);
            $pietergoosen_excerpt = str_replace(']]>', ']]>', $pietergoosen_excerpt);

            //Set the excerpt word count and only break after sentence is complete.
                $excerpt_word_count = 75;
                $excerpt_length = apply_filters('excerpt_length', $excerpt_word_count); 
                $tokens = array();
                $excerptOutput = '';
                $count = 0;

                // Divide the string into tokens; HTML tags, or words, followed by any whitespace
                preg_match_all('/(<[^>]+>|[^<>\s]+)\s*/u', $pietergoosen_excerpt, $tokens);

                foreach ($tokens[0] as $token) { 

                    if ($count >= $excerpt_word_count && preg_match('/[\?\.\!]\s*$/uS', $token)) { 
                    // Limit reached, continue until  ? . or ! occur at the end
                        $excerptOutput .= trim($token);
                        break;
                    }

                    // Add words to complete sentence
                    $count++;

                    // Append what's left of the token
                    $excerptOutput .= $token;
                }

            $pietergoosen_excerpt = trim(force_balance_tags($excerptOutput));

                $excerpt_end = ' <a href="'. esc_url( get_permalink() ) . '">' . '&nbsp;&raquo;&nbsp;' . sprintf(__( 'Read more about: %s &nbsp;&raquo;', 'pietergoosen' ), get_the_title()) . '</a>'; 
                $excerpt_more = apply_filters('excerpt_more', ' ' . $excerpt_end); 

                //$pos = strrpos($pietergoosen_excerpt, '</');
                //if ($pos !== false)
                // Inside last HTML tag
                //$pietergoosen_excerpt = substr_replace($pietergoosen_excerpt, $excerpt_end, $pos, 0);
                //else
                // After the content
                $pietergoosen_excerpt .= $excerpt_end;

            return $pietergoosen_excerpt;   

        }
        return apply_filters('pietergoosen_custom_wp_trim_excerpt', $pietergoosen_excerpt, $raw_excerpt);
    }

endif; 

remove_filter('get_the_excerpt', 'wp_trim_excerpt');
add_filter('get_the_excerpt', 'pietergoosen_custom_wp_trim_excerpt'); 
0

Just in case someone is using the answer, word of advice: This will ruin the Advanced Custom Fields. ACF also invokes wp_insert_post_data when creating or updating a field, but the $data variable is not the same, and will result in the ACF field name to become some unreadable gibberish, which results in not being able to use the field.

0

Here is a different solution, which uses Wordpress in-built wp_trim_words() function to determine roughly how long the text should be, and then truncates the text at the end of that sentence (a little longer than the number of words specified).

Usage: echo im_trim_sentences($very_long_text, 90);

A custom "strpos_array()" function has also been included in this answer, because PHP's strpos() function only accepts a string as the $needle, but we want to search for different sentence endings (full stop, or exclamation mark, etc).

function strpos_array($haystack, $needles, $offset = 0) {
    if (is_array($needles)) {
        $positions = array();
        foreach ($needles as $str) {
            $pos = strpos($haystack, $str, $offset);
            if ($pos !== false) {
                $positions[] = $pos;
            }
        }
        return (count($positions) ? min($positions): false);
    } else {
        return strpos($haystack, $needles, $offset);
    }
}

function im_trim_sentences($text, $number_of_words = 55, $more = null) {
    $allowed_end = array('.', '!', '?', '...');
    $text_no_html = strip_tags($text);
    $trimmed_text = wp_trim_words($text, $number_of_words, $more);
    $trimmed_text_length = strlen($trimmed_text);
    $sentence_end_position = strpos_array($text_no_html, $allowed_end, $trimmed_text_length);
    $text_with_html = (($sentence_end_position !== false) ? substr($text_no_html, 0, ($sentence_end_position + 1)) : $trimmed_text);
    return wpautop($text_with_html);
}
0

Here's what works for me (WP version 6.2.2):

function truncateBySentence($content, $limit) {
    if (strlen($content) <= $limit) return $content;

    $sentences = preg_split('/(?<=[.!?])\s+/', $content);
    $result = "";

    foreach ($sentences as $sentence) {
        if (strlen($result . $sentence) > $limit) {
            break;
        }
        $result .= $sentence . " ";
    }

    return rtrim($result) . "..";
}

// cut off the excerpt at the end of a sentence, not randomly
add_filter('get_the_excerpt', function($excerpt, $post) {
    // remove all <pre> blocks completely, we don't want code in our excerpts (this may be specific to my blog content)
    $content = preg_replace('/<pre.*?>.*?<\/pre>/si', '', $post->post_content);

    // remove all HTML so we can work with plain old text when generating excerpts
    $content = strip_tags($content);

    $target_length = 750;
    $content = truncateBySentence($content, $target_length);

    // remove all newlines to avoid line breaks in the excerpt
    $content = preg_replace('/\s+/', ' ', $content);

    return $content;
}, 1, 2);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.