Contents
Introducción
Este tutorial explica, con todo detalle y ejemplos completos en PHP y JavaScript, cómo añadir y mostrar un avatar y una biografía personalizados para los usuarios de WordPress. Incluye: campos en el perfil de usuario (URL o attachment ID), guardado seguro, integración con el Media Uploader, filtro para reemplazar get_avatar() y funciones de plantilla para mostrar avatar bio. Se asume que dispone de conocimientos básicos de WordPress y que trabajará en un child theme o en un plugin propio.
Resumen de la solución
- Añadir campos personalizados en la página de perfil (avatar personalizado y biografía).
- Guardar esos campos con comprobaciones de permisos y nonces.
- Opcional: integrar el Media Uploader para seleccionar imágenes desde la librería.
- Filtrar get_avatar() para devolver el avatar personalizado en vez del Gravatar.
- Proveer funciones de plantilla para mostrar avatar y biografía con escapes y fallback.
Recomendaciones previas
- Trabaje en un tema hijo o en un plugin para no perder cambios con actualizaciones.
- Haga backup antes de modificar archivos.
- Use siempre funciones de escape y saneamiento: esc_url(), esc_attr(), esc_html(), sanitize_text_field(), esc_textarea().
Paso 1 — Añadir campos al perfil de usuario
Agregar los campos en el formulario de perfil: campo para URL de avatar o para almacenar el ID del attachment (cuando use el Media Uploader), y un textarea para la biografía personalizada.
lt?php // Añadir campos al perfil (show_user_profile y edit_user_profile) add_action(show_user_profile, mi_perfil_campos_personalizados) add_action(edit_user_profile, mi_perfil_campos_personalizados) function mi_perfil_campos_personalizados(user) { // Obtiene valores previos custom_avatar_url = get_user_meta(user-gtID, custom_avatar_url, true) custom_avatar_id = get_user_meta(user-gtID, custom_avatar_id, true) // opcional custom_bio = get_user_meta(user-gtID, custom_bio, true) // Nonce para verificar al guardar wp_nonce_field(guardar_campos_perfil_personalizados, nonce_campos_perfil_personalizados) ?gt ltpgtltlabel for=custom_avatar_urlgtltbgtAvatar personalizado (URL)lt/bgtlt/labelgtltbr /gt ltinput type=url name=custom_avatar_url id=custom_avatar_url value=lt?php echo esc_attr(custom_avatar_url) ?gt class=regular-text /gt ltspan class=descriptiongtDirección completa de la imagen. Alternativamente use el Media Uploader con el campo ID (debajo).lt/spangtlt/pgt ltpgtltlabel for=custom_avatar_idgtltbgtAvatar personalizado (Attachment ID)lt/bgtlt/labelgtltbr /gt ltinput type=text name=custom_avatar_id id=custom_avatar_id value=lt?php echo esc_attr(custom_avatar_id) ?gt class=regular-text /gt ltspan class=descriptiongtID del attachment guardado por el Media Uploader (opcional).lt/spangtlt/pgt ltpgtltlabel for=custom_biogtltbgtBiografía personalizadalt/bgtlt/labelgtltbr /gt lttextarea name=custom_bio id=custom_bio rows=5 class=large-textgtlt?php echo esc_textarea(custom_bio) ?gtlt/textareagt ltspan class=descriptiongtTexto corto que se mostrará en los perfiles/autorías.lt/spangtlt/pgt lt?php } ?gt
Paso 2 — Guardar los campos del perfil
Al guardar el formulario del perfil hay que validar nonce, permisos y sanear los datos antes de guardarlos con update_user_meta().
lt?php add_action(personal_options_update, mi_guardar_campos_perfil_personalizados) add_action(edit_user_profile_update, mi_guardar_campos_perfil_personalizados) function mi_guardar_campos_perfil_personalizados(user_id) { if (!isset(_POST[nonce_campos_perfil_personalizados])) { return } if (!wp_verify_nonce(_POST[nonce_campos_perfil_personalizados], guardar_campos_perfil_personalizados)) { return } if (!current_user_can(edit_user, user_id)) { return } // Guardar URL de avatar if (isset(_POST[custom_avatar_url])) { url = trim(_POST[custom_avatar_url]) url = esc_url_raw(url) update_user_meta(user_id, custom_avatar_url, url) } // Guardar attachment ID (solo números) if (isset(_POST[custom_avatar_id])) { id = intval(_POST[custom_avatar_id]) if (id gt 0) { update_user_meta(user_id, custom_avatar_id, id) } else { delete_user_meta(user_id, custom_avatar_id) } } // Guardar biografía personalizada if (isset(_POST[custom_bio])) { bio = sanitize_text_field(_POST[custom_bio]) update_user_meta(user_id, custom_bio, bio) } } ?gt
Paso 3 (opcional) — Integrar el Media Uploader
Para una mejor UX use la Librería de Medios para seleccionar imágenes. Esto requiere encolar scripts y añadir un botón que abra el uploader. A continuación un ejemplo breve de cómo encolar y el JS necesario.
lt?php // Encolar scripts en el admin para el Media Uploader add_action(admin_enqueue_scripts, mi_encolado_media_uploader) function mi_encolado_media_uploader(hook_suffix) { // Solo en la pantalla de perfil y edición de usuario if (profile.php !== hook_suffix user-edit.php !== hook_suffix) { return } wp_enqueue_media() wp_enqueue_script(mi-uploader-profile-js, plugin_dir_url(__FILE__) . mi-uploader-profile.js, array(jquery), 1.0, true) } ?gt
Archivo JavaScript (mi-uploader-profile.js):
(function(){ var frame (document).on(click, #mi_seleccionar_avatar, function(e){ e.preventDefault() // Si existe el frame, reusar if (frame) { frame.open() return } frame = wp.media({ title: Seleccionar avatar, button: { text: Usar este avatar }, multiple: false }) frame.on(select, function(){ var attachment = frame.state().get(selection).first().toJSON() (#custom_avatar_id).val(attachment.id) (#custom_avatar_preview).attr(src, attachment.sizes attachment.sizes.thumbnail ? attachment.sizes.thumbnail.url : attachment.url).show() }) frame.open() }) })(jQuery)
En el HTML del perfil (ya mostrado en el Paso 1) puede añadir un botón y una etiqueta img para previsualizar:
ltbutton id=mi_seleccionar_avatar class=buttongtSeleccionar avatar desde la libreríalt/buttongt ltimg id=custom_avatar_preview src=lt?php echo esc_url(preview_url) ?gt style=max-width:100pxdisplay: alt=Preview /gt
Paso 4 — Filtrar get_avatar() para usar el avatar personalizado
Use el filtro get_avatar para devolver la imagen personalizada (attachment ID o URL). El ejemplo siguiente gestiona distintas entradas (WP_User, comment, email). Respeta el parámetro size y conserva el markup de avatar típico de WordPress.
lt?php add_filter(get_avatar, mi_get_avatar_personalizado, 10, 6) function mi_get_avatar_personalizado(avatar, id_or_email, size, default, alt, args) { user = false // Obtener el user object if (is_numeric(id_or_email)) { user = get_user_by(id, (int) id_or_email) } elseif (is_object(id_or_email)) { if (!empty(id_or_email->user_id)) { user = get_user_by(id, (int) id_or_email->user_id) } } else { user = get_user_by(email, id_or_email) } if (!user) { return avatar // Mantener comportamiento por defecto si no hay usuario } // Priorizar attachment ID, luego URL, luego fallback a Gravatar avatar_id = get_user_meta(user->ID, custom_avatar_id, true) avatar_url = get_user_meta(user->ID, custom_avatar_url, true) if (avatar_id) { img = wp_get_attachment_image_src(avatar_id, array(size, size)) if (img) { src = esc_url(img[0]) } else { src = } } elseif (avatar_url) { src = esc_url(avatar_url) } else { return avatar } if (empty(src)) { return avatar } alt = alt ? alt : sprintf(esc_attr__(%s avatar, textdomain), user->display_name) class = isset(args[class]) ? args[class] : avatar avatar- . (int)size . photo custom_avatar = sprintf( ltimg alt=%s src=%s class=%s width=%d height=%d /gt, esc_attr(alt), src, esc_attr(class), (int) size, (int) size ) // Mantener envoltorio por si otros plugins esperan estructura similar return custom_avatar } ?gt
Notas sobre el filtro
- Si necesita conservar exactamente el HTML que WordPress genera (p. ej. envoltorios con enlace a author), puede construirlo aquí o aplicar el filtro en otro momento.
- El ejemplo devuelve solo la etiqueta ltimggt. Si quiere el mismo markup completo de WP (con span), puede construirlo con sprintf usando clases de avatar.
Mostrar avatar y biografía en la plantilla
Puede crear una función reutilizable que muestre avatar y biografía de un usuario concreto en su tema.
lt?php function mostrar_avatar_y_bio_personalizados(user_id = 0, size = 96) { if (!user_id) { user_id = get_the_author_meta(ID) } user = get_user_by(id, user_id) if (!user) { return } // Usar nuestro filtro get_avatar (se aplicará automáticamente) avatar_html = get_avatar(user_id, size) custom_bio = get_user_meta(user_id, custom_bio, true) // Si no hay bio personalizada, usar description o user meta por defecto if (empty(custom_bio)) { custom_bio = get_the_author_meta(description, user_id) } echo ltdiv class=mi-avatar-biogt echo ltdiv class=mi-avatargt . avatar_html . lt/divgt echo ltdiv class=mi-biogtlth4gt . esc_html(user->display_name) . lt/h4gt if (custom_bio) { echo ltpgt . esc_html(custom_bio) . lt/pgt } echo lt/divgtlt/divgt } ?gt
Ejemplo completo: plugin mínimo que implementa la funcionalidad
El siguiente bloque resume todo en un único archivo de plugin para que lo pegue en wp-content/plugins/mi-avatar-bio/mi-avatar-bio.php (añádale encabezado de plugin, activo y pruebe).
lt?php / Plugin Name: Avatar y Bio Personalizados Description: Añade avatar personalizado (URL o attachment ID) y biografía por usuario, filtra get_avatar y provee funciones de plantilla. Version: 1.0 Author: Tu Nombre / if (!defined(ABSPATH)) exit // Añadir campos al perfil add_action(show_user_profile, mi_perfil_campos_personalizados) add_action(edit_user_profile, mi_perfil_campos_personalizados) function mi_perfil_campos_personalizados(user) { custom_avatar_url = get_user_meta(user-gtID, custom_avatar_url, true) custom_avatar_id = get_user_meta(user-gtID, custom_avatar_id, true) custom_bio = get_user_meta(user-gtID, custom_bio, true) wp_nonce_field(guardar_campos_perfil_personalizados, nonce_campos_perfil_personalizados) ?> ltpgtltlabel for=custom_avatar_urlgtltbgtAvatar personalizado (URL)lt/bgtlt/labelgtltbr /gt ltinput type=url name=custom_avatar_url id=custom_avatar_url value=lt?php echo esc_attr(custom_avatar_url) ?gt class=regular-text /gtlt/pgt ltpgtltlabel for=custom_avatar_idgtltbgtAvatar personalizado (Attachment ID)lt/bgtlt/labelgtltbr /gt ltinput type=text name=custom_avatar_id id=custom_avatar_id value=lt?php echo esc_attr(custom_avatar_id) ?gt class=regular-text /gt ltbutton id=mi_seleccionar_avatar class=buttongtSeleccionar desde libreríalt/buttongt ltimg id=custom_avatar_preview src=lt?php echo esc_url(custom_avatar_url ? custom_avatar_url : ) ?gt style=max-width:80px alt=Preview /gtlt/pgt ltpgtltlabel for=custom_biogtltbgtBiografía personalizadalt/bgtlt/labelgtltbr /gt lttextarea name=custom_bio id=custom_bio rows=5 class=large-textgtlt?php echo esc_textarea(custom_bio) ?gtlt/textareagtlt/pgt lt?php } // Guardar campos add_action(personal_options_update, mi_guardar_campos_perfil_personalizados) add_action(edit_user_profile_update, mi_guardar_campos_perfil_personalizados) function mi_guardar_campos_perfil_personalizados(user_id) { if (!isset(_POST[nonce_campos_perfil_personalizados])) return if (!wp_verify_nonce(_POST[nonce_campos_perfil_personalizados], guardar_campos_perfil_personalizados)) return if (!current_user_can(edit_user, user_id)) return if (isset(_POST[custom_avatar_url])) { update_user_meta(user_id, custom_avatar_url, esc_url_raw(trim(_POST[custom_avatar_url]))) } if (isset(_POST[custom_avatar_id])) { id = intval(_POST[custom_avatar_id]) if (id gt 0) update_user_meta(user_id, custom_avatar_id, id) else delete_user_meta(user_id, custom_avatar_id) } if (isset(_POST[custom_bio])) { update_user_meta(user_id, custom_bio, sanitize_text_field(_POST[custom_bio])) } } // Filtrar avatar add_filter(get_avatar, mi_get_avatar_personalizado, 10, 6) function mi_get_avatar_personalizado(avatar, id_or_email, size, default, alt, args) { user = false if (is_numeric(id_or_email)) user = get_user_by(id, (int) id_or_email) elseif (is_object(id_or_email) !empty(id_or_email->user_id)) user = get_user_by(id, (int) id_or_email->user_id) else user = get_user_by(email, id_or_email) if (!user) return avatar avatar_id = get_user_meta(user->ID, custom_avatar_id, true) avatar_url = get_user_meta(user->ID, custom_avatar_url, true) src = if (avatar_id) { img = wp_get_attachment_image_src(avatar_id, array(size, size)) if (img) src = img[0] } elseif (avatar_url) { src = avatar_url } if (empty(src)) return avatar alt = alt ? alt : sprintf(esc_attr__(%s avatar, textdomain), user->display_name) class = isset(args[class]) ? args[class] : avatar avatar- . (int)size . photo return sprintf(ltimg alt=%s src=%s class=%s width=%d height=%d /gt, esc_attr(alt), esc_url(src), esc_attr(class), (int) size, (int) size ) } // Función de plantilla function mostrar_avatar_y_bio_personalizados(user_id = 0, size = 96) { if (!user_id) user_id = get_the_author_meta(ID) user = get_user_by(id, user_id) if (!user) return avatar_html = get_avatar(user_id, size) custom_bio = get_user_meta(user_id, custom_bio, true) if (empty(custom_bio)) custom_bio = get_the_author_meta(description, user_id) echo ltdiv class=mi-avatar-biogt echo ltdiv class=mi-avatargt . avatar_html . lt/divgt echo ltdiv class=mi-biogtlth4gt . esc_html(user->display_name) . lt/h4gt if (custom_bio) echo ltpgt . esc_html(custom_bio) . lt/pgt echo lt/divgtlt/divgt } ?gt
Buenas prácticas y seguridad
- Use nonces y current_user_can al guardar para evitar CSRF y escalado de privilegios.
- Saneamiento: esc_url_raw para URLs, sanitize_text_field para textos y esc_textarea/esc_html al mostrar.
- Compruebe que el attachment ID corresponde a una imagen antes de usarlo (wp_get_attachment_image_src devuelve false si no existe).
- Si muestra HTML en front-end, evite imprimir contenido sin escapar para prevenir XSS.
- Cache: si su sitio carga muchos avatares personalizados, considere usar transients o un CDN para las imágenes.
Compatibilidad y consideraciones finales
- Si el usuario no tiene avatar personalizado, deje el comportamiento por defecto (Gravatar) o use una imagen por defecto alojada localmente.
- Respete tamaños y proporciones. Use funciones de imagen de WordPress para obtener tamaños generados: wp_get_attachment_image_src(id, size).
- Si tiene multi-site, verifique que las rutas y permisos de media sean correctos.
- Si otros plugins ya filtran get_avatar, ajuste prioridades o implemente condiciones para evitar conflictos.
Recursos útiles
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |