
Si nous avons vu il y a quelques jours comment diviser les longs articles WordPress en pages sans affecter le référencement, nous allons aujourd'hui ajouter une fonction supplémentaire qui vous permet de trouver et de catégoriser facilement ces longs articles en fonction du type de contenu, de la longueur et de la langue.
Lorsque vous n'avez que quelques entrées longues, il est facile de les connaître presque par cœur. Cela devient plus compliqué lorsqu'il y en a beaucoup et qu'ils ont aussi leurs traductions respectives dans d'autres langues. Ce code a pour but d'accélérer le processus de sélection lors de la division des articles (avec le bloc Gutenberg "Page Break" ou "Page Break") et de vous indiquer rapidement ceux qu'il vous reste à diviser et si vos divisions actuelles sont cohérentes.
Que fait exactement le code ?
Le code suivant ajouté au fichier functions.php de votre modèle ou thème enfant générera un shortcode. En utilisant ce shortcode sur n'importe quelle page ou article, une liste paginée (10 entrées par page) s'affichera avec tous les articles qui ont plus de 1500 mots, y compris ceux qui sont traduits dans d'autres langues. Il fonctionne aussi bien avec Polylang que sans Polylang.
Ces valeurs peuvent être modifiées dans // 2 Configuration (N'augmentez pas trop le nombre total de pages à afficher si vous ne voulez pas que la requête ralentisse la réponse). L'idée est que vous utilisiez le shortcode sur des pages provisoires ou privées afin de ne pas risquer qu'un grand nombre de visiteurs affecte les performances et consomme l'unité centrale de votre serveur si vous avez des ressources limitées).
Ce code vous permettra de voir non seulement combien de ruptures un article a (ou s'il n'en a pas du tout), mais aussi comment les mots sont distribués entre les différentes sections en montrant combien de mots chaque rupture contient, ce qui peut être très utile pour évaluer si votre pagination actuelle est équilibrée.
Détection automatique des types de contenu
La détection automatique est ajoutée pour 4 types de contenu, où "pause" signifie une pause tous les x mots. Le calcul est basé sur la densité de l'information et la limite maximale est configurable.
Technique (700 mots/rupture) : Tutoriels, code
Récit (1200 mots/rupture) : Histoires, récits
Journalisme (900 mots/rupture) : Nouvelles, articles
Généralités (1000 mots/rupture) : Contenu standard
Critères de sélection
Pour les critères de code, j'ai cherché des conseils et des références. J'ai adapté certains paramètres à mon blog, mais il s'agit toujours d'un système élastique qui peut être affiné pour chaque site, vous pouvez toujours ajouter d'autres signaux pour rendre la détection plus précise. Prenez-le comme point de départ et examinez les éléments que vous utilisez le plus souvent dans vos articles en fonction de leur type ou ajoutez vos propres marques que le code peut identifier et certaines des balises que vous utilisez le plus souvent.
Les critères actuels de sélection sont les suivants :
A. Contenu technique (⚙️ 700 mots/rupture)
- Catégories : tutoriel, guide, technique, codage, programmation
- Tags : code, logiciel, technologie, dev
- Structure
:Polylang placeholder ne pas modifier
B. Contenu narratif (📖 1200 mots/rupture)
- Catégories : nouvelle, histoire, récit, littérature
- Tags : nouvelle, roman, fiction, poésie
- Structure
:Polylang placeholder ne pas modifier
C. Contenu journalistique (📰 900 mots/rupture)
- Catégories : nouvelles, actualité, reportage, article
- Tags : presse, interview, opinion
- Structure
:Polylang placeholder ne pas modifier
D. Contenu général (✍️ 1000 mots/rupture)
- S'applique lorsqu'il ne coïncide pas avec les critères ci-dessus.
- Valeur par défaut pour les postes standard
Le code évalue les éléments dans l'ordre suivant :
- Catégories de postes
- Tags de la poste
- Structure du contenu
- Attribuer "Général" s'il n'y a pas de correspondance
Le nombre maximum de pauses suggérées est de 5 par message.
Aspect
Le CSS est inclus dans le code pour des raisons de commodité et renvoie cette apparence aux onglets. Les informations affichées sont assez explicites.
Vous pouvez le modifier pour lier le titre à la page d'édition si vous le jugez plus pratique pour un accès rapide, ou apporter toute autre amélioration à laquelle vous pouvez penser.
Code
// ======================================================================
//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');