Skip to content

Captcha em múltiplos formulários na mesma página: ids duplicados e refresh não escopado #1056

Description

@rpgmem

Levantado durante a revisão do #1055 (PR2 do épico #1053). Pré-existente, mas o #1055 amplia o alcance — detalhado abaixo.

Resolvido pelo #1057. A correção funcional e de acessibilidade foi entregue; a varredura de timers foi medida e deliberadamente adiada — ver o comentário de fechamento.

O problema

O plugin suporta explicitamente vários formulários na mesma página: DynamicFragments tem um ramo dedicado a isso, gerando um desafio distinto por formulário quando count( $form_ids ) > 1 (includes/frontend/class-ffc-dynamic-fragments.php:87-99).

Só que o markup do captcha usa ids fixos:

<label for="ffc_captcha_ans">
<input ... id="ffc_captcha_ans" ...>
<input type="hidden" ... id="ffc_captcha_hash" ...>

Com dois formulários na página, esses ids aparecem duas vezes.

Consequência 1 — funcional (a mais séria)

assets/js/ffc-calendar-frontend.js:517-519 faz o refresh com seletores globais, não escopados ao formulário:

$('.ffc-captcha-row .ffc-captcha-label-text').text(newLabel);
$('#ffc_captcha_hash').val(newHash);
$('#ffc_captcha_ans').val('').focus();

Numa página com dois formulários, uma rejeição em qualquer um deles:

  1. Sobrescreve a pergunta exibida em todos os formulários da página
  2. Atualiza o token oculto apenas do primeiro (#id casa só com o primeiro elemento)

O formulário nº 2 passa a exibir a pergunta nova enquanto carrega o token antigo. O usuário responde o que está na tela e é rejeitado — e a mensagem diz que a resposta matemática está incorreta, o que é literalmente verdade e completamente inútil como diagnóstico.

Compare com o caminho do formulário de certificado, assets/js/ffc-frontend-helpers.js:524-527, que faz certo — escopa tudo em $form.find(...) e casa por name, não por id.

Consequência 2 — acessibilidade

Ids duplicados quebram a associação <label for>: o leitor de tela associa ambos os rótulos ao primeiro input. Como o campo é obrigatório e é um gate de submissão, isso deixa o formulário inacessível a quem depende do rótulo.

O que o #1055 mudou nisso

Não introduziu o defeito, mas ampliou o alcance:

  • Antes, o input oculto do formulário de certificado não tinha id; o do agendamento tinha. O refactor(captcha): put the math challenge behind a strategy contract #1055 unificou as duas cópias divergentes e o id sobreviveu — porque o ffc-calendar-frontend.js depende dele.
  • Consequência: uma página com [ffc_certificate_form] e o shortcode de agendamento agora tem colisão de #ffc_captcha_hash entre shortcodes diferentes, o que antes não acontecia.

O clobber de rótulo via .ffc-captcha-row .ffc-captcha-label-text já era global e já afetava esse caso — só a colisão de #ffc_captcha_hash é nova.

Direção de correção proposta

Sufixar os ids por instância e escopar o refresh:

  • O template templates/captcha/math-fields.php recebe um sufixo único por render (contador estático ou o form_id quando houver), emitindo ffc_captcha_ans_<n> / ffc_captcha_hash_<n> e o for correspondente. Os atributos name não mudam — o servidor lê por name, e mudá-los quebraria os 6 sites de verificação.
  • ffc-calendar-frontend.js passa a escopar por formulário e a casar por name, como o ffc-frontend-helpers.js já faz. É a mudança que de fato conserta o bug funcional; os ids sufixados sozinhos não bastam, porque $('#ffc_captcha_hash') simplesmente deixaria de casar.
  • Teste JS cobrindo o cenário: duas instâncias na mesma página, rejeição numa delas, asserção de que a outra mantém pergunta e token coerentes entre si.
  • Teste PHP de que dois renders na mesma requisição produzem ids distintos.

Achado secundário — timers pendentes sobrevivendo ao teardown do jsdom

O CI do #1055 falhou com ReferenceError: window is not defined com os 1731 testes passando: um setTimeout de 2000ms em ffc-csv-download-flow.js disparava depois de o jsdom ser destruído. Corrigido naquele PR (cb5eb53), drenando o timer com fake timers.

O que não foi medido é se há outras instâncias da mesma classe. Existem 11 timers de 1500ms a 10000ms em assets/js/ffc-pdf-generator.js (6s e 10s), ffc-batched-export.js (4s), ffc-admin-migrations.js (1,5s), entre outros — e vários testes esperam com setTimeout real de poucos milissegundos. Um teste que alcance um desses caminhos sem drenar o timer tem a mesma corrida latente.

Vale uma varredura: para cada timer longo, verificar se algum teste alcança o caminho e, se alcança, se drena. É barato e o sintoma é péssimo de diagnosticar — a suíte falha sem nenhum teste falhando, e só sob a velocidade de execução certa.

  • Adiado com medição — varrer os timers longos e drenar os que forem alcançados por teste. Ver o comentário de fechamento: o inventário foi levantado (16 timers, não 11), mas nenhum vazamento é observável, e a correção especulativa seria churn.

Sequenciamento

Combinado tratar depois do merge do #1055 e antes do PR3 do épico #1053 — o PR3 (ALTCHA) mexe no mesmo markup e no mesmo caminho de refresh, então entrar com essa base já corrigida evita retrabalho.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions