knowledge-base/records/plugin-dev/KB-PLUGIN-005-last-valid-answer-exact-class-match.md

96 lines
3.7 KiB
Markdown

---
id: KB-PLUGIN-005
title: ITILCategoryFieldStrategy LAST_VALID_ANSWER ignora subclasses de QuestionTypeItemDropdown
domain: plugin-dev
tags:
- glpi
- glpi11
- form-builder
- form-destination
- itil-category
- answer-pipeline
- question-type
status: active
severity: critical
created_at: 2026-05-02
updated_at: 2026-05-02
applies_to:
- GLPI 11.x form builder — destino de formulário (FormDestinationTicket/Change/Problem)
- plugins com QuestionType que estendem QuestionTypeItemDropdown para ITILCategory
related_records:
- KB-PLUGIN-002
- KB-PLUGIN-006
---
# Contexto
Ao criar um QuestionType de plugin que retorna uma ITILCategory como resposta,
o GLPI não aplica automaticamente essa categoria ao ticket criado pelo formulário.
# Sintoma observável
- O usuário seleciona uma categoria no formulário.
- O ticket é criado sem `itilcategories_id` (campo vazio/zero).
- Nenhum erro é exibido — a categoria simplesmente é ignorada.
# Causa raiz
`ITILCategoryFieldStrategy::LAST_VALID_ANSWER` filtra respostas por tipo assim:
```php
$valid_answers = $answers_set->getAnswersByType(QuestionTypeItemDropdown::class);
```
`AnswersSet::getAnswersByType()` usa **comparação exata** (`==`), não `is_a()`:
```php
fn(Answer $answer) => $answer->getRawType() == $type
```
O `raw_question_type` de um `QuestionTypeSplititil` é armazenado como
`'GlpiPlugin\Splititil\QuestionTypeSplititil'`, que não é igual a
`'Glpi\Form\QuestionType\QuestionTypeItemDropdown'`.
Resultado: a resposta é invisível para a estratégia padrão.
# Solução — AbstractConfigField silencioso
Registrar um campo de destino de plugin que roda **após** `ITILCategoryField` e
sobrescreve `itilcategories_id` quando encontra uma resposta da nossa classe:
```php
// Em setup.php (dentro de plugin_init_*)
FormDestinationManager::getInstance()->registerPluginCommonITILConfigField(
AbstractCommonITILFormDestination::class, // aplica a Ticket, Change e Problem
new \GlpiPlugin\Splititil\SplititilITILCategoryApplicator()
);
```
O `SplititilITILCategoryApplicator` estende `AbstractConfigField` com:
- `renderConfigForm()` retorna `''` → invisível na UI de configuração
- `applyConfiguratedValueToInputUsingAnswers()` varre as respostas buscando pelo
tipo exato da nossa classe e seta `itilcategories_id` no input do ticket
# Por que esse campo roda após ITILCategoryField
Em `createDestinationItems()`, os campos `Entity`, `Template` e `ITILCategoryField`
são aplicados antecipadamente e adicionados a `$already_applied_fields`.
Os campos de plugin são aplicados no `foreach` subsequente e **não** são filtrados
(porque têm uma classe diferente de `ITILCategoryField`), sobrescrevendo o valor anterior.
# Mínimo para implementar AbstractConfigField de plugin
Além dos defaults de `AbstractConfigField`, implementar obrigatoriamente:
`getLabel()`, `renderConfigForm()`, `getConfigClass()`, `getDefaultConfig()`,
`getWeight()`, `getCategory()`
+ uma classe config mínima implementando `JsonFieldInterface`
(`jsonDeserialize()` + `jsonSerialize()`).
# Validação padrão
1. Submeter formulário com questão de categoria preenchida.
2. Abrir o ticket criado e verificar que `itilcategories_id` está preenchido.
3. Confirmar no banco: `SELECT itilcategories_id FROM glpi_tickets ORDER BY id DESC LIMIT 1;`
# Regra para agentes
Qualquer QuestionType de plugin que retorne ITILCategory DEVE implementar um
`AbstractConfigField` silencioso registrado via `registerPluginCommonITILConfigField()`.
Não existe forma nativa de fazer o `LAST_VALID_ANSWER` reconhecer subclasses
sem modificar o core do GLPI.
# Classificação
- Tipo: Limitação arquitetural do GLPI 11 + solução via ponto de extensão oficial.
- Reutilização: obrigatória em qualquer plugin de categoria ITIL no form builder.