Terms & Conditions Consent Log — Full changelog

For each release, see the entries below. The latest version is always at the top.
The current release notes also live in readme.txt under "== Changelog ==".

= 1.7.4 =
* New: multisite support for the WordPress registration options. On a network, WordPress sends anyone trying to register to the network sign-up page, a form this plugin never touched: the optional consent checkbox was silently doing nothing there, and a network sign-up was never recorded even with the safety net for other sign-up forms turned on. The checkbox is now added to that form and enforced on the server, it survives the second step when the visitor also creates a site, and the acceptance is recorded when the account is activated from the confirmation email, storing the wording that was on screen at sign-up rather than whatever the setting says by the time the email is opened. Creating users from the admin screens or through the REST API is untouched.
* Improved: the Integrations screen now says where each consent checkbox is added. The WooCommerce one is added to the registration form on the My Account page, not to the checkout, where the consent is the order terms checkbox and is recorded separately; on a network, the WordPress one is added to the network sign-up page. The block checkout option now also states that it only affects the block checkout, since it is registered through the WooCommerce additional-checkout-fields API, and that the classic checkout needs nothing of it because its own terms checkbox already reaches the server.
* Fix: the optional consent checkbox for the WooCommerce registration form could block the checkout. WooCommerce runs the same validation filter for every account it creates, while the checkbox is only rendered on the My Account form, so a purchase that created an account was rejected asking the customer to accept a privacy policy checkbox that was nowhere on the page. In a store with guest checkout disabled, where WooCommerce creates the account for everyone, that rejected every purchase, on the classic and on the block checkout alike. The requirement now applies only to the form that actually shows the checkbox. The same fix restores account creation from the order-received page, from stock notification sign-ups, and from any code calling WC_Customer::save(), such as imports or adding a customer from the admin.

= 1.7.3 =
* Improved: Tested up to WordPress 7.1, including the fully iframed post editor that 7.1 makes mandatory for every theme. The consent box block renders and saves there unchanged.
* Fix: screen readers announced nothing for the status icons in the consent log and in the WooCommerce Consent column. The tick in the Integrity column, and the tick, warning sign and dash in the orders list, carried their meaning only in the shape itself or in a hover tooltip, so where a sighted user sees the state of a record, a screen reader user got an empty cell. Every icon now carries its meaning as text for assistive technology, and the decorative glyph is hidden from it.

= 1.7.2 =
* Improved: Contact Form 7, WPForms, Gravity Forms and Fluent Forms now fall back to the email address on the profile of a logged-in submitter when the form itself has no email field, instead of discarding the consent. Sign-up and enrolment forms behind a login are recorded like any other. An anonymous submission to a form with no email field is still skipped, because a record that cannot be attributed to anybody is not evidence of anything.
* Improved: Fluent Forms records the URL of the page even when the Referer header never reaches the server, which happens behind some proxies and with stricter referrer policies. It falls back to the referrer that Fluent Forms itself carries inside the submission.
* Improved: Tested up to WooCommerce 11.0
* Fix: The Fluent Forms integration never recorded a single consent since it was added in 1.5.0. It read the form structure from a property that the Fluent Forms form model does not have, so every submission was discarded before its consent fields were even looked at, whatever the form or the settings. Ticked GDPR Agreement and Terms & Conditions fields are captured now, as documented. Reported in the support forum.

= 1.7.1 =
* Improved: the integrity check now runs on its own once a day and warns you in the admin. Verifying records is not new, but until now it only happened when someone pressed "Verify integrity" or opened the log, so a record whose stored text had been altered could sit there for months with its TAMPERED badge unseen. A scheduled check now goes through every record once a day and, if any of them no longer matches the text that was sealed with it, an alert follows you around the admin until you look into it. Running "Verify integrity" by hand updates the same status, so the alert clears the moment the situation is resolved.
* Improved: the privacy checkbox on comments and product reviews now renders the same row as the registration one. Its box keeps a consistent size and long wording wraps aligned with the text instead of running underneath the checkbox.
* Improved: new FAQ on how to record consent from social logins such as Nextend Social Login, which arrive through an OAuth callback with no form submission to inspect, using the `tccl_consent_detection_payload` filter.
* Fix: the consent checkbox the plugin can add to the WooCommerce registration form was practically invisible with Astra, the most installed theme, when its premium add-on is active. Astra strips the native rendering from every checkbox inside a WooCommerce form and only gives a size back to the ones WooCommerce itself renders, which left this one as a 2px dot, or as a stretched blue rectangle once ticked. The checkbox now brings its own box and its own alignment, while colours and tick still come from the theme, so it looks native in every theme tested.

