Cutting text and/or replacing the standard the_excerpt() function

I noticed that the WordPress function the_excerpt() is cumbersome. It takes a lot of time and resources to execute it. This is because it calls get_the_excerpt(), the_content() and applies various hooks to them, including the_excerpt() itself. As a result, there are quite a few operations - this is not always necessary. For example, I treat quotes simply - a short description of the article, cutting out a small piece of it.

I put up with this drawback until I needed to trim the text to a certain number of characters. That's when I decided to write a text-cropping function.

The result of replacing the_excerpt() pleased me: the page generation decreased on average from 0.850 seconds to 0.550 seconds, with 9 calls to the_excerpt() (this is on a computer, on the server, it's usually less). 9 calls - this is the number of posts displayed in the category, to each of which the_excerpt() was applied.

Below is the function that can replace the standard WordPress function the_excerpt().

GitHub
<?php

/**
 * Cuts the specified text up to specified number of characters.
 * Strips any of shortcodes.
 *
 * Important changes:
 * 2.8.0 - Improved logic to work with HTML tags in cutting text.
 * 2.7.2 - Cuts direct URLs from content.
 * 2.7.0 - `sanitize_callback` parameter.
 * 2.6.5 - `ignore_more` parameter.
 * 2.6.2 - Regular to remove blocky shortcodes like: [foo]some data[/foo].
 * 2.6   - Removed the `save_format` parameter and replaced it with two parameters `autop` and `save_tags`.
 *
 * @author Kama (wp-kama.ru)
 * @version 2.8.0
 *
 * @param string|array $args {
 *     Optional. Arguments to customize output.
 *
 *     @type int       $maxchar            Max number of characters.
 *     @type string    $text               The text to be cut. The default is `post_excerpt` if there is no `post_content`.
 *                                         If the text has `<!--more-->`, then `maxchar` is ignored and everything
 *                                         up to `<!--more-->` is taken including HTML.
 *     @type bool      $autop              Replace the line breaks with `<p>` and `<br>` or not?
 *     @type string    $more_text          The text of `Read more` link.
 *     @type string    $save_tags          Tags to be left in the text. For example `<strong><b><a>`.
 *     @type string    $sanitize_callback  Text cleaning function.
 *     @type bool      $ignore_more        Whether to ignore `<!--more-->` in the content.
 *
 * }
 *
 * @return string HTML
 */
function kama_excerpt( $args = '' ){
	global $post;

	if( is_string( $args ) ){
		parse_str( $args, $args );
	}

	$rg = (object) array_merge( [
		'maxchar'           => 350,
		'text'              => '',
		'autop'             => true,
		'more_text'         => 'Читать дальше...',
		'ignore_more'       => false,
		'save_tags'         => '<strong><b><a><em><i><var><code><span>',
		'sanitize_callback' => static function( string $text, object $rg ){
			return strip_tags( $text, $rg->save_tags );
		},
	], $args );

	$rg = apply_filters( 'kama_excerpt_args', $rg );

	if( ! $rg->text ){
		$rg->text = $post->post_excerpt ?: $post->post_content;
	}

	$text = $rg->text;
	// strip content shortcodes: [foo]some data[/foo]. Consider markdown
	$text = preg_replace( '~\[([a-z0-9_-]+)[^\]]*\](?!\().*?\[/\1\]~is', '', $text );
	// strip others shortcodes: [singlepic id=3]. Consider markdown
	$text = preg_replace( '~\[/?[^\]]*\](?!\()~', '', $text );
	// strip direct URLs
	$text = preg_replace( '~(?<=\s)https?://.+\s~', '', $text );
	$text = trim( $text );

	// <!--more-->
	if( ! $rg->ignore_more && strpos( $text, '<!--more-->' ) ){

		preg_match( '/(.*)<!--more-->/s', $text, $mm );

		$text = trim( $mm[1] );

		$text_append = sprintf( ' <a href="%s#more-%d">%s</a>', get_permalink( $post ), $post->ID, $rg->more_text );
	}
	// text, excerpt, content
	else {

		$text = call_user_func( $rg->sanitize_callback, $text, $rg );
		$has_tags = false !== strpos( $text, '<' );

		// collect html tags
		if( $has_tags ){
			$tags_collection = [];
			$nn = 0;

			$text = preg_replace_callback( '/<[^>]+>/', static function( $match ) use ( & $tags_collection, & $nn ){
				$nn++;
				$holder = "~$nn";
				$tags_collection[ $holder ] = $match[0];

				return $holder;
			}, $text );
		}

		// cut text
		$cuted_text = mb_substr( $text, 0, $rg->maxchar );
		if( $text !== $cuted_text ){

			// del last word, it not complate in 99%
			$text = preg_replace( '/(.*)\s\S*$/s', '\\1...', trim( $cuted_text ) );
		}

		// bring html tags back
		if( $has_tags ){
			$text = strtr( $text, $tags_collection );
			$text = force_balance_tags( $text );
		}
	}

	// add <p> tags. Simple analog of wpautop()
	if( $rg->autop ){

		$text = preg_replace(
			[ "/\r/", "/\n{2,}/", "/\n/" ],
			[ '', '</p><p>', '<br />' ],
			"<p>$text</p>"
		);
	}

	$text = apply_filters( 'kama_excerpt', $text, $rg );

	if( isset( $text_append ) ){
		$text .= $text_append;
	}

	return $text;
}

How the function works

  1. Cuts to a certain number of characters. Specified in the maxchar parameter.

  2. Understands the <!--more--> tag in the record. If it is present, the desired number of characters to be displayed is ignored, and everything above <!--more--> is output with the HTML markup preserved.

  3. You can specify whether to keep line breaks or write the entire text in one line. By default, line breaks are preserved, if you need a "continuous text", set the autop = 0 parameter.

  4. You can specify which HTML tags should not be cut out. For example, if we want to leave the <strong> and <em> tags, then specify them like this: save_tags=<strong><em>

  5. The function can also be used to crop any text passed to it. To do this, specify the text in the text parameter.

In all cases, the cropping calculates the number of characters and then removes the last characters until a space. This is necessary so that in the end there is always a complete word left, not a piece of unfinished one (I think it's ugly).

Using

Insert the code mentioned above into the functions.php file of your theme. And where you need to output the cropped text, call the function like this:

<?php echo kama_excerpt( [ 'maxchar'=>100, 'text'=>'blabla' ] ); ?>

To replace the standard the_excerpt(), just replace the_excerpt() with kama_excerpt(). All this should be inside the WordPress loop.

IMPORTANT: The text parameter is not needed when replacing the_excerpt()!

Example of using the function to crop any text:

$str = "The function [foo]some text[/foo] [foo]some text[/foo] of text cropping for Worpress,
can be applied [foo url='bar'] and on other engines.";

echo kama_excerpt([ 'text'=>$str, 'maxchar'=>70 ]);

We will get:

<p>The function of text cropping for Worpress,<br />
can be applied and on other ...</p>

When cropping the text passed to the function, only that part of the function that is necessary (no extra operations) is triggered.

Very simple example of text cropping

If you don't want to use the function and just need to crop the text without saving html tags and so on, you can use this short string inside the WordPress loop:

<?php
$maxchar = 152;
$text = strip_tags( get_the_content() );
echo mb_substr( $text, 0, $maxchar );
?>

152 - this is the number of characters left.

Or

Use the wp_trim_words() function.