One package for MODX 2.8 and MODX 3, with spam protection on by default. It
installs over FetchIt 1.x on MODX 2 and FetchIt 3.x on MODX 3; system settings
and chunks are kept. What may affect your code is marked Breaking below and
explained in the README, Upgrading to FetchIt 4.
Added
- Spam protection for every form, on by default, both for FetchIt submissions and for forms sent without JavaScript.
fetchit.protectionturns it all off.- A signed single-use token in every form (
fetchit_token), with no session needed. Only a request with a well-signed token of the form gets the next one, in theX-FetchIt-Tokenheader. When the token of a page is stale (a cached page, a page open for long, a new key), the script sends the form once more with the new token by itself. - A minimum fill time (
fetchit.protection.min_time, 3 seconds), counted from the first output of the page, resends included. - A hidden trap field with a random name per installation. A bot that fills it gets a fake success, and the form is not processed.
- A limit of submissions per form and client address (
fetchit.protection.rate_limit,fetchit.protection.rate_window), also behind trusted proxies and CDNs (fetchit.protection.proxies,fetchit.protection.ip_header). - Used tokens are marked atomically in
core/cache/fetchit/tokens/, which "Clear cache" leaves alone. When a mark cannot be written, the form is refused and the cause is logged. - An optional proof of work (
fetchit.protection.pow, in bits, off by default): the browser findsnsuch that SHA-256 oftoken:nstarts with that many zero bits, starting as soon as the visitor enters the form. When a page from a cache asks for less than the server, the refusal carriesX-FetchIt-Pow, and the script solves and sends once more. - Optional captchas: Cloudflare Turnstile, Google reCAPTCHA v3 and Yandex SmartCaptcha (
fetchit.captcha,fetchit.captcha.site_key,fetchit.captcha.secret_key,fetchit.captcha.min_score). FetchIt adds the provider's script, gets its answer before each submission and checks it on the server last. A provider that cannot be reached refuses the form with its own message,fetchit_err_captcha_unavailable; a captcha with no answer to send stops the submission withfetchit_err_captcha_client. - A log of refusals and problems (
fetchit.protection.log), and a signing key generated at install (fetchit.protection.secret). Mistakes in the settings of the captcha and the proof of work are logged.
- A signed single-use token in every form (
- The
OnFetchItBeforeProcessevent: a plugin gets$action,$fields,$propertiesand$FetchIt, and refuses a submission with$modx->event->output(). It fires with the protection off too. - MODX 3 support in the same package, built on MODX 2.8: it installs fresh on MODX 2.8 and MODX 3.
- The API of FetchIt 3.x on both versions: the
FetchIt\FetchItclass,saveActionProperties()andgetActionProperties(), and$modx->services->get('FetchIt')on MODX 3. FetchIt::service(), one shared instance on MODX 2 and MODX 3; the 1.x and 3.x ways to get FetchIt return the same instance.FetchIt::pdoTools(), which finds pdoTools 2 on MODX 2 and pdoTools 3 on MODX 3, for Fenom and@FILEchunks.FetchIt::prepareForm(), which gives the form tags of a chunk the POST method and the key of the form.- TypeScript types,
assets/components/fetchit/js/fetchit.d.ts:FetchIt, the config,FetchIt.Message, the instances of forms and thefetchit:*events with theirdetail. The script is type-checked against them. FetchIt.createNotifier(): the built-in notifications for a site that setsFetchIt.Messageitself.duration: 0keeps a notification until it is closed.detail.errorinfetchit:errorwhen a request fails, and thefetchit_err_requestlexicon entry the visitor sees then.- MODX log entries when the script cannot be added to a page with a form: no
<head>, or afetchit.frontend.jsthat is not a.jsfile. - An upgrade with
fetchit.frontend.default.notifieron says in its log that Notyf is no longer loaded, and warns whenfetchit.frontend.jspoints to a script of the site.
Changed
- Breaking: forms get the service fields of the protection right after the form tag; they are removed from
$_POSTbefore FormIt reads them, so they never reach e-mails. A script of the site used instead of the bundled one must sendfetchit_tokenand take the next token from theX-FetchIt-Tokenheader of every answer. - Breaking: the notifications of
fetchit.frontend.default.notifierare FetchIt's own instead of Notyf, andwindow.Notyfis no longer loaded. Styles for.notyf__toastand scripts that callnew Notyf()need to change. The new notifications are accessible (live regions for screen readers, a labelled close button, focus handling), stay while hovered or focused, and take CSS variables for their colours, by default the pastel green and red of Tailwind CSS 4. Under a Content-Security-Policy they take the nonce of the FetchIt script. - Breaking:
fetchit:erroralso fires when a request fails, withdetail.responseset tonull. - Breaking: the processing snippet gets only the form sent in
fields:$_POST, and$_FILESfor FetchIt submissions. It used to get$_REQUEST, with GET values and, depending onrequest_order, cookies. - With the protection on, the snippet still runs FormIt on every page view, for its preHooks, but only a POST with a token of its form counts as a submission.
- The script is built with ES2019 syntax, so it runs in every browser of the project's browserslist.
- The hooks of
FetchIt.Messageand the events get a stringmessageand an objectdataeven when the processing snippet left them out. An exception in a hook is logged with its name and no longer keeps the answer from the form. fetchit:successcan be cancelled:event.preventDefault()keeps the fields filled.- A second submission while a request is running is ignored.
- On MODX 3 the plugin and
action.phptake FetchIt from the service container instead of the deprecatedgetService(). - The inline
FetchIt.create()call checks that the class offetchit.frontend.js.classnameis loaded, so a page without the script falls back to a normal submission instead of throwing.FetchIt.create()warns in the console when no form matches. methodanddata-fetchitcome last among the attributes of the form tag, and the?v=of the script URL keeps its query string and fragment.
Removed
lib/notyf.min.jsandlib/notyf.min.cssare no longer shipped. An upgrade leaves the old copies in place.
Fixed
- A form sent without JavaScript never showed its success or error message when the chunk used output filters on the FormIt placeholders, like the example chunk: the chunk was rendered before FormIt ran.
- A form tag with its own
data-fetchitcame out broken, look-alike tags such as<form-field>were changed, and so were theformmethodanddata-fetchitof elements inside the form. Form tags are now parsed one by one, quoted values included. - The script was not added on sites without anonymous sessions, and when
<head>was written in capitals or had attributes. - FormIt was not installed together with FetchIt on MODX 3. The installer now also reports each way the download or install of FormIt can fail.
- Two identical snippet calls on one page bound the form twice, and it was sent twice.
- Fields disabled in the markup became enabled after a submission.
- A network error, or an answer that is not FetchIt's (a PHP error page, HTML after a redirect, JSON from a firewall), was only logged to the console. The visitor now sees
fetchit_err_request. - A FormIt error placeholder holding only
marked its field as invalid. - Field names with quotes broke the error selectors.
- A snippet property set that does not exist made
action.phpfail on PHP 8. The snippet now runs without it, and the MODX log names the missing set. - Without the
fetchit.frontend.jssetting the plugin linked the missingjs/default.js. - PHP 8.1 deprecation notices when the invalid-class settings are missing.
Security
- A POST straight to the URL of a page with a FetchIt form reached FormIt and its hooks, e-mails included, without any check. It now has to carry a valid token of the form.
- FormIt 5.2 and later kept the properties of every FetchIt form, hooks included, for its own
action.php, which processes a form without FetchIt's checks. FetchIt now turns that AJAX mode off for its forms: it removes the stored properties, theajaxTokenplaceholder andformit.js. FormIt forms of their own on the same page keep their AJAX mode.