GLPI 11: Qué Cambió y Qué Plugins Se Rompieron

Por qué se rompen los plugins en GLPI 11: los cambios de arquitectura (Symfony/Twig, public/, PHP 8.2, driver sin SQL crudo), la matriz de compatibilidad y el checklist de homologación que ejecutamos antes de migrar.

En GLPI 11, "el plugin se rompió" casi nunca es un bug del plugin: es la base la que cambió debajo de él. En el mantenimiento de entornos de clientes el guion se repite - el cliente levanta el core nuevo, dos o tres plugins desaparecen del menú, uno tira la pantalla con error 500 y el php-errors.log queda irritantemente vacío. Esta guía separa lo que pasó a ser nativo de lo que de verdad dejó de funcionar, muestra el fragmento de código que reprueba un plugin en 11 y entrega el checklist que ejecutamos antes de agendar la ventana de migración.

Qué cambió en el core - y por qué eso tira los plugins

11 no es un tema nuevo sobre 10. Es un cambio de motor. Cada cambio de abajo invalida un patrón que los plugins de GLPI 10 usaban sin ceremonia:

  • Frontend en Symfony + Twig. El antiguo flujo procedural (Html::header() y luego echo de HTML) dio paso a controladores y plantillas Twig. Un plugin que armaba la página "a mano" pierde la cabecera, el menú, a veces la pantalla entera.
  • La aplicación se sirve desde public/. El enrutado es de Symfony ahora. Los includes relativos del tipo include("../../../inc/includes.php") dependen del directorio de trabajo y dejan de resolver.
  • PHP 8.2 como mínimo, con firmas estrictas. Un método heredado necesita el return type compatible. Un canCreate() sin : bool lanza "Declaration must be compatible" y el plugin ni siquiera carga.
  • El driver de base de datos rechaza SQL crudo. Este es el que más duele. $DB->query() fue eliminado y $DB->request() con string literal está bloqueado ("Building and executing raw queries is prohibited"). Cualquier consulta escondida se rompe la primera vez que ese camino se ejecuta.
  • Las señales del hook.php pasaron a forma canónica. Marcadores como csrf_compliant se tratan de forma más estricta en el core; mal declarado, el plugin puede no reconocerse como CSRF-compliant y recibir un 403 en POST.

Es decir: "compatible con 11" no es suerte, es que el autor haya portado esos patrones. Quien no portó se rompe - y suele romperse en silencio.

Plugins que pasaron al core: migre el dato, no reinstale

Buena parte de los plugins más populares no desapareció por incompatibilidad - desapareció porque la función entró en el core. Aquí el trabajo no es "encontrar la versión para 11", es llevar el dato al recurso nativo:

  • FormCreator - pasó a ser el módulo de Formularios nativo, un motor nuevo. No hay importador transparente; solo existe una herramienta de transición, y los formularios no se mudan solos.
  • GenericObject - los objetos personalizados ahora son nativos. Reevalúe cada tipo y mapéelo a un activo o dropdown del core.
  • FusionInventory y dashboards de plugin - ya estaban sustituidos desde 10 por el GLPI Agent y los dashboards nativos. En 11 no hay motivo para insistir.
  • Webhooks de salida - el core ahora notifica a sistemas externos en eventos (apertura de ticket, cambio de estado), cubriendo parte de lo que se resolvía con plugin.

Matriz de compatibilidad: por qué cae cada caso

Antes de reservar la ventana, en mantenimiento clasificamos cada plugin instalado en una de estas filas. Lo que decide no es el nombre, es el motivo técnico:

