CreaCaptcha

Doctor_Command
in package

`wp creacaptcha doctor`

Table of Contents

Methods

__invoke()  : void
Runs the diagnostic checklist and prints a status table.
cloudflare_cron_check()  : array{check: string, status: string, meldung: string}
Check 4 — Cloudflare-Refresh-Cron.
filtered_interceptor_lists()  : array{paths: array, actions: array}
The interceptor's own guard lists, run through the SAME filters `Interceptor::is_guarded()`/`action_patterns()` apply at runtime (`creationell_captcha_interceptor_paths` / `_actions`).
gd_check()  : array{check: string, status: string, meldung: string}
Check 10 — PHP-GD-Extension (für Code-Challenge erforderlich).
hmac_secrets_check()  : array{check: string, status: string, meldung: string}
Check 2 — HMAC-Secrets.
inject_pattern_covered()  : bool
Check 15 — whether a single inject-path pattern has a real counterpart in the guard list.
login_coverage_check()  : array{check: string, status: string, meldung: string}
Check 11 — Login-Formular-Abdeckung.
login_coverage_row()  : array{check: string, status: string, meldung: string}
Ableitung zu Check 11 — B-I7.
mapped_cidr_check()  : array{check: string, status: string, meldung: string}
Check 18 — CIDR-Einträge in IPv4-mapped-Notation (Cross-Strang-Hinweis F1).
mapped_remote_addr_check()  : array{check: string, status: string, meldung: string}
Check 17 — IPv4-mapped REMOTE_ADDR (Cross-Strang-Hinweis F1, Bündel 4/I2).
neutralising_excludes()  : array<int, string>
Returns the `!`-exclusion patterns of a list that on their own match every positive pattern of the same list — i.e. that neutralise it.
probe_paths()  : array<int, string>
Turns a wildcard pattern into concrete sample strings it covers.
proxy_header_choice_check()  : array{check: string, status: string, meldung: string}
Check 20 — Proxy-Header-Auswahl (Cross-Strang-Hinweis N1, "m2").
retention_sweep_check()  : array{check: string, status: string, meldung: string}
Check 22 — läuft die Aufbewahrungsfrist des Event-Logs überhaupt?
sanitized_snapshot()  : array<string, mixed>
Ableitung zu Check 9 / CLI-10 — der sanitisierte Vergleichsstand.
secret_resolvable()  : bool
Ableitung zu Check 2 und Check 19 — löst EIN Secret aus seinen beiden Quellen auf, ohne die Getter zu rufen.
settings_defaults_check()  : array{check: string, status: string, meldung: string}
Check 9 — Settings-Defaults-Vollstaendigkeit + CLI-10-Wertspezifikation.
sodium_check()  : array{check: string, status: string, meldung: string}
Check 7b — Sodium-Extension (für Argon2id-Algorithmus erforderlich).
underattack_cache_check()  : array{check: string, status: string, meldung: string}
Check 21 — Under-Attack-Interstitial und Full-Page-Cache (Cross-Strang-Hinweis N1, "m7", §3.3 — nennt alle drei hier verwendeten Signale ausdrücklich).
underattack_pass_rate_check()  : array{check: string, status: string, meldung: string}
Check 19 — Under-Attack-Pass-Rate (Cross-Strang-Hinweis N1, "m4"/"m7").
is_mapped_cidr()  : bool
Whether a CIDR range's subnet part is written in IPv4-mapped notation (`::ffff:a.b.c.d/N` — RFC 4291 §2.5.5.2), the one shape `creationell_captcha_normalize_ip()` deliberately does not touch (see Check 18's docblock). Mirrors that function's binary-form check, scoped to the subnet segment of a CIDR entry instead of a plain address.

Methods

__invoke()

Runs the diagnostic checklist and prints a status table.

public __invoke(array<int, string> $args, array<string, string> $assoc_args) : void

Exit code 1 if at least one check is error, 0 otherwise.