= 1.7.0 =
* New: support for the WooCommerce block checkout, which has been the default for new stores since WooCommerce 8.3. Those orders go through the Store API and never fire the classic checkout hooks, so until now a purchase in a default WooCommerce store produced no record at all. Each order is now logged with the exact wording of the Terms and Conditions block, which is literally what the shopper read. When that block requires a checkbox the record is a `terms_and_privacy` one, identical to the classic checkout, since the order could not exist without the box being ticked. When the block only shows the informational paragraph WooCommerce ships by default, the record is a `terms_notice` one: the shopper accepted by proceeding, which is contractually meaningful but is not the affirmative act Article 7.1 requires, and a consent log should not blur the two. The Settings screen reads your own checkout page and tells you which of those cases applies to your store.
* New: optional required consent checkbox for the block checkout, off by default. The checkbox in the native Terms and Conditions block is validated in the browser only and its value never reaches the server, so no plugin can prove server-side that it was ticked. This option registers a checkbox of its own through the official WooCommerce additional-checkout-fields API: the value travels with the request, WooCommerce validates it server-side and rejects the order without it, and it is stored on the order. It is skipped automatically, rather than applied, if the block that would render it is missing from your checkout page, because a required field the customer cannot see would reject every order.

= 1.6.0 =
* New: optional privacy consent checkbox on the comment form. WordPress only ships the "save my name and email in this browser" box, which is a cookie preference and not permission to store the commenter's personal data. Turn the new option on and the plugin adds a required privacy checkbox, rejects the comment server-side if it is left unticked, and records the acceptance as `consent_type = comment_privacy`, separate from the existing `comment_consent`. WooCommerce product reviews use the same comment form, so they are covered by the same option. The wording is configurable and accepts HTML, so it can link to your privacy policy. Off by default.
* New: optional capture of sign-ups created by membership, course and custom registration forms. The WordPress and WooCommerce options only cover their own forms, so accounts created by plugins such as MemberPress, LearnDash, Ultimate Member or Paid Memberships Pro, and multisite sign-ups, went unrecorded. Stored as `consent_type = user_register`. Accounts created from the admin Users screen are never recorded, since no consent is given there. Off by default.
* Improved: the consent text can now be set per form. A collapsible section under "Consent text for login / registration" accepts a different wording for the WordPress login, WordPress registration, WooCommerce login and WooCommerce registration forms. Anything left empty falls back to the site-wide text, so existing installs behave exactly as before.
* Improved: new `tccl_consent_detection_payload` filter, which lets a flow that does not submit through `$_POST` (REST, headless registration forms) hand the consent detector its own payload instead of duplicating the detection logic.
* Fix: creating an account during a classic WooCommerce checkout no longer writes a phantom registration record. The order's terms checkbox was being read a second time as if it were a registration consent, so a single purchase produced both the legitimate `terms_and_privacy` record and a `wc_register` one the shopper never gave. The checkout checkbox is now excluded from the registration detection; any other consent checkbox in the same submission is still recorded.
* Fix: every ticked consent checkbox in a login or registration submission is now recorded, not just the first one. A form with a required privacy box plus an optional marketing box was losing the marketing consent, which is precisely the one that most needs an audit trail. The first checkbox keeps its usual consent type so existing records and saved filters stay comparable; any additional one appends the field name.
* Fix: the CSV export also neutralises a value that begins with a line break, completing the set of spreadsheet formula-injection triggers already handled since 1.4.1.