Plugin / casoMotivo técnicoAcción antes de migrar
FormCreatorLa función pasó a Formularios nativo (motor nuevo)Inventariar y migrar los formularios que sostienen la mesa de servicio - no migran solos
GenericObjectObjetos personalizados ahora nativosMapear cada tipo a un activo o dropdown del core
FusionInventorySustituido por el GLPI Agent (desde 10)Mover la recolección al GLPI Agent antes
Plugin con SQL crudo / salida proceduralUsa $DB->query() o Html::header() - eliminados/rotosExige release portada; sin ella, desactivar
Plugin sin return type en can*()PHP 8.2 rechaza la firma; el plugin no cargaEsperar la versión del autor o retirarlo
Fields, DataInjection, PDF, TagTienen release para 11Actualizar tras el core, uno a uno, validando entre cada uno
NexToolCompatible con 10 y 11 (mismo código portado)Actualizar a la versión de 11
Plugin sin release desde 2023Probablemente no portadoDecisión de negocio: sustituir o retirar

Diagnóstico: mida, no decida de memoria

No clasifique de cabeza. Levante el estado real de los plugins y rastree su código buscando los patrones que el core 11 eliminó:

# Estado real de los plugins (ejecute en GLPI 10 antes de migrar; solo lectura)
# state: 0=nuevo  1=activo  2=no instalado  3=a configurar
#        4=no activado  5=a limpiar  6=no actualizado
mysql -u glpi -p glpi -e \
  "SELECT name, directory, version, state FROM glpi_plugins ORDER BY state, name;"

# Rastree el codigo de los plugins buscando los patrones que el core 11 elimino
cd /var/www/glpi/plugins
grep -rln '$DB->query('   . --include='*.php'   # driver de SQL crudo eliminado en 11
grep -rln 'Html::header'  . --include='*.php'   # salida procedural, pre-Twig
grep -rln 'includes.php'  . --include='*.php'   # include relativo fragil bajo el enrutado Symfony

El error común es confiar solo en el sello del marketplace. Una release marcada como "GLPI 11" todavía puede esconder SQL crudo en un camino poco usado - que solo estalla cuando se abre ese informe específico, semanas después del go-live.

El código que reprueba un plugin en 11

Si mantiene un plugin propio, o necesita evaluar uno de terceros con el fuente en mano, estos son los tres patrones que más aparecen cuando portamos código de 10 a 11:

// GLPI 10 (funcionaba): SQL crudo directo en el driver
$res = $DB->query("SELECT id FROM glpi_tickets WHERE status = 1");

// GLPI 11: $DB->query() fue ELIMINADO y request() con cadena cruda esta bloqueado.
// SELECT pasa al query builder por criterios:
$rows = $DB->request([
    'SELECT' => 'id',
    'FROM'   => 'glpi_tickets',
    'WHERE'  => ['status' => 1],
]);

// DDL / SQL literal inevitable (ALTER, SHOW INDEX): use doQuery()
$DB->doQuery("ALTER TABLE glpi_plugin_x ADD COLUMN activo TINYINT DEFAULT 0");

// PHP 8.2+: el metodo heredado exige return type; sin ": bool" el plugin ni carga
public static function canCreate(): bool
{
    return Session::haveRight('plugin_x', CREATE);
}

Fíjese en que el alias de columna también cambió: el viejo truco con comilla invertida inline que GLPI 10 toleraba ahora tiene que ser 'name AS hijo', porque el query builder de 11 escapa las comillas invertidas correctamente.

Cómo probar la compatibilidad antes de la ventana

Nunca descubra la incompatibilidad en producción. La rutina que aplicamos en homologación:

  1. Clon de producción. Levante 11 sobre una copia de la base real, no sobre una de ejemplo. Un plugin se rompe con sus datos, no con datos limpios.
  2. Instale y active por consola. php bin/console glpi:plugin:install <directorio> y glpi:plugin:activate <directorio>, como el usuario del servidor web. Si el plugin corre una migración en install/init, es aquí donde se dispara.
  3. Abra cada página front/ del plugin. No basta con activar sin error; el motor Twig solo se queja cuando la pantalla se renderiza de verdad.
  4. Mire el log correcto. El fallo de carga no se vuelve fatal de PHP - se vuelve glpi.ERROR: Error while loading plugin X en el log de GLPI. Quien solo mira el php-errors.log concluye, mal, que "todo está bien".
  5. Resetee el OPcache entre intentos. Tras cambiar archivos, php-fpm todavía sirve el bytecode viejo; recargue el proceso (por ejemplo, kill -USR2 en el master de php-fpm), no solo Apache.

