Conditional Excerpt Function

I use this function on practically every site that has any posts/archives section. This enables us to display a standardized excerpt inside or outside the Loop.

Arguments (all optional):
$ex_length the word count
$postref the post or text to excerpt. Can be an ID, a post object, a block of text, or if called within the Loop, null or not given
$addmore_keyThe text/html to display when there is additional content. I like to set this to something unique (like the default ‘<!–wpcx_more–>’) and str_replace within the caller instance.
$include_p Whether to include paragraphs within the excerpt
$user_break If false, return no more than the maximum word count; if true, use the entire user-defined excerpt or user-defined ‘<!–more–>’ break
$apply_shortcodes True to apply shortcode formatting within the excerpt

Note: this function always retains b, strong, i and em tags.

View comments within code for detailed explanations.

	function conditional_excerpt( $ex_length=80, $postref=null, $addmore_key='<!--wpcx_more-->', $include_p=false, $user_break=false, $apply_shortcodes=false ) {
		// retrieve or build post object from $postref input (can be
		//     post object, ID, text, or current loop post if not defined/null)
		
		// check if whole $post object was passed
		if ( is_object( $postref ) ) :
			$postob = $postref;
		
		// check if it's a post ID ( integer or string-format number ) :
		elseif ( is_numeric( $postref ) ) :
			$postob = get_post( $postref );
		
		// otherwise assume it's text, define a minimal object with just the properties we need:
		elseif ( is_string( $postref ) && !empty( $postref ) ) :
			$postob = ( object )array( 
				'ID' => 0, 
				'post_content' => $postref
			 );
		
		// finally, if empty / null / not defined, assume we're in The Loop:
		elseif ( empty( $postref ) ) :
			$postob = get_post( get_the_ID() );
		endif;
		
		// bail if none of the above tests passed:
		if ( !is_object( $postob ) ) return;

		// now, check if there is a user-defined excerpt, and act upon it
		if ( $postob->ID && ! empty( $postob->post_excerpt ) ) :
			$featex = $postob->post_excerpt;
			$addmore = $addmore_key;
			if ( $user_break ) :
				return $featex.$addmore;
			endif;
		
		// no user-defined excerpt, so construct it
		else :
			$addmore = '';
			
			// strip out any javascript
			$stripped = preg_replace( '@<script[^>]*?>.*?</script>@si', '', $postob->post_content );
			
			// strip out shortcodes if dictated by $apply_shortcodes:
			if ( !$apply_shortcodes ) {
				$stripped = strip_shortcodes( $stripped );
			}
			
			// if '<!--more-->' is not there ( user did not specify a break ), 
			//     $content_split array will only contain one item.
			$content_split = explode( '<!--more-->', $stripped );
			$featex = $content_split[0];
			if ( count( $content_split ) > 1 ) :
				$addmore = $addmore_key;
			endif;
			
			// this is the recommended way to convert raw post_content;
			//     shortcodes will be applied herein if not stripped out earlier:
			$featex = apply_filters( 'the_content', $featex );
			$featex = str_replace( ']]>', ']]&gt;', $featex );
			
			// prepare argument for strip_tags() (standard PHP function)
			$exclude_open = '<b><i><em><strong><a>';
			if ( $include_p ) :
				$exclude_open .= '<p><br><h1><h2><h3><h4>';
			endif;
			
			$featex = strip_tags( $featex, $exclude_open );

			// will append closing tags to excerpt so any that have been
			//     truncated will be properly closed
			$exclude_close = '</b></i></em></strong></a>';
			if ( $include_p ) :
				$exclude_close .= '</p></h1></h2></h3></h4>';
			endif;

		endif;
		
		if ( !$user_break || count( $content_split ) < 2 ) :
			$words = explode( ' ', $featex, $ex_length + 1 );
			if ( count( $words ) > $ex_length ) :
			        array_pop( $words );
			        $featex = implode( ' ', $words );
					$addmore = $addmore_key;
			endif;
		endif;
		return $featex . $exclude_close . $addmore;
	}

Coders note:
Lines 29 & 30: Why did I test ! empty( $postob->post_excerpt ), and then set $featex = $postob->post_excerpt;? Wouldn’t it be better to use the WordPress functions has_excerpt() and get_the_excerpt()?

No, for two reasons:

  1. This function can become part of a filter so all excerpts are structured this way (see below), hooked into get_the_excerpt — which is applied to the output of the get_the_excerpt() function. If I used get_the_excerpt() herein, I’d create an infinite recursion.
  2. Looking at the core code, has_excerpt() goes to the database for the post object, then simply returns the ->post_excerpt property if it exists. Similarly, get_the_excerpt() simply returns the ->post_excerpt property. Since I already have a post object, why increase overhead with another database call?

How to use it:
Make sure this function is in your functions.php file. Then, you can edit / create your theme files to call this function wherever you see the_excerpt() or get_the_excerpt() (likely archive.php, category.php, search.php and other ‘list’ templates).

Or, hook into the get_the_excerpt filter, which is encountered by both the_excerpt() and get_the_excerpt(), always within The Loop. Add this to your functions.php file.

add_filter( 'get_the_excerpt', 'wpcx_excerpt_filter' );
function wpcx_excerpt_filter( $excerpt ) {
	return conditional_excerpt( 50 ); // ... or your preferred word count
}