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;
}