Lo que nos enseñó el mantenimiento

El incidente más traicionero que vimos no apareció en la migración en sí - apareció meses después, en un simple bump de versión de un plugin administrativo. Corría su migración de schema dentro de plugin_init, un patrón común y aparentemente inofensivo: el bloque solo se ejecuta mientras el schema_version guardado sea menor que la versión de setup.php. Un bug latente allí durmió hasta el siguiente bump - y cuando por fin corrió y lanzó una excepción, GLPI 11 la capturó en Plugin::load y desactivó el plugin solo. El síntoma para el cliente fue ese "se activa y se desactiva sin error visible", con el plugin oscilando al estado 4 (no activado, que parece normal). Desde entonces la regla es dura: todo bump de un plugin con migración-en-init exige un smoke test de activación tras el bump, y el bloque de migración va siempre dentro de try/catch, con el schema_version escrito FUERA del try - si no, reintenta en cada request y entra en bucle. Es el tipo de detalle que solo quien opera GLPI de cliente todos los días lleva en la memoria muscular.

¿Debo migrar ahora?

Si su matriz de plugins está verde (release portada para todos los esenciales) y tiene homologación con clon de producción, sí - 11 es más moderno, seguro y rápido. Si depende de un plugin sin versión para 11, la decisión pasa a ser de negocio: sustituirlo por la función nativa, cambiar de plugin o posponer. Lo que no se puede es migrar a ciegas y descubrir la incompatibilidad con el entorno en el aire.

Si su equipo no tiene ventana para ensayar la migración y clasificar cada plugin con calma, NexTool conduce el upgrade de su GLPI con inventario de plugins, homologación sobre clon y rollback ensayado. Hable con nosotros sobre soporte y mantenimiento de GLPI.


Revisado por el equipo de NexTool Solutions.

Preguntas Frecuentes

Porque la base cambió: frontend en Symfony/Twig, aplicación servida desde public/, PHP 8.2+ con return types estrictos y un driver que rechaza SQL crudo ($DB->query() fue eliminado). Un plugin de GLPI 10 que dependa de cualquiera de esos patrones, cargado sobre el core nuevo, da error 500 o se desactiva al cargar.

No confíe solo en el sello del marketplace. Pruebe en homologación sobre un clon de la base de producción: instale y active por consola, abra cada página front/ del plugin y verifique el glpi.ERROR, no solo el php-errors.log. Una release puede esconder SQL crudo en un camino poco usado que solo estalla semanas después del go-live.

No como plugin de creación. La función pasó a ser el módulo de Formularios nativo, un motor nuevo, sin importador transparente. Solo existe una herramienta de transición para los formularios antiguos, y no se mudan solos. Inventaríe cuántos formularios tiene antes de agendar la migración.

El fallo de carga del plugin no se vuelve fatal de PHP. GLPI captura la excepción en Plugin::load y la registra como 'glpi.ERROR: Error while loading plugin X' en el log propio de GLPI. Mire el log de GLPI, no solo el de PHP - es el error más común al diagnosticar un plugin que 'desapareció del menú'.

Probablemente corre su migración de schema en plugin_init, que solo se ejecuta mientras el schema_version guardado sea menor que la versión de setup.php. Un bug latente allí duerme hasta el siguiente bump; cuando se dispara y lanza excepción, GLPI 11 desactiva el plugin. Envuelva la migración en try/catch y escriba el schema_version fuera del try, si no repite en cada request.

El OPcache de php-fpm todavía sirve el bytecode viejo compilado en memoria. Recargue el proceso de php-fpm (por ejemplo, kill -USR2 en el master de php-fpm), no solo Apache. Es la causa más común de un 'fix que no agarra' justo después de cambiar archivos del plugin.

?Necesitas ayuda?