Обрезать текст для анонса — казалось бы, не о чем говорить: mb_substr, чик, готово. Но mb_substr режет по символам, а не по словам, и «главный герой книги попал на необитаемый остров» превращается в «главный герой книги попа». Герой, может, и тот ещё жук, но в анонсе это лишнее.
В 2020 году я написал для этого функцию. Сейчас прогнал её на неудобных примерах и нашёл три ошибки. Ниже — исправленная версия с тестами, встроенные функции 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 режет эта же функция.
Как она работает
- Убирает HTML-теги, перед этим ставя пробел перед каждым
<, чтобы соседние абзацы не слиплись. Превращает ,&и подобное в обычные символы. - Заменяет переносы строк, табуляции и двойные пробелы одним пробелом.
- Если текст и так короче лимита — отдаёт как есть.
- Отрезает на один символ больше лимита и ищет в этом куске последний пробел. Лишний символ нужен, чтобы не потерять слово, которое заканчивается ровно на границе: если сразу за ним пробел, слово целое.
- Режет по этому пробелу, убирает запятую или точку, если она осталась в конце, и добавляет многоточие.
Проверка на неудобных примерах
Слева — что выдаёт функция из первой версии поста, справа — новая:
- «главный герой книги попал на необитаемый остров», лимит 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 слов, теги убирает сам, в конце добавляет…. Если вам подходит ограничение по словам, а не по символам, — берите её.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 защищает вывод.