Grenze des Werkzeugs (CLI-10): doctor liest ausschließlich — es prüft Vorhandensein und Erreichbarkeit (Datei, Option, Tabelle, Cron, Extension) sowie einige Konsistenzen zwischen Schaltern. Seit der Nachlese vergleicht Check 9 den gespeicherten Wertesatz zusätzlich gegen den Sanitizer (rein lesend, siehe dort) und erkennt damit einen an den Plugin-Schreibwegen vorbei gesetzten Wert, der von der Feldspezifikation abweicht (algorithm=boguswert per wp option patch). Nicht geprüft bleibt, ob ein Wert innerhalb seiner Spezifikation auch inhaltlich sinnvoll ist (ein syntaktisch gültiges, aber unerreichbares Cloudflare-IP-Ziel o. Ä.). „status: ok" heißt deshalb „dieser Check hat nichts gefunden", nicht „die Installation ist korrekt konfiguriert".

Third-party modules can extend the list via the creationell_captcha_doctor_checks filter. Filter receives the array of checks (each [check => string, status => 'ok'|'warn'|'error', meldung => string]) and must return the same shape.

OPTIONS

[--format=] : Output format (table, json, yaml, csv).

EXAMPLES

wp creacaptcha doctor
Parameters
$args : array<int, string>

Positional arguments.

$assoc_args : array<string, string>

Associative arguments.

Tags
when

after_wp_load

cloudflare_cron_check()

Check 4 — Cloudflare-Refresh-Cron.

public static cloudflare_cron_check(array<string, mixed> $settings, int|false $next_scheduled) : array{check: string, status: string, meldung: string}

B-I4: the auto-refresh cron slot is only ever scheduled when BOTH firewall_trust_cloudflare AND firewall_cloudflare_auto_refresh are on (creationell_captcha_sync_cloudflare_cron(), cloudflare-proxies.php:173-176) — reading firewall_cloudflare_auto_refresh alone made this check report a permanent error on a healthy install whose proxy mode is off but whose two dependent fields are still frozen true by the settings requires-rewrite (they are simply gated, not broken). Mirrors the exact condition sync_cloudflare_cron() uses, and downgrades the "gated" case to warn — the field not taking effect is expected, not a defect.

Parameters
$settings : array<string, mixed>

Current plugin settings.

$next_scheduled : int|false

Return value of wp_next_scheduled('creationell_captcha_refresh_cloudflare_ips').

Return values
array{check: string, status: string, meldung: string}

filtered_interceptor_lists()

The interceptor's own guard lists, run through the SAME filters `Interceptor::is_guarded()`/`action_patterns()` apply at runtime (`creationell_captcha_interceptor_paths` / `_actions`).

public static filtered_interceptor_lists(array<string, mixed> $settings) : array{paths: array, actions: array}

W1-9: checks 14 and 15 used to read $settings['interceptor_paths']/ ['interceptor_actions'] directly. That is not what the interceptor itself decides on — a site using the documented creationell_captcha_protect_path() developer API only ever touches these filters, never the settings option, so the raw read was blind to filter-added guards (false "uncovered"/"ok, no exclusion problem") and equally blind to a filter that REMOVES a settings entry.

Public and static so the filter wiring itself — not just what the downstream checks do with the result — can be exercised directly in tests/test-cli-doctor-checks.php.

Parameters
$settings : array<string, mixed>

Current plugin settings.

Return values
array{paths: array, actions: array}

gd_check()

Check 10 — PHP-GD-Extension (für Code-Challenge erforderlich).

public static gd_check(array<string, mixed> $settings, bool $gd_loaded) : array{check: string, status: string, meldung: string}

B-I5: fehlendes GD lässt should_issue_code_challenge() (code-challenge.php:31-33) die Zusatzstufe grundsätzlich nie auslösen — die PoW-Stufe bleibt unverändert aktiv, nichts fällt aus. Working degradation statt Ausfall, also warn statt error (siehe Check 16).

Parameters
$settings : array<string, mixed>

Current plugin settings.

$gd_loaded : bool

Result of extension_loaded('gd').

Return values
array{check: string, status: string, meldung: string}

hmac_secrets_check()

Check 2 — HMAC-Secrets.

public static hmac_secrets_check(bool $sig_resolved, bool $key_resolved) : array{check: string, status: string, meldung: string}

B-M22: signature and key-signature each resolve independently — a site can set one wp-config constant and rely on the stored option for the other. Missing either one leaves the engine unable to sign/verify challenges, so error (not warn) is correct here, matching the error-Regel in Check 16's comment.

Parameters
$sig_resolved : bool

Whether the signature secret resolves (constant or option).

$key_resolved : bool

Whether the key-signature secret resolves (constant or option).

Return values
array{check: string, status: string, meldung: string}

inject_pattern_covered()

Check 15 — whether a single inject-path pattern has a real counterpart in the guard list.

public static inject_pattern_covered(string $pattern, array<int, string> $guard_paths) : bool

Fixes two bugs found for this check (Bündel 5 / W1-10, W1-15):

  • B-I6/W1-10: the previous loop set $covered = true on the FIRST matching probe and broke — an existence quantor answering "is ANY request under this pattern protected?" where the check's own claim ("jeder Inject-Pfad hat ein Gegenstück") requires a universal quantor. neutralising_excludes() right above already gets this direction correct for its own probes; this method aligns with it: ALL probes from probe_paths() must be covered.
  • W1-15: matching only [rawurldecode($probe), $probe] covers the "inject pattern written percent-encoded" direction but not the reverse ("inject pattern written decoded, guard pattern written percent-encoded") — rawurldecode() on an already-decoded string is a no-op, so the guard's percent-encoded spelling was never produced to compare against. Building a third candidate — the probe percent-re-encoded segment-by-segment (preserving /) — covers that direction too, matching what a real browser sends over the wire for a non-ASCII path.

Public and static — like Interceptor::match_path() — so the three fixed constellations (Erst-Treffer, beide Kodierungsrichtungen) can be exercised directly in tests/test-cli-doctor-checks.php without bootstrapping the whole WP-CLI command.

Parameters
$pattern : string

One interceptor_inject_paths pattern (no leading !).

$guard_paths : array<int, string>

Filtered interceptor_paths patterns (see apply_filters() call in __invoke()).

Return values
bool

login_coverage_check()

Check 11 — Login-Formular-Abdeckung.

public static login_coverage_check(array<string, mixed> $settings, bool $woocommerce_installed, bool $wc_login_active) : array{check: string, status: string, meldung: string}

B-I7: reading protect_wc_login raw ignored the kill switch and the protect_woocommerce master toggle — a value frozen true by the settings requires-rewrite from a time when Woo protection was on kept reporting "ok" long after protect_woocommerce was switched off, hiding the exact IN-7 gap (My-Account login unprotected) this check exists to surface. creationell_captcha_wc_login_active() is the single source of truth the render/verify hooks themselves use.

Parameters
$settings : array<string, mixed>

Current plugin settings.

$woocommerce_installed : bool

Result of class_exists('WooCommerce').

$wc_login_active : bool

Result of creationell_captcha_wc_login_active().

Return values
array{check: string, status: string, meldung: string}

login_coverage_row()

Ableitung zu Check 11 — B-I7.

public static login_coverage_row(array<string, mixed> $settings) : array{check: string, status: string, meldung: string}

Die Substanz des B-I7-Fixes ist die QUELLE des dritten Arguments: creationell_captcha_wc_login_active() statt des rohen Toggles $settings['protect_wc_login']. Genau diese Wahl lag inline in run() und war deshalb von keiner Suite bewacht — login_coverage_check() selbst bekommt den Wert ja fertig gereicht. Als eigener Helfer ist der eingefrorene Fall (protect_wc_login = true bei ausgeschaltetem Master protect_woocommerce, AF-1) direkt pruefbar.

Parameters
$settings : array<string, mixed>

Aktueller Wertesatz.

Return values
array{check: string, status: string, meldung: string}

mapped_cidr_check()

Check 18 — CIDR-Einträge in IPv4-mapped-Notation (Cross-Strang-Hinweis F1).

public static mapped_cidr_check(array<string, mixed> $settings) : array{check: string, status: string, meldung: string}

creationell_captcha_normalize_ip() (helpers.php) kanonisiert bewusst nur einzelne Adressen, nie CIDR-Bereiche — ein Umrechnen der Präfixlänge könnte eine Trusted-Proxy-Zeile stillschweigend verbreitern. Ein vor 1.1.0 in mapped Notation eingetragener Bereich (::ffff:203.0.113.0/120) trifft seit 1.1.0 deshalb nicht mehr; dieser Check macht das sichtbar statt es still wirkungslos werden zu lassen.

Parameters
$settings : array<string, mixed>

Current plugin settings.

Return values
array{check: string, status: string, meldung: string}

mapped_remote_addr_check()

Check 17 — IPv4-mapped REMOTE_ADDR (Cross-Strang-Hinweis F1, Bündel 4/I2).

public static mapped_remote_addr_check(string $live_remote_addr, string|null $logged_mapped_ip) : array{check: string, status: string, meldung: string}

Zwei unabhängige Signale, weil ein wp-Aufruf über die CLI-SAPI normalerweise KEINE echte HTTP-Verbindung hat: $_SERVER['REMOTE_ADDR'] ist unter WP-CLI meist gar nicht gesetzt, ein Live-Treffer also nur bei ungewöhnlichen Aufrufkontexten möglich. Das Event-Log ist die verlässlichere Quelle: da creationell_captcha_get_client_ip() seit 1.1.0 jede IP kanonisiert, BEVOR sie geloggt wird, kann eine ::ffff:-Adresse im Log nur aus der Zeit VOR dem Update stammen — ein direkter, installationsspezifischer Nachweis, dass dieser Server REMOTE_ADDR mindestens zeitweise mapped ausliefert.

Parameters
$live_remote_addr : string

$_SERVER['REMOTE_ADDR'] bei diesem Aufruf, oder ''.

$logged_mapped_ip : string|null

Eine im Event-Log gefundene mapped-Adresse, oder null.

Return values
array{check: string, status: string, meldung: string}

neutralising_excludes()

Returns the `!`-exclusion patterns of a list that on their own match every positive pattern of the same list — i.e. that neutralise it.

public static neutralising_excludes(array<int, mixed> $patterns) : array<int, string>

Probing with concrete sample paths derived from the positive patterns (rather than comparing pattern strings) uses the real matcher, so the answer is exactly the runtime behaviour.

B-M23: public (like inject_pattern_covered() above) so Check 14's own matching — not just what the check does with the result — has a direct Ebene-2 test in tests/test-cli-doctor-checks.php. Before this, the ~300-line doctor addition from Modul 27 had no such coverage at all for the BK-15 exclusion logic; class-cli-list-command.php:339 makes the identical visibility argument for a comparable validation seam.

Parameters
$patterns : array<int, mixed>

Raw pattern list from the settings.

Return values
array<int, string> —

Offending !-patterns, original spelling.

probe_paths()

Turns a wildcard pattern into concrete sample strings it covers.

public static probe_paths(string $pattern) : array<int, string>

Two probes per pattern — * expanded to nothing and to a filler — so a narrow exclusion that only catches one of them is not mistaken for one that swallows the whole pattern.

B-M23: public for the same reason as neutralising_excludes() above.

Parameters
$pattern : string

Wildcard pattern (without a leading !).

Return values
array<int, string>

proxy_header_choice_check()

Check 20 — Proxy-Header-Auswahl (Cross-Strang-Hinweis N1, "m2").

public static proxy_header_choice_check(array<string, mixed> $settings) : array{check: string, status: string, meldung: string}

The four values mirror the firewall_proxy_header select field's options in includes/settings.php (and creationell_captcha_get_client_ip()'s $header_map in includes/helpers.php) — kept in sync manually since this check must not depend on either of those files loading.

Parameters
$settings : array<string, mixed>

Current plugin settings.

Return values
array{check: string, status: string, meldung: string}

retention_sweep_check()

Check 22 — läuft die Aufbewahrungsfrist des Event-Logs überhaupt?

public static retention_sweep_check(bool $disabled, bool $table_exists, int $retention_days) : array{check: string, status: string, meldung: string}

Nachlese N6, Befund 6. Analytics::run_scheduled_prune() und ensure_prune_schedule() steigen seit W3-2 beim Kill-Switch sofort aus. Für den kurzen Einsatz ist das richtig — Zeilen zu löschen ist die einzige zerstörende Hintergrundoperation dieses Plugins, und wer während eines Vorfalls CREATIONELL_CAPTCHA_DISABLE setzt, will den Zustand einfrieren, nicht die Beweise wegräumen lassen. Für eine Konstante, die länger steht als die Aufbewahrungsdauer, ist es eine offene Zusage: die Einstellung sagt „ältere Einträge werden entfernt", und es passiert nichts. Der Guard bleibt (dieselbe Entscheidung wie in W3-2), aber der Zustand wird gemeldet statt still zu bleiben.

wp creacaptcha log prune prüft den Kill-Switch NICHT und ist damit der Weg heraus, ohne die Konstante anzufassen — die Meldung nennt ihn.

Parameters
$disabled : bool

Whether the wp-config kill switch is set.

$table_exists : bool

Whether the event-log table is present.

$retention_days : int

Configured retention window in days.

Return values
array{check: string, status: string, meldung: string}

sanitized_snapshot()

Ableitung zu Check 9 / CLI-10 — der sanitisierte Vergleichsstand.

public static sanitized_snapshot(array<string, mixed> $stored) : array<string, mixed>

Ruft den Sanitizer im PROGRAMMATIC-Kontext NUR zum Vergleichen auf und schreibt das Ergebnis nicht. Seit Welle 1 ist der Sanitizer seiteneffektfrei (der ensure_table()-Aufruf haengt an den update_option_/add_option_-Hooks, nicht mehr am Sanitizer), sonst waere dieser Aufruf aus einer rein lesenden Diagnose heraus unzulaessig.

Eigener Helfer aus demselben Grund wie secret_resolvable(): stand diese Zeile inline in run(), liess sich der Wertvergleich auf $sanitized = $stored zuruecknehmen, ohne dass eine Suite rot wurde — Check 9 haette dann strukturell nie eine Wertabweichung sehen koennen.

Parameters
$stored : array<string, mixed>

Gespeicherter Wertesatz.

Return values
array<string, mixed> —

Der sanitisierte Wertesatz.

secret_resolvable()

Ableitung zu Check 2 und Check 19 — löst EIN Secret aus seinen beiden Quellen auf, ohne die Getter zu rufen.

public static secret_resolvable(string $constant, mixed $secrets_option, string $option_key) : bool

Bis zum Re-Review stand diese Disjunktion inline in run(); die Doctor-Suite ruft aber ausschliesslich die reinen Check-Funktionen mit bereits fertigen Argumenten auf, sodass eine Ruecknahme des Konstanten-Zweigs von KEINEM Test bemerkt wurde (Fehlerklasse 2: der Fix stimmt, der Beweis trifft den Pfad nicht). Als eigener Helfer ist die B-M22-Divergenz — Konstante gesetzt, Option leer — direkt und diskriminierend pruefbar.

Der Konstantenname kommt als Zeichenkette herein, damit ein Test beide Zweige im selben Prozess durchlaufen kann; constant() liest ihn erst nach dem defined()-Guard.

