hryvinskyi / magento2-invisible-captcha

hryvinskyi/magento2-invisible-captcha

Magento 2 Google Invisible Captcha (Recaptcha) module

magento2-module Compatibility: 2.4.8-2.4.9 Code Quality: Fail Tests: Fail Security: Pass MIT

Hryvinskyi_InvisibleCaptcha

Multi-provider invisible captcha and bot protection for Magento 2.

Version 3 merges the former Hryvinskyi_TurnstileProtection module into a single,
provider-agnostic extension that protects both individual forms and whole
routes
, using whichever CAPTCHA provider you configure.

Providers

Code Provider Mode
recaptcha_v2_checkbox Google reCAPTCHA v2 — "I'm not a robot" visible checkbox
recaptcha_v2_invisible Google reCAPTCHA v2 — invisible badge invisible
recaptcha_v3 Google reCAPTCHA v3 invisible, score-based
recaptcha_enterprise Google reCAPTCHA Enterprise invisible, score-based (assessments API)
turnstile Cloudflare Turnstile managed / invisible

Capabilities

  • Form protection — invisibly protects the same surface as Magento's native
    reCAPTCHA suite: customer login (incl. AJAX popup & checkout), register,
    forgot password, account edit, contact, newsletter,
    send-to-friend, product review, share wishlist, apply coupon
    (cart), PayPal Payflow Pro, and the admin login / forgot-password
    forms. Each is toggled independently; score-based providers also support a
    per-form threshold.
  • WebAPI / headless protection — REST and GraphQL validation for
    place order, apply coupon (checkout), in-store pickup place order,
    and resend confirmation email. Protected endpoints require the captcha
    token in the X-Captcha-Token request header (X-ReCaptcha also accepted).
    A hryvinskyiInvisibleCaptchaConfig(formType:) GraphQL query exposes the
    active provider's client config for headless storefronts. Endpoint coverage is
    extensible via Api\Webapi\WebapiConfigProviderInterface.
  • Route protection — a full-page interstitial challenge for requests that
    match a Cloudflare-style rule expression (field / operator / value rows joined
    by AND/OR). A verified visitor receives an HMAC-signed cookie and is keyed into
    a separate Varnish cache bucket via the HTTP context. An optional fallback
    provider
    is revealed on the challenge page after a delay. The interstitial is
    a self-contained, system-font page (no external font/CDN requests) whose accent
    color is themeable from the admin.
  • robots.txt-aware gating — the rule field Blocked by robots.txt
    (robots_txt_blocked eq 1) challenges every request for a URL your
    robots.txt disallows. The served robots.txt is resolved per website — a
    physical pub/robots.txt wins, otherwise the Search Engine Robots custom
    instructions — and evaluated with RFC 9309 semantics: user-agent group
    selection with * fallback, longest-match precedence, Allow winning
    specificity ties, * wildcards and $ end anchors. Legitimate crawlers
    never fetch disallowed URLs (and can stay in Excluded User Agents), so the
    rule surfaces exactly the bots that ignore robots.txt. Missing or empty
    robots.txt never challenges (fail-safe).
  • Rule tester — a Test Rules panel under the rules editor simulates any
    storefront request (URL or path, method, User-Agent, client IP, referer,
    store view) against the rules as currently edited, before saving, and
    reports whether it would pass or be challenged: per-condition ✓/✗ trace with
    the actual field values, bypass reasons (excluded IP / user agent, verify
    endpoint), scope context (protection disabled, provider unconfigured) and
    warnings. The simulation runs under full store emulation; the full action
    name is auto-resolved through URL rewrites (SEO URLs, one redirect hop) and
    the front-name → route-id map, and can be overridden manually.

Installation

composer require hryvinskyi/magento2-invisible-captcha
bin/magento module:enable Hryvinskyi_InvisibleCaptcha
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy

Dependencies: hryvinskyi/magento2-base, hryvinskyi/magento2-theme-assets,
hryvinskyi/magento2-configuration-fields, symfony/http-client + PSR-7/17/18.

Configuration

