--- 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.