Skip to content

fix(core): the int request accessors read an array as the number 1 (#1087 passo 1) - #1088

Merged
rpgmem merged 2 commits into
developfrom
claude/request-input-int-guard-4vvuer
Sep 6, 2026
Merged

fix(core): the int request accessors read an array as the number 1 (#1087 passo 1)#1088
rpgmem merged 2 commits into
developfrom
claude/request-input-int-guard-4vvuer

Conversation

@rpgmem

@rpgmem rpgmem commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Passo 1 de três da #1087. Os passos 2 (guarda estático) e 3 (recorte público) vêm em PRs separadas — esta issue fica aberta.

O defeito que paga a PR

RequestInput tem duas famílias de acessores e só uma confere o tipo:

public static function get_post_string( string $key, string $default = '' ): string {
    $raw = wp_unslash( $_POST[ $key ] );
    if ( ! is_string( $raw ) ) { return $default; }   // ← guarda
    return sanitize_text_field( $raw );
}

public static function get_post_int( string $key, int $default = 0 ): int {
    return absint( wp_unslash( $_POST[ $key ] ) );    // ← nenhuma
}

absint( array('45') ) é 1 e absint( array() ) é 0 — os dois em silêncio, sem aviso do PHP. E 1 é um id plausível, uma contagem plausível e um teto plausível. É o mesmo cast que no #1075 transformou uma duração de slot em um minuto, e a mesma assimetria que o SettingsReader carregava até o #1086, agora em outra classe.

A guarda nos dois helpers corrige 74 chamadas de uma vez.

Os 39 casts diretos

Além das 74, 39 sites em 10 arquivos castavam superglobal sem passar por helper nenhum. Todos admin-only, nenhum abre acesso:

Arquivo Sites Efeito de um POST com array
settings/tabs/class-ffc-tab-rate-limit.php 22 todos os tetos (max_per_hour, max_per_day, cooldown_seconds, max_logs, retention_days…) viram 1 — o rate limit inteiro achatado
frontend/class-ffc-public-csv-download.php 6 form_id cai em 1; sem ganho de privilégioform_id não é segredo e a validação de post type + acesso público segue valendo
admin/class-ffc-settings-action-handler.php 3 janelas de carência viram 1 dia; a do save_days persiste
settings/tabs/class-ffc-tab-geolocation.php 2 TTLs caem no piso pelo max() — inócuo
outros 6 arquivos 1 cada ids caem em 1

Mais dois itens da triagem

consume_ticket() atribuía offset sobre o retorno de get_post_meta( …, true ), que é '' quando a meta não existe. Em PHP 8 isso é fatal, não aviso — o teste novo reproduz o TypeError: Cannot access offset of type string on string contra o código antigo. Não é alcançável hoje: o caminho exige is_ticket, que já pressupõe a config lida como array em check( array $form_config, … ). Fica a forma do #1058 tornada segura, não um bug vivo consertado — e a PR diz isso com essas palavras em vez de inflar o achado.

Os 6 wp_kses_post sobre $_POST no audience passam por uma guarda de escalar. O helper é privado na classe porque ela é o único consumidor no plugin inteiro — um helper compartilhado com um chamador só seria fachada que não estreita nada (precedente #1079). Ressalva: o que o wp_kses_post real faz com um array não foi verificado, porque não há WordPress neste ambiente; no teste, o valor que aparece contra o código antigo é artefato do stub returnArg. O teste prova a guarda, não o comportamento antigo — a #1087 registra isso como não verificado.

Testes

Três discriminantes, cada um rodado contra o código antigo com os valores que as tabelas acima afirmam:

1) test_get_get_int_returns_default_for_an_array_value
   Failed asserting that 1 is identical to 7.
2) test_get_post_int_returns_default_for_an_array_value
   Failed asserting that 1 is identical to 90.
3) test_consume_ticket_survives_an_absent_config_meta
   TypeError: Cannot access offset of type string on string

Mais dois de preservação de comportamento, que passam dos dois lados de propósito (get_post_int_casts_through_absint, e o valor string do audience seguindo inalterado).

Uma quebra minha, e a correção

A primeira rodada da suíte fechou vermelha: PublicFormsExportSourceTest faz alias mock de RequestInput e stubava só cinco métodos. Rotear request_form_id() pelo get_post_int() fez o código chamar um sexto, e o Mockery rejeitou — três testes que esperavam RuntimeException receberam BadMethodCallException. É o padrão que o CLAUDE.md registra para as divisões Reader/Writer: um alias mock precisa stubar todo método que o código sob teste passa a chamar. Os 11 alias mocks da classe agora stubam get_post_int com a semântica do helper real.

Junto veio uma anotação que a mudança deixou inerte// phpcs:ignore … -- absint() sanitizes sobre uma linha que não lê mais superglobal. Removida, e conferido que PHPCS segue limpo sem ela: é a classe de resíduo que os #1031/#1035 varreram.

