
Если несколько дней назад мы рассказывали, как разделить длинные посты WordPress на страницы без ущерба для SEO, то сегодня мы добавим дополнительную функцию, которая позволит вам легко находить и классифицировать эти длинные посты по типу контента, длине и языку.
Когда у вас всего несколько длинных записей, их легко выучить почти наизусть. Сложнее, когда их много и они имеют соответствующие переводы на другие языки. Этот код призван ускорить процесс выбора при разделении постов (с помощью блока Gutenberg "Разрыв страницы" или "Разрыв страницы") и быстро дать вам понять, какие посты вам еще предстоит разделить и являются ли ваши текущие разделы согласованными.
Что именно делает этот код?
Следующий код, добавленный в functions.php вашего шаблона или дочерней темы, сгенерирует шорткод, использование которого на любой странице или в любом посте отобразит постраничный список (10 записей на страницу) со всеми постами, содержащими более 1500 слов, включая переведенные на другие языки. Шорткод работает как с Polylang, так и без Polylang.
Эти значения можно изменить в разделе // 2. Конфигурация (только не увеличивайте общее количество отображаемых страниц слишком сильно, если не хотите, чтобы запрос замедлял отклик). Идея заключается в том, чтобы использовать шорткод на черновых или приватных страницах, чтобы не рисковать тем, что посетители повлияют на производительность и съедят процессор вашего сервера, если у вас ограниченные ресурсы).
Этот код позволит вам увидеть не только количество разрывов в посте (или их отсутствие вообще), но и то, как распределяются слова между различными разделами, показывая, сколько слов содержит каждый разрыв, что может быть очень полезно для оценки того, насколько сбалансирована ваша текущая пагинация.
Автоматическое определение типов содержимого
Автоматическое определение добавлено для 4 типов контента, где "break" означает паузу через каждые x слов. Расчет основан на плотности информации, а максимальное ограничение настраивается.
Технические (700 слов/разрыв): Учебники, код
Повествование (1200 слов/разрыв): Истории, повествования
Журналистика (900 слов/разрыв): Новости, статьи
Общие сведения (1000 слов/разрыв): Стандартное содержание
Критерии отбора
Для критериев кода я искал советы и рекомендации. Некоторые параметры я подстроил под свой блог, но эта система всегда эластична и может быть точно настроена для каждого сайта, вы всегда можете добавить еще несколько сигналов, чтобы сделать обнаружение более точным. Возьмите это за отправную точку и изучите, какие элементы вы чаще всего используете в своих статьях в зависимости от их типа, или добавьте свои собственные бренды, которые код может идентифицировать, и некоторые из тегов, которые вы чаще всего используете.
В настоящее время существуют следующие критерии для скрининга:
A. Техническое содержание (⚙️ 700 слов/разрыв)
- Категории: учебник, руководство, технический, кодирование, программирование
- Теги: код, программное обеспечение, технологии, разработка
- Структура
:Polylang placeholder не изменять
B. Содержание повествования (📖 1200 слов/разрыв)
- Категории: рассказ, история, повествование, литература
- Теги: рассказ, роман, фантастика, поэзия
- Структура
:Polylang placeholder не изменять
C. Журналистский контент (📰 900 слов/разрыв)
- Категории: новости, текущие события, репортаж, статья
- Теги: пресса, интервью, мнение
- Структура
:Polylang placeholder не изменять
D. Общее содержание (✍️ 1000 слов/разрыв)
- Применяется, если не совпадает с вышеуказанными критериями.
- Значение по умолчанию для стандартных постов
Код оценивает элементы в следующем порядке:
- Категории постов
- Метки сообщения
- Структура содержания
- Назначьте "Общий", если нет совпадений
Максимальное количество предлагаемых перерывов - 5 в одном посте.
Аспект
Для удобства в код включен CSS, который возвращает вкладкам такой вид. Информация, которую он отображает, довольно явная.
Вы можете изменить его, чтобы связать заголовок со страницей редактирования, если вам так удобнее для быстрого доступа, или внести любые другие улучшения, которые вы можете придумать.
Код
// ======================================================================
//Displays a paginated list of long posts (ordered from most to least number of words) with suggestions for splitting them into pages according to content type
// Shortcode for Page Breaks analysis on long posts. Courtesy of /jrmora.com
// ======================================================================
function analizador_pagebreaks_completo() {
// 1. Protección contra ejecución en admin/AJAX
if (is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
return '';
}
// 2. Configuración
$config = [
'umbral_palabras' => 1500,
'posts_por_pagina' => 10,
'mostrar_todos_idiomas' => true
];
// 3. Detección de tipo de contenido
function detectar_tipo_contenido($post_id) {
$post = get_post($post_id);
$content = strip_tags($post->post_content);
// Por categorías
$categorias = wp_get_post_categories($post_id, ['fields' => 'slugs']);
if (array_intersect($categorias, ['tutorial', 'guia', 'tecnico', 'code', 'programacion'])) {
return ['tipo' => 'técnico', 'color' => '#e3f2fd', 'icono' => '⚙️'];
} elseif (array_intersect($categorias, ['relato', 'historia', 'narrativa', 'literatura'])) {
return ['tipo' => 'narrativo', 'color' => '#f3e5f5', 'icono' => '📖'];
} elseif (array_intersect($categorias, ['noticia', 'actualidad', 'reportaje', 'articulo'])) {
return ['tipo' => 'periodístico', 'color' => '#e8f5e9', 'icono' => '📰'];
}
// Por etiquetas
$etiquetas = wp_get_post_tags($post_id, ['fields' => 'slugs']);
if (array_intersect($etiquetas, ['codigo', 'software', 'tecnologia', 'dev'])) {
return ['tipo' => 'técnico', 'color' => '#e3f2fd', 'icono' => '⚙️'];
} elseif (array_intersect($etiquetas, ['cuento', 'novela', 'ficcion', 'poesia'])) {
return ['tipo' => 'narrativo', 'color' => '#f3e5f5', 'icono' => '📖'];
}
// Por estructura
if (preg_match_all('/```|<pre>|<code>/i', $content) > 2) {
return ['tipo' => 'técnico', 'color' => '#e3f2fd', 'icono' => '⚙️'];
} elseif (preg_match_all('/\n—|\n"|\n\d+\. /i', $content) > 3) {
return ['tipo' => 'narrativo', 'color' => '#f3e5f5', 'icono' => '📖'];
} elseif (preg_match_all('/\n### |\n#### |\*Nota:/i', $content) > 3) {
return ['tipo' => 'periodístico', 'color' => '#e8f5e9', 'icono' => '📰'];
}
return ['tipo' => 'general', 'color' => '#f5f5f5', 'icono' => '✍️'];
}
// 4. Cálculo de breaks por tipo
function calcular_breaks_por_tipo($palabras, $tipo) {
$config = [
'técnico' => 700,
'narrativo' => 1200,
'periodístico' => 900,
'general' => 1000
];
$breaks = max(1, floor($palabras / ($config[$tipo] ?? 1000)) - 1);
return min($breaks, 5); // Máximo 5 breaks
}
// 5. Función para calcular distribución de palabras entre breaks
function calcular_distribucion_palabras($content) {
$total_palabras = str_word_count(strip_tags($content));
$breaks = substr_count($content, '<!--nextpage-->');
if ($breaks === 0) {
return [];
}
// Dividir el contenido por los breaks
$secciones = explode('<!--nextpage-->', $content);
$distribucion = [];
foreach ($secciones as $seccion) {
$palabras_seccion = str_word_count(strip_tags($seccion));
$distribucion[] = $palabras_seccion;
}
return $distribucion;
}
// 6. Función para obtener todos los IDs de posts a procesar
function obtener_ids_posts_a_procesar($mostrar_todos_idiomas) {
$args = [
'post_type' => 'post',
'posts_per_page' => -1,
'fields' => 'ids',
'post_status' => 'publish'
];
// Si Polylang está activo y queremos mostrar todos los idiomas
if ($mostrar_todos_idiomas && function_exists('pll_get_post_translations')) {
$query = new WP_Query($args);
$all_post_ids = [];
foreach ($query->posts as $post_id) {
$translations = pll_get_post_translations($post_id);
$all_post_ids = array_merge($all_post_ids, array_values($translations));
}
return array_unique($all_post_ids);
}
// Caso normal (sin Polylang o no mostrar todos los idiomas)
$query = new WP_Query($args);
return $query->posts;
}
// 7. Obtener y procesar posts
$posts_procesados = [];
$post_ids = obtener_ids_posts_a_procesar($config['mostrar_todos_idiomas']);
foreach ($post_ids as $post_id) {
procesar_post_con_tipo($post_id, $config['umbral_palabras'], $posts_procesados);
}
// 8. Ordenar por palabras (mayor a menor)
usort($posts_procesados, function($a, $b) {
return $b['palabras'] - $a['palabras'];
});
// 9. Paginación manual
$paged = max(1, get_query_var('paged'));
$total_posts = count($posts_procesados);
$total_paginas = ceil($total_posts / $config['posts_por_pagina']);
$offset = ($paged - 1) * $config['posts_por_pagina'];
$posts_paginados = array_slice($posts_procesados, $offset, $config['posts_por_pagina']);
// 10. CSS con mejoras visuales
$output = '<style>
.pb-ultimate-container {
max-width: 800px;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.pb-ultimate-item {
background: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
border: 1px solid #e0e0e0;
}
.pb-ultimate-title {
font-size: 1.25rem;
margin: 0 0 10px 0;
line-height: 1.4;
}
.pb-ultimate-meta {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 12px;
font-size: 0.85rem;
}
.pb-ultimate-meta-item {
display: inline-flex;
align-items: center;
gap: 4px;
}
.pb-ultimate-type {
padding: 2px 10px;
border-radius: 10px;
font-size: 0.75rem;
font-weight: 500;
}
.pb-ultimate-suggestion {
background: #f5f5f5;
border-left: 3px solid #64b5f6;
padding: 10px 15px;
margin-top: 15px;
border-radius: 0 4px 4px 0;
font-size: 0.9rem;
}
.pb-ultimate-pagination {
display: flex;
justify-content: center;
margin: 30px 0;
flex-wrap: wrap;
gap: 8px;
}
.pb-ultimate-page {
padding: 8px 16px;
border-radius: 4px;
text-decoration: none;
}
.pb-ultimate-page-number {
border: 1px solid #e0e0e0;
color: #1976d2;
}
.pb-ultimate-page-number:hover {
background: #e3f2fd;
}
.pb-ultimate-page-current {
background: #1976d2;
color: white;
border: 1px solid #1976d2;
}
.pb-ultimate-wordcount {
font-weight: 600;
color: #212121;
}
.pb-ultimate-language {
background: #e3f2fd;
color: #1565c0;
padding: 2px 10px;
border-radius: 10px;
font-size: 0.75rem;
}
.pb-ultimate-warning {
color: #d32f2f;
font-weight: 500;
}
.pb-ultimate-distribution {
margin-left: 5px;
font-size: 0.95em; /* Aumentado de 0.8em a 0.95em */
font-weight: 600; /* Negrita añadida */
color: #444; /* Color más oscuro para mejor contraste */
background: #f8f8f8; /* Fondo sutil */
padding: 2px 6px;
border-radius: 4px;
border-left: 2px solid #64b5f6; /* Borde izquierdo azul */
}
.pb-ultimate-distribution-separator {
color: #999;
font-weight: normal;
margin: 0 3px;
}
</style>';
$output .= '<div class="pb-ultimate-container">';
if (!empty($posts_procesados)) {
foreach ($posts_paginados as $post) {
$breaks_sugeridos = calcular_breaks_por_tipo($post['palabras'], $post['tipo']['tipo']);
$palabras_por_seccion = ceil($post['palabras'] / ($breaks_sugeridos + 1));
$output .= '<div class="pb-ultimate-item">';
$output .= '<h3 class="pb-ultimate-title"><a href="' . esc_url($post['enlace']) . '">' . esc_html($post['titulo']) . '</a></h3>';
$output .= '<div class="pb-ultimate-meta">';
$output .= '<span class="pb-ultimate-meta-item pb-ultimate-wordcount">📝 ' . number_format($post['palabras']) . ' palabras</span>';
$output .= '<span class="pb-ultimate-meta-item pb-ultimate-type" style="background: ' . $post['tipo']['color'] . '">';
$output .= $post['tipo']['icono'] . ' ' . ucfirst($post['tipo']['tipo']);
$output .= '</span>';
$output .= '<span class="pb-ultimate-meta-item pb-ultimate-language">' . strtoupper($post['idioma']) . '</span>';
if ($post['tiene_break']) {
$distribucion = calcular_distribucion_palabras($post['contenido']);
$distribucion_text = '';
if (!empty($distribucion)) {
$distribucion_text = '<span class="pb-ultimate-distribution">(';
$distribucion_parts = [];
foreach ($distribucion as $index => $palabras) {
$distribucion_parts[] = ($index + 1) . 'º: ' . number_format($palabras);
}
$distribucion_text .= implode('<span class="pb-ultimate-distribution-separator"> · </span>', $distribucion_parts) . ')</span>';
}
$output .= '<span class="pb-ultimate-meta-item">✅ ' . $post['breaks_actuales'] . ' breaks ';
$output .= $distribucion_text;
$output .= '</span>';
} else {
$output .= '<span class="pb-ultimate-meta-item pb-ultimate-warning">⚠️ SIN PAGINAR</span>';
}
$output .= '</div>';
$output .= '<div class="pb-ultimate-suggestion">';
$output .= '<strong>Sugerencia:</strong> ' . $breaks_sugeridos . ' breaks (1 cada ~' . number_format($palabras_por_seccion) . ' palabras)';
$output .= '</div>';
$output .= '</div>';
}
// Paginación
if ($total_paginas > 1) {
$output .= '<div class="pb-ultimate-pagination">';
$output .= paginate_links([
'base' => str_replace(999999999, '%#%', esc_url(get_pagenum_link(999999999))),
'format' => '?paged=%#%',
'current' => $paged,
'total' => $total_paginas,
'prev_text' => __('«'),
'next_text' => __('»'),
'mid_size' => 1
]);
$output .= '</div>';
}
} else {
$output .= '<div class="pb-ultimate-item">No se encontraron posts que requieran paginación.</div>';
}
return $output . '</div>';
}
// Función auxiliar: Procesar post con tipo
function procesar_post_con_tipo($post_id, $umbral, &$posts) {
$post = get_post($post_id);
if (!$post) return;
$content = $post->post_content;
$word_count = str_word_count(strip_tags($content));
if ($word_count >= $umbral) {
$tipo = detectar_tipo_contenido($post_id);
$idioma = 'es'; // Valor por defecto
// Detectar idioma si Polylang está activo
if (function_exists('pll_get_post_language')) {
$idioma = pll_get_post_language($post_id) ?: 'es';
}
$posts[] = [
'ID' => $post_id,
'titulo' => $post->post_title,
'palabras' => $word_count,
'contenido' => $content,
'enlace' => get_permalink($post_id),
'tiene_break' => (strpos($content, '<!--nextpage-->') !== false),
'breaks_actuales' => substr_count($content, '<!--nextpage-->'),
'idioma' => $idioma,
'tipo' => $tipo
];
}
}
add_shortcode('analizador_pagebreaks_ultimate', 'analizador_pagebreaks_completo');