= 1.5.0 =
* New: Gravity Forms integration. Captures consent automatically from any Consent field (GF_Field_Consent) ticked on a Gravity Forms submission, stored as `consent_type = gravityforms_form_{ID}`, one type per form. It also detects the first email field of the form and records the source URL. On by default; opt out in Settings → Integrations.
* New: Fluent Forms integration. Captures consent automatically from GDPR Agreement and Terms & Conditions fields ticked on a Fluent Forms submission, stored as `consent_type = fluentforms_form_{ID}`, one type per form. Works with Fluent Forms Lite and Pro. On by default; opt out in Settings → Integrations.
* New: configurable access by role. A new "Access" section in Settings lets you grant the consent log (records, settings, CSV export and PDF certificate) to additional roles such as a Data Protection Officer, without making them administrators. Administrators always have access, and the `tccl_admin_capability` filter still works as a programmatic override.
* Fix: the "Consent box default text" setting now has a registered default, so a fresh install no longer triggers an "undefined array key" PHP notice on the settings screen before the settings have been saved for the first time.

= 1.4.1 =
* Improved: the public consent REST endpoint (`POST /tccl/v1/consent`) is now rate-limited per IP, so a logged-out visitor can no longer script it to flood the consent log with bogus records. It returns HTTP 429 once the limit is reached; tunable with the new `tccl_consent_rate_max` and `tccl_consent_rate_window` filters.
* Fix: the CSV export now neutralises spreadsheet formula/CSV injection. Any cell whose value begins with `=`, `+`, `-`, `@`, a tab or a carriage return is prefixed with a single quote, so Excel, LibreOffice and Google Sheets render it as literal text instead of executing it. This matters because the accepted text and the user agent can be supplied by anonymous visitors through the public consent endpoint, and the export is the GDPR evidence an admin is expected to open.

= 1.4.0 =
* New: native WordPress login integration. Captures successful logins through wp-login.php as `consent_type = wp_login` whenever the submission carries a ticked consent checkbox. The "Remember me" checkbox is excluded by design — it is an ePrivacy / persistent-cookie preference, not a GDPR Article 7.1 consent. Opt-in toggle in Settings → Integrations, off by default in fresh installs (a normal login form has no consent checkbox, so logging every login would only add noise).
* New: native WordPress registration integration. Captures registrations through wp-login.php?action=register as `consent_type = wp_register` whenever the registration form carries a ticked consent checkbox. Opt-in toggle, on by default in fresh installs.
* New: native WooCommerce login integration. Captures the My Account login as `consent_type = wc_login`. Routed by inspecting the submission for WC-specific markers and the HTTP referer, so it is reliably distinguished from the WP-admin login that shares the same underlying hook.
* New: native WooCommerce registration integration. Captures customer creations through the My Account register form as `consent_type = wc_register`, with email + source URL.
* New: optional "Inject a required consent checkbox" toggle for both the WP and the WC registration forms. WooCommerce does not ship a consent checkbox out of the box (only a privacy-policy paragraph), so this option fills that gap for sites without a separate GDPR plugin. The injected checkbox blocks the registration on the server side if it is left unticked.
* New: configurable "Consent text for login / registration" (used as the stored `consent_text` on every login/registration record, and also as the label of the injected checkbox).
* New: configurable "Custom consent field names" — a comma-separated list that overrides the built-in name heuristic for sites whose GDPR plugin uses unusual field names.
* New: heuristic detector for consent checkboxes. Names containing `consent`, `gdpr`, `privacy`, `terms`, `acceptance`, `agreement`, `accept`, `rgpd`, `politica`, `privacidad` or `terminos` are treated as the consent checkbox (case-insensitive). `rememberme` and standard nonce / referer keys are always excluded.

= 1.3.0 =
* Improved: the three Integrations toggles (WordPress comments, Contact Form 7, WPForms) are now on by default on new installs, aligned with GDPR Article 7.1 (the data controller must be able to demonstrate that consent was given). Existing installs keep whatever value the admin already had — the new default only applies to fresh installations. Each toggle remains independently disengageable from Settings → Integrations (e.g. for sites using Disqus or Jetpack Comments, or with no [acceptance]/GDPR Agreement fields in their forms). The Integrations section intro now spells out the GDPR Article 7.1 rationale so the admin understands the change.
* Improved: section intros on the Settings tab no longer cap at 640 px — they now extend to match the width of the field descriptions, removing a visual inconsistency that forced unnecessary scrolling on wide screens.

