Las 3 reglas que deberían guiar el desarrollo de cualquier plugin de WordPress


Hay una tentación bastante común al desarrollar para WordPress: pensar que un plugin debe resolverlo todo, imponer su propia lógica y dejar el resto del sitio a su suerte. Es un error. Un buen plugin no compite con WordPress, ni con el tema, ni con el criterio del usuario. Hace su trabajo concreto, extendiendo lo existente aportando una funcionalidad.

Hace tiempo encontré un artículo de Daniel Auener que resume muy bien esta idea y que me ha servido de guía con tres reglas que, aunque parezcan obvias, siguen siendo necesarias porque demasiados plugins las ignoran.

Regla 1: No reinventes la interfaz de WordPress

Si el sistema ya ofrece componentes, patrones y formas de trabajo reconocibles, tiene poco sentido forzar al usuario a aprender una experiencia nueva. La familiaridad no es un detalle menor, hace que el uso del plugin resulte más intuitivo y fácil de usar. Aprende los patrones probados y adóptalos.

Regla 2: No quites control al theme

Demasiados plugins intentan decidir por encima de la capa visual, del diseño o de la estructura general del sitio. Eso genera fricción, rompe coherencia y obliga a pelearse con el plugin para conseguir algo tan básico como hacer que el resultado encaje con el proyecto. Un plugin útil aporta sin imponer decisiones de diseño que pertenecen al tema. Además, WordPress cada vez ha dejado más sencillo configurar varaibles de aspecto a nivel de instalación que pueden leer themes y plugins.

Regla 3: mantenlo simple

Para mi probablemente la más importante (y también la más ignorada). Hay una tendencia de los plugin «all-in-one», que dan muchas funcionalidades que, en realidad, el usuario no necesita. Al final acabamos con una colección de opciones, pantallas y ajustes que acaban complicando lo que pretendían simplificar. En WordPress eso se nota especialmente: cuanto más intenta un plugin abarcar, más probable es que termine generando dependencia, mantenimiento innecesario y problemas de usabilidad y rendimiento.

Por estas tres reglas prefiero instalar una serie de plugins, lo imprescindibles y, si es necesario, crear los míos propios. De esta manera estoy en control del sitio. En un ecosistema tan extendido como WordPress, hay mucha gente que ya se ha peleado con muchos problemas y no hay que reinventar la rueda, solo rodar con ella. Una interfaz que desentona, una opción que anula al tema, una función que añade complejidad sin necesidad pueden ser piedras en el camino.

Un plugin debería mejorar el sitio, no convertirlo en un campo de batalla entre capas que deberían cooperar. El control sobre nuestro WordPress no viene de plugins que cambian todo, sino de plugins que resuelven problemas concretos de manera óptima.

Detectar código PHP malicioso oculto en presuntos PDFs (o TTF, JPG, PNG, ICO…) atacando WordPress

En el ataque a instalaciones WordPress comprometidas, es habitual que, además de usar una brecha de seguridad que les permite inyectar un fichero de entrada, luego el grueso del código del webshell (aplicación maliciosa que se instala oculta) vaya en ficheros que a priori no son ejecutables y son de extensiones conocidas: documentos PDFs, imágenes JPG o PNG, ficheros de fuente TTF, o incluso iconos ICO. Esta técnica evade filtros básicos en busca de código malicioso y persiste tras limpiezas superficiales. Para hacer una limpieza completa y detectar si el sistema ha sido infectado habrá que buscar estos ficheros. Vamos a ver que comandos Linux utilizo para ello.

¿Por qué pasa en WordPress?

Por una combinación de estas tres circunstancias (que suele ser bastante habitual):

  • Plugins o themes desactualizados que permiten subidas de ficheros y/o que permiten ejecución PHP en la carpeta uploads; un plugin o theme desactualizado puede tener un agujero de seguridad que ya se ha subsanado en versiones posteriores.
  • .htaccess ausente o débil no fuerza octet-stream, con lo que acceder a estos ficheros puede ejecutar el código php en su interior.
  • Malware que se inyecta en ficheros habituales de WordPress como functions.php o wp-head.php y luego carga el código de estos «PDFs» que no son tal.

¿Un fichero no de PHP con código PHP dentro?

Dentro del fichero, en lugar de la información habitual de estos ficheros, esconden <?php eval(base64_decode(...)); ?>, o sea, código PHP ofuscado que se ejecuta si el servidor lo interpreta como PHP.

