CreaCaptcha

woocommerce.php

WooCommerce integration.

Protects four WooCommerce forms — Checkout, My-Account login, registration and lost-password — when the master toggle protect_woocommerce and the respective sub-toggle are on. Reviews are out of scope: they use the WordPress comment system and are already covered by protect_comments.

Lost-Password is render-only: WooCommerce submits the lost-password form through WordPress core's retrieve_password(), which fires lostpassword_post. Our existing core module (includes/forms/password-reset.php) hooks that action — adding another verify here would just produce duplicate verification and duplicate events. protect_wc_lost_password (render) and protect_password_reset (verify) are independent toggles — see IN-5 below for the resulting split states, surfaced via wp creacaptcha doctor.

Known, deliberately-scoped gaps (Modul 27 security audit, 2026-07-29):

  • IN-2 (P0): the WooCommerce Block Checkout (Store API route) never calls process_checkout(), so neither the render hook (woocommerce_review_order_before_submit) nor the verify hook (woocommerce_after_checkout_validation) below ever runs for it — a Block Checkout is completely unprotected regardless of protect_wc_checkout. Building Store-API protection is explicitly OUT OF SCOPE here (new functionality, its own module per the audit's Phase-2 plan); creationell_captcha_wc_uses_block_checkout() below only detects the gap so wp creacaptcha doctor and the Settings help text can stop giving a false "protected" impression.
  • IN-3/IN-4: the classic checkout's account-creation branch and the standalone My-Account registration form both funnel through woocommerce_registration_errors (fired unconditionally by wc_create_new_customer()) — see the per-request verify cache and the form-context guard below.
  • IN-12 (P3, fail-closed): all four render hooks above sit in overridable WooCommerce templates (payment.php, form-login.php, form-lost-password.php, global/form-login.php), while every verify runs from core PHP hooks that do not depend on the template at all. A theme that copies one of these templates and drops the corresponding do_action() call loses the widget but NOT the server-side check — the form fails closed (no spam bypass) rather than open, but every submission is then rejected. This is a general theme-customization risk common to any WooCommerce hook-based extension, not fixable from inside this plugin; documented here rather than "fixed".

Table of Contents

Functions

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.

Functions

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
bool

creationell_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
bool

creationell_captcha_wc_checkout_active()

Whether the WooCommerce checkout protection is active.

creationell_captcha_wc_checkout_active() : bool
Return values
bool

creationell_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
bool

creationell_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
bool

creationell_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>

$_POST or $_REQUEST.

$dedicated_field : string

The form's own nonce field name.

$action : string

The nonce action to verify against.

Return values
bool

creationell_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 on isset( $_POST['register'], $_POST['email'] ) plus a valid woocommerce-register nonce (dedicated field woocommerce-register-nonce, falling back to _wpnonce) before ever calling wc_create_new_customer().
  • Classic checkout with "Create an account?": WC_Checkout::process_checkout() verifies the woocommerce-process_checkout nonce (dedicated field woocommerce-process-checkout-nonce, falling back to _wpnonce, read from $_REQUEST) before validate_checkout()/process_customer() run — and by the time this filter fires from inside process_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
bool

creationell_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_Error carrier 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
bool

creationell_captcha_wc_lost_password_render()

Renders the widget inside the WooCommerce lost-password form.

creationell_captcha_wc_lost_password_render() : void

        
On this page

Search results