GLPI 11 : Ce Qui a Changé et Quels Plugins Ont Cassé

Pourquoi les plugins cassent sur GLPI 11 : les changements d'architecture (Symfony/Twig, public/, PHP 8.2, pilote sans SQL brut), la matrice de compatibilité et la checklist de recette que nous exécutons avant de migrer.

Sur GLPI 11, "le plugin a cassé" n'est presque jamais un bug du plugin : c'est la fondation qui a changé en dessous. En maintenance d'environnements clients, le scénario se répète - le client monte le nouveau core, deux ou trois plugins disparaissent du menu, un fait tomber l'écran avec une erreur 500 et le php-errors.log reste désespérément vide. Ce guide sépare ce qui est devenu natif de ce qui a vraiment cessé de fonctionner, montre l'extrait de code qui recale un plugin sur 11 et livre la checklist que nous exécutons avant de planifier la fenêtre de migration.

Ce qui a changé dans le core - et pourquoi cela fait tomber les plugins

11 n'est pas un habillage neuf par-dessus 10. C'est un changement de moteur. Chaque changement ci-dessous invalide un schéma que les plugins de GLPI 10 utilisaient sans réfléchir :

  • Frontend en Symfony + Twig. L'ancien flux procédural (Html::header() puis echo de HTML) a laissé place à des contrôleurs et des templates Twig. Un plugin qui construisait sa page "à la main" perd l'en-tête, le menu, parfois tout l'écran.
  • L'application est servie depuis public/. Le routage est désormais celui de Symfony. Les includes relatifs du type include("../../../inc/includes.php") dépendent du répertoire de travail et ne résolvent plus.
  • PHP 8.2 au minimum, avec des signatures strictes. Une méthode héritée a besoin du return type compatible. Un canCreate() sans : bool lève "Declaration must be compatible" et le plugin ne charge même pas.
  • Le pilote de base de données refuse le SQL brut. C'est celui qui fait le plus mal. $DB->query() a été supprimé et $DB->request() avec une chaîne littérale est bloqué ("Building and executing raw queries is prohibited"). Toute requête cachée casse la première fois que ce chemin s'exécute.
  • Les indicateurs de hook.php sont devenus canoniques. Des marqueurs comme csrf_compliant sont traités plus strictement par le core ; mal déclaré, le plugin peut ne pas être reconnu comme CSRF-compliant et prendre un 403 en POST.

Autrement dit : "compatible avec 11" n'est pas une question de chance, c'est que l'auteur a porté ces schémas. Celui qui n'a pas porté casse - et casse souvent en silence.

Plugins devenus core : migrez la donnée, ne réinstallez pas

Une bonne partie des plugins les plus populaires n'a pas disparu par incompatibilité - elle a disparu parce que la fonction est entrée dans le core. Ici le travail n'est pas "trouver la version 11", c'est amener la donnée vers la fonctionnalité native :

  • FormCreator - est devenu le module de Formulaires natif, un nouveau moteur. Pas d'importateur transparent ; il n'existe qu'un outil de transition, et les formulaires ne se déplacent pas tout seuls.
  • GenericObject - les objets personnalisés sont natifs maintenant. Réévaluez chaque type et mappez-le vers un actif ou un dropdown du core.
  • FusionInventory et tableaux de bord de plugin - déjà remplacés depuis 10 par le GLPI Agent et les tableaux de bord natifs. Sur 11, aucune raison d'insister.
  • Webhooks sortants - le core notifie désormais les systèmes externes sur événements (création de ticket, changement de statut), couvrant une partie de ce qu'on réglait avec un plugin.

Matrice de compatibilité : pourquoi chaque cas tombe

Avant de réserver la fenêtre, en maintenance nous classons chaque plugin installé dans l'une de ces lignes. Ce qui décide n'est pas le nom, c'est la raison technique :

Plugin / casRaison techniqueAction avant de migrer
FormCreatorLa fonction est devenue Formulaires natif (nouveau moteur)Inventorier et migrer les formulaires qui portent le service desk - ils ne migrent pas seuls
GenericObjectObjets personnalisés désormais natifsMapper chaque type vers un actif ou un dropdown du core
FusionInventoryRemplacé par le GLPI Agent (depuis 10)Basculer la collecte vers le GLPI Agent d'abord
Plugin avec SQL brut / sortie procéduraleUtilise $DB->query() ou Html::header() - supprimés/cassésExige une release portée ; sans elle, désactiver
Plugin sans return type sur can*()PHP 8.2 refuse la signature ; le plugin ne charge pasAttendre la version de l'auteur ou le retirer
Fields, DataInjection, PDF, TagOnt une release pour 11Mettre à jour après le core, un par un, en validant entre chaque
NexToolCompatible 10 et 11 (même code porté)Mettre à jour vers la version 11
Plugin sans release depuis 2023Probablement non portéDécision métier : remplacer ou retirer

Diagnostic : mesurez, ne décidez pas de mémoire

Ne classez pas de tête. Relevez l'état réel des plugins et analysez leur code à la recherche des schémas que le core 11 a supprimés :

# Etat reel des plugins (executez sur GLPI 10 avant de migrer ; lecture seule)
# state : 0=nouveau  1=actif  2=non installe  3=a configurer
#         4=non active  5=a nettoyer  6=non mis a jour
mysql -u glpi -p glpi -e \
  "SELECT name, directory, version, state FROM glpi_plugins ORDER BY state, name;"

# Analysez le code des plugins a la recherche des schemas retires par le core 11
cd /var/www/glpi/plugins
grep -rln '$DB->query('   . --include='*.php'   # pilote SQL brut retire en 11
grep -rln 'Html::header'  . --include='*.php'   # sortie procedurale, avant Twig
grep -rln 'includes.php'  . --include='*.php'   # include relatif fragile sous le routage Symfony

L'erreur courante est de se fier au seul badge du marketplace. Une release taguée "GLPI 11" peut encore cacher du SQL brut sur un chemin peu utilisé - qui n'explose que lorsque ce rapport précis est ouvert, des semaines après la mise en production.

Le code qui recale un plugin sur 11

Si vous maintenez votre propre plugin, ou devez évaluer un plugin tiers avec le source en main, voici les trois schémas qui reviennent le plus quand nous portons du code de 10 vers 11 :

// GLPI 10 (fonctionnait) : SQL brut directement dans le pilote
$res = $DB->query("SELECT id FROM glpi_tickets WHERE status = 1");

// GLPI 11 : $DB->query() a ete SUPPRIME et request() avec une chaine brute est bloque.
// Le SELECT passe au query builder par criteres :
$rows = $DB->request([
    'SELECT' => 'id',
    'FROM'   => 'glpi_tickets',
    'WHERE'  => ['status' => 1],
]);

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

// PHP 8.2+ : une methode heritee exige un return type ; sans ": bool" le plugin ne charge meme pas
public static function canCreate(): bool
{
    return Session::haveRight('plugin_x', CREATE);
}

Notez que l'alias de colonne a aussi changé : la vieille astuce du backtick inline que GLPI 10 tolérait doit maintenant devenir 'name AS enfant', car le query builder de 11 échappe correctement les backticks.

Comment tester la compatibilité avant la fenêtre

Ne découvrez jamais l'incompatibilité en production. La routine que nous appliquons en recette :

  1. Clone de production. Montez 11 sur une copie de la base réelle, pas sur une base d'exemple. Un plugin casse avec vos données, pas avec des données propres.
  2. Installez et activez par la console. php bin/console glpi:plugin:install <repertoire> et glpi:plugin:activate <repertoire>, en tant qu'utilisateur du serveur web. Si le plugin lance une migration à l'install/init, c'est ici qu'elle se déclenche.
  3. Ouvrez chaque page front/ du plugin. Activer sans erreur ne suffit pas ; le moteur Twig ne se plaint que quand l'écran est réellement rendu.
  4. Regardez le bon log. L'échec de chargement ne devient pas une fatale PHP - il devient glpi.ERROR: Error while loading plugin X dans le log GLPI. Celui qui ne regarde que le php-errors.log conclut, à tort, que "tout va bien".
  5. Réinitialisez l'OPcache entre les tentatives. Après avoir échangé les fichiers, php-fpm sert encore l'ancien bytecode ; rechargez le processus (par exemple, kill -USR2 sur le master php-fpm), pas seulement Apache.

