3.8 KiB
| id | title | domain | tags | status | severity | created_at | updated_at | applies_to | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| KB-PLUGIN-015 | querySelector('.row') colide com elementos de outros plugins no Helpdesk portal | plugin-dev |
|
active | high | 2026-05-10 | 2026-05-10 |
|
querySelector('.row') colide com outros plugins no Helpdesk portal
Sintoma
Plugin funciona perfeitamente no ambiente de desenvolvimento mas falha silenciosamente em produção — não há erro no console, não há AJAX falhando, mas o DOM não é manipulado. A causa é difícil de descobrir porque o servidor retorna os dados corretos e o JS chega a rodar.
No caso real: tilesections/public/js/helpdesk_home.js em produção não reorganizava os tiles em categorias. Em dev funcionava. A diferença era a presença de outro plugin (news_alert) em produção que injetava um elemento com classe row antes dos tiles.
Causa
A container-xl do portal do Helpdesk pode conter múltiplos elementos com class row:
<div class="container-xl">
<table class="central">
<tbody><tr><td>
<!-- Plugin news_alert injeta isto via hook DISPLAY_CENTRAL -->
<div class="plugin_news_alert-container row align-items-stretch"></div>
</td></tr></tbody>
</table>
<!-- A row dos tiles -->
<div class="row">
<div class="col-12 col-sm-6 col-md-4 d-flex">
<a class="card" href="...">...</a>
</div>
</div>
</div>
Código defeituoso:
const container = tilesSection.querySelector('.container-xl');
const tilesRow = container.querySelector('.row'); // ❌ pega o do news_alert
querySelector faz busca em profundidade e retorna o primeiro match na árvore — incluindo elementos aninhados. O plugin_news_alert-container está dentro da <table> que vem antes da row dos tiles, então é encontrado primeiro.
Depois disso, tilesRow.querySelectorAll(':scope > div') retorna zero (o container do news plugin está vazio), e a função desiste sem fazer nada.
Padrão correto
Usar :scope > .row para garantir que apenas filhos diretos sejam considerados:
const tilesRow = container.querySelector(':scope > .row'); // ✅ só filhos diretos
:scope referencia o elemento de partida da query (no caso, container). :scope > .row significa "row que é filha direta de container".
Lições
- Nunca confie que sua produção tem a mesma estrutura DOM do dev. Plugins de terceiros (news, dashboards, customizações antigas) injetam elementos em pontos imprevisíveis.
- Para qualquer seletor que dependa de hierarquia, prefira
:scope >ou caminhos específicos ao invés de buscas globais com classes Bootstrap genéricas como.row,.card,.col-*. - Quando funcionar local e não em produção, não assuma diferenças de container/Docker. Inspecione o DOM real do cliente. O usuário pode rodar no console:
e mandar o resultado.document.querySelector('.tiles-banner .container-xl').innerHTML.substring(0, 600)
Hierarquia de especificidade recomendada
Para seletores de containers em plugins GLPI, em ordem de robustez:
[data-glpi-*]atributos — quando GLPI fornece (ex:[data-glpi-helpdesk-config-tiles]). Mais estável.:scope > .classe— quando só filhos diretos importam.- Caminho específico — ex:
.tiles-banner > .container-xl > .row. .rowsolto — evitar. Praticamente garante colisão com outros plugins.
Onde isso pegou na vida real
/home/glpi/glpi_dev/plugins/tilesections/public/js/helpdesk_home.js::reorganizeTiles() — corrigido em tilesections v1.1.2. O bug não era reproduzível em dev porque o ambiente Docker não tinha o plugin news_alert instalado.