Este snippet crea el shortcode [seoweb], que genera una tabla con los datos SEO (keyword, title y meta description) de diferentes tipos de contenido de WordPress —entradas, páginas, categorías, productos, categorías de producto o tipos personalizados— obteniendo automáticamente la información desde Rank Math o Yoast.
Permite elegir qué mostrar mediante parámetros como post=si, page=si, category=si, cpt="nombre_cpt" o tax="nombre_taxonomia".
// Shortcode: [seoweb post=si page=si category=si product=no product_cat=no cpt="proyectos,oferta_trabajo" tax="categorias"]
add_shortcode('seoweb', function ($atts = []) {
// ---------- Helpers ----------
$bool = function ($v, $default = false) {
if ($v === null) return $default;
$v = strtolower(trim((string)$v));
return in_array($v, ['si','sí','1','true','on','yes'], true);
};
$csv = function ($v) {
if (!$v) return [];
if (is_array($v)) return array_values(array_filter(array_map('trim', $v)));
return array_values(array_filter(array_map('trim', explode(',', $v))));
};
$esc = function ($str) {
return esc_html(is_string($str) ? $str : (string)$str);
};
$get_path = function ($url) {
$path = wp_parse_url($url, PHP_URL_PATH);
if (!$path) $path = '/';
if ($path[0] !== '/') $path = '/' . $path;
return $path;
};
// Rank Math / Yoast fallback para POSTS
$get_post_seo = function ($post_id) {
// Rank Math
$rm_title = get_post_meta($post_id, 'rank_math_title', true);
$rm_desc = get_post_meta($post_id, 'rank_math_description', true);
$rm_kw = get_post_meta($post_id, 'rank_math_focus_keyword', true);
// Yoast (legacy keys incluidas)
$yo_title = get_post_meta($post_id, '_yoast_wpseo_title', true);
$yo_desc = get_post_meta($post_id, '_yoast_wpseo_metadesc', true);
$yo_kw = get_post_meta($post_id, 'yoast_wpseo_focuskw', true);
return [
'kw' => $rm_kw ?: $yo_kw,
'title' => $rm_title ?: $yo_title,
'desc' => $rm_desc ?: $yo_desc,
];
};
// Rank Math / Yoast fallback para TÉRMINOS
$get_term_seo = function ($term_id) {
// Rank Math
$rm_title = get_term_meta($term_id, 'rank_math_title', true);
$rm_desc = get_term_meta($term_id, 'rank_math_description', true);
$rm_kw = get_term_meta($term_id, 'rank_math_focus_keyword', true);
// Yoast en términos
$yo_title = get_term_meta($term_id, 'wpseo_title', true);
$yo_desc = get_term_meta($term_id, 'wpseo_desc', true);
// Nota: Yoast no guarda siempre focus kw en términos de forma estándar
return [
'kw' => $rm_kw,
'title' => $rm_title ?: $yo_title,
'desc' => $rm_desc ?: $yo_desc,
];
};
// ---------- Atributos ----------
$atts = shortcode_atts([
// básicos WP
'post' => 'no',
'page' => 'no',
'category' => 'no',
// WooCommerce
'product' => 'no',
'product_cat' => 'no',
// personalizados
'cpt' => '', // ej: "proyectos,oferta_trabajo"
'tax' => '', // ej: "categorias,brand"
// opciones de consulta posts
'limit' => '-1',
'orderby' => 'date',
'order' => 'DESC',
// opciones de términos
'hide_empty_terms' => 'no',
], $atts, 'seoweb');
$show_post = $bool($atts['post']);
$show_page = $bool($atts['page']);
$show_category = $bool($atts['category']);
$show_product = $bool($atts['product']);
$show_product_cat = $bool($atts['product_cat']);
$cpt_list = $csv($atts['cpt']);
$tax_list = $csv($atts['tax']);
$posts_per_page = intval($atts['limit']);
if ($posts_per_page === 0) $posts_per_page = -1;
$orderby = sanitize_text_field($atts['orderby']);
$order = strtoupper(sanitize_text_field($atts['order'])) === 'ASC' ? 'ASC' : 'DESC';
$hide_empty_terms = $bool($atts['hide_empty_terms'], false);
// ---------- CSS ----------
ob_start();
?>
<style>
.seo-web-wrap{margin:16px 0;}
.seo-web-title{font-weight:700;font-size:20px;margin:24px 0 12px;}
.seo-web-table{width:100%;border-collapse:collapse;font-size:14px;}
.seo-web-table th,.seo-web-table td{border:1px solid #000;padding:8px 10px;vertical-align:top}
.seo-web-table thead th{background:#71c7cb;color:#fff;font-weight:700;text-align:left}
.seo-web-table tbody tr:nth-child(odd){background:#fff}
.seo-web-table tbody tr:nth-child(even){background:#f3feff}
.seo-web-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}
.seo-web-edit a{text-decoration:none}
</style>
<?php
// ---------- Render genérico de tabla de POSTS ----------
$render_posts_table = function ($title, $posts) use ($esc, $get_path, $get_post_seo) {
?>
<div class="seo-web-wrap">
<div class="seo-web-title"><?php echo $esc($title); ?></div>
<table class="seo-web-table">
<thead>
<tr>
<th>Nombre</th>
<th>Slug</th>
<th>Keyword</th>
<th>Title</th>
<th>Meta Description</th>
</tr>
</thead>
<tbody>
<?php if (!empty($posts)) :
foreach ($posts as $p) :
$permalink = get_permalink($p);
$slug = $get_path($permalink);
$seo = $get_post_seo($p->ID);
$edit_link = get_edit_post_link($p->ID);
?>
<tr>
<td class="seo-web-edit">
<?php if ($edit_link): ?>
<a href="<?php echo esc_url($edit_link); ?>" target="_blank" rel="noopener noreferrer">
<?php echo $esc(get_the_title($p)); ?>
</a>
<?php else: ?>
<?php echo $esc(get_the_title($p)); ?>
<?php endif; ?>
</td>
<td class="seo-web-mono"><?php echo $esc($slug); ?></td>
<td><?php echo $esc($seo['kw']); ?></td>
<td><?php echo $esc($seo['title']); ?></td>
<td><?php echo $esc($seo['desc']); ?></td>
</tr>
<?php endforeach;
else: ?>
<tr><td colspan="5">No se han encontrado elementos.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
};
// ---------- Render genérico de tabla de TÉRMINOS ----------
$render_terms_table = function ($title, $terms, $tax_name) use ($esc, $get_path, $get_term_seo) {
?>
<div class="seo-web-wrap">
<div class="seo-web-title"><?php echo $esc($title); ?></div>
<table class="seo-web-table">
<thead>
<tr>
<th>Nombre</th>
<th>Slug</th>
<th>Keyword</th>
<th>Title</th>
<th>Meta Description</th>
</tr>
</thead>
<tbody>
<?php if (!empty($terms) && !is_wp_error($terms)) :
foreach ($terms as $t) :
$link = get_term_link($t);
if (is_wp_error($link)) { $link = ''; }
$slug = $link ? $get_path($link) : '';
$seo = $get_term_seo($t->term_id);
$edit_link = get_edit_term_link($t->term_id, $tax_name);
?>
<tr>
<td class="seo-web-edit">
<?php if ($edit_link): ?>
<a href="<?php echo esc_url($edit_link); ?>" target="_blank" rel="noopener noreferrer">
<?php echo $esc($t->name); ?>
</a>
<?php else: ?>
<?php echo $esc($t->name); ?>
<?php endif; ?>
</td>
<td class="seo-web-mono"><?php echo $esc($slug); ?></td>
<td><?php echo $esc($seo['kw']); ?></td>
<td><?php echo $esc($seo['title']); ?></td>
<td><?php echo $esc($seo['desc']); ?></td>
</tr>
<?php endforeach;
else: ?>
<tr><td colspan="5">No se han encontrado términos.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
};
// ---------- BLOQUES SEGÚN PARÁMETROS ----------
// 1) PÁGINAS
if ($show_page) {
$pages = get_posts([
'post_type' => 'page',
'post_status' => 'publish',
'posts_per_page' => $posts_per_page,
'orderby' => $orderby,
'order' => $order,
]);
$render_posts_table('SEO PÁGINAS', $pages);
}
// 2) ENTRADAS
if ($show_post) {
$posts = get_posts([
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $posts_per_page,
'orderby' => $orderby,
'order' => $order,
]);
$render_posts_table('SEO ENTRADAS', $posts);
}
// 3) CATEGORÍAS DE ENTRADAS
if ($show_category) {
$blog_cats = get_terms([
'taxonomy' => 'category',
'hide_empty' => $hide_empty_terms,
'orderby' => 'name',
'order' => 'ASC',
]);
$render_terms_table('SEO CATEGORÍAS DE ENTRADAS', $blog_cats, 'category');
}
// 4) PRODUCTOS (WooCommerce)
if ($show_product && post_type_exists('product')) {
$products = get_posts([
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => $posts_per_page,
'orderby' => $orderby,
'order' => $order,
]);
$render_posts_table('SEO PRODUCTOS (WooCommerce)', $products);
}
// 5) CATEGORÍAS DE PRODUCTO (WooCommerce)
if ($show_product_cat && taxonomy_exists('product_cat')) {
$terms = get_terms([
'taxonomy' => 'product_cat',
'hide_empty' => $hide_empty_terms,
'orderby' => 'name',
'order' => 'ASC',
]);
$render_terms_table('SEO CATEGORÍAS DE PRODUCTO (WooCommerce)', $terms, 'product_cat');
}
// 6) CPTs personalizados (lista)
if (!empty($cpt_list)) {
foreach ($cpt_list as $cpt) {
if (!post_type_exists($cpt)) continue;
$items = get_posts([
'post_type' => $cpt,
'post_status' => 'publish',
'posts_per_page' => $posts_per_page,
'orderby' => $orderby,
'order' => $order,
]);
$titulo = 'SEO ' . strtoupper($cpt);
$render_posts_table($titulo, $items);
}
}
// 7) Taxonomías personalizadas (lista)
if (!empty($tax_list)) {
foreach ($tax_list as $tax) {
if (!taxonomy_exists($tax)) continue;
$terms = get_terms([
'taxonomy' => $tax,
'hide_empty' => $hide_empty_terms,
'orderby' => 'name',
'order' => 'ASC',
]);
$titulo = 'SEO TAXONOMÍA: ' . $tax;
$render_terms_table($titulo, $terms, $tax);
}
}
// Si no se pidió nada explícito, mostramos un aviso
if (
!$show_page && !$show_post && !$show_category &&
!$show_product && !$show_product_cat &&
empty($cpt_list) && empty($tax_list)
) {
echo '<p>No has seleccionado ningún contenido. Usa parámetros como <code>page=si</code>, <code>post=si</code>, <code>category=si</code>, <code>product=si</code>, <code>product_cat=si</code>, <code>cpt="proyectos,oferta_trabajo"</code> o <code>tax="categorias,brand"</code>.</p>';
}
return ob_get_clean();
});
Ejemplos:
Solo quieres usar las entradas del blog y las páginas usa:
[seoweb post=si page=si category=si ]
Si quieres usar además los productos y sus categorías usa:
[seoweb post=si page=si category=si product=si product_cat=si ]
Si quieres usar algún cpt a medida usa:
[seoweb post=si page=si category=si product=si product_cat=si cpt=»proyectos» tax=»categorias_proyectos»]

