GLPI 11: O Que Mudou e Quais Plugins Deixaram de Funcionar

Porque é que os plugins deixam de funcionar no GLPI 11: as mudanças de arquitetura (Symfony/Twig, public/, PHP 8.2, controlador sem SQL cru), a matriz de compatibilidade e a checklist de homologação que executamos antes de migrar.

No GLPI 11, "o plugin deixou de funcionar" quase nunca é um bug do plugin: é a fundação que mudou por baixo dele. Na sustentação de ambientes de clientes o guião repete-se - o cliente sobe o core novo, dois ou três plugins desaparecem do menu, um deita a página abaixo com erro 500 e o php-errors.log fica irritantemente vazio. Este guia separa o que passou a ser nativo do que de facto deixou de funcionar, mostra o excerto de código que reprova um plugin no 11 e entrega a checklist que corremos antes de agendar a janela de migração.

O que mudou no core - e porque é que isso deita os plugins abaixo

O 11 não é um tema novo por cima do 10. É troca de motor. Cada mudança abaixo invalida um padrão que os plugins de GLPI 10 usavam sem cerimónia:

  • Frontend em Symfony + Twig. O antigo fluxo procedural (Html::header() e depois echo de HTML) deu lugar a controladores e templates Twig. Um plugin que montava a página "à mão" perde o cabeçalho, o menu, às vezes o ecrã inteiro.
  • A aplicação é servida a partir de public/. O encaminhamento é do Symfony agora. Os includes relativos do tipo include("../../../inc/includes.php") dependem do diretório de trabalho e deixam de resolver.
  • PHP 8.2 no mínimo, com assinaturas estritas. Um método herdado precisa do return type compatível. Um canCreate() sem : bool lança "Declaration must be compatible" e o plugin nem sequer carrega.
  • O controlador da base de dados recusa SQL cru. É o que mais custa. O $DB->query() foi removido e o $DB->request() com string literal está bloqueado ("Building and executing raw queries is prohibited"). Qualquer consulta escondida quebra na primeira vez que aquele caminho é executado.
  • As sinalizações do hook.php passaram a forma canónica. Marcadores como csrf_compliant são tratados de forma mais rígida no core; mal declarado, o plugin pode não ser reconhecido como CSRF-compliant e apanhar um 403 em POST.

Ou seja: "compatível com o 11" não é sorte, é o autor ter portado esses padrões. Quem não portou quebra - e costuma quebrar em silêncio.

Plugins que passaram a core: migre o dado, não reinstale

Boa parte dos plugins mais populares não desapareceu por incompatibilidade - desapareceu porque a função entrou no core. Aqui o trabalho não é "encontrar a versão para o 11", é levar o dado para o recurso nativo:

  • FormCreator - passou a ser o módulo de Formulários nativo, um motor novo. Não há importador transparente; existe apenas uma ferramenta de transição, e os formulários não se mudam sozinhos.
  • GenericObject - os objetos personalizados agora são nativos. Reavalie cada tipo e mapeie para um ativo ou dropdown do core.
  • FusionInventory e dashboards de plugin - já eram substituídos desde o 10 pelo GLPI Agent e pelas dashboards nativas. No 11 não há motivo para insistir.
  • Webhooks de saída - o core passou a notificar sistemas externos em eventos (abertura de ticket, mudança de estado), cobrindo parte do que se resolvia com plugin.

Matriz de compatibilidade: porque é que cada caso cai

Antes de marcar a janela, na sustentação classificamos cada plugin instalado numa destas linhas. O que decide não é o nome, é o motivo técnico:

Plugin / casoMotivo técnicoAção antes de migrar
FormCreatorA função passou a Formulários nativo (motor novo)Inventariar e migrar os formulários que sustentam o service desk - não migram sozinhos
GenericObjectObjetos personalizados agora nativosMapear cada tipo para um ativo ou dropdown do core
FusionInventorySubstituído pelo GLPI Agent (desde o 10)Migrar a recolha para o GLPI Agent antes
Plugin com SQL cru / saída proceduralUsa $DB->query() ou Html::header() - removidos/partidosExige release portada; sem ela, desativar
Plugin sem return type nos can*()PHP 8.2 recusa a assinatura; o plugin não carregaAguardar a versão do autor ou reformar
Fields, DataInjection, PDF, TagTêm release para o 11Atualizar depois do core, um a um, validando entre cada
NexToolCompatível com 10 e 11 (mesmo código portado)Atualizar para a versão do 11
Plugin sem release desde 2023Provavelmente não portadoDecisão de negócio: substituir ou reformar

Diagnóstico: meça, não decida de memória

Não classifique de cabeça. Levante o estado real dos plugins e analise o código deles à procura dos padrões que o core 11 removeu:

# Estado real dos plugins (execute no GLPI 10 antes de migrar; apenas leitura)
# state: 0=novo  1=ativo  2=nao instalado  3=a configurar
#        4=nao ativado  5=a limpar  6=nao atualizado
mysql -u glpi -p glpi -e \
  "SELECT name, directory, version, state FROM glpi_plugins ORDER BY state, name;"

# Analise o codigo dos plugins a procura dos padroes que o core 11 removeu
cd /var/www/glpi/plugins
grep -rln '$DB->query('   . --include='*.php'   # controlador de SQL cru removido no 11
grep -rln 'Html::header'  . --include='*.php'   # saida procedural, anterior ao Twig
grep -rln 'includes.php'  . --include='*.php'   # include relativo fragil no encaminhamento Symfony

O erro comum é confiar só no selo do marketplace. Uma release marcada como "GLPI 11" ainda pode esconder SQL cru num caminho pouco usado - que só rebenta quando aquele relatório específico é aberto, semanas depois do go-live.

O código que reprova um plugin no 11

Se mantém um plugin próprio, ou precisa de avaliar um de terceiros com o código-fonte em mãos, estes são os três padrões que mais aparecem quando portamos código de 10 para 11:

// GLPI 10 (funcionava): SQL cru diretamente no controlador
$res = $DB->query("SELECT id FROM glpi_tickets WHERE status = 1");

// GLPI 11: $DB->query() foi REMOVIDO e request() com string crua esta bloqueado.
// O SELECT passa a query builder por criterios:
$rows = $DB->request([
    'SELECT' => 'id',
    'FROM'   => 'glpi_tickets',
    'WHERE'  => ['status' => 1],
]);

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

// PHP 8.2+: metodo herdado exige return type; sem ": bool" o plugin nem carrega
public static function canCreate(): bool
{
    return Session::haveRight('plugin_x', CREATE);
}

Repare que o alias de coluna também mudou: o velho truque com plica invertida inline, que o GLPI 10 tolerava, agora tem de passar a 'name AS filho', porque o query builder do 11 faz o escape das plicas invertidas corretamente.

Como testar a compatibilidade antes da janela

Nunca descubra a incompatibilidade em produção. A rotina que aplicamos em homologação:

  1. Clone de produção. Suba o 11 sobre uma cópia da base de dados real, não sobre uma base de exemplo. Um plugin quebra com os seus dados, não com dados limpos.
  2. Instale e ative pela consola. php bin/console glpi:plugin:install <diretorio> e glpi:plugin:activate <diretorio>, como o utilizador do servidor web. Se o plugin corre uma migração no install/init, é aqui que ela dispara.
  3. Abra cada página front/ do plugin. Não basta ativar sem erro; o motor Twig só se queixa quando o ecrã é realmente renderizado.
  4. Olhe para o ficheiro de log certo. A falha de carregamento não vira fatal do PHP - vira glpi.ERROR: Error while loading plugin X no log do GLPI. Quem só olha para o php-errors.log conclui, erradamente, que "está tudo bem".
  5. Reponha o OPcache entre tentativas. Depois de trocar ficheiros, o php-fpm ainda serve o bytecode antigo; recarregue o processo (por exemplo, kill -USR2 no master do php-fpm), não só o Apache.

O que a sustentação nos ensinou

O incidente mais traiçoeiro que vimos não apareceu na migração em si - apareceu meses depois, num simples bump de versão de um plugin administrativo. Ele corria a migração de schema dentro do plugin_init, um padrão comum e aparentemente inofensivo: o bloco só é executado enquanto o schema_version gravado for menor do que a versão do setup.php. Um bug latente ali ficou a dormir até ao bump seguinte - e quando finalmente correu e lançou uma exceção, o GLPI 11 capturou-a no Plugin::load e desativou o plugin sozinho. O sintoma para o cliente foi aquele "ativa e desativa sem erro visível", com o plugin a oscilar para o estado 4 (não ativado, que parece normal). Desde então a regra é dura: todo o bump de um plugin com migração-no-init exige um smoke test de ativação depois do bump, e o bloco de migração vai sempre dentro de try/catch, com o schema_version gravado FORA do try - senão tenta de novo a cada pedido e entra em ciclo. É o tipo de detalhe que só quem opera GLPI de cliente todos os dias traz na memória muscular.

Devo migrar agora?

Se a sua matriz de plugins está verde (release portada para todos os essenciais) e tem homologação com clone de produção, sim - o 11 é mais moderno, seguro e rápido. Se depende de um plugin sem versão para o 11, a decisão passa a ser de negócio: substituir pela função nativa, trocar de plugin ou adiar. O que não dá é migrar às cegas e descobrir a incompatibilidade com o ambiente no ar.

Se a sua equipa não tem janela para ensaiar a migração e classificar cada plugin com calma, a NexTool conduz a atualização do seu GLPI com inventário de plugins, homologação sobre clone e rollback ensaiado. Fale connosco sobre suporte e sustentação de GLPI.


Revisto pela equipa NexTool Solutions.

Perguntas Frequentes

Porque a fundação mudou: frontend em Symfony/Twig, aplicação servida a partir de public/, PHP 8.2+ com return types estritos e um controlador que recusa SQL cru (o $DB->query() foi removido). Um plugin de GLPI 10 que dependa de qualquer um desses padrões, carregado sobre o core novo, dá erro 500 ou é desativado no carregamento.

Não confie só no selo do marketplace. Teste em homologação sobre um clone da base de dados de produção: instale e ative pela consola, abra cada página front/ do plugin e verifique o glpi.ERROR, não só o php-errors.log. Uma release pode esconder SQL cru num caminho pouco usado que só rebenta semanas depois do go-live.

Não como plugin de criação. A função passou a ser o módulo de Formulários nativo, um motor novo, sem importador transparente. Existe apenas uma ferramenta de transição para os formulários antigos, e eles não se mudam sozinhos. Inventarie quantos formulários tem antes de agendar a migração.

A falha de carregamento do plugin não vira fatal do PHP. O GLPI captura a exceção no Plugin::load e regista-a como 'glpi.ERROR: Error while loading plugin X' no log do próprio GLPI. Olhe para o log do GLPI, não só para o do PHP - é o engano mais comum ao diagnosticar um plugin que 'desapareceu do menu'.

Provavelmente corre a migração de schema no plugin_init, que só é executada enquanto o schema_version gravado for menor do que a versão do setup.php. Um bug latente ali dorme até ao bump seguinte; quando dispara e lança exceção, o GLPI 11 desativa o plugin. Envolva a migração em try/catch e grave o schema_version fora do try, senão repete a cada pedido.

O OPcache do php-fpm ainda serve o bytecode antigo compilado em memória. Recarregue o processo do php-fpm (por exemplo, kill -USR2 no master do php-fpm), não só o Apache. É a causa mais comum de um 'fix que não pega' logo após trocar os ficheiros do plugin.

Precisa de ajuda?