Captcha
Table of Contents
Packages
Classes
- Analytics
- Records security events into per-day and per-hour aggregate counters and, when the detailed event log is enabled, into a dedicated database table.
- EmailObfuscator
- Obfuscates mailto: links and plain-text email addresses in front-end output. The real address is XOR-hex encoded into a data-cce attribute and restored client-side by the decoder script. Two modes share this class: the content-filter mode (process()) and the full-page-buffer mode (process_page()). See the module-7 and module-8 design specs.
- Engine
- Wraps the bundled ALTCHA library for the WordPress plugin.
- Firewall
- Blocks requests by client IP or user-agent before WordPress processes them.
- Interceptor
- Inspects front-end POST requests and enforces a valid ALTCHA payload for requests whose path matches a configured pattern. See the module-2 design spec for the bypass chain (§6) and the guard decision (§7/§8).
- RateLimiter
- Counts requests per client IP in a fixed time window and blocks with HTTP 429 once the configured limit is exceeded. See the module-4 design spec §6.
- UnderAttack
- Gates anonymous front-end page views behind an interstitial proof-of-work challenge while under-attack mode is active. See the module-5 design spec.
- Cloudflare_Command
- `wp creacaptcha cloudflare …` — refresh, clear and status for the cached Cloudflare IP-range list.
- Command
- Inspects and controls CreaCaptcha from the command line.
- Doctor_Command
- `wp creacaptcha doctor`
- List_Command
- Manages one list-type setting — IP block/allow list, UA block list or the interceptor path list. The same class backs the blocklist, allowlist, ua-blocklist and paths command namespaces.
- Log_Command
- Inspects and maintains the optional event-log table.
- Settings_Command
- Reads, writes, exports, imports and resets CreaCaptcha settings.
- Test_Bypass_Command
- `wp creacaptcha test-bypass`
Constants
- CREATIONELL_CAPTCHA_EXPORT_MAX_ROWS = 50000
- The default upper bound on rows in a single CSV export.
- CREATIONELL_CAPTCHA_EXPORT_SCHEMA = 1
- Schema version of the settings-export format.
- CREATIONELL_CAPTCHA_HARDENING_DISMISS_ARG = 'creationell_captcha_hardening_dismiss'
- Query-Parameter des Ausblenden-Links.
- CREATIONELL_CAPTCHA_HARDENING_DISMISS_META = 'creationell_captcha_hardening_dismissed'
- User-Meta-Schlüssel, unter dem ein Benutzer den Hinweis wegklickt.
- CREATIONELL_CAPTCHA_LOCALE_MAP = [ // Deutsch 'de_DE' => 'de', 'de_DE_formal' => 'de', 'de_AT' => 'de', 'de_CH' => 'de', 'de_CH_informal' => 'de', // Englisch 'en_US' => 'en', 'en_GB' => 'en', 'en_AU' => 'en', 'en_CA' => 'en', 'en_NZ' => 'en', 'en_ZA' => 'en', // Französisch 'fr_FR' => 'fr-fr', 'fr_BE' => 'fr-fr', 'fr_LU' => 'fr-fr', 'fr_CH' => 'fr-fr', 'fr_CA' => 'fr-ca', // Spanisch (Europa) 'es_ES' => 'es-es', // Spanisch (LatAm — alle nach es-419) 'es_AR' => 'es-419', 'es_CL' => 'es-419', 'es_CO' => 'es-419', 'es_CR' => 'es-419', 'es_DO' => 'es-419', 'es_EC' => 'es-419', 'es_GT' => 'es-419', 'es_HN' => 'es-419', 'es_MX' => 'es-419', 'es_PA' => 'es-419', 'es_PE' => 'es-419', 'es_PR' => 'es-419', 'es_UY' => 'es-419', 'es_VE' => 'es-419', // Portugiesisch 'pt_PT' => 'pt-pt', 'pt_AO' => 'pt-pt', 'pt_BR' => 'pt-br', // Italienisch / Niederländisch / Polnisch 'it_IT' => 'it', 'nl_NL' => 'nl', 'nl_BE' => 'nl', 'pl_PL' => 'pl', // Tschechisch / Slowakisch 'cs_CZ' => 'cs', 'sk_SK' => 'sk', // Nordisch 'da_DK' => 'da', 'sv_SE' => 'sv', 'fi' => 'fi', 'nb_NO' => 'nb', 'nn_NO' => 'nb', // Sonstige EU 'hu_HU' => 'hu', 'ro_RO' => 'ro', 'el' => 'el', ]
- Maps WordPress locales to the matching ALTCHA i18n locale bundle.
- CREATIONELL_CAPTCHA_PURPOSE_UA_CTX = 'underattack-ctx'
- Purpose label of the under-attack code-challenge suppression token (`ctx`).
- CREATIONELL_CAPTCHA_PURPOSE_UA_PASS = 'underattack-pass'
- Purpose label of the under-attack pass-cookie key.
- CREATIONELL_CAPTCHA_SANITIZE_FORM = 'form'
- Sanitiser context: the settings form (`options.php`).
- CREATIONELL_CAPTCHA_SANITIZE_PROGRAMMATIC = 'programmatic'
- Sanitiser context: programmatischer Schreibvorgang (Import, Werksreset, „Standardwerte laden", WP-CLI).
- CREATIONELL_CAPTCHA_SANITIZE_RESET = 'reset'
- Sanitiser context: Werksreset (`creationell_captcha_reset_settings()`).
- CREATIONELL_CAPTCHA_UA_CTX_TTL = 120
- Lifetime (seconds) of a `ctx` suppression token. The interstitial widget fetches the challenge on load, i.e. within seconds of the 503 being rendered; two minutes is generous for that and replaces the ~10 minutes the old 5-minute-bucket pair accepted (BK-4).
Functions
- creationell_captcha_register_admin_menu() : void
- Registers the top-level "CreaCaptcha" admin menu entry.
- creationell_captcha_render_settings_page() : void
- Renders the tabbed settings page.
- creationell_captcha_render_import_warning() : void
- Renders the "an import replaces everything" warning on the Werkzeuge page.
- creationell_captcha_render_trust_notice() : void
- Renders the "proxy mode on but trust-set empty" admin notice on plugin pages.
- creationell_captcha_tabbed_page_hooks() : array<int, string>
- Records and reports the admin-page hook suffixes of the plugin's tab-organised pages.
- creationell_captcha_active_tab() : string
- Determines the active tab from the request, whitelisted against $tabs.
- creationell_captcha_render_nav_tabs() : void
- Renders the no-JavaScript fallback style and the nav-tab bar.
- creationell_captcha_csv_cell() : string
- Neutralises a CSV cell against spreadsheet formula injection.
- creationell_captcha_export_events() : void
- Streams the filtered event log as a CSV download.
- creationell_captcha_register_analytics_page() : void
- Registers the "Statistik" submenu page under the CreaCaptcha menu.
- creationell_captcha_analytics_labels() : array<string, string>
- Human-readable labels for the seven event types.
-
creationell_captcha_analytics_groups()
: array<int, array{title: string, description: string, types: array
}> - Thematic groups for the overview tab: title, explanation and the member event types with their short in-group labels.
- creationell_captcha_analytics_tabs() : array<string, string>
- The three tabs of the analytics page.
- creationell_captcha_sum_recent_days() : array<string, int>
- Sums the $days most recent day buckets, per event type.
- creationell_captcha_sum_recent_hours() : array<string, int>
- Sums the $hours most recent hour buckets, per event type.
- creationell_captcha_render_analytics_page() : void
- Renders the tabbed analytics dashboard page.
- creationell_captcha_render_analytics_overview() : void
- Renders the "Übersicht" tab: four 24-hour KPI tiles and one four-window comparison table per thematic group.
- creationell_captcha_render_analytics_history() : void
- Renders the "Verlauf" tab: the day-by-day table for the last 30 days.
- creationell_captcha_events_query_args() : array{search: string, event_type: string, date_from: string, date_to: string}
- Reads and sanitises the event-log filter from the request.
- creationell_captcha_render_analytics_events() : void
- Renders the "Ereignisse" tab: filter toolbar, the event table with a detail link per row, the embedded event data and the detail modal.
- creationell_captcha_render_event_modal() : void
- Renders the (initially hidden) event-detail modal skeleton.
- creationell_captcha_render_events_toolbar() : void
- Renders the event-log filter toolbar: search, type filter, date range, the "Filtern"/"Zurücksetzen" controls and the CSV export link.
- creationell_captcha_render_events_pagination() : void
- Renders the pagination navigation below the event-log table.
- creationell_captcha_analytics() : Analytics
- Returns the shared analytics recorder instance.
- creationell_captcha_record_event() : void
- Records a security event of the given type.
- creationell_captcha_enqueue_admin_assets() : void
- Enqueues admin styles and scripts on the CreaCaptcha admin pages.
- creationell_captcha_cloudflare_snapshot() : array{v4: string[], v6: string[], updated_at: string}
- Returns the bundled Cloudflare snapshot.
- creationell_captcha_cloudflare_ranges() : array<string|int, string>
- The active CF range list: cached option (if fresh and valid) → bundled snapshot.
- creationell_captcha_refresh_cloudflare_ips_now() : array{ok: bool, v4: int, v6: int, fetched_at: int|null, error: string|null}
- Fetches the live Cloudflare ranges and writes them to the cache option.
- creationell_captcha_clear_cloudflare_cache() : bool
- Deletes the cached Cloudflare-range option. The next read falls back to the bundled snapshot. Returns true when the option existed and was deleted, false when the option was absent or the delete failed.
- creationell_captcha_fetch_cloudflare_list() : array<string|int, string>
- Fetches one Cloudflare endpoint and returns the valid CIDR entries.
- creationell_captcha_sync_cloudflare_cron() : void
- Ensures the daily refresh cron slot is in sync with the auto-refresh toggle.
- creationell_captcha_code_challenge_font_usable() : bool
- Probes whether GD can actually render text with the vendored TTF font.
- creationell_captcha_render_code_image() : string
- Renders a PNG of the given code and returns the raw bytes. Caller is responsible for emitting headers (`Content-Type: image/png`, `Cache-Control: no-store`) and the body.
- creationell_captcha_should_issue_code_challenge() : bool
- Decides whether the current /challenge request should attach a code-challenge instruction. Returns true iff:
- creationell_captcha_code_charset() : string
- Returns the active charset string for the configured option.
- creationell_captcha_generate_code() : string
- Generates a fresh random code from the configured charset.
- creationell_captcha_code_token_issue() : string
- Issues a token backed by a server-side WP transient that stores the expected code for at most $expiry_seconds. Returns the opaque ID that `/code-image` and `/code-verify` use to look the code up again.
- creationell_captcha_code_token_verify() : string|null
- Looks up the code for a token and returns it, or null on: - malformed token (not 32 hex chars) - missing transient (expired or unknown)
- creationell_captcha_code_fail_key() : string
- Transient name of the per-token failed-attempt counter (CM-3).
- creationell_captcha_code_token_fail() : void
- Records one wrong image-code submission for a token and invalidates the token once its attempt budget is spent.
- creationell_captcha_rest_code_image() : WP_REST_Response
- Handles GET /code-image?t=<token>. Looks up the code from the token (server-side transient), renders the PNG, returns 410 on token failure.
- creationell_captcha_rest_code_verify() : WP_REST_Response
- Handles POST /code-verify. Two body shapes:
- creationell_captcha_register_code_challenge_routes() : void
- Registers the two code-challenge REST routes. The /challenge handler itself stays in includes/rest.php; Task 11 extends it with the codeChallenge field and the data.ccode embed.
- creationell_captcha_register_email_obfuscation() : void
- Registers email obfuscation on `init`. The decoder script is always registered; then, unless the kill-switch is set or the feature is off, the configured mode is wired up — the content filters (module 7) or the full-page output buffer (module 8).
- creationell_captcha_register_email_buffer() : void
- Wires up the full-page-buffer mode: on `template_redirect` for non-feed front-end requests it enqueues the decoder script and starts an output buffer whose callback obfuscates the page body at flush time.
- creationell_captcha_email_buffer_filter() : string
- The buffer callback itself: decides whether this response gets rewritten and hands it to the obfuscator when it does.
- creationell_captcha_response_is_html() : bool
- Whether the response being buffered is (still) an HTML document.
- creationell_captcha_run_firewall() : void
- Runs the IP/user-agent firewall. Hooked on `init` at priority 0 so it fires before the rate limiter, the interceptor and any form-processing handler.
- creationell_captcha_comments_active() : bool
- Whether comment protection applies to the current request.
- creationell_captcha_comments_render() : string
- Injects the widget just above the comment form submit button.
- creationell_captcha_comments_verify() : array<string, mixed>
- Verifies the captcha before a comment is accepted.
- creationell_captcha_login_enabled() : bool
- Whether login protection is enabled.
- creationell_captcha_login_render() : void
- Renders the widget inside the login form.
- creationell_captcha_login_form_middle() : string
- Renders the widget inside `wp_login_form()`-based forms.
- creationell_captcha_login_is_interactive() : bool
- Whether the current request is an interactive, browser-submitted login attempt — as opposed to XML-RPC, REST/Application-Passwords, AJAX or a programmatic `wp_signon()` call made by other code.
- creationell_captcha_login_verify() : WP_User|WP_Error|null
- Verifies the captcha during an interactive login.
- creationell_captcha_password_reset_enabled() : bool
- Whether password-reset protection is enabled.
- creationell_captcha_password_reset_render() : void
- Renders the widget inside the lost-password form.
- creationell_captcha_password_reset_exempt_reason() : string|null
- Why the current request is not a public lost-password form submission.
- creationell_captcha_password_reset_exempt_label() : string
- Human-readable label for an exemption reason, for the event log.
- creationell_captcha_password_reset_verify() : void
- Verifies the captcha during a password-reset request.
- creationell_captcha_registration_enabled() : bool
- Whether registration protection is enabled.
- creationell_captcha_registration_render() : void
- Renders the widget inside the registration form.
- creationell_captcha_registration_verify() : WP_Error
- Verifies the captcha during registration.
- creationell_captcha_hardening_covered_family() : string
- Ermittelt, WELCHE Adressfamilie ein gespeicherter Listeneintrag vollständig abdeckt und die Liste für diese Familie damit als Auswahl aufhebt.
- creationell_captcha_hardening_mapped_ipv4_cidr() : string
- Erkennt einen CIDR-Bereich, der in IPv4-mapped Schreibweise notiert ist (`::ffff:0:0/96`, `::ffff:203.0.113.0/120`), und liefert seine gewöhnliche IPv4-Schreibweise zurück.
- creationell_captcha_hardening_is_catch_all_pattern() : bool
- Prüft, ob ein `bypass_ua_allow`-Muster jeden Browser-Kennzeichner trifft.
- creationell_captcha_hardening_matches_every_user_agent() : bool
- Prüft, ob ein `bypass_ua_allow`-Muster jeden realistischen User-Agent trifft, ohne vom Catch-all-Guard aussortiert zu werden (Befund B-M2).
- creationell_captcha_hardening_scan() : array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>
- Durchsucht einen Satz Einstellungen nach gefährlicher Bestandskonfiguration.
- creationell_captcha_hardening_findings() : array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>
- Führt den Scan gegen die aktuell gespeicherten Einstellungen aus.
- creationell_captcha_hardening_fingerprint() : string
- Kurzkennung des aktuellen Fundbildes.
- creationell_captcha_hardening_dismiss_redirect_target() : string
- Baut das Ziel des Redirects nach dem Ausblenden-Klick: dieselbe Seite ohne den Ausblenden-Parameter und ohne die Nonce.
- creationell_captcha_hardening_handle_dismiss() : void
- Nimmt den Ausblenden-Klick entgegen.
- creationell_captcha_hardening_render_notice() : void
- Zeigt die gefundene Bestandskonfiguration als Admin-Hinweis.
- creationell_captcha_resolve_widget_locale() : string|null
- Resolves the active WordPress locale to a vendored ALTCHA locale code.
- creationell_captcha_get_default_settings() : array<string, mixed>
- Default plugin settings.
- creationell_captcha_get_settings() : array<string, mixed>
- Current plugin settings, merged over the defaults.
- creationell_captcha_invalidate_settings_cache() : void
- Drops the in-request settings cache. Wired to the option-change hooks below so callers that read settings after an update see the fresh value.
- creationell_captcha_is_disabled() : bool
- Whether the captcha is globally disabled via the wp-config constant.
- creationell_captcha_sodium_available() : bool
- Whether ext-sodium (required for Argon2id) is available.
- creationell_captcha_generate_secrets() : array{signature: string, key_signature: string}
- Generates both HMAC secrets and persists them (non-autoloaded).
- creationell_captcha_get_secret() : string
- Returns a stored HMAC secret, generating + persisting it on first use.
- creationell_captcha_get_hmac_secret() : string
- The HMAC signature secret (signs each challenge).
- creationell_captcha_get_hmac_key_secret() : string
- The HMAC key-signature secret (enables the fast verification path).
- creationell_captcha_derive_hmac_key() : string
- Derives a purpose-bound HMAC key from the plugin's base signature secret.
- creationell_captcha_client_ua() : string
- The request's User-Agent, capped at 256 bytes. MAC input for the under-attack tokens — never rendered, never stored.
- creationell_captcha_underattack_pass_binding() : string
- The fingerprint the under-attack pass cookie is bound to (BK-3).
- creationell_captcha_underattack_pass_issue() : string
- Mints an under-attack pass token for the current visitor.
- creationell_captcha_underattack_pass_check() : bool
- Verifies an under-attack pass token against the CURRENT visitor.
- creationell_captcha_underattack_ctx_issue() : string
- Mints a single-use `ctx` suppression token for one interstitial rendering.
- creationell_captcha_underattack_ctx_check() : bool
- Verifies a `ctx` suppression token and consumes it.
- creationell_captcha_engine() : Engine
- Shared captcha engine instance.
- creationell_captcha_log() : void
- Write a message to the debug log when CREATIONELL_CAPTCHA_DEBUG is active.
- creationell_captcha_normalize_ip() : string
- Canonicalises an IPv4-mapped IPv6 address (`::ffff:a.b.c.d`) into its plain IPv4 spelling. Every other value — including anything that is not an IP at all — is handed back unchanged.
- creationell_captcha_get_client_ip() : string
- Resolves the client IP address.
- creationell_captcha_ip_in_list() : bool
- Whether an IP matches any entry in a list of IPs or CIDR ranges.
- creationell_captcha_ip_in_cidr() : bool
- Whether an IP falls within a CIDR range. Supports IPv4 and IPv6.
- creationell_captcha_is_valid_ip_or_cidr() : bool
- Whether a string is a valid IP address or CIDR range (IPv4 or IPv6).
- creationell_captcha_wildcard_match() : bool
- Whether a subject matches any of the given wildcard patterns (case-insensitive).
- creationell_captcha_private_ranges() : array<string|int, string>
- Returns the canonical list of private/loopback CIDR ranges used when the `firewall_trust_private_ranges` toggle is active.
- creationell_captcha_trusted_proxies_constant() : array<string|int, string>
- Reads the optional `CREATIONELL_CAPTCHA_TRUSTED_PROXIES` wp-config constant as a list. Accepts either a string array or a comma/whitespace-separated scalar; invalid entries are dropped.
- creationell_captcha_is_trusted_proxy() : bool
- Whether the given IP belongs to a trusted upstream proxy.
- creationell_captcha_evaluate_bypass() : array{reason: string, source: string}|false
- Pure bypass evaluator — checks the three bypass sources against the supplied inputs without touching $_SERVER, $_COOKIE or any static cache. The caller is responsible for providing the values.
- creationell_captcha_request_bypassed() : array{reason: string, source: string}|false
- Whether the current request is allowed to bypass captcha, under-attack and firewall protections. Reads $_SERVER, $_COOKIE and the request's client IP, then delegates to `creationell_captcha_evaluate_bypass()`.
- creationell_captcha_validate_action_pattern() : string|null
- Validates a single interceptor-action pattern.
- creationell_captcha_validate_cookie_entry() : string|null
- Validates a single bypass-cookie entry of the form `name=value`.
- creationell_captcha_anonymize_ip() : string
- Truncates an IP for DSGVO-compliant storage. IPv4 → last octet zeroed, IPv6 → last 80 bits zeroed. Invalid IPs return ''.
- creationell_captcha_request_body_fingerprint() : string
- Returns a JSON-encoded fingerprint of $_POST: { field-name: value-byte-length }.
- creationell_captcha_block_response() : void
- Sends a fail-closed block response and terminates the request.
- creationell_captcha_base64url_encode() : string
- Base64URL encoder (RFC 4648 §5) — strips standard-base64 padding and replaces +/ with -_ so the value is URL-safe.
- creationell_captcha_base64url_decode() : string
- Base64URL decoder — accepts unpadded URL-safe input and returns the raw bytes. Returns the empty string on malformed input (no exceptions).
- creationell_captcha_ratelimit_current_count() : int
- Reads the current rate-limit counter for an IP without incrementing it.
- creationell_captcha_cf7_active() : bool
- Whether the Contact Form 7 integration is active.
- creationell_captcha_cf7_register_tag() : void
- Registers the [creationell_captcha] Contact Form 7 form-tag.
- creationell_captcha_cf7_tag_handler() : string
- Renders the widget for the [creationell_captcha] form-tag.
- creationell_captcha_cf7_auto_inject() : string
- Auto-injects the widget into CF7 forms without a [creationell_captcha] tag.
- creationell_captcha_cf7_verify() : bool
- Verifies the captcha on a Contact Form 7 submission.
- creationell_captcha_forminator_active() : bool
- Whether the Forminator integration is active.
- creationell_captcha_forminator_inject() : string
- Auto-injects the widget before the submit button of a Forminator custom form.
- creationell_captcha_forminator_verify() : array<int, array<string, string>>
- Verifies the captcha on a Forminator custom-form submission.
- creationell_captcha_woocommerce_active() : bool
- Whether any WooCommerce protection applies right now.
- creationell_captcha_wc_uses_block_checkout() : bool
- Whether the currently configured WooCommerce checkout page uses the Block Checkout (the `woocommerce/checkout` block) rather than the classic `[woocommerce_checkout]` shortcode.
- creationell_captcha_wc_checkout_active() : bool
- Whether the WooCommerce checkout protection is active.
- creationell_captcha_wc_checkout_render() : void
- Renders the widget directly before the Place-Order button on the checkout.
- creationell_captcha_wc_checkout_verify_cache() : bool|null
- Per-request cache of the checkout's own verify verdict, keyed by the raw `altcha` payload currently in `$_POST`.
- creationell_captcha_wc_checkout_verify() : void
- Verifies the captcha during checkout validation.
- creationell_captcha_wc_login_active() : bool
- Whether the WooCommerce my-account login protection is active.
- creationell_captcha_wc_login_render() : void
- Renders the widget at the bottom of the WooCommerce login form.
- creationell_captcha_wc_login_verify() : mixed
- Verifies the captcha on a WooCommerce my-account login submission.
- creationell_captcha_wc_registration_active() : bool
- Whether the WooCommerce registration protection is active.
- creationell_captcha_wc_registration_render() : void
- Renders the widget at the bottom of the WooCommerce registration form.
- creationell_captcha_wc_nonce_verifies() : bool
- Reads a nonce value the way the given WooCommerce field name(s) would, replicating WooCommerce core's own field-name-agnostic fallback: the dedicated field wins if present, otherwise the generic `_wpnonce` field is used. WooCommerce's own nonce checks do not care WHICH field carried the value — only whether the value itself verifies against the action name.
- creationell_captcha_wc_registration_form_context() : bool
- Whether the current `wc_create_new_customer()` call originates from a genuine WooCommerce registration- or checkout-form POST, as opposed to a Store-API, programmatic or CLI call that never had a form (and therefore no `altcha` field) in the first place.
- creationell_captcha_wc_registration_verify() : mixed
- Verifies the captcha during WooCommerce my-account registration.
- creationell_captcha_wc_lost_password_active() : bool
- Whether the WooCommerce lost-password render is active.
- creationell_captcha_wc_lost_password_render() : void
- Renders the widget inside the WooCommerce lost-password form.
- creationell_captcha_wpforms_active() : bool
- Whether the WPForms integration is active.
- creationell_captcha_wpforms_inject() : void
- Auto-injects the widget directly before the WPForms submit button.
- creationell_captcha_wpforms_verify() : void
- Verifies the captcha on a WPForms submission.
- creationell_captcha_interceptor_inject_buffer_start() : void
- Conditionally starts the output buffer on template_redirect priority 0.
- creationell_captcha_interceptor_inject_buffer() : string
- Buffer callback. Replaces every `<form …>…</form>` with the same form plus an `<altcha-widget>` inserted directly before `</form>`. Idempotent — forms that already contain `<altcha-widget` are returned unchanged.
- creationell_captcha_run_interceptor() : void
- Runs the request interceptor. Hooked on `init` at priority 1 so it fires before any form-processing handler.
- creationell_captcha_protect_path() : void
- Registers one or more path patterns to be guarded by the interceptor.
- creationell_captcha_activate() : void
- Runs on plugin activation: seeds default options and HMAC secrets.
- creationell_captcha_deactivate() : void
- Runs on plugin deactivation: clears every cron slot the plugin owns, sweeps the expired replay markers out of the options table and lets every module react via the `creationell_captcha_deactivated` action hook.
- creationell_captcha_deactivate_site() : void
- The deactivation work for exactly one site: clears the three cron slots this plugin owns there and sweeps that site's expired replay markers.
- creationell_captcha_deactivate_network() : int
- Runs the per-site deactivation work on every site of the network.
- creationell_captcha_delete_expired_replay_markers() : int
- Deletes those replay-marker options of the current site whose lifetime has already run out.
- creationell_captcha_run_rate_limiter() : void
- Runs the per-IP rate limiter. Hooked on `init` at priority 0; registered after the firewall so the firewall runs first.
- creationell_captcha_request_path() : string
- Returns the path portion of the current request, the way WordPress core derives it — NOT the way a URL parser would.
- creationell_captcha_request_target() : string
- Der aktuelle Request als relativer Ziel-URI: Pfad aus `creationell_captcha_request_path()`, Query aus derselben Zeichenkette.
- creationell_captcha_current_rest_route() : string
- Returns the REST route the current request actually addresses.
- creationell_captcha_normalize_rest_route() : string
- Normalises a raw `rest_route` value into the plugin's canonical route form.
- creationell_captcha_register_rest_routes() : void
- Registers the public challenge route.
- creationell_captcha_rest_challenge() : WP_REST_Response
- Returns a fresh, single-use challenge. Records the issuance via the standard event channel — aggregate counters always increment, the detail-log entry is gated by the `log_challenge` per-type toggle from Modul 11c.
- creationell_captcha_canonical_params_json() : string
- Canonical-JSON serialisation of ALTCHA challenge parameters, byte- identical to `altcha-lib-php`'s `ChallengeParameters::toCanonicalJson()` (= ksort top-level + recursive ksort on assoc sub-arrays, JSON-encoded with UNESCAPED_SLASHES | UNESCAPED_UNICODE, null keys dropped).
- creationell_captcha_canonical_sort_recursive() : void
- Recursive helper used by `canonical_params_json` — mirrors the lib's `sortRecursive`. List arrays (sequential integer keys) keep their order; associative arrays get `ksort`-ed in place.
- creationell_captcha_list_setting_keys() : array<int, string>
- Setting keys whose value is a list (every `textarea` field).
- creationell_captcha_export_settings() : array<string, mixed>
- Builds the settings-export payload.
- creationell_captcha_import_settings() : array<string, mixed>|WP_Error
- Validates and applies a settings-export payload.
- creationell_captcha_reset_settings() : void
- Full factory reset: writes the complete default settings array, which also empties every list and drops stored keys outside the current field specification. Secrets, analytics counters and the event log are left untouched.
- creationell_captcha_load_default_settings() : void
- Resets every non-list setting to its default while preserving the current list values (IP block/allow, UA block, interceptor paths).
- creationell_captcha_admin_tabs() : array<string, string>
- Ordered list of the admin settings tabs.
- creationell_captcha_admin_sections() : array<string, array<string, string>>
- Settings sections and the tab each one belongs to.
- creationell_captcha_settings_fields() : array<string, array<string, mixed>>
- Field specification for the captcha settings.
- creationell_captcha_register_settings() : void
- Registers the plugin setting, the per-tab sections and the fields.
- creationell_captcha_sanitize_context() : string|null
- Liest — und setzt optional — den angehefteten Sanitizer-Kontext.
- creationell_captcha_with_sanitize_context() : mixed
- Führt $callback aus, während der Sanitizer-Kontext auf $context festgelegt ist.
- creationell_captcha_sanitize_settings_option() : array<string, mixed>
- Einstiegspunkt des `sanitize_option_creationell_captcha_settings`-Filters.
- creationell_captcha_truncate_setting_text() : string
- Kürzt einen Einstellungswert auf höchstens $max_chars Zeichen und garantiert gültiges UTF-8.
- creationell_captcha_truncate_chars() : string
- Kürzt gültiges UTF-8 ohne mbstring auf $max_chars ZEICHEN.
- creationell_captcha_truncate_chars_by_bytes() : string
- Dritte und letzte Kürzungsstufe: zählt UTF-8-Startbytes.
- creationell_captcha_sanitize_settings() : array<string, mixed>
- Sanitises the settings array before it is stored.
- creationell_captcha_sync_event_log_table() : void
- Legt die Ereignis-Log-Tabelle an, sobald das Detail-Log eingeschaltet GESPEICHERT wurde.
- creationell_captcha_store_settings() : void
- Der eine Schreibweg des Plugins auf `creationell_captcha_settings`.
- creationell_captcha_render_engine_section() : void
- Renders the description shown at the top of the Proof-of-Work-Engine section.
- creationell_captcha_render_widget_appearance_section() : void
- Renders the description shown at the top of the widget-appearance section.
- creationell_captcha_render_code_challenge_section() : void
- Renders the description shown at the top of the code-challenge section.
- creationell_captcha_render_core_forms_section() : void
- Renders the description shown at the top of the core-forms section.
- creationell_captcha_render_interceptor_section() : void
- Renders the description shown at the top of the interceptor section.
- creationell_captcha_render_form_plugins_section() : void
- Renders the description shown at the top of the form-plugins section.
- creationell_captcha_render_proxy_section() : void
- Renders the description shown at the top of the proxy section.
- creationell_captcha_render_bypass_section() : void
- Renders the description shown at the top of the bypass section.
- creationell_captcha_render_firewall_section() : void
- Renders the description shown at the top of the firewall section.
- creationell_captcha_render_ratelimit_section() : void
- Renders the description shown at the top of the rate-limiting section.
- creationell_captcha_render_underattack_section() : void
- Renders the description shown at the top of the under-attack section.
- creationell_captcha_render_underattack_appearance_section() : void
- Renders the description shown at the top of the under-attack appearance section.
- creationell_captcha_render_analytics_section() : void
- Renders the description shown at the top of the analytics section.
- creationell_captcha_render_email_section() : void
- Renders the description shown at the top of the email-protection section.
- creationell_captcha_render_field() : void
- Renders a single settings field.
- creationell_captcha_field_label() : string
- Liefert das Label eines Einstellungsfeldes (Fallback: der Schlüssel selbst).
- creationell_captcha_field_spec() : array<string, array<string, mixed>>
- Request-lokal gehaltene Feldspezifikation für die beiden Label-Helfer.
- creationell_captcha_field_option_label() : string
- Liefert das Options-Label eines `select`-Feldes (Fallback: der Rohwert).
- creationell_captcha_tools_redirect() : never
- Stores a one-shot admin notice and redirects back to the Werkzeuge page.
- creationell_captcha_tools_guard() : void
- Guards a tools action: requires manage_options and a valid nonce.
- creationell_captcha_handle_export_settings() : void
- Streams the current settings as a JSON download.
- creationell_captcha_handle_import_settings() : void
- Handles the settings-import upload.
- creationell_captcha_collect_settings_error_messages() : array<int, string>
- Collects the plugin's queued settings-error messages, de-duplicated.
- creationell_captcha_handle_reset_settings() : void
- Handles the full factory reset.
- creationell_captcha_handle_load_defaults() : void
- Handles "load defaults" (keeps the lists).
- creationell_captcha_handle_cloudflare_refresh() : void
- Triggers a manual Cloudflare-range refresh from the Werkzeuge page.
- creationell_captcha_handle_cloudflare_clear() : void
- Empties the cached Cloudflare-range option from the Werkzeuge page.
- creationell_captcha_register_tools_page() : void
- Registers the "Werkzeuge" submenu page under the CreaCaptcha menu.
- creationell_captcha_render_tools_notice() : void
- Renders the one-shot admin notice left behind by a tools action.
- creationell_captcha_render_tools_page() : void
- Renders the "Werkzeuge" page.
- creationell_captcha_render_cloudflare_status() : void
- Renders the Cloudflare-cache status block inside the Werkzeuge tool card.
- creationell_captcha_run_under_attack() : void
- Runs the under-attack interstitial gate for front-end page views. Hooked on `template_redirect` — fires only for front-end requests, so wp-admin, wp-login.php, REST and cron are inherently exempt.
- creationell_captcha_maybe_upgrade() : void
- Runs schema migrations when the stored version differs from the running one.
- creationell_captcha_migrate_widget_mode() : void
- Migrates the legacy `widget_mode` setting (Modul 11a) to the new `widget_display` + `widget_auto_trigger` pair (Modul 14). Idempotent — if `widget_display` is already present in the stored option, the migration is skipped.
- creationell_captcha_store_migrated_settings() : void
- Writes a migrated settings array back — through the sanitiser, with the write context pinned.
- creationell_captcha_register_assets() : void
- Registers the widget script and — for Argon2id — its worker registration.
- creationell_captcha_safe_inline_css() : string
- Macht eine gespeicherte CSS-Zeichenkette sicher für die Ausgabe in einem `<style>`-Element.
- creationell_captcha_build_widget_markup() : string
- Builds the ALTCHA widget markup as a plain string. Enqueues the widget script as a side effect.
- creationell_captcha_render_widget() : void
- Renders the ALTCHA widget markup and enqueues its assets.
- creationell_captcha_verify_payload() : bool
- Verifies a raw base64 ALTCHA payload string.
- creationell_captcha_verify_request() : bool
- Reads and verifies the ALTCHA payload from the current POST request.
- creationell_captcha_widget() : void
- Public template tag — renders the ALTCHA widget.
- creationell_captcha_get_widget_markup() : string
- Returns the ALTCHA widget markup as a string.
- creationell_captcha_widget_shortcode() : string
- Shortcode handler for [creationell_captcha].
- creationell_captcha_uninstall_site() : void
- Removes every trace of the plugin from the site that is currently switched to: options, replay markers, transients, cron slots and the event-log table.
- creationell_captcha_uninstall_user_meta() : void
- Removes the plugin's per-user data.
Constants
CREATIONELL_CAPTCHA_EXPORT_MAX_ROWS
The default upper bound on rows in a single CSV export.
public
mixed
CREATIONELL_CAPTCHA_EXPORT_MAX_ROWS
= 50000
CREATIONELL_CAPTCHA_EXPORT_SCHEMA
Schema version of the settings-export format.
public
mixed
CREATIONELL_CAPTCHA_EXPORT_SCHEMA
= 1
CREATIONELL_CAPTCHA_HARDENING_DISMISS_ARG
Query-Parameter des Ausblenden-Links.
public
mixed
CREATIONELL_CAPTCHA_HARDENING_DISMISS_ARG
= 'creationell_captcha_hardening_dismiss'
CREATIONELL_CAPTCHA_HARDENING_DISMISS_META
User-Meta-Schlüssel, unter dem ein Benutzer den Hinweis wegklickt.
public
mixed
CREATIONELL_CAPTCHA_HARDENING_DISMISS_META
= 'creationell_captcha_hardening_dismissed'
CREATIONELL_CAPTCHA_LOCALE_MAP
Maps WordPress locales to the matching ALTCHA i18n locale bundle.
public
mixed
CREATIONELL_CAPTCHA_LOCALE_MAP
= [
// Deutsch
'de_DE' => 'de',
'de_DE_formal' => 'de',
'de_AT' => 'de',
'de_CH' => 'de',
'de_CH_informal' => 'de',
// Englisch
'en_US' => 'en',
'en_GB' => 'en',
'en_AU' => 'en',
'en_CA' => 'en',
'en_NZ' => 'en',
'en_ZA' => 'en',
// Französisch
'fr_FR' => 'fr-fr',
'fr_BE' => 'fr-fr',
'fr_LU' => 'fr-fr',
'fr_CH' => 'fr-fr',
'fr_CA' => 'fr-ca',
// Spanisch (Europa)
'es_ES' => 'es-es',
// Spanisch (LatAm — alle nach es-419)
'es_AR' => 'es-419',
'es_CL' => 'es-419',
'es_CO' => 'es-419',
'es_CR' => 'es-419',
'es_DO' => 'es-419',
'es_EC' => 'es-419',
'es_GT' => 'es-419',
'es_HN' => 'es-419',
'es_MX' => 'es-419',
'es_PA' => 'es-419',
'es_PE' => 'es-419',
'es_PR' => 'es-419',
'es_UY' => 'es-419',
'es_VE' => 'es-419',
// Portugiesisch
'pt_PT' => 'pt-pt',
'pt_AO' => 'pt-pt',
'pt_BR' => 'pt-br',
// Italienisch / Niederländisch / Polnisch
'it_IT' => 'it',
'nl_NL' => 'nl',
'nl_BE' => 'nl',
'pl_PL' => 'pl',
// Tschechisch / Slowakisch
'cs_CZ' => 'cs',
'sk_SK' => 'sk',
// Nordisch
'da_DK' => 'da',
'sv_SE' => 'sv',
'fi' => 'fi',
'nb_NO' => 'nb',
'nn_NO' => 'nb',
// Sonstige EU
'hu_HU' => 'hu',
'ro_RO' => 'ro',
'el' => 'el',
]
Keys are values returned by get_locale() — including the formal /
informal variants WP exposes (de_DE_formal, de_CH_informal). Values
are the exact locale codes used by the vendored ALTCHA bundles under
assets/js/altcha-i18n/<code>.js. Locales not in the map fall through
to the widget's own auto-detection (which itself falls back to English
since only the bundles enqueued by this plugin are registered).
Extend via the creationell_captcha_widget_locale_map filter rather
than patching this constant.
CREATIONELL_CAPTCHA_PURPOSE_UA_CTX
Purpose label of the under-attack code-challenge suppression token (`ctx`).
public
mixed
CREATIONELL_CAPTCHA_PURPOSE_UA_CTX
= 'underattack-ctx'
CREATIONELL_CAPTCHA_PURPOSE_UA_PASS
Purpose label of the under-attack pass-cookie key.
public
mixed
CREATIONELL_CAPTCHA_PURPOSE_UA_PASS
= 'underattack-pass'
CREATIONELL_CAPTCHA_SANITIZE_FORM
Sanitiser context: the settings form (`options.php`).
public
mixed
CREATIONELL_CAPTCHA_SANITIZE_FORM
= 'form'
Ein disabled-Input sendet beim Absenden keinen Wert — deshalb (und NUR
deshalb) schreibt der Sanitizer in diesem Kontext gegatete Felder aus dem
gespeicherten Zustand zurück.
CREATIONELL_CAPTCHA_SANITIZE_PROGRAMMATIC
Sanitiser context: programmatischer Schreibvorgang (Import, Werksreset, „Standardwerte laden", WP-CLI).
public
mixed
CREATIONELL_CAPTCHA_SANITIZE_PROGRAMMATIC
= 'programmatic'
Hier ist der übergebene Wert eine vollständige, bewusste Angabe; eine Rückschreibung aus dem Altzustand würde sie still überstimmen (AF-1/AF-3).
CREATIONELL_CAPTCHA_SANITIZE_RESET
Sanitiser context: Werksreset (`creationell_captcha_reset_settings()`).
public
mixed
CREATIONELL_CAPTCHA_SANITIZE_RESET
= 'reset'
Wie PROGRAMMATIC — plus die eine Zusage, die den Werksreset von jedem anderen Schreibvorgang unterscheidet: KEIN gespeicherter Schlüssel wird übernommen, auch keiner ausserhalb der aktuellen Feldspezifikation.
Warum das ein eigener Wert sein muss (Nachlese N6, Befund 5): Die
Übernahme-Schleife am Ende von creationell_captcha_sanitize_settings()
hält gespeicherte Schlüssel fest, die creationell_captcha_settings_fields()
gerade nicht liefert — die Schalter der Formular-Plugins hängen an
class_exists(), sind bei deaktiviertem Plugin also nicht in der
Spezifikation. Für jeden regulären Schreibvorgang ist das richtig
(sonst löschte ein Speichern der Einstellungsseite den Schalter eines
gerade deaktivierten Plugins). Für den dokumentierten „vollen Werksreset"
ist es falsch: wp creacaptcha settings reset --yes meldete
„Auf Werkseinstellungen zurückgesetzt", während ein protect_wpforms => false
aus der Zeit vor der Deaktivierung — oder ein beliebiger Fremdschlüssel aus
wp option patch/DB-Restore — die Option unverändert überlebte.
Der Wert ersetzt PROGRAMMATIC ausschliesslich beim Reset. Import, „Standardwerte laden", WP-CLI-Schreibvorgänge und die Listen-Befehle bleiben auf PROGRAMMATIC: sie übergeben zwar ebenfalls einen vollständigen Wertesatz, sind aber kein Werksreset und dürfen den Schalter eines gerade inaktiven Formular-Plugins nicht wegräumen.
Tags
CREATIONELL_CAPTCHA_UA_CTX_TTL
Lifetime (seconds) of a `ctx` suppression token. The interstitial widget fetches the challenge on load, i.e. within seconds of the 503 being rendered; two minutes is generous for that and replaces the ~10 minutes the old 5-minute-bucket pair accepted (BK-4).
public
mixed
CREATIONELL_CAPTCHA_UA_CTX_TTL
= 120
Functions
creationell_captcha_register_admin_menu()
Registers the top-level "CreaCaptcha" admin menu entry.
creationell_captcha_register_admin_menu() : void
creationell_captcha_render_settings_page()
Renders the tabbed settings page.
creationell_captcha_render_settings_page() : void
All tabs share one
creationell_captcha_render_import_warning()
Renders the "an import replaces everything" warning on the Werkzeuge page.
creationell_captcha_render_import_warning() : void
AF-6: Der Import prüft nur den Envelope (plugin/type/schema), keine
Herkunft — das exportierte site_url wurde nie verglichen — und ungenannte
Schlüssel fallen auf ihren Default, ein Teilimport ist also ein Vollreset.
Das ist manage_options-gated und damit kein anonymer Vektor; statt
Signaturinfrastruktur bekommt der Vorgang eine Warnhinweis-Ebene: hier vor
dem Import, und nach dem Import benennt die Erfolgsmeldung die Herkunft der
Datei (siehe creationell_captcha_handle_import_settings()).
creationell_captcha_render_trust_notice()
Renders the "proxy mode on but trust-set empty" admin notice on plugin pages.
creationell_captcha_render_trust_notice() : void
The notice is persistent (not dismissible) — it disappears automatically as soon as any trust source is configured.
creationell_captcha_tabbed_page_hooks()
Records and reports the admin-page hook suffixes of the plugin's tab-organised pages.
creationell_captcha_tabbed_page_hooks([mixed $add = null ]) : array<int, string>
A submenu page's hook suffix derives from the sanitised parent menu title, not its slug, so it cannot be reliably hardcoded. Each tabbed page passes the value WordPress returns from add_menu_page()/add_submenu_page() here, and the admin asset loader matches the current screen against the recorded set.
Parameters
- $add : mixed = null
-
Hook suffix to record; ignored unless a non-empty string.
Return values
array<int, string> —All recorded hook suffixes.
creationell_captcha_active_tab()
Determines the active tab from the request, whitelisted against $tabs.
creationell_captcha_active_tab(array<string, string> $tabs) : string
Falls back to the first tab when no valid tab query parameter is present.
The value only selects which panel is shown and is strictly whitelisted
against the given registry, so no nonce check is required.
Parameters
- $tabs : array<string, string>
-
Tab registry (id => label).
Return values
string —Active tab id.
creationell_captcha_render_nav_tabs()
Renders the no-JavaScript fallback style and the nav-tab bar.
creationell_captcha_render_nav_tabs(array<string, string> $tabs, string $active_tab, string $base_url) : void
Without JavaScript the per-tab panels would each be hidden by admin.css; the
Parameters
- $tabs : array<string, string>
-
Tab registry (id => label).
- $active_tab : string
-
Active tab id.
- $base_url : string
-
Page URL the tab links point at.
creationell_captcha_csv_cell()
Neutralises a CSV cell against spreadsheet formula injection.
creationell_captcha_csv_cell(string $value) : string
A value beginning with =, +, - or @ can be executed as a formula by Excel or
LibreOffice; a leading single quote forces the spreadsheet to treat the value
as text. The path, user_agent, referrer, verification_data and
request_body columns all carry bytes an anonymous sender chose.
B-M10: TAB and CR belong in the same list. Both are stripped as leading
whitespace while the cell is parsed, so \treq(...) and \r=1+1 reach the
formula parser as if the control character were not there — the value is
then executed exactly like the four printable starters above. OWASP names
them alongside; they were the two the original list missed.
NOT covered, deliberately: a value whose formula starter sits behind other text, and a cell that is dangerous only inside a quoted field. The first is not a formula for any spreadsheet, the second is handled by fputcsv()'s quoting. What this function promises is exactly "the first character cannot start a formula".
Parameters
- $value : string
-
Raw cell value.
Return values
stringcreationell_captcha_export_events()
Streams the filtered event log as a CSV download.
creationell_captcha_export_events() : void
Hooked to admin-post.php. Requires the manage_options capability and a
valid nonce. The filter (search, event type, date range) is read from the
request via the shared parser, so the export mirrors the on-screen filter.
Every reason to refuse is checked BEFORE the first byte goes out. Once the Content-Disposition header and the BOM are on the wire the response is committed to being a successful download, and anything that goes wrong afterwards reaches the admin as a file that looks complete (AF-10/DS-9).
creationell_captcha_register_analytics_page()
Registers the "Statistik" submenu page under the CreaCaptcha menu.
creationell_captcha_register_analytics_page() : void
creationell_captcha_analytics_labels()
Human-readable labels for the seven event types.
creationell_captcha_analytics_labels() : array<string, string>
Return values
array<string, string>creationell_captcha_analytics_groups()
Thematic groups for the overview tab: title, explanation and the member event types with their short in-group labels.
creationell_captcha_analytics_groups() : array<int, array{title: string, description: string, types: array}>
Together the groups cover all seven event types exactly once. The long labels from creationell_captcha_analytics_labels() stay untouched for the filter dropdown, CSV export, CLI and history tab.
Return values
array<int, array{title: string, description: string, types: arraycreationell_captcha_analytics_tabs()
The three tabs of the analytics page.
creationell_captcha_analytics_tabs() : array<string, string>
Return values
array<string, string> —Tab id => visible label.
creationell_captcha_sum_recent_days()
Sums the $days most recent day buckets, per event type.
creationell_captcha_sum_recent_days(array<string, array<string, int>> $daily, int $days) : array<string, int>
Iterates today plus the ($days - 1) preceding days — the same window the 30-day history table walks.
Parameters
- $daily : array<string, array<string, int>>
-
Daily counters keyed by 'Y-m-d'.
- $days : int
-
Number of day buckets to sum.
Return values
array<string, int> —Event type => sum.
creationell_captcha_sum_recent_hours()
Sums the $hours most recent hour buckets, per event type.
creationell_captcha_sum_recent_hours(array<string, array<string, int>> $hourly, int $hours) : array<string, int>
Iterates the current hour plus the ($hours - 1) preceding hours.
Parameters
- $hourly : array<string, array<string, int>>
-
Hourly counters keyed by 'Y-m-d H'.
- $hours : int
-
Number of hour buckets to sum.
Return values
array<string, int> —Event type => sum.
creationell_captcha_render_analytics_page()
Renders the tabbed analytics dashboard page.
creationell_captcha_render_analytics_page() : void
creationell_captcha_render_analytics_overview()
Renders the "Übersicht" tab: four 24-hour KPI tiles and one four-window comparison table per thematic group.
creationell_captcha_render_analytics_overview() : void
creationell_captcha_render_analytics_history()
Renders the "Verlauf" tab: the day-by-day table for the last 30 days.
creationell_captcha_render_analytics_history() : void
creationell_captcha_events_query_args()
Reads and sanitises the event-log filter from the request.
creationell_captcha_events_query_args() : array{search: string, event_type: string, date_from: string, date_to: string}
Used by the "Ereignisse" tab and by the CSV export handler. The values only narrow a read-only query and are bound via $wpdb->prepare downstream, so no nonce check is required here; the page number is read separately by the tab.
Return values
array{search: string, event_type: string, date_from: string, date_to: string}creationell_captcha_render_analytics_events()
Renders the "Ereignisse" tab: filter toolbar, the event table with a detail link per row, the embedded event data and the detail modal.
creationell_captcha_render_analytics_events() : void
creationell_captcha_render_event_modal()
Renders the (initially hidden) event-detail modal skeleton.
creationell_captcha_render_event_modal() : void
The value cells carry a data-field matching the event record key; the modal
JavaScript fills them client-side from the embedded JSON map.
creationell_captcha_render_events_toolbar()
Renders the event-log filter toolbar: search, type filter, date range, the "Filtern"/"Zurücksetzen" controls and the CSV export link.
creationell_captcha_render_events_toolbar(array{search: string, event_type: string, date_from: string, date_to: string} $filter) : void
Parameters
- $filter : array{search: string, event_type: string, date_from: string, date_to: string}
-
Active filter.
creationell_captcha_render_events_pagination()
Renders the pagination navigation below the event-log table.
creationell_captcha_render_events_pagination(array{search: string, event_type: string, date_from: string, date_to: string} $filter, int $paged, int $total_pages) : void
Parameters
- $filter : array{search: string, event_type: string, date_from: string, date_to: string}
-
Active filter.
- $paged : int
-
Current page (1-based).
- $total_pages : int
-
Total page count.
creationell_captcha_analytics()
Returns the shared analytics recorder instance.
creationell_captcha_analytics() : Analytics
Return values
Analyticscreationell_captcha_record_event()
Records a security event of the given type.
creationell_captcha_record_event(string $type[, array<string, mixed> $context = [] ]) : void
Parameters
- $type : string
-
The event type.
- $context : array<string, mixed> = []
-
Optional caller-supplied context.
creationell_captcha_enqueue_admin_assets()
Enqueues admin styles and scripts on the CreaCaptcha admin pages.
creationell_captcha_enqueue_admin_assets(string $hook_suffix) : void
Parameters
- $hook_suffix : string
-
Current admin page hook suffix.
creationell_captcha_cloudflare_snapshot()
Returns the bundled Cloudflare snapshot.
creationell_captcha_cloudflare_snapshot() : array{v4: string[], v6: string[], updated_at: string}
Return values
array{v4: string[], v6: string[], updated_at: string}creationell_captcha_cloudflare_ranges()
The active CF range list: cached option (if fresh and valid) → bundled snapshot.
creationell_captcha_cloudflare_ranges() : array<string|int, string>
The cached option is considered stale once it is older than 48 hours, shielding against a silently broken cron job.
Every entry is re-validated on read. The refresh path already filters what
it fetches, but the option is a persisted value: it can predate the day
is_valid_ip_or_cidr() started rejecting a prefix length of 0, and it can be
written from outside this file (wp option update, a restored backup). One
0.0.0.0/0 in there turns every peer into a trusted proxy, which makes
X-Forwarded-For — and with it the client IP the whole bypass chain runs on
— spoofable (LK-13). The validator is the one in helpers.php, deliberately
not a second copy of the rule.
A cached list that does not survive validation is not partially used: the bundled snapshot is the safer answer than a list somebody has been editing.
Return values
array<string|int, string> —IPv4 and IPv6 CIDR ranges, merged.
creationell_captcha_refresh_cloudflare_ips_now()
Fetches the live Cloudflare ranges and writes them to the cache option.
creationell_captcha_refresh_cloudflare_ips_now() : array{ok: bool, v4: int, v6: int, fetched_at: int|null, error: string|null}
Return values
array{ok: bool, v4: int, v6: int, fetched_at: int|null, error: string|null}creationell_captcha_clear_cloudflare_cache()
Deletes the cached Cloudflare-range option. The next read falls back to the bundled snapshot. Returns true when the option existed and was deleted, false when the option was absent or the delete failed.
creationell_captcha_clear_cloudflare_cache() : bool
Return values
boolcreationell_captcha_fetch_cloudflare_list()
Fetches one Cloudflare endpoint and returns the valid CIDR entries.
creationell_captcha_fetch_cloudflare_list(string $url) : array<string|int, string>
Parameters
- $url : string
Return values
array<string|int, string>creationell_captcha_sync_cloudflare_cron()
Ensures the daily refresh cron slot is in sync with the auto-refresh toggle.
creationell_captcha_sync_cloudflare_cron() : void
Hooked on update_option_creationell_captcha_settings (fires on every
settings save). Deactivation cleanup is handled explicitly in
creationell_captcha_deactivate() to avoid re-scheduling the slot during
the deactivation handler.
creationell_captcha_code_challenge_font_usable()
Probes whether GD can actually render text with the vendored TTF font.
creationell_captcha_code_challenge_font_usable() : bool
is_file() alone is not enough (ZP-2): a corrupted font or one swapped for
an unrelated/unreadable file still passes that check, but every
imagettftext() call against it then silently returns false and draws
nothing — the renderer would produce a blank white PNG with no error and
no log line. This probe draws into a throwaway 1-character canvas and
reports the real imagettftext() verdict, so both the renderer and
wp creacaptcha doctor (Check „Code-Challenge-Schriftart") can detect the
defect before a visitor ever requests /code-image.
A corrupted TTF fails identically for every glyph (the failure is a freetype parse error on the file, not a missing character), so probing with a single throwaway character reflects the file's real state.
Return values
boolcreationell_captcha_render_code_image()
Renders a PNG of the given code and returns the raw bytes. Caller is responsible for emitting headers (`Content-Type: image/png`, `Cache-Control: no-store`) and the body.
creationell_captcha_render_code_image(string $code) : string
Falls back to GD's built-in bitmap font 5 if the vendored TTF is missing
OR present but unusable (ZP-2: corrupted/swapped file — is_file() alone
cannot see that) — either way logs a one-line warning so the operator sees
the degradation instead of silently shipping a blank image.
Parameters
- $code : string
-
The code to render (4–8 chars expected; longer is trimmed implicitly by the width budget).
Return values
stringcreationell_captcha_should_issue_code_challenge()
Decides whether the current /challenge request should attach a code-challenge instruction. Returns true iff:
creationell_captcha_should_issue_code_challenge() : bool
- the master toggle is on, AND
- PHP-GD is available, AND
- at least one of the three trigger conditions matches (under-attack, ratelimit threshold, watch-list).
Return values
boolcreationell_captcha_code_charset()
Returns the active charset string for the configured option.
creationell_captcha_code_charset(string $charset_key) : string
Parameters
- $charset_key : string
-
One of: digits / alphanumeric / alphanumeric-no-confusing.
Return values
stringcreationell_captcha_generate_code()
Generates a fresh random code from the configured charset.
creationell_captcha_generate_code() : string
Return values
stringcreationell_captcha_code_token_issue()
Issues a token backed by a server-side WP transient that stores the expected code for at most $expiry_seconds. Returns the opaque ID that `/code-image` and `/code-verify` use to look the code up again.
creationell_captcha_code_token_issue(string $code, int $expiry_seconds) : string
The code is intentionally NOT encoded into the token itself — a base64 round-trip would leak the code to anyone who can read the network response (defeats the OCR-resistant captcha goal). Server state via WP transients is the accepted trade-off.
Parameters
- $code : string
-
The expected code (already from generator).
- $expiry_seconds : int
-
Seconds until the token expires.
Return values
stringcreationell_captcha_code_token_verify()
Looks up the code for a token and returns it, or null on: - malformed token (not 32 hex chars) - missing transient (expired or unknown)
creationell_captcha_code_token_verify(string $token[, bool $consume = false ]) : string|null
Parameters
- $token : string
-
The 32-hex-char ID from
code_token_issue. - $consume : bool = false
-
Delete the transient after successful lookup (single-use semantics). The /code-image handler passes false; /code-verify passes true ONLY after a successful code match so retries on wrong input still work.
Return values
string|nullcreationell_captcha_code_fail_key()
Transient name of the per-token failed-attempt counter (CM-3).
creationell_captcha_code_fail_key(string $token) : string
Parameters
- $token : string
-
The 32-hex-char ID from
code_token_issue.
Return values
stringcreationell_captcha_code_token_fail()
Records one wrong image-code submission for a token and invalidates the token once its attempt budget is spent.
creationell_captcha_code_token_fail(string $token) : void
Before this existed, a wrong code was completely free: /code-verify
returned 401 before the consume, nothing was counted, and the REST route
is not covered by the default rate-limit scopes (core/forms). A client
could therefore walk a token through the entire code space. The counter
lives exactly as long as the token it belongs to, so it cannot be reset by
waiting — only by fetching a fresh challenge, which also means a fresh PoW.
Parameters
- $token : string
-
The 32-hex-char ID from
code_token_issue.
creationell_captcha_rest_code_image()
Handles GET /code-image?t=<token>. Looks up the code from the token (server-side transient), renders the PNG, returns 410 on token failure.
creationell_captcha_rest_code_image(WP_REST_Request $request) : WP_REST_Response
Idempotent — the transient is NOT consumed here so the browser may reload the image.
Parameters
- $request : WP_REST_Request
-
The REST request. Required query parameter
t(opaque server-issued token).
Return values
WP_REST_Response —200 with image/png body on success; 410 when the
token is unknown/expired or PHP-GD is unavailable.
creationell_captcha_rest_code_verify()
Handles POST /code-verify. Two body shapes:
creationell_captcha_rest_code_verify(WP_REST_Request $request) : WP_REST_Response
-
Code-Challenge mode: { "code": "
", "payload": " " } The widget rendered a code-image because the /challenge response embedded data.ccode. Token lookup → case-insensitive match → single-use consume → "code stage passed" marker for this challenge. - Plain server-verify mode: { "payload": "
" } (no code) ALTCHA's widget posts here unconditionally whenever verifyUrl is set (see widget.js logic: verifyUrl ? _e() : verified()). Structural verify only — nothing is consumed, because there is nothing to protect: the endpoint hands back exactly what it was given.
- Plain server-verify mode: { "payload": "
Neither mode mints a payload any more (CM-5, Wurzel 3.1). The old handler
ended in Engine::issue_signed_payload(), which made the SERVER solve a
fresh PoW (solveChallenge()) and returned a ready-to-use payload — one
client-side solve was enough to drive an endless loop of server-side key
derivations. The response now echoes the SUBMITTED payload back verbatim,
so a caller never gains anything it did not already have.
Parameters
- $request : WP_REST_Request
-
The REST request with JSON body
{payload: string, code?: string}.
Return values
WP_REST_Response —200 on success ({payload, verified: true}); 400 on malformed body; 401 on wrong code; 410 on missing/expired/unsigned payload or token.
creationell_captcha_register_code_challenge_routes()
Registers the two code-challenge REST routes. The /challenge handler itself stays in includes/rest.php; Task 11 extends it with the codeChallenge field and the data.ccode embed.
creationell_captcha_register_code_challenge_routes() : void
creationell_captcha_register_email_obfuscation()
Registers email obfuscation on `init`. The decoder script is always registered; then, unless the kill-switch is set or the feature is off, the configured mode is wired up — the content filters (module 7) or the full-page output buffer (module 8).
creationell_captcha_register_email_obfuscation() : void
creationell_captcha_register_email_buffer()
Wires up the full-page-buffer mode: on `template_redirect` for non-feed front-end requests it enqueues the decoder script and starts an output buffer whose callback obfuscates the page body at flush time.
creationell_captcha_register_email_buffer(EmailObfuscator $obfuscator) : void
Der Callback fasst den Puffer nur an, wenn die Antwort HTML ist (EO-5) und
eine Größengrenze nicht überschreitet (EO-4). Beides schützt fremde
Antworten davor, umgeschrieben zu werden, und verhindert, dass der
Obfuskator für eine sehr große Antwort eine zweite Kopie im Speicher
aufbaut. Was beides NICHT verhindert: dass PHPs Ausgabepuffer die Antwort
überhaupt zwischenspeichert. Ein Handler, der eine große Datei per
readfile() ausgibt, ohne die offenen Puffer vorher zu leeren, hält sie so
oder so komplett im Speicher — das ist eine Eigenschaft von ob_start()
und trifft jeden Puffer auf der Seite, nicht nur diesen.
Kein creationell_captcha_request_bypassed() (EO-10, bewusst): Die
Obfuskation ist kein Zugangs-Gate, sondern eine Darstellungsänderung — sie
lässt niemanden durch und hält niemanden auf. Der Content-Filter-Modus
fragt den Bypass ebenso wenig ab; ihn nur hier zu verdrahten, hieße, aus
einer Einstellung zwei Verhaltensweisen zu machen, je nach gewähltem Modus.
Funktionaler Schaden entsteht nicht: der Decoder stellt die Adresse
clientseitig wieder her.
Parameters
- $obfuscator : EmailObfuscator
-
The obfuscator.
creationell_captcha_email_buffer_filter()
The buffer callback itself: decides whether this response gets rewritten and hands it to the obfuscator when it does.
creationell_captcha_email_buffer_filter(string $html, EmailObfuscator $obfuscator) : string
Bewusst eine eigene benannte Funktion statt einer Closure im ob_start():
Die beiden Weichen (EO-5 Content-Type, EO-4 Größengrenze) sind der Fix, und
ein Test muss sie direkt aufrufen können. In der Closure waren sie nur über
einen echten Request erreichbar — der einzige „Nachweis", der dann noch
möglich war, prüfte den Default-Wert gegen sich selbst und wäre auch ohne die
Weiche grün geblieben.
Parameters
- $html : string
-
Der gepufferte Antwort-Body.
- $obfuscator : EmailObfuscator
-
The obfuscator.
Return values
string —Umgeschriebener oder unveränderter Body.
creationell_captcha_response_is_html()
Whether the response being buffered is (still) an HTML document.
creationell_captcha_response_is_html() : bool
EO-5: process_page() prüfte nie den Content-Type. Eine Nicht-HTML-Antwort,
die zufällig <body…>, </body> und ein „@" enthielt — etwa ein von einem
Mu-Plugin auf template_redirect ausgegebenes JSON mit HTML-Fragment —,
wurde umgeschrieben und war danach kaputt.
Ein fehlender Content-Type gilt als HTML: template_redirect ist der
Template-Pfad von WordPress, dessen Antwort per Definition das Theme rendert,
und PHPs default_mimetype ist text/html. Die Prüfung entscheidet
ausschließlich darüber, OB umgeschrieben wird — sie ist keine
Sicherheitskontrolle, und ein „nein" ist immer die harmlosere Antwort.
Return values
boolcreationell_captcha_run_firewall()
Runs the IP/user-agent firewall. Hooked on `init` at priority 0 so it fires before the rate limiter, the interceptor and any form-processing handler.
creationell_captcha_run_firewall() : void
creationell_captcha_comments_active()
Whether comment protection applies to the current request.
creationell_captcha_comments_active() : bool
Return values
boolcreationell_captcha_comments_render()
Injects the widget just above the comment form submit button.
creationell_captcha_comments_render(string $submit_field) : string
Parameters
- $submit_field : string
-
The submit button field HTML.
Return values
stringcreationell_captcha_comments_verify()
Verifies the captcha before a comment is accepted.
creationell_captcha_comments_verify(array<string, mixed> $commentdata) : array<string, mixed>
Parameters
- $commentdata : array<string, mixed>
-
Comment data.
Return values
array<string, mixed>creationell_captcha_login_enabled()
Whether login protection is enabled.
creationell_captcha_login_enabled() : bool
Return values
boolcreationell_captcha_login_render()
Renders the widget inside the login form.
creationell_captcha_login_render() : void
creationell_captcha_login_form_middle()
Renders the widget inside `wp_login_form()`-based forms.
creationell_captcha_login_form_middle(string $content) : string
wp_login_form() — used by themes, widgets and shortcodes to embed a
login form outside wp-login.php — never fires the login_form action
above; it runs the login_form_top/login_form_middle/login_form_bottom
filters instead. Without this, such a form posted the same log/pwd/
wp-submit fields as wp-login.php's own form but never got a widget to
solve, so creationell_captcha_login_verify() below rejected even correct
credentials (IN-6).
Parameters
- $content : string
-
Existing middle-of-form markup.
Return values
stringcreationell_captcha_login_is_interactive()
Whether the current request is an interactive, browser-submitted login attempt — as opposed to XML-RPC, REST/Application-Passwords, AJAX or a programmatic `wp_signon()` call made by other code.
creationell_captcha_login_is_interactive() : bool
Deliberately does NOT use isset( $_POST['wp-submit'] ) as the signal
(IN-1): wp-login.php's own login handler calls wp_signon()
unconditionally, regardless of whether "wp-submit" was posted, so an
attacker posting directly to wp-login.php could simply omit it and skip
the captcha entirely. The log/pwd field pair is what both
wp-login.php's own form and wp_login_form()-based theme forms actually
send. WooCommerce's My-Account login form uses different field names
(username/password) and is intentionally NOT matched here — it has
its own toggle/verification pair (protect_wc_login, see IN-7).
Return values
boolcreationell_captcha_login_verify()
Verifies the captcha during an interactive login.
creationell_captcha_login_verify(WP_User|WP_Error|null $user, string $username, string $password) : WP_User|WP_Error|null
Parameters
- $user : WP_User|WP_Error|null
-
Authenticated user or error.
- $username : string
-
Submitted username.
- $password : string
-
Submitted password.
Return values
WP_User|WP_Error|nullcreationell_captcha_password_reset_enabled()
Whether password-reset protection is enabled.
creationell_captcha_password_reset_enabled() : bool
Return values
boolcreationell_captcha_password_reset_render()
Renders the widget inside the lost-password form.
creationell_captcha_password_reset_render() : void
creationell_captcha_password_reset_exempt_reason()
Why the current request is not a public lost-password form submission.
creationell_captcha_password_reset_exempt_reason() : string|null
lostpassword_post is NOT a form hook. WordPress core fires it inside
retrieve_password() (wp-includes/user.php), and that function is also the
back end of every administrative and programmatic reset:
wp_ajax_send_password_reset() behind the "Send reset link" button on
user-edit.php, the "Send password reset" bulk action in wp-admin/users.php,
and any WP-CLI or third-party plugin call. None of those ever renders our
widget, so demanding a solved challenge there rejected the request with
"Die Sicherheitsabfrage wurde nicht bestanden" and no mail was ever sent —
retrieve_password() bails out before dispatch once $errors is non-empty.
Returns the reason rather than a bare bool for two reasons: the caller logs
it on the verified event exactly like a bypass reason, and every branch
stays individually observable in the regression test. Under the CLI SAPI a
bool predicate could only ever show its CLI branch — which is why the
administrative branch is checked FIRST here, before the non-interactive
ones. In production the order is immaterial: WP-CLI has neither is_admin()
nor a current user, so it can never take the administrative branch.
Return values
string|null —Exemption reason, or null for a genuine form submission.
creationell_captcha_password_reset_exempt_label()
Human-readable label for an exemption reason, for the event log.
creationell_captcha_password_reset_exempt_label(string $reason) : string
Parameters
- $reason : string
-
Reason key from creationell_captcha_password_reset_exempt_reason().
Return values
stringcreationell_captcha_password_reset_verify()
Verifies the captcha during a password-reset request.
creationell_captcha_password_reset_verify(WP_Error $errors) : void
Parameters
- $errors : WP_Error
-
Password-reset errors (passed by WordPress >= 5.4).
creationell_captcha_registration_enabled()
Whether registration protection is enabled.
creationell_captcha_registration_enabled() : bool
Return values
boolcreationell_captcha_registration_render()
Renders the widget inside the registration form.
creationell_captcha_registration_render() : void
creationell_captcha_registration_verify()
Verifies the captcha during registration.
creationell_captcha_registration_verify(WP_Error $errors, string $sanitized_user_login, string $user_email) : WP_Error
Parameters
- $errors : WP_Error
-
Registration errors.
- $sanitized_user_login : string
-
Submitted user login.
- $user_email : string
-
Submitted user email.
Return values
WP_Errorcreationell_captcha_hardening_covered_family()
Ermittelt, WELCHE Adressfamilie ein gespeicherter Listeneintrag vollständig abdeckt und die Liste für diese Familie damit als Auswahl aufhebt.
creationell_captcha_hardening_covered_family(string $entry) : string
Zwei Fragen, zwei vorhandene Wurzeln — hier entsteht bewusst keine dritte IP-Prüfung (die Doppelung eines Validators war in diesem Audit schon zweimal ein Befund, AF-5 und CLI-1):
creationell_captcha_is_valid_ip_or_cidr()— was die Eingabeprüfung dieser Version akzeptiert, ist per Definition kein Altlastwert. Das hält Falschmeldungen auf regulären Einträgen von vornherein fern, auch auf0.0.0.0/1+128.0.0.0/1, mit denen ein Betreiber den gesamten Adressraum weiterhin abdecken darf — wenn er es ausspricht.creationell_captcha_ip_in_cidr()— derselbe Matcher, den Firewall, Proxy-Vertrauen und Watch-Liste zur Laufzeit benutzen. Trifft er die erste UND die letzte Adresse einer Familie, deckt der Eintrag alles dazwischen ebenfalls ab (CIDR-Präfixe sind zusammenhängend).
Deshalb erkennt die Prüfung auch Schreibweisen, die ein Textvergleich gegen
„0.0.0.0/0" verfehlt hätte — 10.0.0.0/0 und 0.0.0.0/00 gehören dazu.
Zurückgegeben wird die Familie und nicht nur ein Ja/Nein, weil die Meldung sie
braucht: 0.0.0.0/0 deckt AUSSCHLIESSLICH IPv4 ab, ::/0 ausschließlich
IPv6. creationell_captcha_ip_in_cidr() verlangt gleiche Binärlänge von
Adresse und Netz, ein Eintrag kann also nie beide Familien treffen — und was
er nicht trifft, läuft an der Liste vorbei. Bei den vertrauenswürdigen
Proxies ist genau das der gefährlichere Teil des Fundes (siehe die Meldung
unten), deshalb darf die Familie hier nicht verlorengehen.
Parameters
- $entry : string
-
Eintrag aus der gespeicherten Liste (bereits getrimmt).
Return values
string —'IPv4', 'IPv6' oder '' — Letzteres heißt: deckt keine Familie vollständig ab, also kein Fund.
creationell_captcha_hardening_mapped_ipv4_cidr()
Erkennt einen CIDR-Bereich, der in IPv4-mapped Schreibweise notiert ist (`::ffff:0:0/96`, `::ffff:203.0.113.0/120`), und liefert seine gewöhnliche IPv4-Schreibweise zurück.
creationell_captcha_hardening_mapped_ipv4_cidr(string $entry) : string
WARUM DAS EINE EIGENE PRÜFUNG IST
Die Prüfung eine Etage höher (creationell_captcha_hardening_covered_family())
kann diese Klasse konstruktionsbedingt nicht finden. Sie steigt bei jedem
Eintrag aus, den creationell_captcha_is_valid_ip_or_cidr() annimmt — und
::ffff:0:0/96 ist eine syntaktisch einwandfreie IPv6-Notation mit
Präfixlänge 96, wird also angenommen. Auch die beiden Familien-Sonden greifen
nicht: 0.0.0.0 ist vier Byte lang, der Eintrag sechzehn, und
creationell_captcha_ip_in_cidr() verlangt zu Recht gleiche Binärlänge.
WAS DER EINTRAG BEDEUTET — GEMESSEN, NICHT VERMUTET
Bis 1.0.2 hat niemand die Client-Adresse vereinheitlicht. Auf einem Server,
dessen PHP die Gegenstelle in mapped Schreibweise sieht (nginx mit
ipv6only=off, Apache auf einem IPv6-Socket, viele Container-Setups), kam
REMOTE_ADDR dort als ::ffff:203.0.113.7 an — sechzehn Byte, dieselbe
Familie wie der Eintrag. ::ffff:0:0/96 traf damit JEDE IPv4-Adresse. In
firewall_trusted_proxies hiess das: jede Gegenstelle gilt als eigener
Proxy, der weitergeleitete Header wird ungeprüft übernommen. Die /0-Regel
der Eingabeprüfung fängt diese Schreibweise nicht.
Seit Modul 27 (Befund I2) kanonisiert creationell_captcha_normalize_ip()
die Client-Adresse auf die gewöhnliche IPv4-Schreibweise — vier Byte. Der
RANGE bleibt bewusst stehen wie geschrieben, weil ein automatisch
umgeschriebenes Vertrauens-Netz die eine Richtung wäre, die hier nie
passieren darf. Folge: der Eintrag trifft heute NICHTS mehr. Gemessen:
ip_in_list( '203.0.113.7', [ '::ffff:0:0/96' ] ) → false
ip_in_list( '::ffff:203.0.113.7', [ '::ffff:0:0/96' ] ) → false
Er ist damit kein offenes Tor mehr, sondern eine Zeile, die für den Betreiber
wie eine wirksame Regel aussieht und keine ist — genau die Klasse unwirksam,
für die dieses Feld existiert. Bei einer Blockliste ist das ein still
verlorener Schutz, bei den Erlaubnis-/Vertrauenslisten eine still verlorene
Ausnahme. Beides gehört gemeldet.
Blanke Einzeladressen in mapped Schreibweise sind NICHT betroffen:
creationell_captcha_ip_in_list() kanonisiert dort beide Seiten, ein
gespeichertes ::ffff:203.0.113.7 trifft also weiterhin. Deshalb verlangt
diese Funktion einen Schrägstrich.
Präfixe unter 96 gelten nicht als mapped Bereich: dort tragen die zwölf
Bytes des Mapped-Präfixes die Entscheidung nicht mehr vollständig, der
Eintrag ist dann ein gewöhnlicher IPv6-Bereich (und ::ffff:0:0/0 fängt
bereits die Familien-Prüfung oben ab).
Parameters
- $entry : string
-
Eintrag aus der gespeicherten Liste (bereits getrimmt).
Return values
string —Gewöhnliche IPv4-Schreibweise des Bereichs, oder '' wenn der Eintrag kein IPv4-Bereich in mapped Notation ist.
creationell_captcha_hardening_is_catch_all_pattern()
Prüft, ob ein `bypass_ua_allow`-Muster jeden Browser-Kennzeichner trifft.
creationell_captcha_hardening_is_catch_all_pattern(string $pattern) : bool
Entschieden wird das vom Produktions-Matcher selbst, nicht von einer zweiten
Regel: creationell_captcha_wildcard_match() kennt den Schalter
$allow_catch_all, und die Bypass-Auswertung ruft ihn mit false auf
(BK-9). Ein Muster, das MIT erlaubtem Catch-all trifft und OHNE nicht mehr,
ist genau eines, das der Schalter aussortiert — was immer die Regel dahinter
gerade ist. Ein Muster wie *pingdom* trifft in beiden Aufrufen gleich und
fällt damit nicht auf.
Ein Catch-all trifft jeden nicht-leeren Kennzeichner, deshalb genügt eine Probe.
GRENZE DIESER PRÜFUNG (Befund B-M2)
Das Kriterium findet definitionsgemäss nur die Menge, die der Laufzeit-Guard
bereits aussortiert — also Muster mit wirkung: unwirksam. Ein Muster, das
jeden realen Kennzeichner trifft, ohne ein blosses Sternchen zu sein, kommt
durch beide Aufrufe gleich zurück und fällt hier NICHT auf. Das genannte
Gegenbeispiel *pingdom* ist harmlos; Stern-Schraegstrich-Stern ist es
nicht — dieses Muster trifft jeden
User-Agent mit einem Schrägstrich, also praktisch jeden. Diese Klasse deckt
creationell_captcha_hardening_matches_every_user_agent() ab.
Nebenwirkung: bei aktivem CREATIONELL_CAPTCHA_DEBUG schreibt der Matcher
eine Zeile ins Log, wenn er ein Catch-all aussortiert — durch diese Prüfung
also auch dann, wenn gerade keine echte Anfrage ausgewertet wird. Die Aussage
der Zeile stimmt trotzdem: das Muster ist wirkungslos.
Parameters
- $pattern : string
-
Muster aus der gespeicherten Liste.
Return values
boolcreationell_captcha_hardening_matches_every_user_agent()
Prüft, ob ein `bypass_ua_allow`-Muster jeden realistischen User-Agent trifft, ohne vom Catch-all-Guard aussortiert zu werden (Befund B-M2).
creationell_captcha_hardening_matches_every_user_agent(string $pattern) : bool
Der Guard in creationell_captcha_wildcard_match() verwirft ein Muster nur,
wenn nach trim( $pattern, '*' ) nichts übrig bleibt — * und ** also,
Stern-Schraegstrich-Stern nicht. Und dieses Muster trifft jeden
User-Agent, der einen Schrägstrich
enthält: Mozilla/5.0 …, curl/8.5.0, python-requests/2.31.0. Wer diese
Zeile einträgt, hat den gesamten Schutz für jeden Besucher abgeschaltet und
bekommt heute keine Meldung.
DAS KRITERIUM IST BEWUSST „ALLE SONDEN" UND NICHT „EINE SONDE"
Gemeldet wird nur, was JEDE der Sonden trifft — also ein Muster, das keinen
realistischen Kennzeichner mehr auslässt. Ein Existenzquantor hätte
Mozilla* (trifft Browser, nicht curl) und *Chrome* mitgemeldet; das
sind enge Auswahlen, keine Totalabschaltungen, und eine Meldung darüber wäre
genau die Falschmeldung, die den Betreiber zum Wegklicken erzieht. Die
Sondenmenge deckt deshalb absichtlich verschiedene Bauformen ab: Browser mit
Klammerausdruck, blankes Werkzeug ohne Leerzeichen, Bibliothek, Bot mit URL.
Anders als beim blossen Sternchen greift hier KEIN Laufzeit-Guard: das Muster
wird ausgewertet und trifft. Der Fund ist deshalb aktiv, nicht unwirksam.
Parameters
- $pattern : string
-
Muster aus der gespeicherten Liste.
Return values
boolcreationell_captcha_hardening_scan()
Durchsucht einen Satz Einstellungen nach gefährlicher Bestandskonfiguration.
creationell_captcha_hardening_scan(array<string, mixed> $settings[, bool|null $gd_available = null ]) : array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>
Keine Option wird gelesen, keine geschrieben. Der Aufrufer liefert die
Einstellungen; das macht die Erkennung ohne WordPress prüfbar
(tests/test-hardening-migration.php). Der einzige Blick nach draußen ist
extension_loaded( 'gd' ) — und auch der ist über $gd_available
abschaltbar, damit der Test nicht davon abhängt, wie der Rechner gebaut ist,
auf dem er läuft.
Jeder Fund trägt ein Feld wirkung. Drei Werte, weil „greift nicht" drei
verschiedene Dinge heißen kann und der Betreiber sie auseinanderhalten muss:
aktiv— der Eintrag greift zur Laufzeit hier und jetzt.unwirksam— ein Laufzeit-Guard überspringt ihn seit dieser Version dauerhaft (BK-9, BK-14). Kein Schalter holt ihn zurück. Er steht trotzdem in der Konfiguration und liest sich für den Betreiber wie eine wirksame Ausnahme — deshalb genannt.ruhend— der Eintrag greift nur nicht, weil die Funktion, die seine Liste liest, gerade ausgeschaltet ist. Er ist scharf, sobald sie eingeschaltet wird — eine Tretmine, kein toter Buchstabe.
Warum das Feld nicht pauschal aktiv sein darf: zwei der drei IP-Listen
werden zur Laufzeit hinter einem Schalter gelesen (siehe unten). Auf einer
Installation mit den Vorgabewerten (firewall_behind_proxy = false,
code_challenge_enabled = false) bekäme der Betreiber sonst eine rote
Fehlermeldung über zwei Optionswerte, die kein Codepfad überhaupt anschaut —
und lernt daraus, Meldungen dieses Plugins wegzuklicken.
Parameters
- $settings : array<string, mixed>
-
Gespeicherte Plugin-Einstellungen.
- $gd_available : bool|null = null
-
PHP-GD vorhanden?
null= selbst nachsehen. Nur zum Testen gesetzt — GD ist Teil des Watch-Listen-Gates (siehe creationell_captcha_should_issue_code_challenge()), aber eine Umgebungs- und keine Einstellungsfrage.
Return values
array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>creationell_captcha_hardening_findings()
Führt den Scan gegen die aktuell gespeicherten Einstellungen aus.
creationell_captcha_hardening_findings() : array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>
Return values
array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>creationell_captcha_hardening_fingerprint()
Kurzkennung des aktuellen Fundbildes.
creationell_captcha_hardening_fingerprint(array<int, array<string, string>> $findings) : string
Ein: id und wirkung. Nicht der Meldungstext — sonst ließe eine
Umformulierung beim nächsten Update einen bereits weggeklickten Hinweis wieder
auftauchen. wirkung muss dagegen einfließen: ein ruhend-Fund wird zu einem
aktiv-Fund, sobald der Betreiber die zugehörige Funktion einschaltet, ohne
dass sich die id ändert. Ohne dieses Feld bliebe die ausgeblendete Warnung
auch dann ausgeblendet, wenn aus der Tretmine ein Loch geworden ist.
Umgekehrt bringt ein NEUER oder geänderter Fund den Hinweis zurück, obwohl der Benutzer den vorigen weggeklickt hat — genau das ist gewollt: ausgeblendet wird ein bestimmtes Fundbild, nicht die Meldung als solche.
Die id geht gehasht ein, nicht roh: sie enthält gespeicherte Listeneinträge
und darf im Zusammenbau kein Trennzeichen zerschießen (ein bypass_cookies-
Eintrag darf jedes Zeichen enthalten, auch Tabulator und Zeilenumbruch).
Parameters
- $findings : array<int, array<string, string>>
-
Fundliste aus dem Scan.
Return values
string —16 Hex-Zeichen, oder '' wenn es nichts zu melden gibt.
creationell_captcha_hardening_dismiss_redirect_target()
Baut das Ziel des Redirects nach dem Ausblenden-Klick: dieselbe Seite ohne den Ausblenden-Parameter und ohne die Nonce.
creationell_captcha_hardening_dismiss_redirect_target() : string
Wirkung des ursprünglichen Fundes war gering und ist es geblieben: Der
Handler hängt hinter current_user_can( 'manage_options' ) und
check_admin_referer(), ein Administrator müsste also selbst eine
//-Schreibweise einer wp-admin-URL aufrufen, und wp_safe_redirect()
fängt einen fremden Host ohnehin ab. Der Ausgang war der von N1 an der
Under-Attack-Stelle beschriebene: statt zurück auf die Seite, von der er
kam, landete der Administrator auf der wp-admin-Startseite — und der
Hinweis, den er gerade weggeklickt hat, wäre dort erneut zu sehen.
Tags
Return values
string —Relativer Ziel-URI für wp_safe_redirect().
creationell_captcha_hardening_handle_dismiss()
Nimmt den Ausblenden-Klick entgegen.
creationell_captcha_hardening_handle_dismiss() : void
Gespeichert wird die serverseitig NEU BERECHNETE Kennung, nicht die aus der URL. Sonst ließe sich mit einem präparierten Link ein Fundbild ausblenden, das es noch gar nicht gibt — der Hinweis wäre schon weggeklickt, bevor er das erste Mal fällig wird.
creationell_captcha_hardening_render_notice()
Zeigt die gefundene Bestandskonfiguration als Admin-Hinweis.
creationell_captcha_hardening_render_notice() : void
Auf allen Admin-Seiten, nicht nur auf den Plugin-Seiten: es geht um einen Schutz, der gerade nicht greift, und wer das erfahren soll, muss dafür nicht erst zufällig die Einstellungsseite öffnen. Damit der Hinweis trotzdem keine Tapete wird, ist er pro Benutzer ausblendbar (und kommt bei einem neuen Fund von selbst zurück, siehe creationell_captcha_hardening_fingerprint()).
Nur für Benutzer mit manage_options — niemand sonst könnte etwas ändern,
und die Meldung nennt Details der Firewall-Konfiguration.
creationell_captcha_resolve_widget_locale()
Resolves the active WordPress locale to a vendored ALTCHA locale code.
creationell_captcha_resolve_widget_locale() : string|null
Returns the locale string (e.g. "de", "fr-fr", "pt-br") if a mapping exists, or null when the WP locale is not in the vendor set — in which case the widget renderer skips both the language attribute and the i18n script enqueue, letting the widget fall through to its own detection (which has only the EN built-in available).
Two filters are applied: creationell_captcha_widget_locale_map to
extend / override the lookup table, and
creationell_captcha_widget_locale for last-mile overrides after
lookup.
Tags
Return values
string|null —Vendored ALTCHA locale code or null.
creationell_captcha_get_default_settings()
Default plugin settings.
creationell_captcha_get_default_settings() : array<string, mixed>
Return values
array<string, mixed>creationell_captcha_get_settings()
Current plugin settings, merged over the defaults.
creationell_captcha_get_settings([bool $force_refresh = false ]) : array<string, mixed>
Memoised for the duration of the request — get_option() itself is cheap thanks to WP's object cache, but the defaults-merge over ~70 keys adds up across the 10+ call sites per request (Interceptor, Firewall, Rate- Limiter, Under-Attack, every form integration). The cache is invalidated automatically when the option is added, updated or deleted.
Parameters
- $force_refresh : bool = false
-
Re-read from the DB even if a cached copy exists. Used by the invalidation hook.
Return values
array<string, mixed>creationell_captcha_invalidate_settings_cache()
Drops the in-request settings cache. Wired to the option-change hooks below so callers that read settings after an update see the fresh value.
creationell_captcha_invalidate_settings_cache() : void
creationell_captcha_is_disabled()
Whether the captcha is globally disabled via the wp-config constant.
creationell_captcha_is_disabled() : bool
Return values
boolcreationell_captcha_sodium_available()
Whether ext-sodium (required for Argon2id) is available.
creationell_captcha_sodium_available() : bool
Return values
boolcreationell_captcha_generate_secrets()
Generates both HMAC secrets and persists them (non-autoloaded).
creationell_captcha_generate_secrets() : array{signature: string, key_signature: string}
Return values
array{signature: string, key_signature: string}creationell_captcha_get_secret()
Returns a stored HMAC secret, generating + persisting it on first use.
creationell_captcha_get_secret(string $which) : string
Parameters
- $which : string
-
Either 'signature' or 'key_signature'.
Return values
stringcreationell_captcha_get_hmac_secret()
The HMAC signature secret (signs each challenge).
creationell_captcha_get_hmac_secret() : string
A wp-config constant takes precedence over the stored option.
Return values
stringcreationell_captcha_get_hmac_key_secret()
The HMAC key-signature secret (enables the fast verification path).
creationell_captcha_get_hmac_key_secret() : string
A wp-config constant takes precedence over the stored option.
Return values
stringcreationell_captcha_derive_hmac_key()
Derives a purpose-bound HMAC key from the plugin's base signature secret.
creationell_captcha_derive_hmac_key(string $purpose) : string
Wurzel 3.5: the challenge signature, the under-attack pass cookie and the
ctx suppression token were all MACs under the SAME key. Cross-purpose use
was only prevented by the differing message layout — a fragile property that
breaks silently as soon as one token family changes its format. HKDF-style
expansion with a purpose label makes the separation structural: a MAC minted
for one purpose verifies under no other key.
The base secret stays exactly where it is (CREATIONELL_CAPTCHA_HMAC_SECRET
/ the stored option) — the derivation sits in front of it, so the ALTCHA
library keeps signing challenges with the unchanged base key.
Parameters
- $purpose : string
-
Stable purpose label, e.g.
underattack-pass.
Tags
Return values
string —64-char hex key, or '' when no base secret is available (callers must fail closed on '').
creationell_captcha_client_ua()
The request's User-Agent, capped at 256 bytes. MAC input for the under-attack tokens — never rendered, never stored.
creationell_captcha_client_ua() : string
Tags
Return values
stringcreationell_captcha_underattack_pass_binding()
The fingerprint the under-attack pass cookie is bound to (BK-3).
creationell_captcha_underattack_pass_binding() : string
Two components, neither of which travels inside the cookie. Only one of them is load-bearing, and it is worth naming which: the NETWORK is what a recipient cannot bring along, because it is where his packets come from. The User-Agent he can bring along — he sends it himself, and whoever passes the cookie on passes the UA string on with it. So the UA is a cheap extra, the network is the half that actually refuses a transferred cookie.
- the client NETWORK (IPv4 /24, IPv6 /48 — the same reduction the analytics
anonymiser applies), resolved through
creationell_captcha_get_client_ip()so a forwarded header only counts behind a trusted proxy (BK-7/BK-8) and never straight off an attacker-set header; - the User-Agent.
Trade-off, deliberately made here and not at the two call sites: the User-Agent alone is weak (whoever passes the cookie on passes the UA string on with it), the full address breaks on every mobile hand-over. The network is the middle ground — it survives the address churn inside one access network (CGNAT pool, IPv6 privacy extensions) and still refuses a cookie that travelled to a different one. A false negative costs exactly one extra proof-of-work: the visitor sees the interstitial again, solves it and gets a cookie bound to the new network. No lock-out, no loop.
Tags
Return values
string —Binding string; '' disables the binding entirely.
creationell_captcha_underattack_pass_issue()
Mints an under-attack pass token for the current visitor.
creationell_captcha_underattack_pass_issue(int $expiry) : string
Parameters
- $expiry : int
-
Absolute Unix timestamp the pass runs out at.
Tags
Return values
string —<expiry>.<mac> token, or '' when no key could be derived.
creationell_captcha_underattack_pass_check()
Verifies an under-attack pass token against the CURRENT visitor.
creationell_captcha_underattack_pass_check(string $cookie) : bool
BK-3: before 1.1.0 the MAC covered the expiry timestamp and nothing else, so
one solved interstitial produced a bearer token that worked from any client,
any address, for up to underattack_pass_duration (86400 s). The MAC now
covers the visitor binding as well, which is not part of the cookie value.
Parameters
- $cookie : string
-
Raw cookie value.
Tags
Return values
boolcreationell_captcha_underattack_ctx_issue()
Mints a single-use `ctx` suppression token for one interstitial rendering.
creationell_captcha_underattack_ctx_issue() : string
BK-4/CM-4/W5: the old token was HMAC(secret, 'ua-ctx|' . floor(time()/300))
— identical for every visitor inside a five-minute bucket and accepted for
the current AND the previous bucket, i.e. up to ten minutes. It was handed
to every anonymous 503 visitor inside the HTML. Unforgeable, yes — but
trivially obtainable, transferable and replayable.
The replacement carries a random nonce (so every rendering gets its own
token), an explicit expiry and a MAC over nonce, expiry and the visitor's
User-Agent. It is burned on first use in …_ctx_check().
What this closes, precisely — and what it does NOT:
- REPLAY is closed. The nonce is burned on first use, so one token buys at most one suppressed challenge (1.0.2: unlimited inside its bucket pair).
- The accepted WINDOW shrinks from ~10 minutes to 120 seconds.
- ACQUISITION stays open. One GET of a 503 page still yields one fresh
token, i.e. one code-stage-free challenge. Closing that needs the issued
challenge itself to be marked under-attack-only and checked on redemption
inside
Engine::verify()— outside this file (R1 in the task report). - HANDING A FRESH TOKEN ON stays open as well. The MAC covers the User-Agent, but the User-Agent is a value the RECIPIENT sends himself: whoever passes the token to a bot passes the UA string along with it. The binding costs an attacker one header, no more. It is kept because it is free and it does stop a token that leaked WITHOUT its UA (log excerpt, referrer, a URL shared out of band). It is not a transfer barrier, and no comment on this token may claim that it is — W5 was exactly that kind of claim, made about exactly this token.
WHAT SINGLE USE COSTS (m7): the token is spent by the FIRST challenge fetch
of the interstitial that carried it. If the very same 503 HTML reaches a
browser a second time — restored from the back/forward cache, or replayed by
a full-page cache or CDN that ignores the response headers — the widget
fetches again with a token that is already burned. /challenge then answers
without the suppression, and with code_challenge_enabled on that means the
INVISIBLE interstitial widget receives an image code it cannot display: that
visitor never passes the gate at all. The interstitial itself asks not to be
stored (nocache_headers() sends no-store, private, measured against the
WordPress 7.0.2 of the test instance), so this needs a cache that disregards
that — but such caches exist, and "under attack" is exactly when an operator
puts one in front of the site. Making that visible is a doctor job
("under-attack mode on and a full-page cache detected"); it cannot be fixed
from the token side without giving up the single use that closed BK-4.
Why no client-network binding here, unlike the pass cookie: it would not
reduce what an attacker can do — every bot can fetch its own 503 page, so
handing a token around buys nothing over simply acquiring one — while a
false negative here is a dead end instead of a retry. A rejected pass cookie
costs one extra proof-of-work; a rejected ctx gets the interstitial's
invisible widget an image code it cannot display, and that visitor never
passes the gate at all. On top of that the address is not even constant
across the two requests of ONE visitor: the XHR that fetches the challenge
can leave through a different address than the page view that carried the
token (dual-stack clients pick the family per connection, egress pools
rotate well beyond a /24).
Tags
Return values
string —<nonce>.<expiry>.<mac>, or '' when no key could be derived.
creationell_captcha_underattack_ctx_check()
Verifies a `ctx` suppression token and consumes it.
creationell_captcha_underattack_ctx_check(string $token) : bool
Returns true at most ONCE per token: the nonce is recorded for the rest of the token's lifetime, every later presentation of the same token fails.
Parameters
- $token : string
-
Raw
ctxquery value.
Tags
Return values
boolcreationell_captcha_engine()
Shared captcha engine instance.
creationell_captcha_engine() : Engine
Return values
Enginecreationell_captcha_log()
Write a message to the debug log when CREATIONELL_CAPTCHA_DEBUG is active.
creationell_captcha_log(string $message) : void
W1-13: Steuerzeichen werden entfernt, BEVOR die Zeile geschrieben wird —
dieselbe Reduktion, die Analytics::current_path() für die Log-Tabelle
vornimmt. Mehrere Meldungen tragen vom Absender bestimmte Bestandteile in
die Zeile, allen voran der Interceptor („interceptor blocked POST to " plus
dem EINMAL dekodierten Pfad, class-interceptor.php): ein anonymer
POST /kontakt%0A auf einen per /kontakt* geschützten Pfad wird von
WordPress geroutet (WP::parse_request() trimmt, und die Rewrite-Regel
^kontakt/?$ trifft dank $ vor dem Zeilenumbruch), der Interceptor
blockiert — und die Logzeile enthielt einen echten Zeilenumbruch. Damit
bestimmte der Absender, wo eine Zeile im PHP-Fehlerlog endet, und konnte
eine zweite, frei gewählte anhängen.
Bewusst hier und nicht an der einen Aufrufstelle: dies ist die Senke, durch die JEDE Meldung des Plugins geht, und keine von ihnen enthält eine beabsichtigte mehrzeilige Ausgabe (nachgezählt über alle Aufrufer). Eine Reparatur je Aufrufstelle wäre dieselbe Zeile mehrfach — und die nächste neue Meldung hätte sie wieder nicht.
Parameters
- $message : string
-
Message to log.
creationell_captcha_normalize_ip()
Canonicalises an IPv4-mapped IPv6 address (`::ffff:a.b.c.d`) into its plain IPv4 spelling. Every other value — including anything that is not an IP at all — is handed back unchanged.
creationell_captcha_normalize_ip(string $ip) : string
WHY (audit module 27, finding I2)
On every server whose PHP sees the peer address in IPv4-mapped notation
(nginx listen [::]:443 ipv6only=off, Apache on an IPv6 socket, plenty of
container and proxy setups) REMOTE_ADDR reads ::ffff:203.0.113.7 for an
ordinary IPv4 client. That string passes FILTER_VALIDATE_IP — it is a
perfectly valid IPv6 address — so nothing ever complained, but:
ip_in_cidr()refuses a family change (4 vs. 16 bytes, correctly so), andip_in_list()otherwise compares plain strings;- so
firewall_ip_block,firewall_ip_allow,firewall_trusted_proxiesandcode_challenge_watchlistmatched NOTHING for those clients — the IP firewall was silently inert, and the admin who allow-listed his own address locked himself out anyway; anonymize_ip()reduced all of them to::, which took the network half out of the BK-3 under-attack pass binding and left a pure bearer token behind.
The check runs on the binary form rather than on the string, so the rarer
spellings (::FFFF:cb00:7107, 0:0:0:0:0:ffff:203.0.113.7) are caught as
well. :: and ::ffff:0:0:0 are NOT mapped addresses and stay untouched.
Parameters
- $ip : string
-
Candidate address.
Tags
Return values
string —Plain IPv4 spelling for mapped addresses, else the input.
creationell_captcha_get_client_ip()
Resolves the client IP address.
creationell_captcha_get_client_ip() : string
Returns the validated REMOTE_ADDR by default. When the firewall_behind_proxy
setting is on, the configured forwarded header is used instead — falling back
to REMOTE_ADDR if it yields no valid IP.
This is the single ingress for client addresses: every list check, the rate-limit bucket key, the pass binding and the event log take their value from here. Canonicalising IPv4-mapped addresses therefore happens HERE and not at each of those places (I2).
WHAT THE FALLBACK COSTS (m3 / W2-3) — say it here, because it is not obvious
at the call sites: every return $remote below hands the PROXY's address to
everything downstream. That is the safe direction (BK-8: an entry we cannot
classify must never let a forged one to its left win), but it is not a free
one. If a fallback fires on EVERY request — an upstream that appends
unknown or an obfuscated identifier per RFC 7239, an Azure-style
ip:port hop, a firewall_proxy_header naming a header this installation
does not actually receive — then all visitors share one address:
- the rate limiter counts the whole site into one bucket and locks everyone out at the threshold;
firewall_ip_blockandcode_challenge_watchlisthit all or nothing;- and if the proxy address happens to sit in
firewall_ip_allow, every visitor is bypassed.
Each fallback therefore names itself through creationell_captcha_log().
That is only visible with CREATIONELL_CAPTCHA_DEBUG; making it visible
without the debug switch belongs to the settings help text and to
wp creacaptcha doctor, not here.
Return values
stringcreationell_captcha_ip_in_list()
Whether an IP matches any entry in a list of IPs or CIDR ranges.
creationell_captcha_ip_in_list(string $ip, mixed $list) : bool
This is the choke point all four address lists run through — blocklist, allowlist, trusted proxies and the code-challenge watchlist — which is why the IPv4-mapped canonicalisation is applied here and not four times over.
Admin-typed entries are canonicalised as well, so an installation that spelled
an entry ::ffff:203.0.113.7 (the only spelling that worked on an affected
server before this release) keeps matching. CIDR entries are deliberately NOT
rewritten: a mapped range like ::ffff:0:0/96 would widen to "every IPv4
address", and silently widening a TRUSTED-PROXY range is the one direction
this plugin must never take (LK-13/AF-5). A CIDR written in mapped notation
therefore stops matching — see the note on ip_in_cidr().
Parameters
- $ip : string
-
The client IP.
- $list : mixed
-
A list of IPs / CIDR ranges (non-arrays are ignored).
Return values
boolcreationell_captcha_ip_in_cidr()
Whether an IP falls within a CIDR range. Supports IPv4 and IPv6.
creationell_captcha_ip_in_cidr(string $ip, string $cidr) : bool
The subject is canonicalised (I2); the RANGE is taken as written. A range
spelled in IPv4-mapped notation (::ffff:203.0.113.0/120) consequently no
longer matches an IPv4 client — deliberately, because converting it would
mean rewriting prefix lengths, and a wrong prefix in a trusted-proxy list is
the failure mode this plugin has already had to close twice.
Parameters
- $ip : string
-
The client IP.
- $cidr : string
-
A CIDR range, e.g. "203.0.113.0/24".
Return values
boolcreationell_captcha_is_valid_ip_or_cidr()
Whether a string is a valid IP address or CIDR range (IPv4 or IPv6).
creationell_captcha_is_valid_ip_or_cidr(string $entry) : bool
Prefix length 0 (0.0.0.0/0, ::/0) is refused: it is valid CIDR notation
but matches every address, so as a firewall-allow, trusted-proxy or
blocklist entry it silently disables the very list it is in (AF-5). An admin
who really wants to cover the whole address space can still spell it out as
two halves (0.0.0.0/1 + 128.0.0.0/1) and thereby say so on purpose.
This is an input guard only — it decides what may be STORED. Values already in the database keep working; reporting on those is the fail-safe migration's job, not this function's.
Parameters
- $entry : string
-
The candidate string.
Return values
boolcreationell_captcha_wildcard_match()
Whether a subject matches any of the given wildcard patterns (case-insensitive).
creationell_captcha_wildcard_match(string $subject, mixed $patterns[, bool $empty_subject_matches = false ][, bool $allow_catch_all = true ]) : bool
The pattern alphabet is the same as the firewall UA-blocklist: * is the
single wildcard, everything else is matched literally.
The two edge cases used to be decided implicitly, and the decision was wrong for one of the two call sites (BK-9). Both are now the caller's to make:
$empty_subject_matches— a missing header is not "the empty string", it is no information at all. For a blocklist "no match" is the safe answer, for an allowlist it is too, but the two reach it for opposite reasons, so neither may inherit it silently.$allow_catch_all— a pattern of nothing but*matches every non-empty subject. Harmless in a blocklist, a total shutdown of the protection in an allowlist. Pass false there and such a pattern is skipped.
WHERE $allow_catch_all STOPS — READ THIS BEFORE TRUSTING IT
The guard is SYNTACTIC and nothing else: it drops a pattern when
trim( $pattern, '*' ) leaves nothing behind, i.e. *, **, ***. It does
NOT drop a pattern that merely happens to match everything in practice.
Measured against the production path: star-slash-star (written out because the
literal form would close this comment block) passes this guard and matches
every realistic User-Agent — every one of them carries a slash. Same for
star-dot-star. false here therefore means "no bare star", not "no
catch-all".
That boundary is deliberate. "Matches every real subject" is not a decidable
property of a pattern; the nearest thing to it is the five-probe criterion in
creationell_captcha_hardening_matches_every_user_agent(), and a heuristic
has no business deciding a single request — least of all one that would run
on every request, for every stored pattern. Applying it here would also
silently reinterpret patterns an operator has already stored. Reporting such
an entry is the fail-safe migration's job
(includes/hardening-migration.php, section 3, finding class jeder-ua,
wirkung: aktiv): it names the entry and leaves the decision with the
operator. tests/test-bypass-roots.php section 4a pins both halves.
Parameters
- $subject : string
-
The string to test.
- $patterns : mixed
-
A list of patterns; non-arrays return false.
- $empty_subject_matches : bool = false
-
What an empty subject means for this call site. Default false (previous behaviour).
- $allow_catch_all : bool = true
-
Whether a bare
*pattern is honoured. Default true (previous behaviour).
Return values
boolcreationell_captcha_private_ranges()
Returns the canonical list of private/loopback CIDR ranges used when the `firewall_trust_private_ranges` toggle is active.
creationell_captcha_private_ranges() : array<string|int, string>
Return values
array<string|int, string>creationell_captcha_trusted_proxies_constant()
Reads the optional `CREATIONELL_CAPTCHA_TRUSTED_PROXIES` wp-config constant as a list. Accepts either a string array or a comma/whitespace-separated scalar; invalid entries are dropped.
creationell_captcha_trusted_proxies_constant() : array<string|int, string>
Return values
array<string|int, string>creationell_captcha_is_trusted_proxy()
Whether the given IP belongs to a trusted upstream proxy.
creationell_captcha_is_trusted_proxy(string $ip) : bool
Sources are checked in this order; the first match wins:
- firewall_trusted_proxies (the explicit textarea list)
- CREATIONELL_CAPTCHA_TRUSTED_PROXIES (wp-config constant)
- firewall_trust_private_ranges (when on): the private/loopback ranges
- firewall_trust_cloudflare (when on): the cached/bundled CF ranges
Parameters
- $ip : string
-
A validated client IP address.
Return values
boolcreationell_captcha_evaluate_bypass()
Pure bypass evaluator — checks the three bypass sources against the supplied inputs without touching $_SERVER, $_COOKIE or any static cache. The caller is responsible for providing the values.
creationell_captcha_evaluate_bypass(string|null $ip, string|null $ua, array<string, string> $cookies) : array{reason: string, source: string}|false
Sources are checked in this order; the first match wins:
- firewall_ip_allow vs $ip
- bypass_ua_allow vs $ua
- bypass_cookies vs $cookies (strict name=value)
Parameters
- $ip : string|null
-
Client IP, or null to skip the IP check.
- $ua : string|null
-
User-Agent, or null to skip the UA check.
- $cookies : array<string, string>
-
Cookie map (name => value).
Return values
array{reason: string, source: string}|falsecreationell_captcha_request_bypassed()
Whether the current request is allowed to bypass captcha, under-attack and firewall protections. Reads $_SERVER, $_COOKIE and the request's client IP, then delegates to `creationell_captcha_evaluate_bypass()`.
creationell_captcha_request_bypassed() : array{reason: string, source: string}|false
Result is memoised for the request — settings, IP and cookies do not change
within a single PHP request. Only reason flows into the event-log context;
source is exposed for diagnostic logging by callers.
Return values
array{reason: string, source: string}|falsecreationell_captcha_validate_action_pattern()
Validates a single interceptor-action pattern.
creationell_captcha_validate_action_pattern(string $entry) : string|null
Allowed: lowercase/uppercase letters, digits, _, -, * (wildcard),
with an optional leading ! for exclusion patterns. Empty input or
patterns of only ! are rejected.
Parameters
- $entry : string
-
Raw entry (already trimmed by the caller).
Return values
string|null —Normalised entry, or null if invalid.
creationell_captcha_validate_cookie_entry()
Validates a single bypass-cookie entry of the form `name=value`.
creationell_captcha_validate_cookie_entry(string $entry) : string|null
Name must be alphanumeric, _ or -. The value is length-capped to 200
bytes and passed through sanitize_text_field(); an entry whose value is
empty — before or after sanitising — is refused (BK-14): hash_equals('','')
is true, so such an entry would let anybody past who sends the bare cookie
name. A bypass cookie is a shared secret; a secret of zero length is none.
Parameters
- $entry : string
-
Raw entry (already trimmed by the caller).
Return values
string|null —Normalised name=value entry, or null if invalid.
creationell_captcha_anonymize_ip()
Truncates an IP for DSGVO-compliant storage. IPv4 → last octet zeroed, IPv6 → last 80 bits zeroed. Invalid IPs return ''.
creationell_captcha_anonymize_ip(string $ip) : string
I2: an IPv4-mapped address is canonicalised first. Without that every such
client reduced to :: — one value for the whole IPv4 internet, which made
the event log useless AND emptied the network half of the under-attack pass
binding (BK-3). Callers normally pass creationell_captcha_get_client_ip(),
which canonicalises already; this repeats it for the direct callers.
Parameters
- $ip : string
-
A validated client IP address.
Return values
stringcreationell_captcha_request_body_fingerprint()
Returns a JSON-encoded fingerprint of $_POST: { field-name: value-byte-length }.
creationell_captcha_request_body_fingerprint() : string
No values are recorded — only structural metadata for attack-pattern
diagnosis. Field names that contain known sensitive substrings (password,
iban, api_key, …) are replaced with [masked:<8-char-sha256>] so the
fingerprint does not leak custom-form schema (e.g. bank_iban_input).
Output is length-capped to 2048 bytes; if longer, the JSON is collapsed
to "}" rather than truncated mid-entry.
Return values
stringcreationell_captcha_block_response()
Sends a fail-closed block response and terminates the request.
creationell_captcha_block_response(int $status, string $message[, int $retry_after = 0 ]) : void
Parameters
- $status : int
-
HTTP status code (403 firewall, 429 rate limit).
- $message : string
-
The message shown to the client.
- $retry_after : int = 0
-
Optional Retry-After value in seconds.
creationell_captcha_base64url_encode()
Base64URL encoder (RFC 4648 §5) — strips standard-base64 padding and replaces +/ with -_ so the value is URL-safe.
creationell_captcha_base64url_encode(string $bytes) : string
Parameters
- $bytes : string
-
Raw bytes to encode.
Return values
stringcreationell_captcha_base64url_decode()
Base64URL decoder — accepts unpadded URL-safe input and returns the raw bytes. Returns the empty string on malformed input (no exceptions).
creationell_captcha_base64url_decode(string $encoded) : string
Parameters
- $encoded : string
-
URL-safe base64 string.
Return values
stringcreationell_captcha_ratelimit_current_count()
Reads the current rate-limit counter for an IP without incrementing it.
creationell_captcha_ratelimit_current_count(string $ip) : int
Uses the same bucket key as Creationell\Captcha\RateLimiter::run() so the
value matches what the run-loop would see. Returns 0 if no transient exists
for the current window.
Parameters
- $ip : string
-
Client IP (call
creationell_captcha_get_client_ip()).
Return values
intcreationell_captcha_cf7_active()
Whether the Contact Form 7 integration is active.
creationell_captcha_cf7_active() : bool
Return values
boolcreationell_captcha_cf7_register_tag()
Registers the [creationell_captcha] Contact Form 7 form-tag.
creationell_captcha_cf7_register_tag() : void
Registered unconditionally (no creationell_captcha_cf7_active() guard) so CF7 always recognises the tag and never prints it as raw text; the tag handler returns an empty string when the integration is inactive.
creationell_captcha_cf7_tag_handler()
Renders the widget for the [creationell_captcha] form-tag.
creationell_captcha_cf7_tag_handler() : string
Return values
stringcreationell_captcha_cf7_auto_inject()
Auto-injects the widget into CF7 forms without a [creationell_captcha] tag.
creationell_captcha_cf7_auto_inject(string $elements) : string
Parameters
- $elements : string
-
The form's inner HTML.
Return values
stringcreationell_captcha_cf7_verify()
Verifies the captcha on a Contact Form 7 submission.
creationell_captcha_cf7_verify(mixed $spam, mixed $submission) : bool
Hooked on wpcf7_spam: returning true marks the submission as spam, which
CF7 then rejects through its standard flow.
Parameters
- $spam : mixed
-
Whether CF7 already classified the submission as spam.
- $submission : mixed
-
The WPCF7_Submission object.
Return values
boolcreationell_captcha_forminator_active()
Whether the Forminator integration is active.
creationell_captcha_forminator_active() : bool
Return values
boolcreationell_captcha_forminator_inject()
Auto-injects the widget before the submit button of a Forminator custom form.
creationell_captcha_forminator_inject(mixed $html, mixed $form_id) : string
The forminator_render_form_submit_markup filter also fires for polls and
quizzes; injection is restricted to the forminator_forms post type.
Parameters
- $html : mixed
-
The submit-section HTML.
- $form_id : mixed
-
The form's post ID.
Return values
stringcreationell_captcha_forminator_verify()
Verifies the captcha on a Forminator custom-form submission.
creationell_captcha_forminator_verify(mixed $errors) : array<int, array<string, string>>
Hooked on forminator_custom_form_submit_errors (custom forms only): a
non-empty errors array makes Forminator reject the submission.
Parameters
- $errors : mixed
-
The current array of submission errors.
Return values
array<int, array<string, string>>creationell_captcha_woocommerce_active()
Whether any WooCommerce protection applies right now.
creationell_captcha_woocommerce_active() : bool
Shared gate that the per-form predicates _wc_*_active() route through —
encapsulates the kill-switch, the class_exists check and the master
toggle so each form predicate just needs to add its own sub-toggle check.
Return values
boolcreationell_captcha_wc_uses_block_checkout()
Whether the currently configured WooCommerce checkout page uses the Block Checkout (the `woocommerce/checkout` block) rather than the classic `[woocommerce_checkout]` shortcode.
creationell_captcha_wc_uses_block_checkout() : bool
IN-2: this codebase only ever protects the classic checkout — the render
hook (woocommerce_review_order_before_submit) and the verify hook
(woocommerce_after_checkout_validation) both fire exclusively from
WC_Checkout::process_checkout(), which the Block Checkout's Store API
route never calls. This function exists purely to DETECT that gap for
wp creacaptcha doctor and the Settings help text — it deliberately does
NOT attempt to inject the widget or hook the Store API route (out of
scope per the audit's Phase-2 plan; that would be new functionality
belonging to its own module).
Return values
boolcreationell_captcha_wc_checkout_active()
Whether the WooCommerce checkout protection is active.
creationell_captcha_wc_checkout_active() : bool
Return values
boolcreationell_captcha_wc_checkout_render()
Renders the widget directly before the Place-Order button on the checkout.
creationell_captcha_wc_checkout_render() : void
creationell_captcha_wc_checkout_verify_cache()
Per-request cache of the checkout's own verify verdict, keyed by the raw `altcha` payload currently in `$_POST`.
creationell_captcha_wc_checkout_verify_cache([bool|null $set = null ]) : bool|null
IN-3: the classic checkout, when the customer checks "create an
account?", posts exactly ONE altcha payload but runs it through TWO of
our verify hooks within the same request —
creationell_captcha_wc_checkout_verify() first (inside
WC_Checkout::validate_checkout()), then
creationell_captcha_wc_registration_verify() a few lines later (inside
WC_Checkout::process_customer() → wc_create_new_customer()), reading
the IDENTICAL $_POST['altcha']. The underlying ALTCHA challenge is
single-use (class-engine.php's replay-transient marker), so the second
check would always find the payload already marked used and fail — an
account-creating order could never succeed. Recording the checkout
verdict here lets the registration verify reuse it for the same payload
instead of re-running the used-marker check a second time.
Scoped to this file/request only (static, resets every request) — it
does not weaken replay protection across requests: a genuinely replayed
payload from an EARLIER request still hits the used-marker on its very
first check here, because this cache starts empty every time.
Parameters
- $set : bool|null = null
-
Pass a bool to record the verdict for the current raw payload; omit (null, default) to just read a previously recorded one.
Return values
bool|null —The cached verdict for the current raw payload, or
null if none has been recorded yet this request.
creationell_captcha_wc_checkout_verify()
Verifies the captcha during checkout validation.
creationell_captcha_wc_checkout_verify(array<string, mixed> $data, mixed $errors) : void
woocommerce_after_checkout_validation fires inside WooCommerce's
process_checkout() after all other validation has run; adding an error
to the passed-through WP_Error aborts the order.
Parameters
- $data : array<string, mixed>
-
Posted checkout data (unused).
- $errors : mixed
-
The checkout
WP_Error(passed by reference of the object).
creationell_captcha_wc_login_active()
Whether the WooCommerce my-account login protection is active.
creationell_captcha_wc_login_active() : bool
Return values
boolcreationell_captcha_wc_login_render()
Renders the widget at the bottom of the WooCommerce login form.
creationell_captcha_wc_login_render() : void
creationell_captcha_wc_login_verify()
Verifies the captcha on a WooCommerce my-account login submission.
creationell_captcha_wc_login_verify(mixed $validation_error, string $username, string $password) : mixed
Returns a WP_Error to fail the login; otherwise returns the incoming
$validation_error value unchanged (so other filters can keep working).
Parameters
- $validation_error : mixed
-
The current validation error (
WP_Error|null|false). - $username : string
-
Submitted username (unused).
- $password : string
-
Submitted password (unused).
creationell_captcha_wc_registration_active()
Whether the WooCommerce registration protection is active.
creationell_captcha_wc_registration_active() : bool
Return values
boolcreationell_captcha_wc_registration_render()
Renders the widget at the bottom of the WooCommerce registration form.
creationell_captcha_wc_registration_render() : void
creationell_captcha_wc_nonce_verifies()
Reads a nonce value the way the given WooCommerce field name(s) would, replicating WooCommerce core's own field-name-agnostic fallback: the dedicated field wins if present, otherwise the generic `_wpnonce` field is used. WooCommerce's own nonce checks do not care WHICH field carried the value — only whether the value itself verifies against the action name.
creationell_captcha_wc_nonce_verifies(array<string, mixed> $source, string $dedicated_field, string $action) : bool
Review fix (Fix-Runde 1, IN-4 critical): an earlier version of this guard
checked the dedicated field's mere PRESENCE
(isset( $_POST['woocommerce-register-nonce'] )) as its "genuine form"
signal. That was exploitable — WooCommerce itself resolves the nonce
VALUE via the same _wpnonce fallback implemented here, so an anonymous
attacker could read the nonce value off the real registration form's
hidden field and resubmit register=…&email=…&_wpnonce=<value> while
OMITTING the woocommerce-register-nonce field. WooCommerce would still
accept the nonce via its fallback and create the account, while the old,
presence-only guard saw no dedicated field and treated the request as
"no form context" — skipping the captcha check entirely. Checking the
NONCE VALUE (via wp_verify_nonce()) instead of field presence closes
this: the fallback is followed identically on both sides, so there is no
field-name an attacker can omit to fool this guard while WooCommerce
itself still accepts the request.
Parameters
- $source : array<string, mixed>
-
$_POSTor$_REQUEST. - $dedicated_field : string
-
The form's own nonce field name.
- $action : string
-
The nonce action to verify against.
Return values
boolcreationell_captcha_wc_registration_form_context()
Whether the current `wc_create_new_customer()` call originates from a genuine WooCommerce registration- or checkout-form POST, as opposed to a Store-API, programmatic or CLI call that never had a form (and therefore no `altcha` field) in the first place.
creationell_captcha_wc_registration_form_context() : bool
IN-4: wc_create_new_customer() fires woocommerce_registration_errors
— the filter this module hooks — UNCONDITIONALLY, including for the
Woo Blocks Store API's own account-creation path and for direct
programmatic calls (wp eval, custom scripts, other plugins). Hard-
rejecting those whenever no altcha field happens to be present made
wc_create_new_customer() unusable outside an actual captcha-protected
form — the customer was simply never created.
Both genuine entry points already verify their OWN nonce, inside
WooCommerce core, before ever calling wc_create_new_customer() — so
independently re-verifying the SAME nonce value/action here (not
consuming anything, wp_verify_nonce() is read-only) is a safe, precise
signal, as long as it replicates WC's exact field-name fallback (see
creationell_captcha_wc_nonce_verifies() above — a presence-only check
was exploitable, see that function's docblock):
- Standalone My-Account registration form:
WC_Form_Handler::process_registration()gates onisset( $_POST['register'], $_POST['email'] )plus a validwoocommerce-registernonce (dedicated fieldwoocommerce-register-nonce, falling back to_wpnonce) before ever callingwc_create_new_customer(). - Classic checkout with "Create an account?":
WC_Checkout::process_checkout()verifies thewoocommerce-process_checkoutnonce (dedicated fieldwoocommerce-process-checkout-nonce, falling back to_wpnonce, read from$_REQUEST) beforevalidate_checkout()/process_customer()run — and by the time this filter fires from insideprocess_customer(), our OWN checkout verify (creationell_captcha_wc_checkout_verify()) has already run too, see IN-3's cache below.
CLI/cron/XML-RPC calls need no explicit exclusion here: none of them ever
populate $_POST/$_REQUEST with a nonce that verifies against either
action, so both checks below already resolve to false for them without
a dedicated SAPI/constant check — one less thing to keep in sync, and it
keeps this guard testable by directly setting $_POST (see tests/eval/).
Deliberately does NOT exclude on wp_doing_ajax(): WooCommerce's classic
checkout is itself normally submitted via wc-ajax=checkout
(WC_AJAX::checkout(), which defines DOING_AJAX itself before
dispatch) — excluding AJAX outright would silently re-open IN-3/IN-4 for
the majority of real-world checkouts.
Return values
boolcreationell_captcha_wc_registration_verify()
Verifies the captcha during WooCommerce my-account registration.
creationell_captcha_wc_registration_verify(mixed $errors, string $username, string $email) : mixed
Parameters
- $errors : mixed
-
The current
WP_Errorcarrier from WooCommerce. - $username : string
-
Submitted username (unused).
- $email : string
-
Submitted email (unused).
creationell_captcha_wc_lost_password_active()
Whether the WooCommerce lost-password render is active.
creationell_captcha_wc_lost_password_active() : bool
Return values
boolcreationell_captcha_wc_lost_password_render()
Renders the widget inside the WooCommerce lost-password form.
creationell_captcha_wc_lost_password_render() : void
creationell_captcha_wpforms_active()
Whether the WPForms integration is active.
creationell_captcha_wpforms_active() : bool
Return values
boolcreationell_captcha_wpforms_inject()
Auto-injects the widget directly before the WPForms submit button.
creationell_captcha_wpforms_inject(array<string, mixed> $form_data[, mixed $form = null ]) : void
Fires inside the
Parameters
- $form_data : array<string, mixed>
-
WPForms form configuration.
- $form : mixed = null
-
WPForms form post (unused, optional — der Hook
wpforms_display_submit_beforeliefert nur $form_data).
creationell_captcha_wpforms_verify()
Verifies the captcha on a WPForms submission.
creationell_captcha_wpforms_verify(array<int, mixed> $fields, array<string, mixed> $entry, array<string, mixed> $form_data) : void
Hooked on wpforms_process (action). On failure we set an entry in
wpforms()->process->errors[ $form_id ]['header'] — WPForms then renders
the message above the form and refuses to save the entry.
Parameters
- $fields : array<int, mixed>
-
Sanitized field values (unused).
- $entry : array<string, mixed>
-
Raw
$_POST['wpforms'](unused). - $form_data : array<string, mixed>
-
Form configuration.
creationell_captcha_interceptor_inject_buffer_start()
Conditionally starts the output buffer on template_redirect priority 0.
creationell_captcha_interceptor_inject_buffer_start() : void
The buffer only runs when (a) the master interceptor toggle is on, (b) at least one inject path is configured, AND (c) the current request path matches that pattern list. On non-matching pages the request is unaffected.
creationell_captcha_interceptor_inject_buffer()
Buffer callback. Replaces every `<form …>…</form>` with the same form plus an `<altcha-widget>` inserted directly before `</form>`. Idempotent — forms that already contain `<altcha-widget` are returned unchanged.
creationell_captcha_interceptor_inject_buffer(string $html) : string
Parameters
- $html : string
-
Full page HTML.
Return values
stringcreationell_captcha_run_interceptor()
Runs the request interceptor. Hooked on `init` at priority 1 so it fires before any form-processing handler.
creationell_captcha_run_interceptor() : void
creationell_captcha_protect_path()
Registers one or more path patterns to be guarded by the interceptor.
creationell_captcha_protect_path(string|array<int, string> $patterns) : void
Developer API — later form-plugin integrations call this to protect their
submission endpoints without an admin entering patterns by hand. The
patterns are merged into the creationell_captcha_interceptor_paths filter.
Parameters
- $patterns : string|array<int, string>
-
A path pattern or list of patterns.
creationell_captcha_activate()
Runs on plugin activation: seeds default options and HMAC secrets.
creationell_captcha_activate() : void
creationell_captcha_deactivate()
Runs on plugin deactivation: clears every cron slot the plugin owns, sweeps the expired replay markers out of the options table and lets every module react via the `creationell_captcha_deactivated` action hook.
creationell_captcha_deactivate([bool $network_deactivating = false ]) : void
B-M5: cron slots and replay markers are per-site data, so on a network-wide
deactivation the work has to be done on every site of the network — not just
on whichever site happens to be current when deactivate_{$plugin} fires
(that is the network admin's site, usually the main site). uninstall.php
already walks the network this way since DS-3; this is the same walk for the
same reason.
Parameters
- $network_deactivating : bool = false
-
Whether the plugin is being deactivated for the whole network. WordPress passes this as the single argument of
deactivate_{$plugin}(wp-admin/includes/plugin.php,deactivate_plugins()); the default keeps the function callable by hand.
creationell_captcha_deactivate_site()
The deactivation work for exactly one site: clears the three cron slots this plugin owns there and sweeps that site's expired replay markers.
creationell_captcha_deactivate_site() : void
Everything in here is relative to the CURRENT site, so it is safe to call
from inside a switch_to_blog() bracket — wp_clear_scheduled_hook() works
on the current site's cron option and the sweep re-reads $wpdb->options
on every call.
creationell_captcha_deactivate_network()
Runs the per-site deactivation work on every site of the network.
creationell_captcha_deactivate_network() : int
Walks the sites in batches instead of materialising every site id at once —
same shape and same batch size as the DS-3 walk in uninstall.php, so both
routines behave the same way on the same network.
Return values
int —Number of sites processed.
creationell_captcha_delete_expired_replay_markers()
Deletes those replay-marker options of the current site whose lifetime has already run out.
creationell_captcha_delete_expired_replay_markers() : int
Live markers are deliberately left in place. Removing them as well would be
tidier — nothing sweeps them while the plugin is unloaded — but it would
re-open the window CM-9 closed: a payload whose marker is gone passes
Engine::verify() a second time as long as its own signed expiresAt has
not been reached (challenge_expiry, default 300 s, settings range
60–3600 s, checked inside the ALTCHA library independently of the marker —
lib/altcha-org/altcha/src/Altcha.php, verifySolution()). Deactivating
and re-activating inside that window is an everyday admin action — update,
debugging, changing the plugin load order — so the marker has to survive it.
What stays behind when the plugin is never re-activated is one option row per
payload whose challenge has not run out yet (since 1.1.0 the marker lifetime
is the challenge's own remaining validity — Engine::replay_marker_lifetime()).
uninstall.php removes those unconditionally, and after a re-activation the
next successful claim re-arms the cron slot cleared in
creationell_captcha_deactivate_site() (Engine::claim_replay_marker() →
schedule_replay_cleanup()), whose sweep then drops them.
The predicate is the one Engine::cleanup_replay_markers() uses: the marker
value is the Unix timestamp the marker expires at, written by
Engine::claim_replay_marker(). A row with a non-numeric value casts to 0
and is therefore treated as expired — same fail-open-on-garbage behaviour as
the sweep, and no caller outside the engine ever writes these rows.
Return values
int —Number of option rows removed.
creationell_captcha_run_rate_limiter()
Runs the per-IP rate limiter. Hooked on `init` at priority 0; registered after the firewall so the firewall runs first.
creationell_captcha_run_rate_limiter() : void
creationell_captcha_request_path()
Returns the path portion of the current request, the way WordPress core derives it — NOT the way a URL parser would.
creationell_captcha_request_path() : string
The return value is still percent-encoded (the "wire spelling"); callers that
need the decoded form apply rawurldecode() to it exactly once, after this
function has split off the query.
WHY THIS EXISTS (audit module 27, finding C1)
Every call site used to run wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ).
wp_parse_url() is a URL parser, and REQUEST_URI is not a URL — it is a
request target. For a target that starts with // the parser treats the
string as a scheme-relative URL (it prepends placeholder:, see
wp-includes/http.php), so the first segment becomes the HOST:
REQUEST_URI wp_parse_url(…, PHP_URL_PATH) WP::parse_request()
/kontakt/ '/kontakt/' kontakt
//kontakt/ '/' kontakt ← same page
///kontakt/ '' kontakt ← same page
Core reduces the target in WP::parse_request() with
list( $req_uri ) = explode( '?', $_SERVER['REQUEST_URI'] ); $req_uri = trim( $req_uri, '/' );
(wp-includes/class-wp.php) and therefore routes all three spellings to the
same page. The guards saw / or '', matched no pattern, and let the
request through: POST //kontakt was a one-character bypass of the entire
interceptor path guard.
WHAT THIS DOES
- Split off query and fragment BEFORE anything is decoded, so a
percent-encoded
?inside the path cannot smuggle a query string in. - Strip scheme and authority if the target arrived in absolute form
(
POST http://example.com/kontakt HTTP/1.1, RFC 9112 §3.2.2). Core does not do this and simply 404s such a request, butwp_parse_url()did — so keeping it guards MORE than core routes, never less. Dropping it would have taken protection away that 1.0.2 had. - Collapse leading slashes to exactly one — the same reduction core's
trim( $req_uri, '/' )performs. INNER double slashes are kept, because core keeps them too, and the TRAILING slash is kept, because existing patterns are written against the spellingwp_parse_url()produced.
Tags
Return values
string —Request path with exactly one leading slash, still encoded.
creationell_captcha_request_target()
Der aktuelle Request als relativer Ziel-URI: Pfad aus `creationell_captcha_request_path()`, Query aus derselben Zeichenkette.
creationell_captcha_request_target() : string
WARUM DAS EINE EIGENE FUNKTION IST (Nachlese Modul 27, Strang N1; Umfang erweitert im Re-Review und in der Nachlese N6)
remove_query_arg() OHNE URL-Argument und add_query_arg() ohne URL-
Argument sind dieselbe Mechanik: beide delegieren an add_query_arg(), und
dessen Zweig count( $args ) < 3 (bzw. < 2 bei der Array-Form) liest
$_SERVER['REQUEST_URI'] ROH. Sie leiten also eine Pfadangabe aus dem
Request ab, ohne das sichtbar zu tun — deshalb standen die betroffenen
Stellen in keinem der vier Review-Berichte.
Gemessen wurde das am Ausblenden-Link des Härtungs-Hinweises: bei
REQUEST_URI = "//wp-admin/admin.php?page=…" rendert er ein
protokollrelatives href="//wp-admin/admin.php?…"; esc_url() reicht jede
mit / beginnende Zeichenkette unverändert durch. Der Browser liest das als
Host wp-admin, der Klick kommt nie an. Dieselbe Konstruktion stand am
„↻ Aktualisieren"-Link der Statistik-Seite.
Die Query kommt bewusst aus DERSELBEN Zeichenkette wie der Pfad, damit
beide Hälften zusammenpassen und nicht die eine aus REQUEST_URI und die
andere aus QUERY_STRING stammt (die beiden können auseinanderlaufen, etwa
hinter einer Rewrite-Regel). Und der Rückgabewert ist bewusst RELATIV: er
ersetzt genau das, was der Kern ohne URL-Argument selbst eingesetzt hätte.
VOLLZÄHLIGKEIT: Seit der Nachlese N6 übergibt jeder
add_query_arg()/remove_query_arg()-Aufruf im ausgelieferten Code eine
explizite URL — entweder diese Funktion, eine $base_url aus
menu_page_url() oder admin_url(). Nachgezählt wird das nicht von Hand,
sondern von tests/test-hardening-migration.php (Abschnitt „Bestandsaufnahme"),
das alle Aufrufe unter includes/ auszählt und rot wird, sobald einer wieder
ohne URL-Argument dasteht. Ein früherer Stand dieses Docblocks nannte die
Redirect-Funktion der Härtungs-Migration „die neunte und letzte Stelle im
Plugin, die eine Pfadangabe aus dem Request ableitet" — das war messbar
falsch und ist hiermit ersetzt.
Tags
Return values
string —Relativer Ziel-URI (führender Schrägstrich, genau einer).
creationell_captcha_current_rest_route()
Returns the REST route the current request actually addresses.
creationell_captcha_current_rest_route() : string
Returns a normalised route (leading slash, no trailing slash, no query
part) — for example /creationell-captcha/v1/challenge — or the empty
string when the request is not served by the REST API.
The function is deliberately conservative: whenever it cannot establish that WordPress core will hand the request to the REST server, it returns the empty string. Both consumers fail closed on that answer (the interceptor guards the request, the rate limiter counts it), so under-detection is safe while over-detection hands out a bypass.
It must work at init priority 0/1, i.e. long before parse_request has
populated $GLOBALS['wp']->query_vars and before core defines the
REST_REQUEST constant, so it reconstructs core's own decision from the
request instead of reading either of those.
Tags
Return values
string —Normalised REST route, or '' when this is not a REST request.
creationell_captcha_normalize_rest_route()
Normalises a raw `rest_route` value into the plugin's canonical route form.
creationell_captcha_normalize_rest_route(string $raw) : string
Returns '' for every value that does not address a concrete REST route.
Parameters
- $raw : string
-
Raw
rest_routevalue as core would see it.
Tags
Return values
string —Route with a leading slash and without a trailing one, or ''.
creationell_captcha_register_rest_routes()
Registers the public challenge route.
creationell_captcha_register_rest_routes() : void
REST-GATE-OK: /challenge is deliberately public (permission_callback __return_true) — it is the shared entry point that issues a fresh, single-use PoW challenge for ALL captcha types, not a feature specific to one toggle. It remains subject to the global kill switch (creationell_captcha_is_disabled()) below.
creationell_captcha_rest_challenge()
Returns a fresh, single-use challenge. Records the issuance via the standard event channel — aggregate counters always increment, the detail-log entry is gated by the `log_challenge` per-type toggle from Modul 11c.
creationell_captcha_rest_challenge(WP_REST_Request $request) : WP_REST_Response
Parameters
- $request : WP_REST_Request
-
The REST request. Optional query parameter
ctx(single-use, 120-second token the under-attack interstitial passes to suppress the code-challenge attachment). A challenge issued for an acceptedctxis marked under-attack-only inside its signed parameters and is worthless at a protected form — see Engine::verify().
Return values
WP_REST_Response —JSON challenge envelope with algorithm, challenge,
salt, signature, parameters and optional
codeChallenge.image URL.
creationell_captcha_canonical_params_json()
Canonical-JSON serialisation of ALTCHA challenge parameters, byte- identical to `altcha-lib-php`'s `ChallengeParameters::toCanonicalJson()` (= ksort top-level + recursive ksort on assoc sub-arrays, JSON-encoded with UNESCAPED_SLASHES | UNESCAPED_UNICODE, null keys dropped).
creationell_captcha_canonical_params_json(array<string, mixed> $params) : string
Needed for the re-sign step in Modul 15's /challenge handler after we mutate parameters.data.ccode.
Parameters
- $params : array<string, mixed>
-
Parameter array from
create_challenge().
Return values
stringcreationell_captcha_canonical_sort_recursive()
Recursive helper used by `canonical_params_json` — mirrors the lib's `sortRecursive`. List arrays (sequential integer keys) keep their order; associative arrays get `ksort`-ed in place.
creationell_captcha_canonical_sort_recursive(array<string|int, mixed> &$data) : void
Parameters
- $data : array<string|int, mixed>
creationell_captcha_list_setting_keys()
Setting keys whose value is a list (every `textarea` field).
creationell_captcha_list_setting_keys() : array<int, string>
Derived from the field specification so the list never drifts. "Load defaults" preserves these keys; "reset" clears them.
Return values
array<int, string>creationell_captcha_export_settings()
Builds the settings-export payload.
creationell_captcha_export_settings() : array<string, mixed>
The HMAC secrets are deliberately excluded — they must never leave the site.
Return values
array<string, mixed>creationell_captcha_import_settings()
Validates and applies a settings-export payload.
creationell_captcha_import_settings(array<string, mixed> $payload) : array<string, mixed>|WP_Error
The settings array is run through creationell_captcha_sanitize_settings(),
so the same guarantees as the settings form apply: whitelisted selects,
clamped numbers, bounded lists, unknown keys dropped, missing keys defaulted.
Parameters
- $payload : array<string, mixed>
-
Decoded export payload.
Return values
array<string, mixed>|WP_Error —On success: { imported, version_notice, source_notice }.
creationell_captcha_reset_settings()
Full factory reset: writes the complete default settings array, which also empties every list and drops stored keys outside the current field specification. Secrets, analytics counters and the event log are left untouched.
creationell_captcha_reset_settings() : void
creationell_captcha_load_default_settings()
Resets every non-list setting to its default while preserving the current list values (IP block/allow, UA block, interceptor paths).
creationell_captcha_load_default_settings() : void
Die übernommenen Listen laufen dabei durch dieselbe Eingabeprüfung wie ein
regulärer Speichervorgang (E3a): ein Eintrag, der an den Plugin-Schreibwegen
vorbei in die Option kam (wp option update, DB-Restore), überlebte
„Standardwerte laden" bisher ungeprüft — auch dann, wenn die Prüfung ihn
heute ablehnt.
creationell_captcha_admin_tabs()
Ordered list of the admin settings tabs.
creationell_captcha_admin_tabs() : array<string, string>
The array key is the tab id; it doubles as the suffix of the Settings-API
page slug (creationell-captcha-tab-<id>) that do_settings_sections() uses.
Return values
array<string, string> —Tab id => visible label.
creationell_captcha_admin_sections()
Settings sections and the tab each one belongs to.
creationell_captcha_admin_sections() : array<string, array<string, string>>
Section order within a tab follows this array's order.
Return values
array<string, array<string, string>> —Section id => { tab, title, callback }.
creationell_captcha_settings_fields()
Field specification for the captcha settings.
creationell_captcha_settings_fields() : array<string, array<string, mixed>>
Every field carries a section key naming the section (and thereby the tab)
it renders in; the section ids match creationell_captcha_admin_sections().
Return values
array<string, array<string, mixed>>creationell_captcha_register_settings()
Registers the plugin setting, the per-tab sections and the fields.
creationell_captcha_register_settings() : void
creationell_captcha_sanitize_context()
Liest — und setzt optional — den angehefteten Sanitizer-Kontext.
creationell_captcha_sanitize_context([string|false|null $set = false ]) : string|null
Der Kontext wird bewusst EXPLIZIT gesetzt und nicht aus is_admin(),
admin_init oder ähnlichen Umgebungsindizien erraten: genau dieses Raten ist
die Wurzel der Kartenwidersprüche W1/W2 der Befunddatei (ein Menüpunkt, zwei
Semantiken, je nachdem ob admin_init gefeuert hat).
Parameters
- $set : string|false|null = false
-
FALSE liest nur; ein String oder NULL setzt.
Return values
string|null —Aktuell angehefteter Kontext oder NULL.
creationell_captcha_with_sanitize_context()
Führt $callback aus, während der Sanitizer-Kontext auf $context festgelegt ist.
creationell_captcha_with_sanitize_context(string $context, callable $callback) : mixed
Nötig, weil register_setting() den Sanitizer zusätzlich als
sanitize_option_creationell_captcha_settings-Filter anhängt: jeder
update_option()-Aufruf im Admin-Kontext läuft durch ihn hindurch, auch
Import und Reset über admin-post.php. Ohne diese Klammer bekämen genau
jene Pfade Formular-Semantik.
Parameters
- $context : string
-
Einer der CREATIONELL_CAPTCHA_SANITIZE_*-Werte.
- $callback : callable
-
Auszuführender Schreibvorgang.
Return values
mixed —Rückgabewert von $callback.
creationell_captcha_sanitize_settings_option()
Einstiegspunkt des `sanitize_option_creationell_captcha_settings`-Filters.
creationell_captcha_sanitize_settings_option(mixed $input) : array<string, mixed>
Parameters
- $input : mixed
-
Rohwert aus dem Schreibvorgang.
Return values
array<string, mixed>creationell_captcha_truncate_setting_text()
Kürzt einen Einstellungswert auf höchstens $max_chars Zeichen und garantiert gültiges UTF-8.
creationell_captcha_truncate_setting_text(string $value, int $max_chars) : string
Bewusst zeichen- statt byteorientiert: substr() schnitt bisher mitten in
eine Mehrbyte-Sequenz (AF-7). Ungültiges UTF-8 wird entfernt, bevor der Wert
in den serialisierten Options-Blob wandert — nicht danach in der DB repariert.
Parameters
- $value : string
-
Bereits vorsanitisierter Wert.
- $max_chars : int
-
Obergrenze in Zeichen (nicht Bytes).
Return values
stringcreationell_captcha_truncate_chars()
Kürzt gültiges UTF-8 ohne mbstring auf $max_chars ZEICHEN.
creationell_captcha_truncate_chars(string $value, int $max_chars) : string
Drei Stufen, weil jede einzelne ausfallen kann — und der Ausfall darf den Wert nicht kosten:
preg_match( '/^.{0,N}/us' )— der schnelle Weg.slässt „.“ auch Zeilenumbrüche treffen (relevant fürtextblock).iconv_substr()— greift, wenn Stufe 1 an einem PCRE-Limit scheitert (Backtrack-Limit, JIT-Stack). iconv ist keine PCRE-Bibliothek und deshalb von diesen Limits nicht betroffen.- Ein Byte-Lauf über die UTF-8-Startbytes — braucht überhaupt keine Erweiterung und keine PCRE.
WARUM ES DIESE FUNKTION GIBT (Nachlese N6, Befund 7; Fehlerklasse 4)
Nach Stufe 1 stand hier bis zur Nachlese return '';. Das war als
„lieber ein leeres Feld, das der Betreiber sieht" begründet — nur sieht er
es nicht: an dieser Stelle wird keine add_settings_error() erzeugt, das
leere Feld ist also genau so still wie die kaputte Sequenz, gegen die die
Begründung argumentierte. Und der Sanitizer läuft bei JEDEM Schreibvorgang,
auch bei einem, der dieses Feld gar nicht anfasst (Werksreset,
„Standardwerte laden", settings set eines anderen Schlüssels, jedes
Speichern der Einstellungsseite): ein gespeicherter CSS- oder
Übersetzungsblock wäre damit still und vollständig weg gewesen. Der
Vorgängerstand vor W2-7 lieferte an dieser Stelle wenigstens den Anfang des
Wertes (substr(), byteweise und deshalb möglicherweise mitten in einer
Mehrbyte-Sequenz). Beide Stufen unten liefern denselben Anfang, nur an einer
ZEICHEN-Grenze — der Datenverlust entfällt, ohne den AF-7-Fehler
zurückzuholen.
Vorbedingung: $value ist gültiges UTF-8. Der Aufrufer stellt das her; Stufe 3 verlässt sich darauf, weil sie Startbytes zählt und Fortsetzungsbytes (0x80–0xBF) in gültigem UTF-8 nie an einer Zeichengrenze stehen.
Parameters
- $value : string
-
Gültiges UTF-8.
- $max_chars : int
-
Obergrenze in Zeichen.
Tags
Return values
stringcreationell_captcha_truncate_chars_by_bytes()
Dritte und letzte Kürzungsstufe: zählt UTF-8-Startbytes.
creationell_captcha_truncate_chars_by_bytes(string $value, int $max_chars) : string
Braucht weder mbstring noch iconv noch PCRE — deshalb steht sie am Ende der
Kette in creationell_captcha_truncate_chars(). Eigene Funktion, weil sie
sonst auf jedem Rechner mit iconv unerreichbar und damit ungetestet wäre.
Startbytes: 0xxxxxxx = 1 Byte, 110xxxxx = 2, 1110xxxx = 3,
11110xxx = 4. Fortsetzungsbytes (10xxxxxx, 0x80–0xBF) stehen in gültigem
UTF-8 nie an einer Zeichengrenze; ungültige Eingaben schliesst der Aufrufer
aus (creationell_captcha_truncate_setting_text() prüft und repariert vorher).
Parameters
- $value : string
-
Gültiges UTF-8.
- $max_chars : int
-
Obergrenze in Zeichen.
Tags
Return values
stringcreationell_captcha_sanitize_settings()
Sanitises the settings array before it is stored.
creationell_captcha_sanitize_settings(mixed $input[, string $context = CREATIONELL_CAPTCHA_SANITIZE_PROGRAMMATIC ]) : array<string, mixed>
Parameters
- $input : mixed
-
Raw input from the settings form.
- $context : string = CREATIONELL_CAPTCHA_SANITIZE_PROGRAMMATIC
-
Schreibkontext, einer der CREATIONELL_CAPTCHA_SANITIZE_*-Werte. Default ist „programmatisch": alle direkten Aufrufer (Import, WP-CLI) übergeben einen vollständigen Wertesatz. Ausgewertet werden ausschließlich die zwei bekannten programmatischen Werte …_PROGRAMMATIC und …_RESET; jeder andere Wert — auch ein unerwarteter, etwa der Optionsname aus einer Legacy-
sanitize_option- Registrierung — erhält die verlustfreie Formular-Semantik. …_RESET unterscheidet sich von …_PROGRAMMATIC an genau einer Stelle: er übernimmt auch keine gespeicherten Schlüssel ausserhalb der aktuellen Feldspezifikation (voller Werksreset).
Return values
array<string, mixed>creationell_captcha_sync_event_log_table()
Legt die Ereignis-Log-Tabelle an, sobald das Detail-Log eingeschaltet GESPEICHERT wurde.
creationell_captcha_sync_event_log_table() : void
E3b — warum das hier hängt und nicht mehr im Sanitizer: Der Sanitizer ist
über register_setting() zugleich der sanitize_option-Filter der Option
und eine öffentliche, dokumentierte Funktion. Ein ensure_table() in ihm
bedeutete DDL für jeden Aufrufer, der nur prüfen, vergleichen oder eine
Vorschau bauen will — wp creacaptcha doctor verzichtet deshalb bereits
ausdrücklich auf den Sanitizer-Vergleich (Check 9). Die Tabellenanlage
gehört an das Ergebnis des Schreibvorgangs; das ist dieselbe Bauform, die
creationell_captcha_sync_cloudflare_cron() für den Cron-Slot benutzt.
Reichweite gegenüber vorher:
- wp-admin (Formular, Import, Werksreset, „Standardwerte laden"): unverändert — die Tabelle entsteht weiterhin beim Speichern.
- WP-CLI und jeder andere Kontext ohne
admin_init: NEU abgedeckt. Dort hing dersanitize_option-Filter gar nicht;wp option update creationell_captcha_settingskonnte das Log einschalten, ohne dass je eine Tabelle entstand (die Selbstheilungs-Lücke aus DS-4). - Ein Speichern, das den Optionswert unverändert lässt: WordPress schreibt
dann nichts und feuert keinen der beiden Hooks. Diese Lücke deckt seit der
Nachlese N6
creationell_captcha_store_settings()ab — jeder Plugin-eigene Schreibweg auf die Option ruft die Synchronisierung danach selbst noch einmal auf. Nicht abgedeckt bleibt ein Schreibvorgang an diesen Wegen vorbei (wp option update, DB-Restore) ohne Wertänderung; dafür bleibtwp creacaptcha repairder Weg, auf den Doctor-Check 3 und der Log-Hinweis in Analytics::log_row() verweisen.
DS-2-Grenze: Auslöser ist ein bereits erfolgter Schreibvorgang auf die
Plugin-Option. Anders als beim admin_init-Aufhänger der Migration in
upgrade.php gibt es hier keinen anonym erreichbaren Pfad — wer die Option
schreiben kann, hat die Site ohnehin in der Hand.
creationell_captcha_store_settings()
Der eine Schreibweg des Plugins auf `creationell_captcha_settings`.
creationell_captcha_store_settings(array<string, mixed> $clean) : void
Warum es ihn gibt (Nachlese N6, Befund 4): update_option() feuert
update_option_{$option} NUR, wenn sich der gespeicherte Wert wirklich
ändert (wp-includes/option.php vergleicht alt/neu und bricht sonst ohne
DB-Schreibvorgang und ohne Hook ab). Seit E3b hängt die Anlage der
Ereignis-Tabelle ausschliesslich an diesen Hooks — ein Schreibvorgang ohne
Wertänderung ist damit ein Erfolg ohne Wirkung:
wp creacaptcha settings set analytics_event_log 1 # steht schon auf 1
wp creacaptcha blocklist add 203.0.113.9 # steht schon drin
meldeten Erfolg, während eine von Hand gelöschte Tabelle (DB-Restore,
Migration) weg blieb und jedes Ereignis still verworfen wurde. Strang N2
hatte das für Command::enable() einzeln kompensiert; der zweite und dritte
CLI-Weg auf denselben Schlüssel hatten die Kompensation nicht. Statt sie an
jeder Stelle zu wiederholen, steht sie jetzt EINMAL hier, und alle
Plugin-eigenen Schreibwege gehen hindurch.
Nachgerufen wird nur, wenn update_option() FALSE liefert — also genau
dann, wenn der Hook nicht gefeuert hat. ensure_table() ist zwar
idempotent, aber nicht kostenlos (dbDelta liest das Schema), und ein
zweiter Durchlauf bei jedem echten Speichervorgang wäre reine Wiederholung.
Der FALSE-Zweig deckt beide Ursachen ab: unveränderter Wert und
fehlgeschlagener Schreibvorgang.
Parameters
- $clean : array<string, mixed>
-
Bereits sanitisierter Wertesatz.
Tags
creationell_captcha_render_engine_section()
Renders the description shown at the top of the Proof-of-Work-Engine section.
creationell_captcha_render_engine_section() : void
creationell_captcha_render_widget_appearance_section()
Renders the description shown at the top of the widget-appearance section.
creationell_captcha_render_widget_appearance_section() : void
creationell_captcha_render_code_challenge_section()
Renders the description shown at the top of the code-challenge section.
creationell_captcha_render_code_challenge_section() : void
Includes a warning notice when the PHP-GD extension is missing — without it, image rendering cannot work and the trigger logic stays disabled.
creationell_captcha_render_core_forms_section()
Renders the description shown at the top of the core-forms section.
creationell_captcha_render_core_forms_section() : void
creationell_captcha_render_interceptor_section()
Renders the description shown at the top of the interceptor section.
creationell_captcha_render_interceptor_section() : void
creationell_captcha_render_form_plugins_section()
Renders the description shown at the top of the form-plugins section.
creationell_captcha_render_form_plugins_section() : void
When no supported form plugin is active the section has no fields, so the description doubles as a hint.
creationell_captcha_render_proxy_section()
Renders the description shown at the top of the proxy section.
creationell_captcha_render_proxy_section() : void
creationell_captcha_render_bypass_section()
Renders the description shown at the top of the bypass section.
creationell_captcha_render_bypass_section() : void
creationell_captcha_render_firewall_section()
Renders the description shown at the top of the firewall section.
creationell_captcha_render_firewall_section() : void
creationell_captcha_render_ratelimit_section()
Renders the description shown at the top of the rate-limiting section.
creationell_captcha_render_ratelimit_section() : void
creationell_captcha_render_underattack_section()
Renders the description shown at the top of the under-attack section.
creationell_captcha_render_underattack_section() : void
creationell_captcha_render_underattack_appearance_section()
Renders the description shown at the top of the under-attack appearance section.
creationell_captcha_render_underattack_appearance_section() : void
creationell_captcha_render_analytics_section()
Renders the description shown at the top of the analytics section.
creationell_captcha_render_analytics_section() : void
creationell_captcha_render_email_section()
Renders the description shown at the top of the email-protection section.
creationell_captcha_render_email_section() : void
creationell_captcha_render_field()
Renders a single settings field.
creationell_captcha_render_field(array<string, mixed> $args) : void
Parameters
- $args : array<string, mixed>
-
Field arguments (key + field spec).
creationell_captcha_field_label()
Liefert das Label eines Einstellungsfeldes (Fallback: der Schlüssel selbst).
creationell_captcha_field_label(string $key) : string
Parameters
- $key : string
-
Feldschlüssel.
Return values
stringcreationell_captcha_field_spec()
Request-lokal gehaltene Feldspezifikation für die beiden Label-Helfer.
creationell_captcha_field_spec() : array<string, array<string, mixed>>
W2-8: Beide hielten je einen wortgleichen static $spec-Block und damit zwei
unabhängige Kopien der ~75-Felder-Spezifikation im Speicher. Eine gemeinsame
Zugriffsfunktion genügt — und sie macht zugleich sichtbar, dass beide
denselben Stand meinen.
Return values
array<string, array<string, mixed>>creationell_captcha_field_option_label()
Liefert das Options-Label eines `select`-Feldes (Fallback: der Rohwert).
creationell_captcha_field_option_label(string $key, string $value) : string
Parameters
- $key : string
-
Feldschlüssel.
- $value : string
-
Optionswert.
Return values
stringcreationell_captcha_tools_redirect()
Stores a one-shot admin notice and redirects back to the Werkzeuge page.
creationell_captcha_tools_redirect(string $type, string $message) : never
Parameters
- $type : string
-
'success' or 'error'.
- $message : string
-
Notice text.
Return values
nevercreationell_captcha_tools_guard()
Guards a tools action: requires manage_options and a valid nonce.
creationell_captcha_tools_guard(string $action) : void
Parameters
- $action : string
-
The nonce action name.
creationell_captcha_handle_export_settings()
Streams the current settings as a JSON download.
creationell_captcha_handle_export_settings() : void
creationell_captcha_handle_import_settings()
Handles the settings-import upload.
creationell_captcha_handle_import_settings() : void
creationell_captcha_collect_settings_error_messages()
Collects the plugin's queued settings-error messages, de-duplicated.
creationell_captcha_collect_settings_error_messages() : array<int, string>
Der Sanitizer läuft auf dem Importweg zweimal (einmal explizit, einmal über
den sanitize_option-Filter in update_option()), meldet identische Funde
also doppelt — deshalb array_unique.
Return values
array<int, string>creationell_captcha_handle_reset_settings()
Handles the full factory reset.
creationell_captcha_handle_reset_settings() : void
creationell_captcha_handle_load_defaults()
Handles "load defaults" (keeps the lists).
creationell_captcha_handle_load_defaults() : void
creationell_captcha_handle_cloudflare_refresh()
Triggers a manual Cloudflare-range refresh from the Werkzeuge page.
creationell_captcha_handle_cloudflare_refresh() : void
creationell_captcha_handle_cloudflare_clear()
Empties the cached Cloudflare-range option from the Werkzeuge page.
creationell_captcha_handle_cloudflare_clear() : void
creationell_captcha_register_tools_page()
Registers the "Werkzeuge" submenu page under the CreaCaptcha menu.
creationell_captcha_register_tools_page() : void
creationell_captcha_render_tools_notice()
Renders the one-shot admin notice left behind by a tools action.
creationell_captcha_render_tools_notice() : void
creationell_captcha_render_tools_page()
Renders the "Werkzeuge" page.
creationell_captcha_render_tools_page() : void
creationell_captcha_render_cloudflare_status()
Renders the Cloudflare-cache status block inside the Werkzeuge tool card.
creationell_captcha_render_cloudflare_status() : void
creationell_captcha_run_under_attack()
Runs the under-attack interstitial gate for front-end page views. Hooked on `template_redirect` — fires only for front-end requests, so wp-admin, wp-login.php, REST and cron are inherently exempt.
creationell_captcha_run_under_attack() : void
creationell_captcha_maybe_upgrade()
Runs schema migrations when the stored version differs from the running one.
creationell_captcha_maybe_upgrade() : void
Hooked on admin_init and gated twice — see the inline notes on DS-2. When
the event-log table already exists it is re-run through dbDelta so new
columns are added; a missing table is created only when
analytics_event_log says it should exist (DS-4).
creationell_captcha_migrate_widget_mode()
Migrates the legacy `widget_mode` setting (Modul 11a) to the new `widget_display` + `widget_auto_trigger` pair (Modul 14). Idempotent — if `widget_display` is already present in the stored option, the migration is skipped.
creationell_captcha_migrate_widget_mode() : void
Mapping: visible → widget_display=standard, widget_auto_trigger=none auto → widget_display=invisible, widget_auto_trigger=onload overlay → widget_display=floating, widget_auto_trigger=onsubmit
Den Alt-Schlüssel widget_mode nimmt die Migration aus dem Wertesatz, den
sie übergibt; ab Modul 14 wird er nirgends mehr gelesen. Ob er damit aus der
Option verschwindet, entscheidet der Sanitizer: Er trägt am Ende jeden
Schlüssel nach, der zwar gespeichert ist, aber nicht in der
Feldspezifikation steht (Carry-over für den Toggle eines gerade inaktiven
Formular-Plugins) — und er liest dafür den Stand VOR diesem Schreibvorgang.
Im wp-admin, wo er zusätzlich als sanitize_option-Filter hängt, kommt
widget_mode deshalb zurück. Folgenlos: keine Codestelle wertet den
Schlüssel noch aus, und creationell_captcha_get_settings() merged ohnehin
über die Defaults. Aus demselben Grund ist der Aufräum-Zweig unten
(widget_display vorhanden, widget_mode noch da) dort ein Schreibvorgang
ohne Änderung.
creationell_captcha_store_migrated_settings()
Writes a migrated settings array back — through the sanitiser, with the write context pinned.
creationell_captcha_store_migrated_settings(array<string, mixed> $settings) : void
B-M6: Die Migration schrieb den rohen Options-Inhalt zurück. Heute ist das
folgenlos (der Pfad läuft nur im wp-admin, wo der sanitize_option-Filter
hängt, und die gesetzten Werte stammen aus einer Konstantentabelle), aber es
ist derselbe Bauart-Fehler wie in settings-manager.php: Ob sanitisiert
wird, hing am Request-Kontext statt am Aufrufer. Der Kontext ist
PROGRAMMATIC — der übergebene Wertesatz ist vollständig und bewusst gewählt,
die requires-Rückschreibung der Formular-Semantik würde eine vom Sanitizer
verworfene Eingabe wieder einsetzen.
Was diese Funktion NICHT leistet: einen Schlüssel aus der Option entfernen.
Die Nachtrags-Schleife am Ende des Sanitizers setzt jeden gespeicherten
Schlüssel wieder ein, der nicht in der Feldspezifikation steht — sie liest
dabei den Stand VOR diesem Schreibvorgang. Ein unset() hier würde nur in
einem Request ohne sanitize_option-Filter wirken und genau die
Kontextabhängigkeit wiederherstellen, die dieser Fix beseitigt. Der
Alt-Schlüssel widget_mode bleibt deshalb gegebenenfalls stehen; er wird
nirgends mehr gelesen (siehe Docblock der Migration).
Parameters
- $settings : array<string, mixed>
-
Migrierter Wertesatz.
creationell_captcha_register_assets()
Registers the widget script and — for Argon2id — its worker registration.
creationell_captcha_register_assets() : void
creationell_captcha_safe_inline_css()
Macht eine gespeicherte CSS-Zeichenkette sicher für die Ausgabe in einem `<style>`-Element.
creationell_captcha_safe_inline_css(string $css) : string
Gemeinsame Ausgabe-Härtung für beide Stellen, an denen Nutzer-CSS in einen
<style>-Block geschrieben wird: das Widget (widget_custom_css) und die
Under-Attack-Interstitial-Seite (underattack_custom_css).
Warum an der AUSGABE und nicht (nur) im Schreibpfad: Der Sanitizer räumt
widget_custom_css/underattack_custom_css zwar per wp_strip_all_tags() auf,
aber mindestens drei Schreibwege erreichen ihn nie — wp option patch, fremde
update_option()-Aufrufe außerhalb des Admin-Kontexts und die eigenen
CLI-Befehle reset/load-defaults (W2 der Befunddatei). Was in der Option
steht, ist also nicht garantiert sanitisiert; die Ausgabe muss unabhängig
davon sicher sein.
Parameters
- $css : string
-
Rohes CSS aus den Einstellungen.
Return values
string —CSS, das den umgebenden <style>-Block nicht verlassen kann.
creationell_captcha_build_widget_markup()
Builds the ALTCHA widget markup as a plain string. Enqueues the widget script as a side effect.
creationell_captcha_build_widget_markup() : string
Safe to call from inside an ob_start callback because it does not use
output-buffering itself — unlike the legacy creationell_captcha_get_widget_markup
wrapper that this function now powers.
Reads eight widget-customization settings (display, type, auto_trigger, theme, hide_branding, primary_color, custom_css, strings_override) and maps them to the corresponding v3 attributes. Boolean attributes are emitted as empty-string values per HTML5 convention.
Return values
stringcreationell_captcha_render_widget()
Renders the ALTCHA widget markup and enqueues its assets.
creationell_captcha_render_widget() : void
creationell_captcha_verify_payload()
Verifies a raw base64 ALTCHA payload string.
creationell_captcha_verify_payload(string $raw) : bool
Shared by the POST-based request helper and the third-party form integrations, which read the payload from their plugin's submission data.
Parameters
- $raw : string
-
The raw
altchapayload.
Return values
boolcreationell_captcha_verify_request()
Reads and verifies the ALTCHA payload from the current POST request.
creationell_captcha_verify_request() : bool
The ALTCHA payload itself is the anti-bot token — no separate WordPress nonce applies here.
Return values
boolcreationell_captcha_widget()
Public template tag — renders the ALTCHA widget.
creationell_captcha_widget() : void
For use in theme templates or custom-form markup; the call must sit inside the
creationell_captcha_get_widget_markup()
Returns the ALTCHA widget markup as a string.
creationell_captcha_get_widget_markup() : string
Used by the shortcode and by the third-party form integrations, which embed the widget into another plugin's form markup. Implementation routes through the underlying string builder rather than ob_start so the function is safe to call from inside other output-buffer callbacks (e.g. Modul 12's auto-inject buffer).
Return values
string —The widget markup.
creationell_captcha_widget_shortcode()
Shortcode handler for [creationell_captcha].
creationell_captcha_widget_shortcode() : string
Place the shortcode inside a
Return values
string —The widget markup.
creationell_captcha_uninstall_site()
Removes every trace of the plugin from the site that is currently switched to: options, replay markers, transients, cron slots and the event-log table.
creationell_captcha_uninstall_site() : void
Reads $wpdb freshly on each call because switch_to_blog() repoints
$wpdb->prefix and $wpdb->options at the other site's tables.
creationell_captcha_uninstall_user_meta()
Removes the plugin's per-user data.
creationell_captcha_uninstall_user_meta() : void
B-M4: the hardening notice introduced in this release remembers per
administrator that it was dismissed. Without this, one wp_usermeta row per
administrator who ever clicked the notice away survives the uninstall — and
the routine above promises to remove "every trace".
Deliberately NOT part of creationell_captcha_uninstall_site(): wp_usermeta
is a network-global table that switch_to_blog() does not repoint, so this
runs exactly once for the whole installation. delete_metadata() with
$delete_all = true ignores the object id and removes the key for every user
in a single statement.