Validação

  • PHPStan nível 8 nos 12 arquivos: limpo, sem supressão nova
  • WPCS e phpcs-tests.xml.dist: limpos
  • Régua de linhas: 45 classes, 0 erros, folga 0 — PASS
  • Suíte completa: 7650 testes, 21936 asserções, verde (13m40s)
  • Nível 9 do repositório: 2584 → 2535

O que esta PR não faz, de propósito: não zera nada. Os 296 erros do recorte de gravadores continuam de pé, e os 2535 do repositório também. O alvo é o defeito, não o número.

Refs #1087, #1060, #1075, #1086, #1058

🤖 Generated with Claude Code

https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU


Generated by Claude Code

…1087)

RequestInput::get_post_int() e get_get_int() faziam absint( wp_unslash( ... ) )
sem conferir o tipo, enquanto get_post_string()/get_get_string() sempre
conferiram com is_string. absint( array('45') ) e 1 e absint( array() ) e 0 —
os dois em silencio, sem aviso do PHP, e 1 e um id, uma contagem e um teto
plausiveis. E a mesma assimetria que o SettingsReader tinha ate o #1086, em
outra classe, e o mesmo cast que no #1075 transformou uma duracao de slot em
um minuto.

A guarda nos dois helpers corrige 74 chamadas de uma vez. Alem delas, 39 casts
diretos sobre superglobal, em 10 arquivos, passam a ir pelos helpers — 22 no
tab-rate-limit, onde um POST com array achatava para 1 todos os tetos
(max_per_hour, max_per_day, cooldown_seconds, max_logs, retention_days); 3 no
settings-action-handler, onde as janelas de carencia viravam 1 dia e a do
save_days persistia; 7 form_id na superficie publica, sem ganho de privilegio
porque form_id nao e segredo e a validacao seguinte continua valendo.

Mais dois itens que a triagem dos 20 gravadores levantou:

- consume_ticket() atribuia offset sobre o retorno de get_post_meta( ..., true ),
  que e '' quando a meta nao existe — em PHP 8 isso e fatal, nao aviso. O teste
  novo reproduz o TypeError contra o codigo antigo. NAO e alcancavel hoje: o
  caminho exige is_ticket, que ja pressupoe a config lida como array. Fica a
  forma do #1058 tornada segura, nao um bug vivo consertado.
- os 6 wp_kses_post sobre $_POST no audience passam por uma guarda de escalar.
  O helper e privado na classe porque ela e o unico consumidor no plugin —
  compartilhar com um chamador so seria fachada que nao estreita nada (#1079).
  O que o wp_kses_post real faz com um array nao foi verificado: nao ha
  WordPress neste ambiente. O teste prova a guarda, nao o comportamento antigo.

Regua de linhas segue em 0/45. Nivel 9 do repositorio: 2584 para 2535.
Esta PR nao mira em zerar nada — o alvo e o defeito, nao o numero.

Refs #1087, #1060, #1075, #1086, #1058

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU
PublicFormsExportSourceTest faz alias mock de RequestInput e stubava so
get_user_ip, get_post_string, get_get_key, get_get_int e has_get. Rotear
request_form_id() pelo get_post_int() fez o codigo chamar um metodo que o mock
nao conhece — o Mockery rejeita, e os tres testes que esperavam RuntimeException
receberam BadMethodCallException. Mesmo padrao que o CLAUDE.md registra para as
divisoes Reader/Writer: um alias mock precisa stubar todo metodo que o codigo
sob teste passa a chamar.

Os 11 alias mocks da classe passam a stubar get_post_int com a semantica do
helper real, guarda de escalar inclusa.

Junto, uma anotacao que a mudanca deixou inerte: o
// phpcs:ignore ... -- absint() sanitizes sobre request_form_id(), numa linha
que nao le mais superglobal. Removida e conferida: PHPCS segue limpo sem ela.
E a classe de residuo que os #1031/#1035 varreram.

Refs #1087

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU
@rpgmem
rpgmem enabled auto-merge (squash) September 6, 2026 23:06
@rpgmem
rpgmem merged commit 8cd72aa into develop Sep 6, 2026
20 checks passed
@rpgmem
rpgmem deleted the claude/request-input-int-guard-4vvuer branch September 6, 2026 23:20
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34065938230

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Warning

No base build found for commit 4d2c3df on develop.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 89.973%

Details

  • Patch coverage: 3 uncovered changes across 1 file (53 of 56 lines covered, 94.64%).

Uncovered Changes

File Changed Covered %
includes/audience/class-ffc-audience-admin-settings.php 7 4 57.14%
Total (12 files) 56 53 94.64%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 53942
Covered Lines: 48533
Line Coverage: 89.97%
Coverage Strength: 4.87 hits per line

💛 - Coveralls

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants