Habilidad de Envío de Plugins a WordPress.org
Haz que cualquier plugin pase el escaneo automatizado y la revisión manual de wp.org sin tener que subirlo cinco veces. Destilado de ciclos de revisión reales (del tipo en que un simple .gitkeep hace fallar todo el escaneo).
Mantenedor: moksa · moksaweb.com · Licencia: MIT
Cuándo usarla
Activa esta habilidad cuando el usuario pida:
- Empaquetar / compilar un zip de instalación limpio para wordpress.org
- Enviar o reenviar un plugin al directorio de wp.org
- Corregir comentarios del equipo de revisión de wp.org (las 4 categorías clásicas)
- Diagnosticar un rechazo del escaneo automatizado (archivos ocultos, prefijos, llamadas a BD…)
- Subir la versión de un plugin de forma consistente (cabecera + readme + constantes)
- Verificar un plugin antes de subirlo
NO usar para:
- Proyectos que no son de WordPress
- Plugins privados/internos que nunca irán a wp.org (aunque las reglas de revisión no aplican, las prácticas de seguridad siguen siendo útiles)
Las dos puertas
- Escaneo automatizado (Plugin Check, se ejecuta al subir) — bloqueos duros. Debe pasarse para siquiera llegar a un humano. Ver
references/automated-scan.md. - Revisión manual (un humano, después de pasar el escaneo) — te envía problemas por correo, tú corriges y respondes. Casi siempre es una de 4 categorías de código (
references/review-categories.md) además de directrices de política que ningún linter detecta: licencias, código remoto, rastreo, avisos administrativos, marcas registradas, divulgación de servicios externos (references/guidelines.md).
Mapa de referencias
| Tema | Archivo |
|---|---|
| Plugin Check (PCP) guía de uso — CLI/UI, las 31 comprobaciones → mapa de corrección, errores vs advertencias | references/plugin-check.md |
| Bloqueos duros del escaneo automatizado (archivos ocultos, BD, llamadas no permitidas) | references/automated-scan.md |
| Las 4 categorías de revisión manual + redacción de respuestas | references/review-categories.md |
| Lecciones de revisiones reales (muestra→barrido, nonce de 3 casos, phpcs:ignore≠escape, cast≠sanitize, rutas, prefijos de fork) | references/real-review-lessons.md |
| Prefijado de cada global (funciones/opciones/hooks/manejadores/CPT/meta…) | references/prefixing.md |
Sanitizar · validar · escapar + $wpdb->prepare + firmas de webhook | references/sanitization-escaping.md |
| Colas de JS/CSS (deps/versión, inline, control, bloques, sin etiquetas HTML directas) | references/enqueue.md |
| i18n (textdomain==slug, cadenas literales, comentarios de traductores, JS) | references/i18n.md |
| Capacidades y callbacks de permisos (autorización, REST, priv/nopriv) | references/capabilities.md |
| Directrices de política (licencia, código remoto, rastreo, avisos, marcas, divulgación) | references/guidelines.md |
| Especificación de formato readme.txt (cabeceras, etiquetas≤5, descripción corta≤150, capturas de pantalla, servicios externos) | references/readme-txt.md |
| API de Habilidades · Cliente de IA · MCP (autorización por habilidad, límite de datos, confirmación destructiva, divulgación del proveedor) | references/abilities-ai-mcp.md |
| Extras de seguridad (archivos/WP_Filesystem, subidas, SSRF, llamadas no permitidas/ofuscadas) | references/security-extras.md |
| Desinstalación y ciclo de vida (limpieza de datos, no eliminar contenido de usuario, multisitio) | references/uninstall.md |
| Empaquetado del zip + consistencia de versión/cabecera/readme + .mo | references/packaging.md |
| Búsqueda de error de escaneo/PHPCS → corrección | data/plugin-check-errors.csv |
Lista de verificación rápida antes de subir (copiar y pegar para verificar)
ESCANEO AUTOMATIZADO (bloqueos duros)
[ ] No hay archivos ocultos en el zip: `find . -name '.*' -not -name '.' -not -name '..'` está VACÍO
(.gitkeep / .gitignore / .DS_Store / .editorconfig todos fallan "hidden_files")
[ ] No hay node_modules/, .git/, tests/, scripts/, phpcs.xml, webpack.config.js en el zip
[ ] Todas las globales del plugin usan un prefijo único de >=4 caracteres (sin wp_ / __ / _ sola)
[ ] Sin consultas directas a $wpdb sin prepare(); sin ejecución de código remoto; sin "phone-home"
REVISIÓN MANUAL (las 4 categorías)
[ ] Colas: sin echo '<script>'/'<style>' directo; usar wp_enqueue_* + wp_add_inline_*
[ ] Nonce + current_user_can en cada AJAX/admin_post que cambie estado; los webhooks verifican
firma del proveedor con hash_equals antes de usar (no es posible usar nonce de WP)
[ ] Cada $_POST/$_GET sanitizado (wp_unslash + sanitize_*); json_decode -> map_deep;
salida escapada tarde (esc_html/esc_attr/esc_url/wp_kses)
[ ] textdomain == slug; los argumentos de __() son cadenas literales; /* translators: */ antes de %s
POLÍTICA (revisión manual, ningún linter detecta esto — references/guidelines.md)
[ ] Licencia compatible con GPL declarada + archivo LICENSE; cada librería/recurso empaquetado es compatible con GPL
[ ] Sin ejecución remota de código / sin cargar JS ejecutable desde un CDN; empaquetar recursos localmente
[ ] Cualquier llamada a API de terceros divulgada en readme "== Servicios externos ==" (qué datos, cuándo, URLs)
[ ] Sin recolección/rastreo de datos sin consentimiento explícito (por defecto OFF); sin enlace de crédito
en el front-end a menos que sea opt-in; avisos administrativos descartables y limitados a la pantalla (sin spam de avisos)
[ ] El slug/nombre no comienza con una marca registrada que no posees (woocommerce-… / google-…)
README.TXT (references/readme-txt.md — validar en el validador de readme de wp.org)
[ ] La cabecera parsea: Contributors=usuarios de wp.org, Tags<=5, Stable tag = versión real enviada
[ ] Descripción corta <=150 caracteres, texto plano; Changelog tiene una entrada para la versión estable
[ ] Capturas de pantalla/icono/banner van en SVN /assets/, NO en el zip
IA / HABILIDADES / MCP (si el plugin registra habilidades, llama a un LLM o expone MCP)
[ ] Cada wp_register_ability tiene un permission_callback real (capacidad a nivel de objeto), no __return_true
[ ] Límite de datos: una herramienta de habilidad/MCP devuelve solo lo que permiten las capacidades del usuario actual
[ ] La entrada del modelo/agente se trata como no confiable; las habilidades destructivas están protegidas con confirmación
[ ] SDK de LLM/IA empaquetado (GPL); proveedor divulgado en == Servicios externos ==; las claves nunca se registran
[ ] El endpoint MCP autentica (App Password/OAuth) Y ejecuta el permission_callback de cada habilidad
SEGURIDAD ADICIONAL + CICLO DE VIDA
[ ] Sin eval/shell/ofuscación/extract; sin var_dump/print_r/error_log dejados
[ ] Archivos a través de WP_Filesystem solo en uploads; subidas mediante wp_handle_upload + lista blanca MIME
[ ] Llamadas remotas mediante wp_safe_remote_* (SSRF: bloquear rangos internos) + timeout + verificación de respuesta
[ ] uninstall.php protegido por WP_UNINSTALL_PLUGIN; elimina solo tus datos prefijados; nunca contenido de usuario
EMPAQUETADO + METADATOS
[ ] Versión sincronizada: cabecera del archivo principal, constante de versión, Stable tag de readme, Changelog
[ ] Las cabeceras de "Requiere" coinciden TANTO en el archivo principal COMO en readme.txt (WP / PHP / WC) — y cualquier
constante MIN_* que controle la activación en tiempo de ejecución
[ ] vendor/ es `composer install --no-dev` (sin squizlabs/phpunit/phpcompatibility)
[ ] El nombre de la carpeta raíz del zip == slug del plugin
Flujo de trabajo
- Lee las reglas para la tarea (ver el mapa de referencias arriba) — comienza con
references/automated-scan.md+references/review-categories.md, luego la inmersión profunda que coincida con el hallazgo (prefixing.md/sanitization-escaping.md/enqueue.md/i18n.md) yreferences/packaging.mdpara la compilación. - Compila el zip limpio — ejecuta
scripts/build-zip.sh(maneja git archive, elimina archivos de desarrollo + TODOS los archivos con punto, instalavendor/--no-dev, comprime con el slug como raíz). Nunca comprimas manualmente el árbol de trabajo; así es como se filtran residuos de desarrollo y archivos con punto. - Verifica — ejecuta
scripts/preflight.sh <ruta-al-zip-o-directorio>: escanea archivos ocultos, comprueba la sincronización de versiones + consistencia de cabecera/readme, ejecuta PHPCS si está configurado. Corrige hasta que esté limpio. - Ejecuta Plugin Check (PCP) — la herramienta oficial que ejecuta las comprobaciones automatizadas del directorio
localmente.
wp plugin check <slug> --require=./wp-content/plugins/plugin-check/cli.php(o Herramientas → Plugin Check). Corrige cada ERROR, revisa cada ADVERTENCIA. Verreferences/plugin-check.mdpara las 31 comprobaciones, banderas y qué capítulo de la habilidad corrige cada una. - Sube. Si el escaneo automatizado rechaza → lee el código exacto, mapealo mediante
data/plugin-check-errors.csv, corrige, recompila, vuelve a subir. - Respuesta a la revisión manual — cuando el revisor envíe problemas por correo, corrige cada uno en todas partes (los
ejemplos son una muestra — busca en todo el árbol, ejecuta Plugin Check + PHPCS sobre todo el
plugin; la misma categoría recurrente = rechazo instantáneo). Luego responde punto por punto
referenciando lo que cambió. Ver
references/review-categories.mdpara correcciones canónicas + redacción de respuestas yreferences/real-review-lessons.mdpara los comandos de barrido por categoría y las trampas que sobreviven a una primera corrección.
Errores difíciles (los costosos)
.gitkeephace fallar el escaneo automatizado. Es un archivo oculto. Elimina todos los archivos con punto del paquete; elimina.gitkeepde git para quegit archivenunca lo vuelva a añadir.- Deriva de versiones requeridas. wp.org lee la cabecera del archivo principal Y el readme.txt; una constante
MIN_WC/MIN_PHPen tiempo de ejecución puede controlar la activación por separado. Los tres deben coincidir, o un usuario con una versión demasiado baja instalará correctamente y luego encontrará funcionalidades rotas. - Los webhooks no pueden usar nonces de WP. Los revisores marcan "sin nonce" en los manejadores de IPN — eso es un
falso positivo; la respuesta correcta es la verificación de la firma del proveedor con
hash_equals(). Documentarlo con unphpcs:ignorepor línea + motivo. json_decodeno es sanitización. Siempre aplicamap_deep(sanitize_text_field)al resultado.composer install(sin--no-dev) filtra phpcs/phpunit en vendor/ → paquete inflado y marcado. Siempre usa--no-devpara el zip final.