Bewusst NICHT ueber creationell_captcha_get_hmac_secret(): der Getter heilt eine fehlende Option durch Neuerzeugen und PERSISTIEREN — ein Schreibvorgang, den diese rein lesende Diagnose nie ausloesen darf (Vertrag „doctor liest ausschliesslich" im Klassen-Docblock).

Parameters
$constant : string

Name der wp-config-Konstante (Vorrang).

$secrets_option : mixed

Inhalt der Option creationell_captcha_secrets (Rueckfall).

$option_key : string

Schluessel innerhalb dieser Option.

Return values
bool

settings_defaults_check()

Check 9 — Settings-Defaults-Vollstaendigkeit + CLI-10-Wertspezifikation.

public static settings_defaults_check(array<string, mixed> $defaults, array<string, mixed> $stored, array<string, mixed> $sanitized) : array{check: string, status: string, meldung: string}

Nachlese N2: schliesst die CLI-10-Grenze ("nur die Schluesselmenge"), seit der Sanitizer-Aufruf selbst kein DDL mehr ausloest (Welle 1). $sanitized ist der Aufrufer-seitige, rein lesende Sanitizer-Durchlauf ueber $stored (creationell_captcha_sanitize_settings($stored, CREATIONELL_CAPTCHA_SANITIZE_PROGRAMMATIC)) — diese Methode selbst ruft ihn nicht auf und bleibt dadurch ohne jede settings.php-Abhaengigkeit direkt testbar.

Parameters
$defaults : array<string, mixed>

creationell_captcha_get_default_settings().

$stored : array<string, mixed>

Roher Optionswert.

$sanitized : array<string, mixed>

$stored durch den Sanitizer geschickt (PROGRAMMATIC, nicht geschrieben).

Return values
array{check: string, status: string, meldung: string}

sodium_check()

Check 7b — Sodium-Extension (für Argon2id-Algorithmus erforderlich).

public static sodium_check(array<string, mixed> $settings, bool $sodium_available) : array{check: string, status: string, meldung: string}

B-I5: Engine::algorithm_key() (class-engine.php:601-611) falls back to PBKDF2 silently and logs when ext-sodium is missing — challenges keep working. That is a working degradation, not the "cannot work" state error is reserved for (see the Check-16 comment below), so this is warn.

Parameters
$settings : array<string, mixed>

Current plugin settings.

$sodium_available : bool

Result of creationell_captcha_sodium_available().

Return values
array{check: string, status: string, meldung: string}

underattack_cache_check()

Check 21 — Under-Attack-Interstitial und Full-Page-Cache (Cross-Strang-Hinweis N1, "m7", §3.3 — nennt alle drei hier verwendeten Signale ausdrücklich).

public static underattack_cache_check(array<int, string> $active_cache_plugins, bool $wp_cache_constant, bool $advanced_cache_dropin) : array{check: string, status: string, meldung: string}
Parameters
$active_cache_plugins : array<int, string>

Labels of active known caching plugins.

$wp_cache_constant : bool

Whether WP_CACHE is defined and truthy.

$advanced_cache_dropin : bool

Whether wp-content/advanced-cache.php exists.

Return values
array{check: string, status: string, meldung: string}

underattack_pass_rate_check()

Check 19 — Under-Attack-Pass-Rate (Cross-Strang-Hinweis N1, "m4"/"m7").

public static underattack_pass_rate_check(bool $sig_resolvable, array{attempts: int, passed: int}|null $data) : array{check: string, status: string, meldung: string}

Two independent signals, checked in order of confidence:

  1. $sig_resolvable false — no HMAC signature secret resolves (neither the wp-config constant nor the stored option), so mint_pass() cannot derive a token for ANY visitor. This is N1's suggested direct condition (creationell_captcha_derive_hmac_key(...) === ''), reformulated without calling the self-healing getter chain (see the __invoke() comment above this call) — error, because the site is durably unusable, matching the error-reservation rule from Check 16's comment.
  2. $data — the event-log symptom (see below), for the other cause (headers_sent()), which cannot be observed directly from a WP-CLI process.

$data is null when the prerequisites for a meaningful measurement are not met (event log off, either log gate off, or the table missing) — reported as "not measurable", never as "ok" by absence of evidence. Below the attempt threshold the same applies: zero passes out of zero or a handful of attempts says nothing about whether the gate is passable at all.

Parameters
$sig_resolvable : bool

Whether the HMAC signature secret resolves (constant or option).

$data : array{attempts: int, passed: int}|null

Event counts, or null if unmeasurable.

Return values
array{check: string, status: string, meldung: string}

is_mapped_cidr()

Whether a CIDR range's subnet part is written in IPv4-mapped notation (`::ffff:a.b.c.d/N` — RFC 4291 §2.5.5.2), the one shape `creationell_captcha_normalize_ip()` deliberately does not touch (see Check 18's docblock). Mirrors that function's binary-form check, scoped to the subnet segment of a CIDR entry instead of a plain address.

private static is_mapped_cidr(string $entry) : bool
Parameters
$entry : string

One list entry, already trimmed.

Return values
bool

        
On this page

Search results