KB-PLUGIN-038: GLPI 11 — Monaco, fim do jQuery UI, add_css e estáticos em public/
Gotchas de porte GLPI 10 → 11 aprendidos no plugin butterfly: CodeMirror removido (padrão GLPI.Monaco.createEditor + formdata), jQuery UI extinto, add_css interpola valor uma vez (+?v= do core), e estáticos de plugin só servidos de public/ sem o segmento /public. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
13b0e1827d
commit
235e4679ac
2 changed files with 157 additions and 0 deletions
21
index.json
21
index.json
|
|
@ -719,6 +719,27 @@
|
|||
"severity": "high",
|
||||
"path": "records/plugin-dev/KB-PLUGIN-037-redmine-plugin-api-contract.md",
|
||||
"summary": "id: KB-PLUGIN-037"
|
||||
},
|
||||
{
|
||||
"id": "KB-PLUGIN-038",
|
||||
"title": "\"GLPI 11 — Assets de plugin: Monaco no lugar do CodeMirror, fim do jQuery UI, add_css/add_javascript e estáticos só em public/\"",
|
||||
"domain": "plugin-dev",
|
||||
"tags": [
|
||||
"glpi11",
|
||||
"plugin",
|
||||
"assets",
|
||||
"monaco",
|
||||
"codemirror",
|
||||
"jquery-ui",
|
||||
"add_css",
|
||||
"add_javascript",
|
||||
"public",
|
||||
"gotcha"
|
||||
],
|
||||
"status": "active",
|
||||
"severity": "high",
|
||||
"path": "records/plugin-dev/KB-PLUGIN-038-glpi11-frontend-assets-monaco-public.md",
|
||||
"summary": "id: KB-PLUGIN-038"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
---
|
||||
id: KB-PLUGIN-038
|
||||
title: "GLPI 11 — Assets de plugin: Monaco no lugar do CodeMirror, fim do jQuery UI, add_css/add_javascript e estáticos só em public/"
|
||||
domain: plugin-dev
|
||||
tags:
|
||||
- glpi11
|
||||
- plugin
|
||||
- assets
|
||||
- monaco
|
||||
- codemirror
|
||||
- jquery-ui
|
||||
- add_css
|
||||
- add_javascript
|
||||
- public
|
||||
- gotcha
|
||||
status: active
|
||||
severity: high
|
||||
created_at: 2026-07-03
|
||||
updated_at: 2026-07-03
|
||||
applies_to:
|
||||
- plugins GLPI 11.x portados do GLPI 9/10
|
||||
- qualquer plugin que injete CSS/JS ou sirva arquivos estáticos
|
||||
related_records:
|
||||
- KB-PLUGIN-004
|
||||
- KB-PLUGIN-028
|
||||
- KB-PLUGIN-036
|
||||
---
|
||||
|
||||
# GLPI 11 — Assets de plugin: Monaco, fim do jQuery UI, add_css e public/
|
||||
|
||||
Aprendizados do porte do plugin `butterfly` (2.1.0, GLPI 10 → 11.0.8, 2026-07-03).
|
||||
Quatro armadilhas independentes que atingem qualquer plugin vindo do GLPI 9/10.
|
||||
|
||||
## 1. CodeMirror foi removido — o editor de código do core é o Monaco
|
||||
|
||||
`public/lib/` do GLPI 11 não tem mais CodeMirror; tem `monaco-editor`/`monaco.js`.
|
||||
Qualquer `CodeMirror.fromTextArea(...)` de plugin quebra com `ReferenceError`
|
||||
silencioso no console (o form continua funcionando como textarea puro).
|
||||
|
||||
### Carregar a lib
|
||||
|
||||
`monaco` é chave válida do mecanismo `$CFG_GLPI['javascript']` (testado em
|
||||
`Html.php` ~linha 1178, `in_array('monaco', $jslibs)`):
|
||||
|
||||
```php
|
||||
// setup.php, em plugin_init_<key>()
|
||||
$CFG_GLPI['javascript']['config']['config'][] = 'monaco';
|
||||
```
|
||||
|
||||
### Padrão de uso (o mesmo do core em templates/pages/admin/entity/custom_ui.html.twig)
|
||||
|
||||
```php
|
||||
echo '<div id="meu_editor" style="height: 300px"></div>';
|
||||
echo Html::scriptBlock('
|
||||
$(function() {
|
||||
window.GLPI.Monaco.createEditor("meu_editor", "scss", ' . json_encode($valor, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) . ', []).then(() => {
|
||||
$("#meu_editor").closest("form").on("formdata", (e) => {
|
||||
const editors = window.monaco.editor.getEditors().filter(
|
||||
(ed) => ed._domElement.id === "meu_editor"
|
||||
);
|
||||
if (editors.length) {
|
||||
e.originalEvent.formData.delete("meu_campo");
|
||||
e.originalEvent.formData.append("meu_campo", editors[0].getValue());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
');
|
||||
```
|
||||
|
||||
Pontos-chave:
|
||||
- O Monaco **não** usa textarea: é um `<div>` container + sync no submit via
|
||||
evento `formdata` (por isso o form precisa ser um `<form>` nativo).
|
||||
- Manter um `<textarea class="d-none" name="meu_campo">` com o valor original
|
||||
como fallback: se a lib não carregar, o valor antigo é preservado no POST
|
||||
(o handler `formdata` faz `delete` + `append` quando o editor existe).
|
||||
- Em Twig, preferir a macro `fields_macros.html.twig` do core que já embute
|
||||
esse padrão.
|
||||
|
||||
## 2. jQuery UI não existe mais
|
||||
|
||||
`$(...).tabs()`, `.dialog()`, `.sortable()` etc. quebram com
|
||||
`TypeError: ... is not a function`. Plugins antigos costumam ter tabs jQuery UI
|
||||
na tela de config. Substituir por tabs do Tabler/Bootstrap ou remover.
|
||||
(jQuery em si continua disponível.)
|
||||
|
||||
## 3. add_css / add_javascript: valor é interpolado UMA vez e ganha ?v= do core
|
||||
|
||||
`Glpi\Application\View\Extension\PluginExtension` monta
|
||||
`"/plugins/{$plugin}/{$file}"` e acrescenta `'version' => Plugin::getPluginFilesVersion($plugin)`
|
||||
(vira `?v=<hash>` na URL). Consequências:
|
||||
|
||||
- Truques de objeto com `__toString` para injetar query string (padrão
|
||||
"file_exists chama primeiro, concatenação chama depois" do GLPI 9/10)
|
||||
**não funcionam mais** — o cast acontece uma única vez. Usar string simples.
|
||||
- Cache busting por mudança de config deve ser resolvido pelo próprio endpoint
|
||||
(ex.: redirect para `?_=<timestamp>`) ou aceitando o `?v=` por versão.
|
||||
- Continua valendo KB-PLUGIN-004: a chave do array é o plugin key exato.
|
||||
|
||||
## 4. Estáticos de plugin: só o que está em public/ é servido
|
||||
|
||||
`RequestRouterTrait` (core) só expõe recursos de plugin que estejam no
|
||||
diretório `public/` do plugin, acessados **sem** o segmento `/public` na URL:
|
||||
|
||||
```
|
||||
plugins/meuplugin/public/pics/logo.png → GET /plugins/meuplugin/pics/logo.png
|
||||
```
|
||||
|
||||
Arquivos fora de `public/` (ex.: `pics/`, `css/` na raiz do plugin) retornam
|
||||
**404** — mesmo que `.htaccess` os liberasse no Apache do GLPI 10. Scripts PHP
|
||||
legados (`front/*.php`) continuam roteados pelo `LegacyFileLoadController`
|
||||
(com as firewall strategies registradas no setup.php).
|
||||
|
||||
Migração típica: `git mv pics public/pics` e trocar referências relativas
|
||||
(`../plugins/...`) por `$CFG_GLPI['root_doc'] . '/plugins/<key>/...'`.
|
||||
|
||||
## Validação padrão
|
||||
|
||||
```bash
|
||||
# lib presente?
|
||||
ls /var/www/glpi/public/lib | grep -i monaco # existe
|
||||
ls /var/www/glpi/public/lib | grep -i codemirror # NÃO existe
|
||||
# estático servido?
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost/plugins/<key>/pics/x.png
|
||||
```
|
||||
|
||||
## Regra para agentes
|
||||
|
||||
Ao portar plugin GLPI 9/10 → 11, fazer grep por: `CodeMirror`, `.tabs(`,
|
||||
`.dialog(`, `new .*FileVersion|__toString`, `'../plugins/`, e conferir se todo
|
||||
asset estático referenciado por URL está dentro de `public/`. Cada acerto
|
||||
desses é quebra silenciosa (só aparece no console do browser ou como 404).
|
||||
|
||||
# Classificação
|
||||
- Tipo: gotchas de porte GLPI 10 → 11 (front-end/assets).
|
||||
- Reutilização: obrigatória em qualquer porte de plugin com UI.
|
||||
Loading…
Reference in a new issue