Pues ya estamos en el módulo 2, de desarrollo intermedio. Ya dominas los fundamentos de WordPress y has completado la primera parte del tutorial. Ahora es el momento de profundizar en las características que te convertirán en un desarrollador profesional capaz de trabajar en proyectos reales y complejos.
En la lección 4 tuviste una introducción básica a los hooks. Aprendiste que existen add_action y add_filter, viste ejemplos simples y ahora vamos a ir mucho más allá. Los hooks son el corazón de WordPress, el sistema que permite que todo el ecosistema de plugins y temas funcione sin conflictos. Dominar los hooks es lo que separa a un developer junior de uno senior.
Al terminar esta lección, entenderás en profundidad cómo funciona el sistema de hooks, conocerás los hooks más importantes de WordPress, sabrás crear tus propios hooks, dominarás la prioridad y los argumentos, y serás capaz de modificar prácticamente cualquier comportamiento de WordPress sin tocar el core. Este conocimiento es absolutamente fundamental para todo lo que viene después.
Tabla de contenidos
Qué son los hooks y por qué son tan importantes
Los hooks son puntos de enganche en el código de WordPress donde puedes meter tu propio código. Son como ganchos literales donde cuelgas tu funcionalidad. WordPress ejecuta tu código en momentos específicos sin que tengas que modificar el core.
La filosofía detrás de los hooks
WordPress tiene miles de líneas de código en su core. Si cada desarrollador tuviera que modificar ese código para añadir funcionalidades, sería un desastre: conflictos constantes, actualizaciones imposibles, código roto por todas partes. Los hooks solucionan esto elegantemente.
En lugar de modificar el core, WordPress dice: «Oye, voy a ejecutar esta acción. Si alguien quiere hacer algo en este momento, que se enganche aquí«. Y tú te enganchas con tu código.
Actions vs Filters: La diferencia real
Ya viste esto antes, pero ahora vamos a entenderlo en profundidad:
- Actions (
do_action): Ejecutan código en un momento específico. No esperan respuesta. «Haz esto ahora« - Filters (
apply_filters): Modifican un valor y lo devuelven. «Cambia esto antes de usarlo«
Ejemplo conceptual:
Imagina una fábrica de coches. Hay una línea de producción donde se ensambla el coche.
- Action: «Cuando el coche llegue a esta estación, pon las ruedas». No cambias el coche, añades algo.
- Filter: «Cuando el coche pase por aquí, cámbialo de rojo a azul». Modificas algo existente y lo devuelves.
Hooks en el código real
WordPress está lleno de hooks. Cada vez que ves esto en el core de WordPress:
do_action( 'wp_head' );
Es un punto donde puedes engancharte. Y cuando ves esto:
$content = apply_filters( 'the_content', $content );
Es un punto donde puedes modificar el contenido antes de que se muestre.
Conceptos clave que debes dominar
En esta lección vas a dominar estos conceptos avanzados:
- Ciclo de vida de WordPress: orden de ejecución de hooks
- Hooks principales:
init,wp_head,wp_footer,the_content, etc - Prioridad de hooks: controlar el orden de ejecución
- Argumentos en hooks: pasar y recibir múltiples parámetros
- Remove hooks: desengancharse de acciones y filtros
- Crear tus propios hooks:
do_actionyapply_filterspersonalizados - Hooks condicionales: ejecutar solo en ciertas situaciones
- Class methods en hooks: usar métodos de clases
- Anonymous functions: funciones anónimas en hooks
has_actionyhas_filter: verificar si hay hooks
Recursos oficiales y recomendados
Estos recursos son esenciales para dominar hooks:
- Plugin API: Documentación oficial de hooks
- Action Reference: Lista completa de actions
- Filter Reference: Lista completa de filters
- Hook Order: Orden de ejecución
- Adam Brown’s Hook Database: Base de datos de hooks
IA en esta etapa
La IA puede ayudarte enormemente a encontrar el hook adecuado para cada situación.
Prompts útiles para esta lección:
¿Qué hook de WordPress debo usar para ejecutar código después de que un post se publique?Necesito modificar el título de los posts solo en el frontend. ¿Qué filter uso?Dame ejemplos de cómo usar prioridades en hooks para controlar el orden¿Cómo paso múltiples argumentos a un action personalizado?Quiero eliminar una acción añadida por otro plugin. ¿Cómo lo hago?Explícame la diferencia entre save_post y publish_post hooksDame un ejemplo completo de crear mis propios hooks personalizados en un plugin
Debug con IA:
Si un hook no funciona, pregunta:"Este hook no se ejecuta: [código]. ¿Por qué puede ser?"La IA puede detectar problemas de orden de ejecución, prioridad incorrecta o uso del hook equivocado.
Ejercicios prácticos progresivos
Ejercicio 1: El ciclo de vida de una petición en WordPress (básico)
Antes de usar hooks avanzados, necesitas entender CUÁNDO se ejecuta cada cosa en WordPress.
Crear un plugin de debug:
<?php
/**
* Plugin Name: Debug Hooks Lifecycle
* Description: Muestra el orden de ejecución de hooks
*/
// Hook más temprano
function debug_muplugins_loaded() {
error_log( '1. muplugins_loaded' );
}
add_action( 'muplugins_loaded', 'debug_muplugins_loaded' );
// Plugins cargados
function debug_plugins_loaded() {
error_log( '2. plugins_loaded' );
}
add_action( 'plugins_loaded', 'debug_plugins_loaded' );
// Setup del tema
function debug_after_setup_theme() {
error_log( '3. after_setup_theme' );
}
add_action( 'after_setup_theme', 'debug_after_setup_theme' );
// Init - momento más común
function debug_init() {
error_log( '4. init' );
}
add_action( 'init', 'debug_init' );
// Widgets inicializados
function debug_widgets_init() {
error_log( '5. widgets_init' );
}
add_action( 'widgets_init', 'debug_widgets_init' );
// Query principal preparada
function debug_wp() {
error_log( '6. wp' );
}
add_action( 'wp', 'debug_wp' );
// Template cargado
function debug_template_redirect() {
error_log( '7. template_redirect' );
}
add_action( 'template_redirect', 'debug_template_redirect' );
// Head del HTML
function debug_wp_head() {
error_log( '8. wp_head' );
}
add_action( 'wp_head', 'debug_wp_head' );
// Antes del Loop
function debug_loop_start() {
error_log( '9. loop_start' );
}
add_action( 'loop_start', 'debug_loop_start' );
// Después del Loop
function debug_loop_end() {
error_log( '10. loop_end' );
}
add_action( 'loop_end', 'debug_loop_end' );
// Footer del HTML
function debug_wp_footer() {
error_log( '11. wp_footer' );
}
add_action( 'wp_footer', 'debug_wp_footer' );
// Shutdown - último momento
function debug_shutdown() {
error_log( '12. shutdown' );
}
add_action( 'shutdown', 'debug_shutdown' );
Activar el plugin y ver el log:
- Activa el plugin
- Visita cualquier página del sitio
- Ve al archivo de log (
wp-content/debug.logsi tienesWP_DEBUGactivo) - Observa el orden exacto de ejecución
Orden típico de ejecución:
muplugins_loaded→ Plugins MU cargadosplugins_loaded→ Todos los plugins cargadosafter_setup_theme→ Tema configuradoinit→ Momento de inicialización (CPT, taxonomías, etc)widgets_init→ Widgets registradoswp→ Query principal ejecutadatemplate_redirect→ Antes de cargar el templatewp_head→ Dentro de<head>loop_start→ Antes del primer post del looploop_end→ Después del último post del loopwp_footer→ Antes de cerrar </body>shutdown→ Último momento, respuesta ya enviada
Cuándo usar cada hook:
plugins_loaded: Tu plugin depende de otro pluginafter_setup_theme: Añadir theme support, registrar menúsinit: Registrar CPT, taxonomías, rewrite rules, sesioneswidgets_init: Registrar sidebars y widgetswp: Modificar query principal después de que se ejecutetemplate_redirect: Redirecciones, comprobaciones antes de mostrarwp_head: Añadir meta tags, scripts en elheadwp_footer: Añadir scripts antes de cerrarbody
Verificación:
- Entiendes el orden de ejecución de WordPress
- Sabes qué hook usar para cada situación
- Has visto el log de ejecución completo
- Identificas cuándo se ejecuta cada cosa
Ejercicio 2: Prioridad de hooks y control de orden (intermedio)
La prioridad es el tercer parámetro de add_action y add_filter. Controla el ORDEN en que se ejecutan múltiples funciones enganchadas al mismo hook.
Sintaxis completa:
add_action( $hook, $function, $priority, $accepted_args );
$hook: Nombre del hook$function: Función a ejecutar$priority: Orden (default: 10, menor = antes)$accepted_args: Cuántos argumentos acepta tu función (default: 1)
Ejemplo práctico de prioridades:
<?php
// Función con prioridad por defecto (10)
function modificar_titulo_normal( $title ) {
return ' ' . $title;
}
add_filter( 'the_title', 'modificar_titulo_normal' );
// Función con prioridad baja (20) - se ejecuta DESPUÉS
function modificar_titulo_despues( $title ) {
return $title . ' ';
}
add_filter( 'the_title', 'modificar_titulo_despues', 20 );
// Función con prioridad alta (5) - se ejecuta ANTES
function modificar_titulo_antes( $title ) {
return '⭐ ' . $title;
}
add_filter( 'the_title', 'modificar_titulo_antes', 5 );
// Resultado final: ⭐ Título del Post
// Orden: Prioridad 5 → 10 → 20
Casos de uso reales de prioridades:
Caso 1: Asegurar que tu código se ejecute después de otro plugin
<?php
// Plugin X añade algo con prioridad 10
// Tu código necesita ejecutarse DESPUÉS de Plugin X
function mi_funcion_posterior() {
// Tu código
}
add_action( 'init', 'mi_funcion_posterior', 20 );
Caso 2: Tu código debe ejecutarse ANTES que otros
<?php
// Quieres ejecutar antes que la mayoría de plugins
function mi_funcion_prioritaria() {
// Tu código
}
add_action( 'init', 'mi_funcion_prioritaria', 5 );
Caso 3: Modificar contenido añadido por otro plugin
<?php
// Un plugin añade algo al contenido con prioridad 10
// Tú quieres modificar lo que añadió ese plugin
function modificar_contenido_plugin( $content ) {
// Tu modificación del contenido ya modificado por otros
return $content;
}
add_filter( 'the_content', 'modificar_contenido_plugin', 99 );
// Prioridad alta (99) asegura que se ejecuta casi al final
Valores comunes de prioridad:
- 1-5: Muy temprano, antes que casi todo
- 10: Default, la mayoría de código usa esto
- 20-50: Después del comportamiento estándar
- 99-999: Muy tarde, casi al final
- 9999: Último momento posible
Ejercicio práctico:
<?php
/**
* Plugin Name: Ejercicio Prioridades
*/
// Modificar excerpt con diferentes prioridades
function excerpt_primero( $excerpt ) {
return '[PRIMERO] ' . $excerpt;
}
add_filter( 'get_the_excerpt', 'excerpt_primero', 5 );
function excerpt_medio( $excerpt ) {
return $excerpt . ' [MEDIO]';
}
add_filter( 'get_the_excerpt', 'excerpt_medio', 10 );
function excerpt_final( $excerpt ) {
return $excerpt . ' [FINAL]';
}
add_filter( 'get_the_excerpt', 'excerpt_final', 20 );
// Resultado: [PRIMERO] Texto del excerpt [MEDIO] [FINAL]
Verificación:
- Entiendes cómo funciona la prioridad
- Sabes cuándo usar prioridades altas o bajas
- Has probado código con diferentes prioridades
- Puedes controlar el orden de ejecución
Ejercicio 3: Argumentos múltiples en hooks (intermedio)
Muchos hooks pasan múltiples argumentos a tu función. Necesitas saber cómo recibirlos y usarlos.
Anatomía de un hook con múltiples argumentos:
<?php
// WordPress ejecuta esto:
do_action( 'save_post', $post_id, $post, $update );
// Tú te enganchas así:
function mi_funcion_al_guardar( $post_id, $post, $update ) {
// $post_id = ID del post
// $post = Objeto WP_Post completo
// $update = true si es actualización, false si es nuevo
}
add_action( 'save_post', 'mi_funcion_al_guardar', 10, 3 );
// ↑
// Acepta 3 argumentos
Importante: Si no especificas el número de argumentos, solo recibirás el primero.
Ejemplos prácticos con múltiples argumentos:
Ejemplo 1: Hook save_post (3 argumentos)
<?php
function hacer_algo_al_guardar( $post_id, $post, $update ) {
// Verificar que no es autoguardado
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Verificar que no es revisión
if ( wp_is_post_revision( $post_id ) ) {
return;
}
// Solo para posts, no páginas
if ( $post->post_type !== 'post' ) {
return;
}
// Solo si es actualización, no creación
if ( ! $update ) {
return;
}
// Tu código aquí
error_log( 'Post actualizado: ' . $post->post_title );
}
add_action( 'save_post', 'hacer_algo_al_guardar', 10, 3 );
Ejemplo 2: Filter the_content (2 argumentos)
<?php
function modificar_contenido_avanzado( $content, $post_id ) {
// Solo en posts
if ( get_post_type( $post_id ) !== 'post' ) {
return $content;
}
// Solo en el frontend
if ( is_admin() ) {
return $content;
}
// Añadir algo al final
$extra = '<div class="post-extra">';
$extra .= '<p>Este post tiene ' . str_word_count( $content ) . ' palabras.</p>';
$extra .= '</div>';
return $content . $extra;
}
add_filter( 'the_content', 'modificar_contenido_avanzado', 10, 2 );
Ejemplo 3: Action wp_insert_post (3 argumentos)
<?php
function despues_insertar_post( $post_id, $post, $update ) {
// Solo nuevos posts, no actualizaciones
if ( $update ) {
return;
}
// Solo tipo 'post'
if ( $post->post_type !== 'post' ) {
return;
}
// Enviar notificación
$admin_email = get_option( 'admin_email' );
$subject = 'Nuevo post creado: ' . $post->post_title;
$message = 'Se ha creado un nuevo post en tu sitio.';
wp_mail( $admin_email, $subject, $message );
}
add_action( 'wp_insert_post', 'despues_insertar_post', 10, 3 );
Ejemplo 4: Filter pre_get_posts (1 argumento pero es un objeto)
<?php
function modificar_query_principal( $query ) {
// Solo en el query principal
if ( ! $query->is_main_query() ) {
return;
}
// Solo en el frontend
if ( is_admin() ) {
return;
}
// Solo en archivos de categoría
if ( ! $query->is_category() ) {
return;
}
// Mostrar 20 posts en lugar de los configurados
$query->set( 'posts_per_page', 20 );
// Ordenar por título
$query->set( 'orderby', 'title' );
$query->set( 'order', 'ASC' );
}
add_action( 'pre_get_posts', 'modificar_query_principal' );
Cómo saber cuántos argumentos tiene un hook:
- Lee la documentación oficial
- Busca en el código fuente de WordPress dónde se ejecuta el hook
- Usa herramientas como Query Monitor
Verificación:
- Entiendes cómo recibir múltiples argumentos
- Sabes especificar el número de argumentos en
add_action/add_filter - Has usado hooks con 2-3 argumentos
- Puedes acceder a todos los datos que WordPress pasa
Ejercicio 4: Remove hooks – Eliminar acciones y filtros (avanzado)
A veces necesitas eliminar hooks añadidos por plugins o por el tema. Esto es crucial cuando trabajas con código de terceros.
Funciones para remover hooks:
remove_action()– Elimina una acciónremove_filter()– Elimina un filtroremove_all_actions()– Elimina TODAS las acciones de un hookremove_all_filters()– Elimina TODOS los filtros de un hook
Sintaxis de remove_action:
remove_action( $hook, $function, $priority );
IMPORTANTE: Debes usar la MISMA prioridad que se usó al añadir el hook.
Ejemplo 1: Eliminar scripts de emojis de WordPress
<?php
// WordPress añade scripts de emojis por defecto
// Para eliminarlos:
function eliminar_emojis() {
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
}
add_action( 'init', 'eliminar_emojis' );
Ejemplo 2: Eliminar generator meta tag
<?php
// WordPress añade <meta name="generator" content="WordPress X.X">
// Por seguridad, muchos lo eliminan:
remove_action( 'wp_head', 'wp_generator' );
Ejemplo 3: Eliminar filtro de otro plugin
<?php
// Supongamos que un plugin añade esto:
// add_filter( 'the_content', 'plugin_funcion', 10 );
// Para eliminarlo:
function quitar_filtro_plugin() {
remove_filter( 'the_content', 'plugin_funcion', 10 );
}
add_action( 'init', 'quitar_filtro_plugin' );
Ejemplo 4: Eliminar hook de una clase
Si el hook fue añadido desde una clase, es más complicado:
<?php
// Plugin X tiene esto:
class Plugin_X {
public function __construct() {
add_action( 'init', array( $this, 'mi_funcion' ) );
}
public function mi_funcion() {
// código
}
}
// Para eliminarlo necesitas acceder a la instancia:
function quitar_hook_clase() {
global $plugin_x_instance;
if ( isset( $plugin_x_instance ) ) {
remove_action( 'init', array( $plugin_x_instance, 'mi_funcion' ) );
}
}
add_action( 'init', 'quitar_hook_clase', 1 ); // Prioridad 1 para ejecutar antes
Ejemplo 5: Remove all – Eliminar todos los hooks
<?php // Eliminar TODAS las acciones de wp_footer // CUIDADO: Esto puede romper cosas remove_all_actions( 'wp_footer' ); // Mejor: Eliminar todas excepto las del core de WordPress remove_all_actions( 'wp_footer', false ); // false = mantener las del core
Debugging: Encontrar qué función está enganchada
<?php
// Ver todas las funciones enganchadas a un hook
global $wp_filter;
print_r( $wp_filter['wp_head'] );
// Función helper para debug
function debug_hooks( $hook_name ) {
global $wp_filter;
if ( ! isset( $wp_filter[$hook_name] ) ) {
echo "Hook '$hook_name' no existe o no tiene funciones.";
return;
}
echo "<h3>Funciones enganchadas a '$hook_name':</h3>";
echo '<pre>';
print_r( $wp_filter[$hook_name] );
echo '</pre>';
}
// Uso: debug_hooks('wp_head');
Caso práctico completo: Limpiar wp_head
<?php
/**
* Plugin Name: Limpiar WP Head
* Description: Elimina código innecesario del head
*/
function limpiar_wp_head() {
// Eliminar RSS feeds
remove_action( 'wp_head', 'feed_links', 2 );
remove_action( 'wp_head', 'feed_links_extra', 3 );
// Eliminar RSD link
remove_action( 'wp_head', 'rsd_link' );
// Eliminar Windows Live Writer
remove_action( 'wp_head', 'wlwmanifest_link' );
// Eliminar versión de WordPress
remove_action( 'wp_head', 'wp_generator' );
// Eliminar shortlink
remove_action( 'wp_head', 'wp_shortlink_wp_head' );
// Eliminar links de posts adjacent
remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head', 10 );
// Eliminar REST API link
remove_action( 'wp_head', 'rest_output_link_wp_head' );
// Eliminar oEmbed
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
// Eliminar emojis
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
}
add_action( 'init', 'limpiar_wp_head' );
Verificación:
- Sabes usar
remove_actionyremove_filter - Entiendes la importancia de la prioridad al remover
- Puedes eliminar hooks de plugins y temas
- Sabes debuggear qué hooks están activos
- Has limpiado
wp_headcorrectamente
Ejercicio 5: Crear tus propios hooks personalizados (avanzado)
Los hooks no solo los usa WordPress. Tú puedes crear tus propios hooks en tus plugins y temas para que otros desarrolladores puedan extender tu código.
¿Por qué crear tus propios hooks?
- Hacer tu código extensible
- Permitir que otros modifiquen tu plugin sin tocarlo
- Separar lógica de presentación
- Buenas prácticas de desarrollo
Crear un action personalizado:
<?php
/**
* Plugin Name: Plugin con Hooks Personalizados
*/
// Tu código que ejecuta el hook
function mi_funcion_principal() {
echo '<div class="mi-plugin">';
// Hook ANTES del contenido
do_action( 'mi_plugin_antes_contenido' );
echo '<p>Contenido principal del plugin</p>';
// Hook DESPUÉS del contenido
do_action( 'mi_plugin_despues_contenido' );
echo '</div>';
}
// Otros desarrolladores pueden engancharse así:
function anadir_algo_antes() {
echo '<p>Algo añadido ANTES por otro plugin</p>';
}
add_action( 'mi_plugin_antes_contenido', 'anadir_algo_antes' );
function anadir_algo_despues() {
echo '<p>Algo añadido DESPUÉS por otro plugin</p>';
}
add_action( 'mi_plugin_despues_contenido', 'anadir_algo_despues' );
Crear un filter personalizado:
<?php
// Tu código que aplica el filtro
function obtener_precio_producto( $producto_id ) {
$precio = get_post_meta( $producto_id, 'precio', true );
// Permitir que otros modifiquen el precio
$precio = apply_filters( 'mi_plugin_precio_producto', $precio, $producto_id );
return $precio;
}
// Otros desarrolladores pueden modificar el precio:
function aplicar_descuento_precio( $precio, $producto_id ) {
// Aplicar 10% de descuento
return $precio * 0.9;
}
add_filter( 'mi_plugin_precio_producto', 'aplicar_descuento_precio', 10, 2 );
Hooks con múltiples argumentos:
<?php
// Action con múltiples argumentos
function procesar_pedido( $pedido_id ) {
$pedido_data = array(
'id' => $pedido_id,
'total' => 100,
'items' => array( 'Item 1', 'Item 2' )
);
// Pasar múltiples argumentos
do_action( 'mi_plugin_pedido_procesado', $pedido_id, $pedido_data );
}
// Otros se enganchan así:
function enviar_email_pedido( $pedido_id, $pedido_data ) {
$email = 'cliente@ejemplo.com';
$subject = 'Pedido #' . $pedido_id . ' procesado';
$message = 'Total: ' . $pedido_data['total'];
wp_mail( $email, $subject, $message );
}
add_action( 'mi_plugin_pedido_procesado', 'enviar_email_pedido', 10, 2 );
Ejemplo completo: plugin extensible
<?php
/**
* Plugin Name: Sistema de Testimonios Extensible
*/
class Testimonios_Plugin {
public function __construct() {
add_shortcode( 'testimonios', array( $this, 'shortcode_testimonios' ) );
}
public function shortcode_testimonios( $atts ) {
$atts = shortcode_atts( array(
'numero' => 3
), $atts );
// Hook para modificar atributos
$atts = apply_filters( 'testimonios_shortcode_atts', $atts );
$query = new WP_Query( array(
'post_type' => 'testimonio',
'posts_per_page' => $atts['numero']
) );
ob_start();
// Hook ANTES de los testimonios
do_action( 'testimonios_antes_listado' );
if ( $query->have_posts() ) :
echo '<div class="testimonios-grid">';
while ( $query->have_posts() ) : $query->the_post();
// Hook ANTES de cada testimonio
do_action( 'testimonios_antes_item', get_the_ID() );
$this->mostrar_testimonio( get_the_ID() );
// Hook DESPUÉS de cada testimonio
do_action( 'testimonios_despues_item', get_the_ID() );
endwhile;
echo '</div>';
wp_reset_postdata();
endif;
// Hook DESPUÉS de los testimonios
do_action( 'testimonios_despues_listado' );
return ob_get_clean();
}
private function mostrar_testimonio( $id ) {
$nombre = get_post_meta( $id, 'nombre', true );
$cargo = get_post_meta( $id, 'cargo', true );
// Permitir modificar el contenido HTML
$html = '<div class="testimonio">';
$html .= '<div class="contenido">' . get_the_content() . '</div>';
$html .= '<div class="autor">' . esc_html( $nombre ) . ' - ' . esc_html( $cargo ) . '</div>';
$html .= '</div>';
// Filter para modificar el HTML completo
$html = apply_filters( 'testimonios_item_html', $html, $id );
echo $html;
}
}
new Testimonios_Plugin();
// EJEMPLO DE USO DE LOS HOOKS PERSONALIZADOS:
// Añadir título antes del listado
function testimonios_titulo() {
echo '<h2>Lo que dicen nuestros clientes</h2>';
}
add_action( 'testimonios_antes_listado', 'testimonios_titulo' );
// Modificar HTML de cada testimonio
function testimonios_anadir_estrella( $html, $id ) {
$valoracion = get_post_meta( $id, 'valoracion', true );
$estrellas = str_repeat( '⭐', $valoracion );
$html = str_replace( '<div class="contenido">', '<div class="valoracion">' . $estrellas . '</div><div class="contenido">', $html );
return $html;
}
add_filter( 'testimonios_item_html', 'testimonios_anadir_estrella', 10, 2 );
// Cambiar número por defecto del shortcode
function testimonios_cambiar_default( $atts ) {
$atts['numero'] = 6; // Mostrar 6 en lugar de 3
return $atts;
}
add_filter( 'testimonios_shortcode_atts', 'testimonios_cambiar_default' );
Buenas prácticas al crear hooks:
- Usa prefijos:
mi_plugin_mi_hooknomi_hook - Nombra descriptivamente:
antes_contenido,despues_guardar - Documenta tus hooks: Explica qué hacen y qué argumentos pasan
- Sé consistente: Si usas
antes/despues, úsalo siempre - Pasa datos útiles: IDs, objetos completos, arrays de datos
Verificación:
- Has creado tus propios
do_action - Has creado tus propios
apply_filters - Entiendes cómo hacer tu código extensible
- Sabes documentar tus hooks personalizados
- Has visto ejemplos de plugins reales con hooks
Ejercicio 6: Hooks condicionales y casos de uso avanzados (integrador)
Ahora vamos a combinar todo lo aprendido en casos de uso reales y complejos.
Caso 1: Modificar queries solo en ciertas condiciones
<?php
function modificar_query_condicional( $query ) {
// Solo query principal
if ( ! $query->is_main_query() ) {
return;
}
// Solo en frontend
if ( is_admin() ) {
return;
}
// Solo en archivo de autor
if ( $query->is_author() ) {
// Excluir páginas, solo posts
$query->set( 'post_type', 'post' );
// Mostrar 20 por página
$query->set( 'posts_per_page', 20 );
}
// Solo en búsquedas
if ( $query->is_search() ) {
// Buscar solo en posts y páginas
$query->set( 'post_type', array( 'post', 'page' ) );
// Ordenar por relevancia
$query->set( 'orderby', 'relevance' );
}
// Solo en home
if ( $query->is_home() ) {
// Excluir categoría ID 5
$query->set( 'cat', '-5' );
}
}
add_action( 'pre_get_posts', 'modificar_query_condicional' );
Caso 2: Contenido diferente según tipo de usuario
<?php
function contenido_segun_usuario( $content ) {
// Solo en posts individuales
if ( ! is_single() ) {
return $content;
}
// Usuario no logueado
if ( ! is_user_logged_in() ) {
$aviso = '<div class="aviso-login">';
$aviso .= '<p>Regístrate para ver el contenido completo.</p>';
$aviso .= '</div>';
// Mostrar solo 100 palabras
$words = explode( ' ', $content );
$preview = implode( ' ', array_slice( $words, 0, 100 ) );
return $preview . '...' . $aviso;
}
// Usuario logueado pero no suscriptor
if ( ! current_user_can( 'subscriber' ) ) {
$contenido_extra = '<div class="contenido-premium">';
$contenido_extra .= '<p>Hazte premium para acceder a contenido exclusivo.</p>';
$contenido_extra .= '</div>';
return $content . $contenido_extra;
}
// Usuario suscriptor: contenido completo sin modificar
return $content;
}
add_filter( 'the_content', 'contenido_segun_usuario' );
Caso 3: Sistema de notificaciones con múltiples hooks
<?php
/**
* Sistema de notificaciones por email
*/
// Notificar cuando se publica un post
function notificar_post_publicado( $post_id, $post ) {
// Solo posts, no páginas
if ( $post->post_type !== 'post' ) {
return;
}
// Solo si cambia de borrador a publicado
if ( $post->post_status !== 'publish' ) {
return;
}
$admin_email = get_option( 'admin_email' );
$subject = 'Nuevo post publicado: ' . $post->post_title;
$message = 'Se ha publicado un nuevo post en tu sitio: ' . get_permalink( $post_id );
wp_mail( $admin_email, $subject, $message );
// Hook personalizado para que otros plugins puedan actuar
do_action( 'mi_sistema_post_publicado', $post_id, $post );
}
add_action( 'publish_post', 'notificar_post_publicado', 10, 2 );
// Notificar cuando hay un comentario nuevo
function notificar_comentario_nuevo( $comment_id, $comment ) {
$post = get_post( $comment->comment_post_ID );
$author_email = get_the_author_meta( 'user_email', $post->post_author );
$subject = 'Nuevo comentario en: ' . $post->post_title;
$message = 'De: ' . $comment->comment_author . 'nn' . $comment->comment_content;
wp_mail( $author_email, $subject, $message );
}
add_action( 'comment_post', 'notificar_comentario_nuevo', 10, 2 );
// Notificar cuando un usuario se registra
function notificar_registro_usuario( $user_id ) {
$user = get_userdata( $user_id );
$admin_email = get_option( 'admin_email' );
$subject = 'Nuevo usuario registrado';
$message = 'Usuario: ' . $user->user_login . 'nEmail: ' . $user->user_email;
wp_mail( $admin_email, $subject, $message );
}
add_action( 'user_register', 'notificar_registro_usuario' );
Caso 4: Performance – Lazy load de imágenes con hooks
<?php
/**
* Añadir lazy loading a todas las imágenes
*/
function anadir_lazy_load_imagenes( $content ) {
// Solo en el frontend
if ( is_admin() || is_feed() ) {
return $content;
}
// Buscar todas las imágenes
preg_match_all( '/<img[^>]+>/i', $content, $matches );
if ( ! empty( $matches[0] ) ) {
foreach ( $matches[0] as $img ) {
// Si ya tiene loading, saltar
if ( strpos( $img, 'loading=' ) !== false ) {
continue;
}
// Añadir loading="lazy"
$new_img = str_replace( '<img', '<img loading="lazy"', $img );
$content = str_replace( $img, $new_img, $content );
}
}
return $content;
}
add_filter( 'the_content', 'anadir_lazy_load_imagenes', 99 );
add_filter( 'post_thumbnail_html', 'anadir_lazy_load_imagenes', 99 );
add_filter( 'widget_text', 'anadir_lazy_load_imagenes', 99 );
Caso 5: SEO – Modificar títulos automáticamente
<?php
/**
* Optimizar títulos para SEO
*/
function optimizar_titulo_seo( $title ) {
// Solo en el frontend
if ( is_admin() ) {
return $title;
}
// En posts individuales
if ( is_single() ) {
$post_id = get_the_ID();
$categoria = get_the_category( $post_id );
if ( ! empty( $categoria ) ) {
$cat_name = $categoria[0]->name;
// Añadir categoría al final del título
$title = $title . ' | ' . $cat_name;
}
}
// En páginas de autor
if ( is_author() ) {
$author = get_queried_object();
$title = 'Artículos de ' . $author->display_name;
}
// En búsquedas
if ( is_search() ) {
$title = 'Resultados para: ' . get_search_query();
}
return $title;
}
add_filter( 'the_title', 'optimizar_titulo_seo' );
Caso 6: Seguridad – Sanitizar comentarios extra
<?php
/**
* Seguridad adicional en comentarios
*/
function sanitizar_comentario_extra( $commentdata ) {
// Eliminar URLs del contenido del comentario
$commentdata['comment_content'] = preg_replace(
'/https?://[^s]+/i',
'[enlace eliminado]',
$commentdata['comment_content']
);
// Limitar longitud
if ( strlen( $commentdata['comment_content'] ) > 500 ) {
$commentdata['comment_content'] = substr( $commentdata['comment_content'], 0, 500 ) . '...';
}
// Detectar spam básico
$spam_words = array( 'viagra', 'casino', 'poker' );
foreach ( $spam_words as $word ) {
if ( stripos( $commentdata['comment_content'], $word ) !== false ) {
// Marcar como spam
$commentdata['comment_approved'] = 'spam';
}
}
return $commentdata;
}
add_filter( 'preprocess_comment', 'sanitizar_comentario_extra' );
Verificación:
- Dominas el uso condicional de hooks
- Sabes combinar múltiples hooks para sistemas complejos
- Entiendes casos de uso reales y prácticos
- Puedes modificar comportamiento según contexto
- Has visto ejemplos de SEO, performance y seguridad con hooks
Proyecto integrador: Sistema de gamificación con hooks
Vamos a crear un sistema completo de puntos y badges para usuarios usando hooks avanzados.
Descripción del proyecto:
Un plugin que otorga puntos a usuarios por diferentes acciones (comentar, publicar, etc.) y badges cuando alcanzan hitos. Todo es extensible con hooks personalizados.
Funcionalidades:
- Puntos por publicar posts
- Puntos por comentar
- Puntos por registrarse
- Badges automáticos por niveles
- Shortcode para mostrar ranking
- Widget con top usuarios
- Hooks personalizados para extender
Código del plugin:
<?php
/**
* Plugin Name: Sistema de Gamificación
* Description: Puntos y badges para usuarios con hooks extensibles
* Version: 1.0.0
*/
class Sistema_Gamificacion {
private $puntos_por_post = 10;
private $puntos_por_comentario = 5;
private $puntos_por_registro = 20;
public function __construct() {
// Hooks para otorgar puntos
add_action( 'publish_post', array( $this, 'puntos_post_publicado' ), 10, 2 );
add_action( 'comment_post', array( $this, 'puntos_comentario' ), 10, 2 );
add_action( 'user_register', array( $this, 'puntos_registro' ) );
// Shortcode
add_shortcode( 'ranking', array( $this, 'shortcode_ranking' ) );
// Permitir modificar puntos con filtros
$this->puntos_por_post = apply_filters( 'gamificacion_puntos_post', $this->puntos_por_post );
$this->puntos_por_comentario = apply_filters( 'gamificacion_puntos_comentario', $this->puntos_por_comentario );
$this->puntos_por_registro = apply_filters( 'gamificacion_puntos_registro', $this->puntos_por_registro );
}
// Dar puntos por publicar
public function puntos_post_publicado( $post_id, $post ) {
if ( $post->post_type !== 'post' ) {
return;
}
$user_id = $post->post_author;
$this->anadir_puntos( $user_id, $this->puntos_por_post );
// Hook personalizado
do_action( 'gamificacion_post_publicado', $user_id, $post_id, $this->puntos_por_post );
}
// Dar puntos por comentar
public function puntos_comentario( $comment_id, $comment_approved ) {
// Solo si está aprobado
if ( $comment_approved !== 1 ) {
return;
}
$comment = get_comment( $comment_id );
$user_id = $comment->user_id;
if ( ! $user_id ) {
return;
}
$this->anadir_puntos( $user_id, $this->puntos_por_comentario );
// Hook personalizado
do_action( 'gamificacion_comentario_publicado', $user_id, $comment_id, $this->puntos_por_comentario );
}
// Dar puntos por registro
public function puntos_registro( $user_id ) {
$this->anadir_puntos( $user_id, $this->puntos_por_registro );
// Hook personalizado
do_action( 'gamificacion_usuario_registrado', $user_id, $this->puntos_por_registro );
}
// Función principal para añadir puntos
private function anadir_puntos( $user_id, $puntos ) {
$puntos_actuales = get_user_meta( $user_id, 'gamificacion_puntos', true );
$puntos_actuales = $puntos_actuales ? intval( $puntos_actuales ) : 0;
$nuevos_puntos = $puntos_actuales + $puntos;
// Permitir modificar puntos antes de guardar
$nuevos_puntos = apply_filters( 'gamificacion_antes_anadir_puntos', $nuevos_puntos, $user_id, $puntos );
update_user_meta( $user_id, 'gamificacion_puntos', $nuevos_puntos );
// Comprobar badges
$this->comprobar_badges( $user_id, $nuevos_puntos );
// Hook después de añadir puntos
do_action( 'gamificacion_puntos_anadidos', $user_id, $puntos, $nuevos_puntos );
}
// Sistema de badges
private function comprobar_badges( $user_id, $puntos ) {
$badges = array(
'novato' => 50,
'experimentado' => 200,
'experto' => 500,
'maestro' => 1000,
);
// Permitir modificar badges
$badges = apply_filters( 'gamificacion_badges', $badges );
$badges_usuario = get_user_meta( $user_id, 'gamificacion_badges', true );
$badges_usuario = $badges_usuario ? $badges_usuario : array();
foreach ( $badges as $badge => $puntos_requeridos ) {
if ( $puntos >= $puntos_requeridos && ! in_array( $badge, $badges_usuario ) ) {
$badges_usuario[] = $badge;
// Hook cuando se consigue un badge
do_action( 'gamificacion_badge_conseguido', $user_id, $badge, $puntos_requeridos );
}
}
update_user_meta( $user_id, 'gamificacion_badges', $badges_usuario );
}
// Shortcode ranking
public function shortcode_ranking( $atts ) {
$atts = shortcode_atts( array(
'numero' => 10
), $atts );
// Obtener top usuarios
$users = get_users( array(
'meta_key' => 'gamificacion_puntos',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'number' => intval( $atts['numero'] )
) );
ob_start();
?>
<div class="gamificacion-ranking">
<h3>Ranking de Usuarios</h3>
<table>
<thead>
<tr>
<th>Posición</th>
<th>Usuario</th>
<th>Puntos</th>
<th>Badges</th>
</tr>
</thead>
<tbody>
<?php
$posicion = 1;
foreach ( $users as $user ) :
$puntos = get_user_meta( $user->ID, 'gamificacion_puntos', true );
$badges = get_user_meta( $user->ID, 'gamificacion_badges', true );
$badges = $badges ? $badges : array();
?>
<tr>
<td><?php echo $posicion++; ?></td>
<td><?php echo esc_html( $user->display_name ); ?></td>
<td><?php echo intval( $puntos ); ?></td>
<td><?php echo implode( ', ', $badges ); ?></td>
</tr>
<?php
endforeach;
?>
</tbody>
</table>
</div>
<?php
return ob_get_clean();
}
}
new Sistema_Gamificacion();
// EJEMPLOS DE EXTENSIÓN CON LOS HOOKS PERSONALIZADOS:
// Enviar email cuando se consigue un badge
function notificar_badge_conseguido( $user_id, $badge, $puntos_requeridos ) {
$user = get_userdata( $user_id );
$subject = '¡Felicidades! Has conseguido el badge: ' . $badge;
$message = 'Has alcanzado ' . $puntos_requeridos . ' puntos.';
wp_mail( $user->user_email, $subject, $message );
}
add_action( 'gamificacion_badge_conseguido', 'notificar_badge_conseguido', 10, 3 );
// Puntos dobles los fines de semana
function puntos_dobles_fin_semana( $puntos, $user_id, $puntos_originales ) {
$dia = date( 'N' ); // 1 = Lunes, 7 = Domingo
if ( $dia >= 6 ) { // Sábado o Domingo
$puntos = $puntos + $puntos_originales; // Duplicar
}
return $puntos;
}
add_filter( 'gamificacion_antes_anadir_puntos', 'puntos_dobles_fin_semana', 10, 3 );
// Cambiar puntos por defecto
function aumentar_puntos_post( $puntos ) {
return 20; // En lugar de 10
}
add_filter( 'gamificacion_puntos_post', 'aumentar_puntos_post' );
Tiempo estimado: Completado en el código anterior
Verificación:
- El sistema de puntos funciona correctamente
- Se otorgan puntos por las acciones definidas
- Los badges se consiguen automáticamente
- El shortcode muestra el ranking
- Los hooks personalizados permiten extender el sistema
- Has probado las extensiones de ejemplo
Checklist para dominar esta lección
Entiendes el ciclo de vida completo de WordPress
Conoces el orden de ejecución de los hooks principales
Sabes qué hook usar en cada situación
Dominas el sistema de prioridades completamente
Puedes controlar el orden de ejecución de funciones
Sabes recibir múltiples argumentos en hooks
Especificas correctamente el número de argumentos aceptados
Sabes usar
remove_actionyremove_filterPuedes eliminar hooks de plugins y temas
Has limpiado
wp_headcorrectamenteSabes crear tus propios hooks con
do_actionSabes crear tus propios filtros con
apply_filtersHaces tu código extensible con hooks personalizados
Dominas el uso condicional de hooks
Combinas múltiples hooks en sistemas complejos
Has completado el sistema de gamificación
Entiendes casos de uso reales de hooks avanzados
Puedes hacer debug de problemas relacionados con hooks
Si has marcado todos los puntos, estás preparado para la lección sobre WP_Query y database. Los hooks son la base de todo en WordPress, y ahora los dominas completamente.
Recursos adicionales
- Plugin API Handbook: Documentación oficial completa
- Action Reference: Lista completa de actions
- Filter Reference: Lista completa de filters
- Hook Database: Buscador de hooks por versión
- Query Monitor: Plugin para debuggear hooks
Duración estimada: 2-3 semanas (25-30 horas entre lectura, ejercicios y proyecto)
Próximo paso: AprenderásWP_Queryen profundidad, queries complejas con la base de datos y optimización de consultas
Archivos de la lección
Tablas de referencia de hooks WordPress (MD)
Gráfico de prioridad de ejecución de hooks (SVG)