Curso de programación para WordPress

0 de 34 lecciones completas (0%)

Por favor, accede para ver tu progreso.

Programación para WordPress – Nivel intermedio

Hooks avanzados y filtros

Esta es una vista previa de la lección

Compra el curso, o accede si ya te has inscrito, para acceder a esta lección.

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.

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_action y apply_filters personalizados
  • Hooks condicionales: ejecutar solo en ciertas situaciones
  • Class methods en hooks: usar métodos de clases
  • Anonymous functions: funciones anónimas en hooks
  • has_action y has_filter: verificar si hay hooks

Recursos oficiales y recomendados

Estos recursos son esenciales para dominar 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 hooks
  • Dame 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:

  1. Activa el plugin
  2. Visita cualquier página del sitio
  3. Ve al archivo de log (wp-content/debug.log si tienes WP_DEBUG activo)
  4. Observa el orden exacto de ejecución

Orden típico de ejecución:

  1. muplugins_loaded → Plugins MU cargados
  2. plugins_loaded → Todos los plugins cargados
  3. after_setup_theme → Tema configurado
  4. init → Momento de inicialización (CPT, taxonomías, etc)
  5. widgets_init → Widgets registrados
  6. wp → Query principal ejecutada
  7. template_redirect → Antes de cargar el template
  8. wp_head → Dentro de <head>
  9. loop_start → Antes del primer post del loop
  10. loop_end → Después del último post del loop
  11. wp_footer → Antes de cerrar </body>
  12. shutdown → Último momento, respuesta ya enviada

Cuándo usar cada hook:

  • plugins_loaded: Tu plugin depende de otro plugin
  • after_setup_theme: Añadir theme support, registrar menús
  • init: Registrar CPT, taxonomías, rewrite rules, sesiones
  • widgets_init: Registrar sidebars y widgets
  • wp: Modificar query principal después de que se ejecute
  • template_redirect: Redirecciones, comprobaciones antes de mostrar
  • wp_head: Añadir meta tags, scripts en el head
  • wp_footer: Añadir scripts antes de cerrar body

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:

  1. Lee la documentación oficial
  2. Busca en el código fuente de WordPress dónde se ejecuta el hook
  3. 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ón
  • remove_filter() – Elimina un filtro
  • remove_all_actions() – Elimina TODAS las acciones de un hook
  • remove_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_action y remove_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_head correctamente

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:

  1. Usa prefijos: mi_plugin_mi_hook no mi_hook
  2. Nombra descriptivamente: antes_contenido, despues_guardar
  3. Documenta tus hooks: Explica qué hacen y qué argumentos pasan
  4. Sé consistente: Si usas antes/despues, úsalo siempre
  5. 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_action y remove_filter

  • Puedes eliminar hooks de plugins y temas

  • Has limpiado wp_head correctamente

  • Sabes crear tus propios hooks con do_action

  • Sabes crear tus propios filtros con apply_filters

  • Haces 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


Duración estimada: 2-3 semanas (25-30 horas entre lectura, ejercicios y proyecto)
Próximo paso: Aprenderás WP_Query en 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)

Scroll al inicio