Así que, como el código PHP, al menos su etiqueta de apertura, va en claro podemos utilizar el siguiente comando linux:

grep -RIn --include="*.pdf" -e "<?php" /ruta/a/wp-content/uploads/
  • -R: Recursivo en subdirectorios (uploads suele tenerlos).
  • -I: Ignora binarios puros, pero procesa ficheros híbridos.
  • -n: Muestra línea exacta.
  • --include="*.pdf": Solo PDFs.

Ejemplo de salida sospechosa:

wp-content/uploads/2026/04/malicious.pdf:154:<?php eval(gzuncompress(base64_decode('3vilc0d3-3vilc0d33vilc0d3-3vilc0d3')));?>

Podemos hacer una búsqueda más amplia si no sabemos en que extensiones se pueden ocultar, aunque esto puede dar mucho falso positivo:

grep -RIn --exclude="*.php" -e "<?php" /ruta/a/wp-content/uploads/

--exclude="*.php": Todos los ficheros que no sean PHP.

Lo anterior podemos replantearlo con un pipe para borrar lo encontrado directamente. ATENCIÓN esto borra todo lo que encuentra, incluidos falsos positivos.

grep -RIlZ --include="*.pdf" -e "<?php" /ruta/a/uploads/ | xargs -0 rm -f
  • -l esta opción de grep nos devuelve solo nombres de archivo, listos para pasar por el pipe.

Principalmente el vector de ataque en WordPress suele ser el directorio /wp-content/uploads/ específicamente; otros directorios son menos comunes.

Prevención básica: .htaccess

Por otro lado, revisa que la carpeta uploads tiene prohibido ejecutar ficheros PHP que se encuentren allí, cualquier código PHP no se interpreta.

# En .htaccess de /uploads/
<Files "*.php">
Order Deny,Allow
Deny from all
</Files>
php_flag engine off

Pero no confiemos solo en .htaccess: mejor usar open_basedir en php.ini y validar cualquier subida a uploads.

Aún así, también es interesante hacer el mismo grep por toda la instalación desde la raíz y quizá ampliar a buscar cosas como <?=, <% o eval() ofuscado. Prueba con grep -P '(?i)(<\?php|eval\()'.

Fuente: wpsecurityninja

¿Cómo puedo borrar un archivo de linux que no se deja?

Cuando en Linux no puedes borrar un archivo, el problema casi siempre no está en los permisos del fichero sino en los del directorio que lo contiene.

Qué permisos controlan el borrado

En Unix/Linux, para borrar un fichero hace falta permiso de escritura (w) y ejecución (x) sobre el directorio que lo contiene, no sobre el propio fichero. Eso significa que puedes tener un fichero 777 dentro de un directorio 555 y aún así rm dará Permission denied, porque el directorio no es escribible.

Motivos típicos de “Permission denied” al borrar

  1. Directorio sin permiso de escritura
    El usuario no tiene w sobre el directorio que contiene el fichero.
  2. Directorio sin permiso de ejecución
    Aunque parezca raro, sin x no puedes “navegar” la entrada del fichero en el directorio, por lo que rm falla.
  3. Mezcla de permisos y sudo
    Si el fichero o los directorios son propiedad de otro usuario, normalmente necesitas sudo o cambiar propietario/permisos antes de borrar.
  4. Atributos especiales (immutable, etc.)
    En algunos casos el fichero tiene atributos especiales (+i) que impiden borrarlo incluso con permisos de escritura.

Cambiar permisos del directorio antes de borrar

La solución básica es asegurarse de que el usuario tiene permisos de escritura sobre el directorio:

chmod u+w /ruta/al/directorio
rm -f /ruta/al/directorio/archivo

Si quieres borrar todo el contenido de un directorio, suele ser necesario:

chmod -R u+w /ruta/al/directorio
rm -rf /ruta/al/directorio

Bola EXTRA: Hagamos una función de terminal para borrar y chmod en una sola línea

Sabiendo lo anterior, y ante la necesidad de usar varias veces ambos comandos sobre un mismo directorio, he creado una función:

chmodrm() { chmod -R u+w "$1" && rm -rf "$1"}
  • chmod -R u+w "$1" da permiso de escritura al propietario (u) sobre el directorio y todo su contenido.
  • rm -rf "$1" borra el directorio y su contenido recursivamente solo si el chmod tiene éxito (por el &&).