Ce que la maintenance nous a appris

L'incident le plus traître que nous ayons vu n'est pas apparu à la migration elle-même - il est apparu des mois plus tard, sur un simple bump de version d'un plugin d'administration. Il lançait sa migration de schéma dans plugin_init, un schéma courant et apparemment anodin : le bloc ne s'exécute que tant que le schema_version enregistré est inférieur à la version de setup.php. Un bug latent y a dormi jusqu'au bump suivant - et quand il a fini par s'exécuter et lever une exception, GLPI 11 l'a capturée dans Plugin::load et a désactivé le plugin tout seul. Le symptôme pour le client, c'était ce "il s'active et se désactive sans erreur visible", le plugin basculant à l'état 4 (non activé, qui paraît normal). Depuis, la règle est stricte : tout bump d'un plugin avec migration-dans-init exige un smoke test d'activation après le bump, et le bloc de migration va toujours dans un try/catch, avec le schema_version écrit EN DEHORS du try - sinon il réessaie à chaque requête et boucle. C'est le genre de détail que seul quelqu'un qui opère du GLPI client tous les jours garde en mémoire musculaire.

Dois-je migrer maintenant ?

Si votre matrice de plugins est verte (release portée pour tous les essentiels) et que vous avez une recette avec clone de production, oui - 11 est plus moderne, sûr et rapide. Si vous dépendez d'un plugin sans version 11, la décision devient métier : le remplacer par la fonction native, changer de plugin ou reporter. Ce qu'on ne peut pas faire, c'est migrer à l'aveugle et découvrir l'incompatibilité avec l'environnement en ligne.

Si votre équipe n'a pas de fenêtre pour répéter la migration et classer chaque plugin calmement, NexTool pilote la montée de version de votre GLPI avec inventaire des plugins, recette sur clone et rollback répété. Parlez-nous du support et maintenance GLPI.


Révisé par l'équipe NexTool Solutions.

Questions fréquentes

Parce que la fondation a changé : frontend en Symfony/Twig, application servie depuis public/, PHP 8.2+ avec des return types stricts et un pilote qui refuse le SQL brut ($DB->query() a été supprimé). Un plugin GLPI 10 qui repose sur l'un de ces schémas, chargé sur le nouveau core, renvoie une 500 ou se désactive au chargement.

Ne vous fiez pas au seul badge du marketplace. Testez en recette sur un clone de la base de production : installez et activez par la console, ouvrez chaque page front/ du plugin et vérifiez le glpi.ERROR, pas seulement le php-errors.log. Une release peut cacher du SQL brut sur un chemin peu utilisé qui n'explose que des semaines après la mise en production.

Pas en tant que plugin de création. La fonction est devenue le module de Formulaires natif, un nouveau moteur sans importateur transparent. Il n'existe qu'un outil de transition pour les anciens formulaires, et ils ne se déplacent pas seuls. Inventoriez combien de formulaires vous avez avant de planifier la migration.

L'échec de chargement du plugin ne devient pas une fatale PHP. GLPI capture l'exception dans Plugin::load et l'enregistre comme 'glpi.ERROR: Error while loading plugin X' dans le log GLPI. Regardez le log GLPI, pas seulement celui de PHP - c'est l'erreur la plus courante quand on diagnostique un plugin qui 'a disparu du menu'.

Il lance probablement sa migration de schéma dans plugin_init, qui ne s'exécute que tant que le schema_version enregistré est inférieur à la version de setup.php. Un bug latent y dort jusqu'au bump suivant ; quand il se déclenche et lève une exception, GLPI 11 désactive le plugin. Enveloppez la migration dans un try/catch et écrivez le schema_version en dehors du try, sinon il recommence à chaque requête.

L'OPcache de php-fpm sert encore l'ancien bytecode compilé en mémoire. Rechargez le processus php-fpm (par exemple, kill -USR2 sur le master php-fpm), pas seulement Apache. C'est la cause la plus courante d'un 'correctif qui ne prend pas' juste après avoir remplacé les fichiers du plugin.

Besoin d'aide ?