= 1.2.0 =
* New: native WPForms integration. Opt-in toggle in Settings → Integrations; once enabled, every submission of a form that contains a ticked GDPR Agreement field is logged automatically, with the first email field of the form as subject. Stored as consent_type `wpforms_form_{ID}`, one type per form, same pattern as the existing Contact Form 7 integration. Works with WPForms Lite and Pro; no snippets or `accepted_args=4` boilerplate to maintain.
* New: the records filter now also searches inside `consent_text`. A new "Text contains" field on the filters bar runs a LIKE on the accepted text; the live filter, the AJAX pagination and the filtered CSV export all honour it. Useful to find records by a phrase that appeared in a previous version of your terms.
* New: hover tooltip on the "Outdated" badge in the records list — shows the document version stored in the record and the current version side by side, so you can tell at a glance how far back that consent was signed.
* Improved: the admin menu now always lives under Users → Consent log, with or without WooCommerce (previously it lived under the WooCommerce parent when WC was active). One single location, less clutter inside the WooCommerce submenu and one fewer condition to remember.
* Improved: the capability required to manage the plugin defaults to `manage_options` on every install (previously `manage_woocommerce` when WC was active). If you grant access to Shop Managers, add a one-line filter: `add_filter( 'tccl_admin_capability', fn() => 'manage_woocommerce' );`. Both `tccl_admin_menu_parent` and `tccl_admin_capability` filters keep working as before for any other customisation.

= 1.1.0 =
* New: the site icon (Settings > General > Site Icon, available in both classic and block/FSE themes since WordPress 6.5) is now shown on the printable consent certificate, next to the site name. Works for both new and previously stored records — the icon is rendered on demand each time the certificate is opened. Falls back gracefully when no site icon is set.
* Docs: clarified that [tccl_consent_box] is a self-contained form — not meant to be nested inside another form builder's form. Updated the FAQ and the source-file docblock.
* Docs: expanded the WPForms snippet in the FAQ — full example with the mandatory `accepted_args=4`, reading `consent_version` from the global setting, and the multi-form `id_form => [email, checkbox]` pattern. Same fix applied to the Gravity Forms snippet (now reads the version from the setting instead of hardcoding a date).
* Docs: clarified the version bump FAQ — the Settings string must match the document text character by character; pre-bump records are deliberately marked Outdated as the GDPR audit trail demands; do NOT delete Outdated rows.
* Docs: clarified that the certificate is rendered as a print-optimised A4 view — no PDFs are stored on disk and the plugin owns no uploads folder.
* Docs: clarified anonymisation vs deletion in the FAQ; documented the SQL escape hatch for cleaning up test rows without promoting it as a regular flow.

= 1.0.0 =
* Initial release.
* Works with or without WooCommerce. Menu under WooCommerce > Consent log when WC is active, otherwise under Users > Consent log. Capability defaults to manage_woocommerce or manage_options accordingly. Filterable via tccl_admin_menu_parent and tccl_admin_capability.
* Activation notice on the plugins screen with quick links to the Records and the Settings tabs.
* WooCommerce checkout capture (timestamp UTC, IP, user agent, version, source URL, exact text). Order metabox, "Consent" column on the orders list, optional consent lines in the New order admin email and the customer order email. HPOS compatible.
* Contact Form 7 integration (opt-in): captures every form submission that ticks an [acceptance] field, including the source URL of the page that hosted the form. Stored as cf7_form_{ID}, one type per form.
* WordPress comments integration (opt-in): captures the native wp-comment-cookies-consent checkbox (WP 4.9.6+) along with the post permalink. Stored as comment_consent.
* [tccl_consent_box] shortcode and Gutenberg block: stand-alone consent checkbox with REST endpoint, drop-in anywhere. The default text falls back to a configurable site-wide value in Settings > Integrations.
* Public tccl_save_consent() function (now accepting an optional source_url) for any other source (Gravity Forms, WPForms, custom flows…).
* Source URL recorded with every acceptance and shown on the records list, the PDF certificate, the CSV export and the Privacy Tools export.
* SHA-256 integrity sealing per record.
* Printable A4 certificate per record (browser saves it as PDF).
* Native WordPress Privacy Tools integration (export and erase).
* Live partial-match filters (email, order, date range, type) with filtered CSV export.
* Optional opt-in deletion of plugin data on uninstall (off by default).
