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

3.7 KiB

id title domain tags status severity created_at updated_at applies_to related_records
KB-PLUGIN-005 ITILCategoryFieldStrategy LAST_VALID_ANSWER ignora subclasses de QuestionTypeItemDropdown plugin-dev
glpi
glpi11
form-builder
form-destination
itil-category
answer-pipeline
question-type
active critical 2026-05-02 2026-05-02
GLPI 11.x form builder — destino de formulário (FormDestinationTicket/Change/Problem)
plugins com QuestionType que estendem QuestionTypeItemDropdown para ITILCategory
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:

$valid_answers = $answers_set->getAnswersByType(QuestionTypeItemDropdown::class);

AnswersSet::getAnswersByType() usa comparação exata (==), não is_a():

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:

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