Stores → Configuration → Hryvinskyi → Invisible Captcha & Bot Protection
(hryvinskyi_invisible_captcha/*):

  • General — master switch, active provider, lazy-load, disable-submit, debug.
  • Provider Credentials — site/secret keys (+ Enterprise project id, widget
    options) per provider. Secret keys are encrypted and flagged sensitive.
  • Form Protection — per-form toggles and score thresholds (storefront + admin).
  • Route Protection — rules editor, route-gate provider override, fallback
    provider, cookie lifetime, IP / user-agent exclusions, AJAX-marker params, etc.
    The Ignored Filter Params, AJAX Marker Params and Background AJAX Marker
    Params
    lists use tag-style inputs (via hryvinskyi/magento2-configuration-fields);
    values are still stored newline-separated, so existing data is untouched.
    Includes a Challenge Page Appearance sub-group that themes the interstitial
    accent palette — primary_color (#2f6bd8), primary_color_deep (#2557b6) and
    primary_color_soft (rgba(47,107,216,0.12)) under
    hryvinskyi_invisible_captcha/route_protection/appearance/*, injected as
    CSP-safe --primary* overrides and configurable per store view.
  • Advanced — outbound verification HTTP timeout.

Architecture (extension points)

  • Api\Provider\ProviderInterface + ProviderPoolInterface — add a provider by
    implementing the interface and adding it to the Model\Provider\Pool DI array.
  • Api\Verification\VerifierInterface / VerificationRequestInterface /
    VerificationResultInterface — provider-agnostic verification.
  • Api\Filter\FieldProviderInterface / OperatorProviderInterface — extend the
    route rule engine with custom fields/operators via DI arrays. Optionally
    implement Api\Filter\OperatorMetadataInterface (value shape: text /
    text_required / list / pattern / number) and
    Api\Filter\FieldValueHintInterface (format pattern, message, placeholder)
    to plug into the editor's dynamic value validation.
  • Api\RobotsTxt\{SourceInterface, ParserInterface, MatcherInterface} — swap
    where robots.txt content comes from, how it is parsed, or how rules are
    matched (all bound via di.xml preferences).
  • Model\Strategy\{Token,Failure}\* — pluggable token extraction & failure handling.

WebAPI / checkout note

The place_order, coupon_code (checkout), store_pickup and
resend_confirmation_email keys validate server-side on the WebAPI/GraphQL
call. They are off by default. Before enabling one, make sure your storefront
/ headless client sends the captcha token in the X-Captcha-Token header on that
call (for score-based providers, execute the provider with the matching action,
e.g. place_order) — otherwise those requests will be rejected. The token for
form-level (non-WebAPI) submissions is handled automatically via the hidden
hryvinskyi_invisible_token field.

Verify endpoint & caching

The route-gate challenge POSTs to invisiblecaptcha/verify. On success it sets the
hryvinskyi_captcha_verified cookie and the CAPTCHA_VERIFIED HTTP-context vary
key. After deploying v3, run a full page cache flush (the vary key / cookie /
X-InvisibleCaptcha-Challenge header were renamed from the old Turnstile names).

Upgrading from v2.x / TurnstileProtection

Run setup:upgrade. The data patch MigrateLegacyCaptchaConfig copies your old
hryvinskyi_invisible_captcha/* (reCAPTCHA v3) and hryvinskyi_turnstile/*
settings into the new tree (encrypting the legacy plaintext v3 secret). The
standalone Hryvinskyi_TurnstileProtection module is removed — disable it after
upgrading.

Migrating from Magento's native Google reCAPTCHA

If the store already uses Magento's built-in Google reCAPTCHA (Magento_ReCaptcha*),
import its configuration into this module with:

bin/magento hryvinskyi:invisible-captcha:migrate-recaptcha --dry-run   # preview the change set
bin/magento hryvinskyi:invisible-captcha:migrate-recaptcha             # apply

The command reads every recaptcha_frontend/* and recaptcha_backend/* row from
core_config_data (all scopes) and writes the equivalent
hryvinskyi_invisible_captcha/* settings:

  • Credentials — v3, v2-checkbox and v2-invisible site/secret keys map to the
    matching provider under Provider Credentials. The native module stores both
    keys encrypted; the secret key is copied verbatim (this module's field is
    encrypted too, so the ciphertext stays valid under the same crypt key), while the
    site key is decrypted on copy because this module stores it in plain text.
    Run the command on the installation that owns the crypt key. Frontend keys win
    when frontend and backend differ.
  • Per-form selectors — each type_for/<form> that isn't "disabled" turns on the
    corresponding form under Form Protection (the native place_order gate enables
    both checkout and in-store pickup here). The v3 section-wide score threshold is
    fanned out to each enabled v3 form.
  • Derived — the active provider (most-used across the enabled forms), the master
    switch and the form-protection switches are set so protection stays live.
  • Status hand-over — after copying, each migrated native type_for/* selector
    row is cleared, so the built-in reCAPTCHA stops challenging those forms and
    this module takes over in the same run (source_disabled in the summary table).
    Native credentials are left in place, so the switch is easy to revert.

Existing values in the target tree are never overwritten unless you pass --force.
The command flushes the config cache and prints a per-path summary table.

Upgrading from a version whose migration copied the site key without decrypting
it? Re-run with --force to overwrite the broken ciphertext values.

skipped_undecryptable rows / missing site keys? The installation's crypt key
(app/etc/env.php) cannot decrypt those values — typical when the database was
imported from another environment without its crypt key. Nothing else encrypted
(payment keys, SMTP passwords) will decrypt on such an installation either.
Restore the original crypt key (all versions, in order) or re-enter the keys in
the admin, then re-run the command.

Because the hand-over disables the native reCAPTCHA at config level, physically
removing its modules is optional. If you still want them gone, the companion
metapackage hryvinskyi/magento2-invisible-captcha-recaptcha-replace replaces
every magento/module-re-captcha-* package (Two-Factor Auth and security.txt
from magento/security-package are left untouched). Caution: other installed
code may compile against the reCAPTCHA API modules — notably the bundled
PayPal Braintree module implements
Magento\ReCaptchaWebapiApi\Api\WebapiValidationConfigProviderInterface, so
removing all 25 packages breaks setup:di:compile on standard installs. On such
stores prefer the config-level disable above.

CLI

bin/magento hryvinskyi:invisible-captcha:disable [global|frontend|adminhtml] [--website_id=N]
bin/magento hryvinskyi:invisible-captcha:migrate-recaptcha [--dry-run] [--force]

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.

[3.1.0] - 2026-07-11

Added

  • Blocked by robots.txt route-protection rule field. The Protection Rules
    editor gains a numeric field robots_txt_blocked that resolves to 1 when the
    requested URL is disallowed by the website's robots.txt — so the single rule
    robots_txt_blocked eq 1 challenges every client that ignores robots.txt,
    while legitimate crawlers (which never fetch disallowed URLs, and typically
    sit in Excluded User Agents anyway) are unaffected. The served robots.txt
    is resolved per website: a physical pub/robots.txt wins (the web server
    delivers it directly), otherwise the Search Engine Robots custom
    instructions — the same website-scoped config Magento_Robots renders at
    /robots.txt. Evaluation follows RFC 9309 / Google semantics: stacked
    User-agent groups with the most specific agent token winning and * as
    fallback, tied groups merging, longest-path-match precedence with Allow
    winning specificity ties, * wildcards and $ end anchors, comment/BOM
    stripping, and lenient relative-path normalization. Content and parsed rules
    are memoized per request; missing, empty or unreadable robots.txt never
    challenges (fail-safe). The field introduces a first-class boolean rule
    type: in the builder the operator list narrows to equals / does not equal
    and the Value cell renders as a strict Yes/No select (persisted as 1/0,
    so the raw-expression form stays robots_txt_blocked eq 1). Extensible via
    the new Api\RobotsTxt
    SourceInterface / ParserInterface / MatcherInterface preferences.
    Covered by unit tests (parser, matcher, source, field) and a
    RouteProtection\RobotsTxtRuleTest integration scenario.
  • Tag-style editors for the route-protection parameter lists. Ignored
    Filter Params
    , AJAX Marker Params and Background AJAX Marker Params are
    now tag inputs (type a name, press Enter) rendered by the TagList field from
    the new hryvinskyi/magento2-configuration-fields dependency, wired as
    RouteParamsTagList virtual types. Values are still stored
    newline-separated, so existing configuration keeps working unchanged.
  • "Test Rules" panel — simulate a request against the Protection Rules.
    The rules editor gains a collapsible tester (collapsed by default, the
    header toggles it) that answers "would this page/request pass or hit the
    rules?" without leaving the admin: enter a URL (absolute or path), pick the
    store view and method, optionally set User-Agent, client IP and referer,
    and run. The verdict distinguishes CHALLENGE (expression
    matched and the gate would fire), MATCHED — but no challenge (with the
    reasons: excluded IP/user agent, verify endpoint, protection disabled,
    provider unconfigured) and PASS, and shows a per-condition ✓/✗ trace
    with each field's actual resolved value plus a full field-value snapshot.
    The tester evaluates the draft rules currently in the editor (unsaved
    changes included; falls back to the saved config), runs server-side under
    full store emulation (Magento\Store\Model\App\Emulation) so store-scoped
    rules, exclusion lists, credentials and robots.txt resolve exactly as on
    the live storefront, and rebuilds the real field pool around a synthetic
    request — the same field implementations the gate uses, no duplicated
    value logic. The full action name is resolved best-effort through URL
    rewrites (SEO URLs, one redirect hop honored) and the front-name →
    route-id map, with a manual override and an explicit warning when
    action_name conditions couldn't be grounded. Under the hood this release
    also extracts the exclusion checks into Api\ExclusionPolicyInterface
    (shared by the live gate and the tester) and adds
    Api\ExpressionTracerInterface — a no-short-circuit diagnostic twin of the
    evaluator. New admin POST endpoint hryvinskyi_invisible_captcha/tester/run
    (ACL Hryvinskyi_InvisibleCaptcha::config); new dependencies
    magento/module-backend and magento/module-url-rewrite.
  • Dynamic value validation and per-field placeholders in the rules editor.
    Every operator now declares the shape of value it consumes via the new
    optional Api\Filter\OperatorMetadataInterfacetext (equals; empty is
    legal), text_required (contains / starts with / ends with — empty never
    matches, so it's flagged), list (is in list — one or more comma/space
    separated items), pattern (regex operators) and number (relational
    operators) — and fields can expose a format hint via the new optional
    Api\Filter\FieldValueHintInterface (anchored pattern + message +
    placeholder). The Value cell validates live as you type: numbers for
    numeric comparisons, per-item checks for lists, regex compilation for
    pattern operators (bare and ~…~i-delimited forms), and full-value format
    checks for exact-match operators on hinted fields — Client IP requires a
    valid IPv4/IPv6 literal, HTTP Method a method token, Full Action Name a
    catalog_product_view-style identifier. Fragment-taking operators
    (contains, regex, …) deliberately skip format hints. Invalid values get a
    red state plus an inline message; validation is advisory (the evaluator
    stays fail-safe) but catches conditions that would silently never match.
    All eleven built-in fields now ship example placeholders instead of the
    generic "Value". Third-party operators/fields without the new optional
    interfaces keep working unchanged (default: free text, no hint).
  • Tag input for the list operators. With is in list / is not in list
    selected, the Value cell renders each item as a removable chip: Enter,
    comma, space, paste or blur commits the typed text (pasted content is
    split on commas/whitespace, duplicates are skipped), Backspace on an empty
    input pops the last chip, and items failing the field's format hint (e.g.
    a malformed IP on Client IP) are highlighted individually. The canonical
    comma-separated string lives in a hidden input with the row's form name,
    so persistence, the parser's /[\s,]+/ split, raw-expression mode and the
    rule tester all see exactly what a plain text input would have produced.
    Switching between list and scalar operators swaps the control in place.

[3.0.4] - 2026-07-08

Fixed

  • Catch-all route rules deadlocked verification. The route gate also fired on
    its own invisiblecaptcha/verify endpoint, so with a rule broad enough to match
    it (e.g. action_name regex .*) the challenge page's token POST was answered
    with another 403 challenge and a visitor could never pass. RequestChecker now
    always exempts the verify path (same normalization as the router); a direct GET
    on the endpoint now yields a plain 404 instead of a challenge.

[3.0.3] - 2026-07-08

Fixed

  • Route-protection regex rules accept bare (Cloudflare-style) patterns. The
    regex / not_regex operators passed the admin value straight to
    preg_match(), which requires PCRE delimiters — so a natural pattern like
    .* or ^catalog_.* errored out ("invalid regex skipped" in the log) and the
    rule never fired. Values are now tried as delimited PCRE first (modifiers like
    ~…~i keep working) and auto-wrapped in ~…~ on a parse error; only patterns
    invalid in both forms are skipped (still fail-safe, still logged).

Changed

  • migrate-recaptcha: undecryptable values are reported, not silently skipped.
    When a native encrypted value cannot be decrypted with this installation's crypt
    key (e.g. a DB imported from another environment), the row now appears in the
    change table with the new skipped_undecryptable status and the command prints
    a crypt-key warning with remediation steps, instead of omitting the row without
    a trace.

[3.0.2] - 2026-07-08

Fixed

  • Login-failure redirect never used the before-auth URL. RedirectUrl\BeforeAuthUrl
    was typed against SessionManagerInterface, whose global preference is
    Session\Generic — a session object with the default storage namespace, while
    before_auth_url is written into the customer namespace. The provider therefore
    always fell back to the login URL. It now depends on Magento\Customer\Model\Session
    (Proxy-wired in etc/frontend/di.xml), so a failed login challenge redirects back
    to the customer's intended page again.
  • Unit test suite runs green end-to-end on PHP 8.5 / PHPUnit 10 (598 tests): fixed a
    by-reference capture bug in RouteGateTest, seeded the global ObjectManager for the
    backend block in ProtectionRulesTest, avoided the undefined BP constant in
    DebugTest, and re-pointed BeforeAuthUrlTest at the concrete customer session
    (mocking the magic accessor on the interface fatally failed under PHPUnit 10).

[3.0.1] - 2026-07-08

Fixed

  • migrate-recaptcha: native site key was copied as ciphertext. Magento's
    native reCAPTCHA stores public_key through Config\Backend\Encrypted, while
    this module keeps site_key in plain text — the migration now decrypts values
    carrying the Magento crypt envelope (N:N:…) and passes legacy plaintext keys
    through untouched; undecryptable values are skipped instead of written. Stores
    that migrated with the broken version can repair by re-running with --force.

Added

  • migrate-recaptcha: status hand-over. After copying, the command clears
    every migrated native type_for/* selector row (new source_disabled change
    status, previewed in --dry-run), so the built-in reCAPTCHA is disabled and
    this module is enabled in one run. Native credentials are left in place;
    physically removing the native modules becomes optional — which also avoids
    setup:di:compile breakage on installs whose bundled code (e.g. PayPal
    Braintree) compiles against the reCAPTCHA API modules.

[3.0.0] - 2026-07-08

Major rewrite into a multi-provider captcha & bot-protection module. The
former Hryvinskyi_TurnstileProtection module is merged in and removed.

Added

  • Five providers behind one abstraction (Api\Provider\ProviderInterface +
    ProviderPoolInterface): Google reCAPTCHA v2 checkbox, v2 invisible, v3,
    reCAPTCHA Enterprise, and Cloudflare Turnstile. Pick the active provider in
    Stores → Configuration → Hryvinskyi → Invisible Captcha & Bot Protection.

  • Route-level protection (capability B, from TurnstileProtection): a
    site-wide interstitial challenge driven by a Cloudflare-style rule engine
    (Model\Filter\*), an HMAC-signed verified cookie, Varnish HTTP-context vary,
    and a verify endpoint at invisiblecaptcha/verify.

  • reCAPTCHA Enterprise verifier using the projects/{id}/assessments API.

  • Bounded PSR-18 HTTP transport (Model\Verification\Http\BoundedHttpClient)
    with a configurable wall-clock budget; the verifier fails closed and fast.

  • Per-provider client JS strategies (view/base/web/js/provider/*) so any
    provider/widget type renders from one form component.

  • Provider-independent verified-cookie HMAC key (Magento crypt key) so switching
    providers does not invalidate verified sessions.

  • Data patch Setup\Patch\Data\MigrateLegacyCaptchaConfig migrating both the
    legacy hryvinskyi_invisible_captcha/* (v2.x) and hryvinskyi_turnstile/*
    settings into the unified v3 config tree.

  • Configurable challenge accent color. New Challenge Page Appearance
    sub-group under Route Protection
    (hryvinskyi_invisible_captcha/route_protection/appearance/*) exposes
    --primary (#2f6bd8), --primary-deep (#2557b6) and --primary-soft
    (rgba(47,107,216,0.12)). ChallengeRenderer injects them as a CSP-safe
    :root override (values sanitized against CSS-injection) so the interstitial
    re-themes per store view.

  • Migration from Magento's native Google reCAPTCHA. New CLI command
    hryvinskyi:invisible-captcha:migrate-recaptcha [--dry-run] [--force] imports
    recaptcha_frontend/* / recaptcha_backend/* config (all scopes) into the
    hryvinskyi_invisible_captcha/* tree: provider credentials (secrets copied
    verbatim), per-form type_for/* selectors, the v3 score threshold, and the
    derived active provider + master switches. The migrator holds only mapping
    policy (Model\Migration\RecaptchaMigrator) behind
    Api\Migration\RecaptchaMigratorInterface, reads/writes core_config_data
    through the Api\Migration\CoreConfigGatewayInterface port, carries per-run
    state in Model\Migration\MigrationRun, and returns typed
    Model\Migration\ChangeRecord results; never clobbers existing values
    without --force.

  • Companion metapackage hryvinskyi/magento2-invisible-captcha-recaptcha-replace
    (published separately) that replaces every magento/module-re-captcha-*
    package to remove the native reCAPTCHA while leaving the rest of
    magento/security-package intact.

Changed

  • Unified, snake-case config tree under hryvinskyi_invisible_captcha/{general, providers,form_protection,route_protection,advanced}.

  • The form-verify flow is now provider-agnostic: one rich
    Api\Verification\VerificationResultInterface replaces the reCAPTCHA-only
    response; the "Provider" namespace now means a captcha vendor, while failure /
    token / redirect strategies moved to Model\Strategy\*.

  • Score thresholds apply only to score-based providers (v3 / Enterprise).

  • CSP whitelist is now global and covers Cloudflare + Google hosts across
    script/frame/connect/form-action.

  • Submit gating ("disable submit until ready") is now done with CSS + a small
    parse-time inline script rendered next to the captcha marker, instead of
    rewriting the whole HTML response server-side. The script adds the
    hryvinskyi-captcha-disabled-submit class to the surrounding form (the CSS in
    _module.less makes the submit button unclickable while that class is
    present); form-manager.js removes the class once the captcha is ready.

  • Redesigned route-gate challenge page. The inline interstitial
    (view/frontend/web/inline_challenge/) was reworked into a clean,
    system-font layout and no longer loads Google Fonts (Inter / Cormorant
    Garamond) — one fewer third-party request and a tighter CSP surface. The
    placeholder contract and script.js bootstrap are unchanged.

Removed / BREAKING

  • Hryvinskyi_TurnstileProtection is merged in and deleted. Its config under
    hryvinskyi_turnstile/* is migrated automatically; external references to the
    old turnstile/verify route, X-Turnstile-Challenge header,
    hryvinskyi_turnstile_verified cookie and TURNSTILE_VERIFIED vary key are
    renamed (run a one-time full-page-cache flush after deploy).
  • Legacy v2.x classes (Helper\Config\*, Model\ReCaptcha\*, Model\Verify\*,
    Model\Provider\{Failure,TokenResponse}*, Observer\Captcha, LayoutSettings)
    are removed; integrators should target the new Api\* contracts.
  • Requires hryvinskyi/magento2-theme-assets (replaces the previous
    asset-renderer dependency).
  • Removed the server-side Plugin\DisableSubmit response rewriter and dropped
    the voku/simple_html_dom, symfony/dom-crawler and ext-dom requirements
    it needed; submit gating is now handled in CSS + JS (see above).

[2.5.5] - 2026-06-01

Fixed

  • PHP 8.5: added the : int return type and an explicit return Command::SUCCESS;
    to Command\Captcha::execute(), matching Symfony Console Command::execute(): int
    (fixes the "must be compatible" fatal error).
  • Monolog 3: Model\Debug::write() now accepts Monolog\LogRecord instead of array
    to match Magento\Framework\Logger\Handler\Base::write().

2.5.4 - 2026-06-01

Added

  • PHP 8.5 support (~8.5 added to the php requirement).

Fixed

  • PHP 8.4/8.5 compatibility: replaced implicit-nullable parameter declarations
    (Type $param = null) with explicit nullable types (?Type $param = null)
    across the failure providers, area config, and abstract config classes, removing
    the "Implicitly marking parameter as nullable is deprecated" notices.

Security

  • Widened the symfony/dom-crawler requirement from >=2.7 <5.0 to
    ^5.4.52 || ^6.4.40 || ^7.4.12 || ^8.0.12. The previous constraint could only
    resolve to versions affected by CVE-2026-45071 (XXE / local file disclosure
    in DomCrawler::addXmlContent() via validateOnParse = true), because no fix
    exists for the 2.x–4.x branches. The new constraint pins to the patched releases
    and aligns with the Symfony components shipped by Magento 2.4.x.

2.5.3 - 2025-10-06

Changed

  • Updated dependencies and removed leftover console.log debug output.

2.5.2 - 2025-08-12

Changed

  • composer.json maintenance.

2.5.1 - 2025-06-24

Changed

  • composer.json maintenance.

2.5.0 - 2025-06-17

Changed

  • composer.json maintenance.
Versions
Version Stability QA Status Compatibility Released
3.1.0 stable Fail Magento 2.4.8-2.4.9 Details 2026-07-11 08:35:50
3.0.4 stable Fail Magento 2.4.8-2.4.9 Details 2026-07-08 17:32:52
3.0.3 stable Not tested Not yet tested Details 2026-07-08 17:00:33
3.0.2 stable Not tested Not yet tested Details 2026-07-08 16:06:56
3.0.1 stable Not tested Not yet tested Details 2026-07-08 15:21:10
3.0.0 stable Not tested Not yet tested Details 2026-07-08 14:14:55
2.5.5 stable Fail Magento 2.4.8 Details 2026-06-01 19:51:18
2.5.4 stable Not tested Not yet tested Details 2026-06-01 12:25:56
2.5.3 stable Not tested Not yet tested Details 2025-11-05 18:06:24
2.5.2 stable Not tested Not yet tested Details 2025-08-12 09:01:06
2.5.1 stable Not tested Not yet tested Details 2025-06-24 09:54:36
2.5.0 stable Not tested Not yet tested Details 2025-06-16 22:29:28
2.4.10 stable Not tested Not yet tested Details 2025-06-16 22:27:23
2.4.9 stable Not tested Not yet tested Details 2025-05-05 18:55:28
2.4.8.1 stable Not tested Not yet tested Details 2025-05-05 13:18:08
2.4.8 stable Not tested Not yet tested Details 2024-03-28 20:23:25
2.4.7 stable Not tested Not yet tested Details 2024-01-18 12:29:05
2.4.6 stable Not tested Not yet tested Details 2024-01-18 11:31:05
2.4.5 stable Not tested Not yet tested Details 2023-12-13 10:56:58
2.4.5-dev dev Not tested Not yet tested Details 2022-09-01 07:46:00
2.4.4 stable Not tested Not yet tested Details 2022-06-23 09:59:18
2.4.3 stable Not tested Not yet tested Details 2022-06-21 12:26:03
2.4.2 stable Not tested Not yet tested Details 2022-03-22 10:43:26
2.4.1 stable Not tested Not yet tested Details 2022-03-22 10:34:54
2.4 stable Not tested Not yet tested Details 2022-03-22 10:29:50
2.3.2 stable Not tested Not yet tested Details 2021-06-19 08:57:54
2.3.1 stable Not tested Not yet tested Details 2020-09-23 22:33:58
2.3.0 stable Not tested Not yet tested Details 2020-09-07 14:03:20
2.2.7 stable Not tested Not yet tested Details 2020-08-13 19:20:15
2.2.6 stable Not tested Not yet tested Details 2020-08-07 20:19:11
2.2.5 stable Not tested Not yet tested Details 2020-08-03 19:09:47
2.2.4 stable Not tested Not yet tested Details 2020-06-16 20:43:55
2.2.3 stable Not tested Not yet tested Details 2020-06-16 20:35:41
2.2.2 stable Not tested Not yet tested Details 2020-06-16 19:32:58
2.2.1 stable Not tested Not yet tested Details 2020-06-16 17:29:41
2.2.0 stable Not tested Not yet tested Details 2020-06-16 11:40:32
2.1.10.1 stable Not tested Not yet tested Details 2020-06-09 13:23:05
2.1.10 stable Not tested Not yet tested Details 2020-05-21 17:55:11
2.1.9.1 stable Not tested Not yet tested Details 2020-02-12 21:05:09
2.1.9 stable Not tested Not yet tested Details 2020-02-09 20:23:41
2.1.8 stable Not tested Not yet tested Details 2020-02-05 13:28:44
2.1.7.1 stable Not tested Not yet tested Details 2020-01-30 09:31:19
2.1.7 stable Not tested Not yet tested Details 2020-01-10 13:06:04
2.1.6 stable Not tested Not yet tested Details 2019-10-20 10:46:16
2.1.5 stable Not tested Not yet tested Details 2019-09-25 13:33:54
2.1.4 stable Not tested Not yet tested Details 2019-09-24 20:37:24
2.1.3 stable Not tested Not yet tested Details 2019-09-17 12:16:12
2.1.2 stable Not tested Not yet tested Details 2019-09-17 11:19:58
2.1.1 stable Not tested Not yet tested Details 2019-09-17 07:22:08
2.1.0.4 stable Not tested Not yet tested Details 2019-06-11 14:19:19
2.1.0.3 stable Not tested Not yet tested Details 2019-06-11 13:38:50
2.1.0.2 stable Not tested Not yet tested Details 2019-06-05 13:36:06
2.1.0.1 stable Not tested Not yet tested Details 2019-06-01 17:21:40
2.1.0 stable Not tested Not yet tested Details 2019-05-26 10:36:24
2.0.4 stable Not tested Not yet tested Details 2019-04-22 14:56:14
2.0.3 stable Not tested Not yet tested Details 2019-04-18 15:26:56
2.0.2 stable Not tested Not yet tested Details 2019-04-09 12:02:45
2.0.1 stable Not tested Not yet tested Details 2019-03-22 08:50:42
2.0.0 stable Not tested Not yet tested Details 2019-03-21 23:52:56
1.0.1 stable Not tested Not yet tested Details 2018-06-27 12:07:03
1.0.0 stable Not tested Not yet tested Details 2017-11-10 09:37:22

Requires 29

Package Constraint
hryvinskyi/magento2-base ^2.1
hryvinskyi/magento2-configuration-fields ^1.0.11
hryvinskyi/magento2-theme-assets ^1.0
magento/framework *
magento/module-backend *
magento/module-catalog *
magento/module-catalog-search *
magento/module-checkout *
magento/module-config *
magento/module-contact *
magento/module-customer *
magento/module-graph-ql *
magento/module-layered-navigation *
magento/module-newsletter *
magento/module-paypal *
magento/module-quote *
magento/module-review *
magento/module-sales-rule *
magento/module-send-friend *
magento/module-store *
magento/module-url-rewrite *
magento/module-webapi *
magento/module-wishlist *
nyholm/psr7 ^1.5
php ~8.1.0||~8.2.0||~8.3.0||~8.4.0||~8.5.0
psr/http-client ^1.0
psr/http-factory ^1.0
psr/http-message ^1.0 || ^2.0
symfony/http-client ^5 || ^6 || ^7 || ^8

Requires-dev 1

Package Constraint
roave/security-advisories dev-latest

Compatibility

Each Magento release line is installed on its supported PHP versions, then the module is built (DI compilation + static-content deploy) and its unit and integration suites are run. The matrix shows the lines and PHP versions the module is confirmed to install and run on. Code-quality results further down (phpstan, phpcs, …) are reported separately and never affect compatibility.

Compatibility matrix (Magento × PHP)
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 Fail di error Fail di error
2.4.8 Pass Pass
2.4.9 Pass Pass

Code Quality

Advisory checks against the module's source. Static analysis runs once across the whole module; PHPStan re-runs per Magento + PHP version because resolvable symbols differ between releases. These NEVER affect the Compatibility badge. A phpcs finding can't make a module incompatible.

Static analysis

Coding standards (phpcs), mess detection (phpmd), copy-pasted code (cpd), PHP cross-version compatibility, composer.json validity. Each runs once for the whole module.

Static analysis results
Tool Status Findings Summary
PHPCS Fail 270 13 errors, 257 warnings (ruleset: Magento2), 10 auto-fixable with phpcbf
PHPMD Warning 33 33 rule violations (MissingImport:12, TooManyPublicMethods:7, NPathComplexity:4, ErrorControlOperator:3, UnusedFormalParameter:3)
Cpd Warning 9 9 duplicated chunks spanning 305 total lines (min-lines=5, min-tokens=70)
Composer validate Info 21 valid; 21 advisory notes (composer validate --strict)

PHPStan

Type-checks the module's PHP against a real Magento install at the configured gate level. Re-runs per Magento and PHP version because resolvable symbols differ between releases.

PHPStan results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 16 16
2.4.8 16 15
2.4.9 15 15

Tests

Unit and integration suites, run for each applicable Magento and PHP version. A test failure speaks to the module's behaviour, not its compatibility with a Magento line, so it is reported here separately and never reddens the compatibility matrix.

Unit tests

Unit tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 N/A N/A
2.4.8 N/A N/A
2.4.9 N/A N/A

Integration tests

Integration tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 Error Error
2.4.8 27 27
2.4.9 27 not tested

Security

Security checks run directly against the module: an audit of its declared dependencies for known vulnerabilities (composer audit) and a scan of its source for malware and web-shell signatures. Each runs once. A malware detection fails the version outright.

Security results
Tool Status Findings Summary
Composer audit N/A 0 no resolvable dependency tree to audit:
Malware scan Pass 0
License
MIT
Authors

More from hryvinskyi

View vendor
Make it pay

Turn an existing module into recurring revenue.

If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.