Consideraciones de seguridad

  • Usa esta función solo en directorios donde realmente tengas autoridad para borrar; rm -rf es irreversible.
  • Si el directorio es propiedad de otro usuario, necesitas sudo o cambiar el propietario (chown) antes de chmod -R.
  • En entornos compartidos o de producción, considera usar rm -ri (interactivo) o loggear antes de borrar en lugar de un rm -rf ciego.

Fuentes: StackOverflow | RedHat

TypeScript nos da la tranquilidad en el frontend

Cuando empecé a programar para web me encontraba cómodo con PHP y Javascript, me permitían el salvaje mundo de no definir la estructura de los datos y de reutilizar variables. Mientras, estaba aprendiendo Java y «sufriendo» la definición de clases y tipos. Era joven e inexperto. No sabía que esto está muy bien cuando estás tirando código rápido tú solo. Está bien para prototipar, hacer una prueba de concepto con pocos ficheros y pocas funciones, cuando tienes todo el código en tu cabeza. Ideal para un hackaton o similar.

Pero cuando llegas al mundo real, empiezas a utilizar librerías, a compartir código con otras personas, a tener que entender como funciona código ajeno (a veces sin leer documentación, por que no la hay). Si, ahora prefiero un código que se expresa por si solo, más allá de buenos nombres de variables o funciones, está un buen tipado, entender que puedo esperar de una función y como espera esa función ser usada, construir objetos que tienen sentido.

TypeScript evoluciona JavaScript, de hecho es más «java» ya que no solo tiene orientación a objetos sino que me devuelve a ese fuerte tipado. Sin embargo, los navegadores ejecutan javascript, así que el código TypeScript que hagamos tendrá que llevar una fase de compilación para convertirse en JS. Lo bueno, que en esa compilación saltarán errores de tipado que nos ayudarán a tener un JS más limpio y estable, pillando errores como asignaciones inválidas antes de runtime, lo que reduce bugs en producción hasta un 15-20%.

La curva de aprendizaje es mayor que JS, y quizá podemos entrar haciendo JS (para conocer como funciona y sabiendo que podríamos estar haciendo chapuzas) y luego dar el salto a TS para aprender de interfaces y tipos. Las interfaces definen contratos para objetos/APIs, facilitando el uso de funciones ajenas como cajas negras (sabemos lo que tenemos que darle y tenemos garantizado lo que sale, nos da igual lo que pasa dentro).

La principal desventaja es que hay que preparar un entorno de compilación y eso puede resultar engorroso poner en marcha. Además esa compilación lleva a retrasar unos segundos cada cambio que hagamos en nuestro desarrollo para poder probarlo. Pero si vamos a trabajar con frameworks modernos, con proyectos medianos-grande… no lo dudaría ni un segundo.

Por otro lado, que placer es el autocompletado que te aporta en el IDE poder tener clara la librería que estás usando.

Proyectos legacy: de JS a TS

[Leer más en TypeScript: Migrating from JavaScript]

Lo ideal en un proyecto legacy es ir cambiando piezas poco a poco, no queremos tirarlo todo y empezar de cero. La compilación de TypeScript permite usar código JS puro, pero aquí entraremos en terreno pantanoso que tendremos que andar con cuidado.

Lo ideal en este caso será reemplazar de fuera hacia dentro, ya que el core suele estar fuertemente acoplado. Iremos migrando piezas por su valor en negocio (criticidad, mantenimiento…) a fondo e ir cambiando su nivel de «strictness» de manera local. Creamos types para lo que necesiten estos módulos (que ya se usarán en toda la aplicación) y separamos responsabilidades.

Lamentablemente (o como solución temporal), usaremos el type any con toda relación con el núcleo que no ha sido migrado (dejando anotada esta deuda), pero solo en esas conexiones.

Cuando estén migrados todas las capas exteriores podremos ponernos serios con el core de la aplicación y levantar el nivel de strict de manera global.

Yoda conditions, código menos legible pero menos propenso a errores

El otro día me encontré leyendo código ajeno de PHP una condición «invertida»: primero se presentaba el valor buscado y luego se comparaba con la variable:

if('aguja' == $pajar){ // la encontraste!

Me dejó completamente descolocado porque supone leer al revés, y es en honor a ese personaje de Star Wars que anteponía predicado a sujeto que se ha nombrado esta manera de hacer las condiciones.

¿Los motivos para usarla? Con operadores de igualdad normales (==) en algunos lenguajes podrías escribir por error if ( $pajar = 'aguja' ), lo que asignaría el valor en vez de comparar (y siempre daría true); sin embargo, si el literal está a la izquierda ('aguja' == $pajar), esa asignación daría un error de sintaxis y el bug saltaría automáticamente.

Fuente: Yoda conditions

Primeros pasos para domar un código legacy

Me gusta el reto de llegar a un proyecto ajeno y domarlo. Entender como lo diseñaron y ver como mejorarlo. Pero antes de poder ampliar ese código hay que estabilizarlo para poder caminar seguros. El objetivo con el que atacamos ese código es poder tener una base de código entendible y con cobertura de tests que nos dé tranquilidad.

El enfoque es gradual y seguro, priorizando la comprensión del código, las pruebas y la refactorización incremental. Haremos primero los pasos 0, 1 y 2 y luego realizaremos los pasos 3 por bloques de funcionalidad.

Seguir leyendo Primeros pasos para domar un código legacy

Como Docker y buenas prácticas me permiten poner en marcha mi entorno de desarrollo en tiempo record

Mi espacio de desarrollo puede ir conmigo a cualquier parte del mundo que tenga conexión a internet (de banda ancha). Obviamente, es mejor tenerlo todo instalado, que sea arrancar y ponerse en manos a la obra. Pero ¿qué pasa si necesito levantar el entorno de desarrollo en un equipo nuevo? Ya sea porque mi equipo se haya roto, lo he perdido o simplemente esté a kilómetros de donde estoy en este momento. Aquí, tener una serie de buenas prácticas me permite levantar mi entorno de desarrollo (o el de alguien de mi equipo) en tiempo record, y marca la diferencia entre estar parado o poder recuperar el control rápidamente.

[Hoy dejaré de lado la solución de tener todo virtualizado en un servidor y tener mi IDE (editor de código) en el navegador. @TODO escribir artículo sobre entorno de desarrollo virtualizado online].

Seguir leyendo Como Docker y buenas prácticas me permiten poner en marcha mi entorno de desarrollo en tiempo record

La importancia del directorio .well-known en nuestro servidor para conectar servicios de terceros

El directorio .well-known es un estándar definido por el RFC 8615 que actúa como un directorio especial ubicado en la raíz de un sitio web, accesible como /.well-known/. Sirve para almacenar archivos de metadatos y configuraciones importantes para diversos protocolos web, como validaciones de certificados SSL, configuraciones de seguridad, autenticación (OpenID, OAuth 2.0) y otras integraciones automáticas entre servicios y aplicaciones.

Este mecanismo permite que aplicaciones, navegadores y servicios automaticen procesos de configuración, validación y descubrimiento de servicios de forma estandarizada, sin necesidad de configuración personalizada para cada caso.

Por ejemplo, aquí podemos dejar archivos como security.txt para indicar como reportar vulnerabilidades, o carpetas como /.well-known/pki-validation/ usadas para validar la propiedad de un dominio al emitir certificados SSL.

Y sin duda es un avance de limpieza frente a dejar estos ficheros en la carpeta raíz.

Usos más habituales

Los usos más comunes del directorio .well-known están ligados a la seguridad, la validación de dominio y la interoperabilidad entre aplicaciones y servicios web modernos.​

  • Validación de certificados SSL/TLS: Autoridades certificadoras (como Let’s Encrypt) solicitan archivos específicos en subcarpetas como /.well-known/acme-challenge/ o /.well-known/pki-validation/ para verificar la propiedad de un dominio antes de emitir un certificado.
  • Validación de email: SMTP Strict Transport Security puede configurar que los servodres solo acepten correos cifrados. /.well-known/mta-sts.txt
  • Archivos de políticas de seguridad: Como security.txt, donde se publica información de contacto para reportar incidentes de seguridad en el sitio, según recomendación RFC 9116.
  • Descubrimiento de servicios de identidad: Protocolos como OpenID Connect y OAuth 2.0 usan rutas como /.well-known/openid-configuration o /.well-known/oauth-authorization-server para que las aplicaciones detecten automáticamente endpoints y configuraciones relacionadas con autenticación y autorización.​
  • Integración de aplicaciones móviles: Android y Apple utilizan archivos en /.well-known/ para validar que un dominio es legítimo y permitir enlaces universales o “deep links”.​
  • Cumplimiento legal y privacidad: Algunas normativas como GDPR han motivado la publicación de información de contacto y políticas de privacidad accesibles públicamente en este directorio.​

Todos estos casos facilitan la automatización, estandarización, y seguridad de procesos web, permitiendo que diferentes servicios interactúen sin intervención manual y de manera segura.

En general, lo ideal es crear esta carpeta y ficheros reales directamente en nuestro servidor y revisar que no hay reglas (en .htaccess o similar) que redirijan el tráfico a esta ruta, impidiendo servir los ficheros.

La mejor manera de esperar a que el DOM esté listo y poder manipularlo

Aún recuerdo mis tiempos de jQuery (si, esa librería ha sido una gran herramienta durante mucho tiempo) en que había que esperar a $(document).ready() para garantizar que el DOM y las librerías habían cargado para poder ejecutar código y que todo funcionara.

Pero cuando empezamos a poner el javascript al final del HTML (si, fuera del Header) para aliviar los tiempos de carga provocamos que en realidad no hiciera falta esperar, porque si se había cargado ese código es porque ya se había parseado todo el DOM.

Pero ¿eso quiere decir que el DOM está listo? ¿que se han cargado todos los elementos? Pues se han cargado todos los elementos renderizados en servidor, PERO actualmente hay mucho renderizado que se hace en cliente (React, Angular… )

Así que si queremos manipular el DOM, necesitamos garantizar que existe ese elemento que queremos manipular. Puede que se lance un script cuando el DOM está listo pero necesite esperar a que ese elemento esté disponible.

La solución habitual es hacer un bucle con setTimeout que hace una comprobación cada cierto tiempo y se lanza a si mismo si no encuentra el elemento para intentarlo de nuevo.

function check() {
  if (!$('#elementId').size()){
    setTimeout(check, 200);
  } else {
    // el código a ejecutar
  }
}

Y esto puede que funcione, pero no es la manera más eficiente… y con los navegadores modernos puede que falle porque, cuando la pestaña / ventana no esté en primer plano puede que ahorren recursos y no corran ese código suficientemente «rápido».

Así que toca hacer una solución moderna para navegadores modernos, que no dependa de que la pestaña esté funcionando, o de que el timeout se ejecuta a tiempo. Y la respuesta es requestAnimationFrame

Se suele usar para animaciones, garantizando 60 refrescos por segundo. Pero también se puede usar para notificar al código que está esperando.

Así que usaremos algo como:

function check() {
  if (!$('#elementId').size()){
    window.requestAnimationFrame(check);
  } else {
    // el código a ejecutar
  }
}

Y aunque en teoría podría parecer que este código se comprobaría el DOM cada 1/60 de segundo para comprobar el elemento buscado, solo se hará una vez, cuando el elemento sea renderizado.

Y así es la manera adecuada de esperar a un elemento de DOM en los navegadores modernos.

Mas info: Swizec

git insteadOf: Jugando con las URLs en git

Si te dedicas al desarrollo, tienes que tener algún sistema de versionado de código. Cada cambio registrado, poder hacer ramificaciones de código, integrar código de varias personas…

También puede ser que empieces a tener muchos repositorios de código, y cada uno en un servicio distinto: github, gitlab, bitbucket… o alguno en tus propios servidores.

Hay gente que lo usa para acortar urls y no tener que acordarse de / tener que teclear toda la ruta a sus repositorios.

git config --global url."https://github.com/".insteadOf "gh:"

En mi caso este mecanismo me ha resultado útil cuando las políticas de seguridad del repositorio de trabajo han cambiado de poder usar unas urls a otras (podrían también haberse dado un cambio de ruta en el servidor). Pero no era plan de cambiar todas las referencias en las dependencias, hay demasiados proyectos.

Así, es tan fácil como indicarlo algo como:

git config --global url.https://bitbucket.project-x.info/.insteadof ssh://git@project-x.info:7899/

¿Y si tienes más que referencia a la misma url? Pues toca añadir más entradas a la config de tu cliente git con –add

git config --global --add url.https://bitbucket.project-x.info/.insteadof ssh://project-x.info:7899/

Esta configuración también se puede usar por proyecto añadiendolo al .gitconfig

+ info: Graphite | Kovrinic