Перейти к содержанию

PHP: как обрезать текст для анонса, не разрывая слова

Обрезать текст для анонса — казалось бы, не о чем говорить: mb_substr, чик, готово. Но mb_substr режет по символам, а не по словам, и «главный герой книги попал на необитаемый остров» превращается в «главный герой книги попа». Герой, может, и тот ещё жук, но в анонсе это лишнее.

В 2020 году я написал для этого функцию. Сейчас прогнал её на неудобных примерах и нашёл три ошибки. Ниже — исправленная версия с тестами, встроенные функции WordPress, которые иногда делают то же без своего кода, и куда вставлять функцию, чтобы не уронить сайт.

Коротко: в WordPress для обрезки по числу слов есть готовая wp_trim_words(). Если нужно ограничение по символам и без разрыва слов — функция trim_text() ниже, проверена на PHP 8.5.

Функция

function trim_text( string $text, int $limit = 100, string $more = '…' ): string {
	$text = html_entity_decode( strip_tags( str_replace( '<', ' <', $text ) ) );
	$text = trim( preg_replace( '/\s+/u', ' ', $text ) );
	if ( mb_strlen( $text ) <= $limit ) {
		return $text;
	}
	$cut   = mb_substr( $text, 0, $limit + 1 );
	$space = mb_strrpos( $cut, ' ' );
	$cut   = false === $space ? mb_substr( $text, 0, $limit ) : mb_substr( $cut, 0, $space );
	return rtrim( $cut, ' ,.;:-—' ) . $more;
}

Вызов: echo trim_text( $post_text, 150 ); — не больше 150 символов плюс многоточие.

Анонсы в списках записей на lexium.ru режет эта же функция.

Как она работает

  1. Убирает HTML-теги, перед этим ставя пробел перед каждым <, чтобы соседние абзацы не слиплись. Превращает &nbsp;, &amp; и подобное в обычные символы.
  2. Заменяет переносы строк, табуляции и двойные пробелы одним пробелом.
  3. Если текст и так короче лимита — отдаёт как есть.
  4. Отрезает на один символ больше лимита и ищет в этом куске последний пробел. Лишний символ нужен, чтобы не потерять слово, которое заканчивается ровно на границе: если сразу за ним пробел, слово целое.
  5. Режет по этому пробелу, убирает запятую или точку, если она осталась в конце, и добавляет многоточие.

Проверка на неудобных примерах

Слева — что выдаёт функция из первой версии поста, справа — новая:

  • «главный герой книги попал на необитаемый остров», лимит 24: главный герой книги... и главный герой книги… — обе правильно.
  • Одно длинное слово, лимит 10: старая вернула одно многоточие ..., новая — Суперкалиф….
  • Текст с переносом строки «Первая строка⏎вторая строка…», лимит 20: старая оставила перенос внутри анонса, новая — Первая строка вторая….
  • <p>Абзац</p><p>Второй абзац…</p>, лимит 15: старая склеила АбзацВторой..., новая — Абзац Второй….
  • «Раз, два, три, четыре, пять», лимит 9: старая — Раз, два,... с висящей запятой, новая — Раз, два….

Тест простой, его можно оставить рядом с функцией и запускать после каждой правки:

assert( trim_text( 'главный герой книги попал на необитаемый остров', 24 ) === 'главный герой книги…' );
assert( trim_text( 'главный герой книги попал на необитаемый остров', 25 ) === 'главный герой книги попал…' );
assert( trim_text( 'Суперкалифрагилистикэкспиалидоцияслово', 10 ) === 'Суперкалиф…' );
assert( trim_text( '<p>Абзац</p><p>Второй абзац с текстом</p>', 15 ) === 'Абзац Второй…' );
assert( trim_text( 'Раз, два, три, четыре, пять', 9 ) === 'Раз, два…' );
assert( trim_text( 'короткий', 100 ) === 'короткий' );

Запуск: php -d zend.assertions=1 -d assert.exception=1 test.php. Ничего не вывел — всё в порядке, упал с AssertionError — смотрите, какая строка.

В WordPress можно без своей функции

  • wp_trim_words( $text, 20 ) — первые 20 слов, теги убирает сам, в конце добавляет &hellip;. Если вам подходит ограничение по словам, а не по символам, — берите её.
  • wp_html_excerpt( $text, 24, '…' ) — по символам, но слова не бережёт. Проверил: «главный герой книги попал…» с лимитом 24 она обрезала до Главный герой книги попа…. Ровно то, от чего мы уходили.

Куда вставлять функцию в WordPress

Знакомый, прочитав первую версию поста, спросил: «А куда эту функцию вставлять?». Обычно советуют в конец functions.php активной темы (в первой версии у меня была опечатка — function.php, такой файл WordPress не читает). Но у этого места два недостатка: при обновлении чужой темы правка пропадёт, а опечатка в файле роняет сайт.

Надёжнее — отдельный файл в wp-content/mu-plugins/, например trim-text.php: начните его с <?php и вставьте функцию. Такие плагины включаются сами, тема их не затрёт. А чтобы не получить ошибку «Cannot redeclare function», если функция с таким именем уже где-то есть, оберните её:

if ( ! function_exists( 'trim_text' ) ) {
	function trim_text( … ) { … }
}

Вызывают функцию там, где выводится анонс, — обычно в шаблоне списка записей темы: echo esc_html( trim_text( strip_shortcodes( get_the_content() ), 150 ) );. strip_shortcodes убирает шорткоды вроде , иначе они попадут в анонс текстом, а esc_html защищает вывод.

Написать комментарий

Адрес почты не публикуется. Обязательные поля отмечены звёздочкой.

Возможно, вы не читали

Преимущества автономного блога

31 июля 2011
Что такое автономный блог? Автономный блог - это сайт, расположенный на платном хостинге, имеющий собственное доменное имя. Недостатки: Это…
Как создать блог за полторы минуты

Как создать блог за полторы минуты

14 ноября 2009
Давно читаю блог Сергея Жуковского . Обнаружил его случайно. Ставил на свой сайт рекламу от ГуглАдвордс, там и увидел заветную ссылку…

Еще один способ убить свой сайт

9 апреля 2012
Как говорил товарищ Саахов: "Абыдна, слюшай! Ничего не сделал, только зашел!". Вот и я, всего-лишь перекинул сайт на виртуальный сервер и…