{"id":314214,"date":"2026-05-17T19:54:21","date_gmt":"2026-05-17T19:54:21","guid":{"rendered":"https:\/\/es.wordpress.org\/plugins\/terms-conditions-consent-log\/"},"modified":"2026-05-18T00:11:24","modified_gmt":"2026-05-18T00:11:24","slug":"terms-conditions-consent-log","status":"publish","type":"plugin","link":"https:\/\/es.wordpress.org\/plugins\/terms-conditions-consent-log\/","author":1320655,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_crdt_document":"","version":"1.1.0","stable_tag":"1.1.0","tested":"7.0","requires":"6.0","requires_php":"7.4","requires_plugins":null,"header_name":"Terms & Conditions Consent Log","header_author":"Fernando Tellado","header_description":"Records consent acceptance with timestamp, IP, version, and the exact text shown to the user. Native WooCommerce checkout, Contact Form 7 and WordPress comments integrations, plus a public API and a [tccl_consent_box] shortcode\/block to log any consent. Tamper-evident SHA-256 sealing, downloadable PDF certificate, and native WordPress Privacy Tools integration. Article 7.1 GDPR-compliant evidence in a dedicated table.","assets_banners_color":"002b98","last_updated":"2026-05-18 00:11:24","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/servicios.ayudawp.com","header_author_uri":"https:\/\/tellado.es","rating":0,"author_block_rating":0,"active_installs":0,"downloads":57,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"fernandot","date":"2026-05-17 20:47:02"},"1.1.0":{"tag":"1.1.0","author":"fernandot","date":"2026-05-18 00:11:24"}},"upgrade_notice":{"1.1.0":"<p>Adds the site icon to the printable consent certificate. Expanded FAQs cover the [tccl_consent_box] scope, the WPForms snippet (with the mandatory <code>accepted_args=4<\/code> and <code>consent_version<\/code> from settings), version-bump semantics, certificate rendering and anonymisation vs deletion.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.jpg":{"filename":"icon-128x128.jpg","revision":3535064,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.jpg":{"filename":"icon-256x256.jpg","revision":3535064,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500-es.jpg":{"filename":"banner-1544x500-es.jpg","revision":3535064,"resolution":"1544x500","location":"assets","locale":"es","width":1544,"height":500},"banner-1544x500.jpg":{"filename":"banner-1544x500.jpg","revision":3535064,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250-es.jpg":{"filename":"banner-772x250-es.jpg","revision":3535064,"resolution":"772x250","location":"assets","locale":"es","width":772,"height":250},"banner-772x250.jpg":{"filename":"banner-772x250.jpg","revision":3535064,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{"blueprint.json":{"filename":"blueprint.json","revision":3535077,"resolution":false,"location":"assets","locale":"","contents":"{\"$schema\":\"https:\\\/\\\/playground.wordpress.net\\\/blueprint-schema.json\",\"preferredVersions\":{\"php\":\"latest\",\"wp\":\"latest\"},\"phpExtensionBundles\":[\"kitchen-sink\"],\"features\":{\"networking\":true},\"steps\":[{\"step\":\"login\",\"username\":\"admin\",\"password\":\"password\"},{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"woocommerce\"},\"options\":{\"activate\":false}},{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"terms-conditions-consent-log\"},\"options\":{\"activate\":true}}],\"landingPage\":\"\\\/wp-admin\\\/plugins.php\"}"}},"all_blocks":{"tccl\/consent-box":{"$schema":"https:\/\/schemas.wp.org\/trunk\/block.json","apiVersion":3,"name":"tccl\/consent-box","title":"Consent box","category":"widgets","icon":"yes-alt","description":"A self-contained consent checkbox that records each acceptance in the consent log.","textdomain":"terms-conditions-consent-log","keywords":["consent","gdpr","privacy","checkbox"],"supports":{"html":false,"reusable":true,"align":["wide"]},"attributes":{"text":{"type":"string","default":""},"consentType":{"type":"string","default":"consent_box"},"consentVersion":{"type":"string","default":""},"submitLabel":{"type":"string","default":""},"successMessage":{"type":"string","default":""},"requireEmail":{"type":"string","default":"auto","enum":["auto","yes","no"]}},"editorScript":"tccl-consent-box-editor","editorStyle":"tccl-consent-box-editor-style"}},"tagged_versions":["1.0.0","1.1.0"],"block_files":[],"assets_screenshots":{"screenshot-1-es.jpg":{"filename":"screenshot-1-es.jpg","revision":3535054,"resolution":"1","location":"assets","locale":"es","width":1920,"height":1080},"screenshot-1.jpg":{"filename":"screenshot-1.jpg","revision":3534907,"resolution":"1","location":"assets","locale":"","width":1920,"height":1080},"screenshot-2-es.jpg":{"filename":"screenshot-2-es.jpg","revision":3535054,"resolution":"2","location":"assets","locale":"es","width":1920,"height":2190},"screenshot-2.jpg":{"filename":"screenshot-2.jpg","revision":3534907,"resolution":"2","location":"assets","locale":"","width":1920,"height":2127},"screenshot-3-es.jpg":{"filename":"screenshot-3-es.jpg","revision":3535054,"resolution":"3","location":"assets","locale":"es","width":1920,"height":1080},"screenshot-3.jpg":{"filename":"screenshot-3.jpg","revision":3534907,"resolution":"3","location":"assets","locale":"","width":1920,"height":1080},"screenshot-4-es.jpg":{"filename":"screenshot-4-es.jpg","revision":3535054,"resolution":"4","location":"assets","locale":"es","width":1920,"height":1080},"screenshot-4.jpg":{"filename":"screenshot-4.jpg","revision":3534907,"resolution":"4","location":"assets","locale":"","width":1920,"height":1080},"screenshot-5-es.jpg":{"filename":"screenshot-5-es.jpg","revision":3535054,"resolution":"5","location":"assets","locale":"es","width":1920,"height":1253},"screenshot-5.jpg":{"filename":"screenshot-5.jpg","revision":3534907,"resolution":"5","location":"assets","locale":"","width":1920,"height":1585},"screenshot-6.jpg":{"filename":"screenshot-6.jpg","revision":3534907,"resolution":"6","location":"assets","locale":"","width":1920,"height":1084}},"screenshots":{"1":"Records list with live filters, integrity column and CSV export.","2":"Settings tab with editable texts, version control, retention, email options and uninstall control.","3":"Order metabox with consent summary, integrity badge and printable-certificate button.","4":"Consent column on the orders list.","5":"Printable A4 certificate ready to be saved as PDF from the browser.","6":"Exported CSV file with filtered records (metadata header + nice column names)."},"jetpack_post_was_ever_published":false},"plugin_section":[],"plugin_tags":[20011,1152,131785,396,286],"plugin_category":[42,45,54],"plugin_contributors":[245779,133550],"plugin_business_model":[],"class_list":["post-314214","plugin","type-plugin","status-publish","hentry","plugin_tags-consent","plugin_tags-contact-form-7","plugin_tags-gdpr","plugin_tags-privacy","plugin_tags-woocommerce","plugin_category-contact-forms","plugin_category-ecommerce","plugin_category-security-and-spam-protection","plugin_contributors-ayudawp","plugin_contributors-fernandot","plugin_committers-ayudawp","plugin_committers-fernandot","plugin_support_reps-ayudawp"],"banners":{"banner":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/banner-772x250-es.jpg?rev=3535064","banner_2x":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/banner-1544x500-es.jpg?rev=3535064","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/icon-128x128.jpg?rev=3535064","icon_2x":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/icon-256x256.jpg?rev=3535064","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/screenshot-1-es.jpg?rev=3535054","caption":"Lista de registros con los filtros activos, columna de integridad y exportaci\u00f3n a CSV."},{"src":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/screenshot-2-es.jpg?rev=3535054","caption":"P\u00e1gina de ajustes con los textos modificables, el control de versiones, opciones de correo electr\u00f3nico y control de desinstalaci\u00f3n."},{"src":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/screenshot-3-es.jpg?rev=3535054","caption":"Caja meta en el pedido, con el resumen del consentimiento, sello de integridad y bot\u00f3n para imprimir el certificado."},{"src":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/screenshot-4-es.jpg?rev=3535054","caption":"Columna de consentimientos en el listado de pedidos."},{"src":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/screenshot-5-es.jpg?rev=3535054","caption":"Certificado A4 imprimible listo para guardarse como PDF desde el navegador."},{"src":"https:\/\/ps.w.org\/terms-conditions-consent-log\/assets\/screenshot-6.jpg?rev=3534907","caption":"Archivo CSV exportado con registros filtrados (cabecera con metadatos + nombres de columnas comprensibles)."}],"raw_content":"<!--section=description-->\n<p>Article 7.1 of the GDPR demands more than a boolean: the defensible consent record needs the timestamp, the IP, the user agent, the document version in force at that moment and the exact text the user was shown.<\/p>\n\n<p>Terms &amp; Conditions Consent Log fills that gap for any acceptance checkbox on your site, with or without WooCommerce. Every accepted consent \u2014 at the WooCommerce checkout, in a Contact Form 7 form, in the WordPress comments form or in a stand-alone shortcode\/block \u2014 writes a row to a dedicated indexed table, sealed with a SHA-256 hash of the accepted text so any later change is detectable. From a clean admin screen you can filter, search, export to CSV, integrate with the native WordPress Privacy Tools, and open a one-page printable A4 certificate per record (your browser saves it as PDF in one click).<\/p>\n\n<h4>Works with or without WooCommerce<\/h4>\n\n<p>If WooCommerce is active, the menu lives under WooCommerce \u2192 Consent log and the checkout is captured automatically. If it is not, the menu lives under Users \u2192 Consent log and the rest of the plugin (Records, Settings, CSV export, PDF certificate, Privacy Tools integration) keeps working the same way.<\/p>\n\n<h4>Four sources of consent<\/h4>\n\n<ul>\n<li><strong>WooCommerce checkout<\/strong> (auto when WC is active): captures the native terms checkbox.<\/li>\n<li><strong>Contact Form 7<\/strong> (opt-in): detects [acceptance] fields automatically and the first email field of the form. Stored as <code>cf7_form_{ID}<\/code>, one type per form. No snippets required.<\/li>\n<li><strong>WordPress comments<\/strong> (opt-in): logs the native <code>wp-comment-cookies-consent<\/code> checkbox (introduced in WP 4.9.6) when the visitor opts in. Stored as <code>comment_consent<\/code>.<\/li>\n<li><strong><code>[tccl_consent_box]<\/code> shortcode and Gutenberg block<\/strong>: drop a self-contained consent checkbox in any page, post, widget area or HTML field of a form builder. Submission posts to a REST endpoint and writes a record. Always available.<\/li>\n<\/ul>\n\n<p>For anything else (Gravity Forms, WPForms, Fluent Forms, custom flows), call <code>tccl_save_consent()<\/code> from the appropriate hook.<\/p>\n\n<h4>Why a dedicated table<\/h4>\n\n<p>Storing thousands of consent records in <code>wp_postmeta<\/code> is wasteful and slow. The plugin uses its own indexed table and exposes a public function (<code>tccl_save_consent<\/code>) that you can call from anywhere to log additional consents in the same place.<\/p>\n\n<h4>Main features<\/h4>\n\n<ul>\n<li>Records timestamp UTC, IP, user agent, document version, source URL and full consent text per acceptance.<\/li>\n<li>Custom database table with the right indexes (no <code>wp_postmeta<\/code> bloat).<\/li>\n<li><strong>Tamper-evident<\/strong>: each record is sealed with a SHA-256 hash. Any later change to the stored text is detected and reported as TAMPERED in the records list.<\/li>\n<li><strong>Printable A4 certificate<\/strong> per record, with a built-in \"Print \/ Save as PDF\" button \u2014 the browser exports the certificate to PDF natively, no external library bundled.<\/li>\n<li><strong>Native Privacy Tools integration<\/strong>: <code>Tools &gt; Export Personal Data<\/code> and <code>Tools &gt; Erase Personal Data<\/code> both include consent records (erasure anonymises rather than deletes \u2014 the record itself is the lawful basis to keep it).<\/li>\n<li>WooCommerce checkout texts are <strong>optional<\/strong> \u2014 leave them empty and the WooCommerce native text is shown to the customer and stored verbatim.<\/li>\n<li>Automatic version bump when the text changes (suggests <code>MAJOR.MINOR-YYYY-MM-DD<\/code>).<\/li>\n<li>Optional opt-out of IP and\/or user agent tracking.<\/li>\n<li>Configurable retention with a one-click anonymise button (records kept; PII scrubbed).<\/li>\n<li>Live partial-match filters (email, order, date range, type) + filtered CSV export with UTF-8 BOM (opens cleanly in Excel).<\/li>\n<li>(When WooCommerce is active) Order metabox with the consent summary, integrity badge and outdated-version indicator. \"Consent\" column on the orders list (legacy and HPOS) with a quick visual status. Optional consent line in the New order email (admin) and the order confirmation email (customer) \u2014 both off by default.<\/li>\n<li>Optional <code>delete_data_on_uninstall<\/code> setting (off by default) \u2014 uninstalling does not destroy consent evidence unless you explicitly opt in.<\/li>\n<li>HPOS (custom order tables) compatible.<\/li>\n<li>Public <code>tccl_save_consent()<\/code> function to log consents from anywhere.<\/li>\n<\/ul>\n\n<h4>Translation ready<\/h4>\n\n<p>All strings use the <code>terms-conditions-consent-log<\/code> text domain. Translations are managed through translate.wordpress.org.<\/p>\n\n<h3>Support<\/h3>\n\n<p>Need help or have suggestions?<\/p>\n\n<ul>\n<li><a href=\"https:\/\/servicios.ayudawp.com\">Official website<\/a><\/li>\n<li><a href=\"https:\/\/wordpress.org\/support\/plugin\/terms-conditions-consent-log\/\">WordPress support forum<\/a><\/li>\n<li><a href=\"https:\/\/www.youtube.com\/AyudaWordPressES\">YouTube channel<\/a><\/li>\n<li><a href=\"https:\/\/ayudawp.com\">Documentation and tutorials<\/a><\/li>\n<\/ul>\n\n<p>Love the plugin? Please leave us a 5-star review and help spread the word!<\/p>\n\n<h3>About AyudaWP.com<\/h3>\n\n<p>We are specialists in WordPress security, SEO, AI and performance optimization plugins. We create tools that solve real problems for WordPress site owners while maintaining the highest coding standards and accessibility requirements.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin folder to <code>\/wp-content\/plugins\/terms-conditions-consent-log\/<\/code> or install through Plugins &gt; Add New.<\/li>\n<li>Activate the plugin.<\/li>\n<li>Open the plugin admin page:\n\n<ul>\n<li>If WooCommerce is active: WooCommerce &gt; Consent log.<\/li>\n<li>Otherwise: Users &gt; Consent log.<\/li>\n<\/ul><\/li>\n<li>In the Settings tab, optionally enable the WordPress comments and\/or Contact Form 7 integrations (off by default), or just paste <code>[tccl_consent_box]<\/code> in any page or post.<\/li>\n<li>(Optional, WooCommerce only) Override the checkbox text or add a pre-checkout informational paragraph. Leave them empty to keep the WooCommerce native text.<\/li>\n<li>Every accepted consent from any of the enabled sources will be logged automatically from now on.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"can%20i%20use%20the%20plugin%20without%20woocommerce%3F\"><h3>Can I use the plugin without WooCommerce?<\/h3><\/dt>\n<dd><p>Yes. Activate it on any WordPress site and the menu appears under Users &gt; Consent log. The Records, Settings, CSV export, PDF certificate and Privacy Tools integration all work the same way. The WooCommerce-specific bits (checkout capture, order metabox, order list column, order email line) only load when WooCommerce is active.<\/p><\/dd>\n<dt id=\"how%20do%20i%20capture%20consents%20from%20contact%20form%207%3F\"><h3>How do I capture consents from Contact Form 7?<\/h3><\/dt>\n<dd><p>Open Consent log &gt; Settings &gt; Integrations and tick \"Log every CF7 form submission that ticks an [acceptance] field\". Then make sure your CF7 forms include an [acceptance] field, e.g.:<\/p>\n\n<pre><code>[acceptance privacy] I have read and agree to the privacy policy. [\/acceptance]\n<\/code><\/pre>\n\n<p>The plugin uses the form ID as part of the consent_type (cf7_form_{ID}), so each form is filterable separately. The first email field of the form is used as the subject email. No snippets, no functions.php edits.<\/p><\/dd>\n<dt id=\"how%20does%20the%20%5Btccl_consent_box%5D%20shortcode%20work%3F\"><h3>How does the [tccl_consent_box] shortcode work?<\/h3><\/dt>\n<dd><p>It renders a self-contained consent checkbox + submit button, with optional email field for visitors who are not logged in. Submission posts to a REST endpoint that records the consent through tccl_save_consent(). Drop it in any page, post or widget area as a stand-alone block, e.g.:<\/p>\n\n<pre><code>[tccl_consent_box text=\"I have read and agree to the privacy policy.\" consent_type=\"newsletter_signup\"]\n<\/code><\/pre>\n\n<p>The same functionality is also available as a Gutenberg block called \"Consent box\".<\/p>\n\n<p>Important: the shortcode renders its own <code>&lt;form&gt;<\/code> with a submit button, so it should NOT be nested inside another form builder's form (Contact Form 7, WPForms, Gravity Forms, Fluent Forms, Elementor Forms, etc.). If you embed it inside another form you will end up with two submit buttons and conflicting submit flows. For form builders, use the dedicated integration (CF7 is built in; for the rest, hook tccl_save_consent() from the relevant submission action \u2014 see the Gravity Forms \/ WPForms FAQ below).<\/p>\n\n<p>Also: do NOT use this shortcode as a substitute for the cookie checkbox of a cookie\/banner plugin (Complianz, CookieYes, Real Cookie Banner, etc.). The legal context is different \u2014 cookie banners cover ePrivacy\/cookies, this consent log covers GDPR art. 7.1 specific consents to specific personal-data processing. Mixing them yields ambiguous evidence.<\/p><\/dd>\n<dt id=\"are%20wordpress%20comment%20opt-ins%20logged%20automatically%3F\"><h3>Are WordPress comment opt-ins logged automatically?<\/h3><\/dt>\n<dd><p>No, they are off by default. Enable them in Consent log &gt; Settings &gt; Integrations. Only comments where the visitor ticks the native \"Save my name, email, and website...\" checkbox are recorded.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20the%20new%20woocommerce%20block%20checkout%3F\"><h3>Does it work with the new WooCommerce Block Checkout?<\/h3><\/dt>\n<dd><p>Not yet. The classic checkout is fully supported. Block Checkout support is on the roadmap.<\/p><\/dd>\n<dt id=\"where%20is%20the%20data%20stored%3F\"><h3>Where is the data stored?<\/h3><\/dt>\n<dd><p>In a custom indexed table called <code>wp_tccl_consents<\/code> (with your site prefix). When WooCommerce is active, each order also gets three meta entries (<code>_tccl_terms_accepted<\/code>, <code>_tccl_terms_version<\/code>, <code>_tccl_recorded_at<\/code>) so the order edit screen can show the summary without querying the table.<\/p><\/dd>\n<dt id=\"how%20do%20i%20bump%20the%20document%20version%20when%20i%20change%20my%20terms%3F\"><h3>How do I bump the document version when I change my terms?<\/h3><\/dt>\n<dd><p>Edit the version field in Consent log &gt; Settings, or simply check \"Bump version on save\". The plugin can also bump it automatically if it detects the checkbox text has changed but the version field has not.<\/p>\n\n<p>Three things to keep in mind:<\/p>\n\n<ol>\n<li>The version string in Settings must match the version label of your terms document character by character (e.g. <code>1.1-2026-05-17<\/code>). It is a free-text identifier and the plugin just compares strings, so a trailing space or a different separator will be treated as a different version.<\/li>\n<li>Once you bump the version, every record stored under the previous version is automatically flagged as \"Outdated\" in the records list. This is on purpose \u2014 it is the GDPR audit trail showing which exact wording each subject accepted at that moment. \"Outdated\" is a feature, not a bug.<\/li>\n<li>Do NOT delete or \"clean up\" Outdated records. They are the legal proof of consent for the version that was in force when the subject accepted it. If the user retires the terms and a regulator later asks for evidence, those rows are what you show them.<\/li>\n<\/ol><\/dd>\n<dt id=\"how%20do%20i%20delete%20or%20anonymise%20data%20for%20a%20specific%20customer%3F\"><h3>How do I delete or anonymise data for a specific customer?<\/h3><\/dt>\n<dd><p>Use the WordPress native Tools &gt; Erase Personal Data screen. The plugin registers an eraser that anonymises records linked to the requested email (it does not delete them, since the record itself is the lawful basis to keep the proof of consent). You can also anonymise filtered records from the Records tab.<\/p><\/dd>\n<dt id=\"how%20do%20i%20export%20a%20customer%27s%20consent%20history%3F\"><h3>How do I export a customer's consent history?<\/h3><\/dt>\n<dd><p>Use Tools &gt; Export Personal Data. The plugin registers an exporter that returns every consent record linked to the requested email.<\/p><\/dd>\n<dt id=\"will%20an%20uninstall%20destroy%20my%20data%3F\"><h3>Will an uninstall destroy my data?<\/h3><\/dt>\n<dd><p>Only if you explicitly opt in. The setting \"Delete all data on uninstall\" is off by default. Even if you uninstall accidentally, your consent evidence will survive.<\/p>\n\n<p>If you need to clean up a handful of test rows you generated while configuring the plugin (and you do not yet have the upcoming bulk-delete UI), you can remove them with a direct SQL statement against the consent table, e.g.:<\/p>\n\n<pre><code>DELETE FROM wp_tccl_consents WHERE id IN (1, 2, 3);\n<\/code><\/pre>\n\n<p>Replace <code>wp_<\/code> with your site's actual table prefix. This is an escape hatch for legitimate clean-up after a misconfigured form; it is not a recommended day-to-day flow \u2014 to handle real subject requests, use Tools &gt; Erase Personal Data (anonymises) instead.<\/p><\/dd>\n<dt id=\"how%20do%20i%20capture%20consents%20from%20gravity%20forms%2C%20wpforms%2C%20or%20any%20other%20source%3F\"><h3>How do I capture consents from Gravity Forms, WPForms, or any other source?<\/h3><\/dt>\n<dd><p>Call the public <code>tccl_save_consent()<\/code> function from the relevant hook. Always read the document version from the plugin setting (<code>tccl_get_setting( 'consent_version', '1.0' )<\/code>) so all records line up with the current version in Settings \u2014 if you hardcode a date here that differs from the one in Settings, every record will be flagged as \"Outdated\" forever.<\/p>\n\n<p>Example for Gravity Forms:<\/p>\n\n<pre><code>add_action( 'gform_after_submission', function ( $entry, $form ) {\n    if ( ! empty( $entry['1.1'] ) ) { \/\/ ID of your consent checkbox in the entry.\n        tccl_save_consent( array(\n            'email'           =&gt; sanitize_email( $entry['2'] ?? '' ),\n            'consent_type'    =&gt; 'gravity_form_' . absint( $form['id'] ),\n            'consent_version' =&gt; tccl_get_setting( 'consent_version', '1.0' ),\n            'consent_text'    =&gt; 'I have read and agree to the privacy policy.',\n            'consent_value'   =&gt; 1,\n        ) );\n    }\n}, 10, 2 );\n<\/code><\/pre>\n\n<p>Example for WPForms (a few things differ from Gravity Forms \u2014 read the comments):<\/p>\n\n<pre><code>add_action( 'wpforms_process_complete', function ( $fields, $entry, $form_data, $entry_id ) {\n    \/\/ 1) Only act on the form(s) you want. Use the WPForms map below if you have several.\n    $forms = array(\n        \/\/ form_id =&gt; array( 'email' =&gt; email_field_id, 'checkbox' =&gt; gdpr_field_id ),\n        123 =&gt; array( 'email' =&gt; 5, 'checkbox' =&gt; 7 ),\n    );\n    $form_id = (int) $form_data['id'];\n    if ( ! isset( $forms[ $form_id ] ) ) {\n        return;\n    }\n    $map = $forms[ $form_id ];\n\n    \/\/ 2) The GDPR Agreement checkbox must be ticked.\n    if ( empty( $fields[ $map['checkbox'] ]['value'] ) ) {\n        return;\n    }\n\n    tccl_save_consent( array(\n        'email'           =&gt; sanitize_email( $fields[ $map['email'] ]['value'] ?? '' ),\n        'consent_type'    =&gt; 'wpforms_form_' . $form_id,\n        'consent_version' =&gt; tccl_get_setting( 'consent_version', '1.0' ),\n        'consent_text'    =&gt; 'I have read and agree to the privacy policy.',\n        'consent_value'   =&gt; 1,\n    ) );\n}, 20, 4 );\n<\/code><\/pre>\n\n<p>Two critical details specific to WPForms:<\/p>\n\n<ul>\n<li><code>accepted_args=4<\/code> is mandatory (the <code>, 20, 4<\/code> at the end of <code>add_action<\/code>). Without it, the callback never receives <code>$fields<\/code>, the <code>if ( empty( $fields[...] ) )<\/code> check evaluates to true on every submission and nothing is recorded. This is the most common copy-paste mistake when adapting a Gravity Forms snippet.<\/li>\n<li>You need both field IDs: the email field ID and the GDPR Agreement checkbox field ID. To find them, open the form in WPForms (the URL contains <code>form_id=X<\/code> \u2014 that is the form ID) and click each field in the builder; the right-hand sidebar shows the field ID under \"Advanced\".<\/li>\n<\/ul>\n\n<p>Same idea for <code>fluentform\/submission_inserted<\/code>, <code>user_register<\/code>, <code>forminator_custom_form_after_submission<\/code>, <code>elementor_pro\/forms\/new_record<\/code>, etc. \u2014 adapt the callback signature to each plugin's documented arguments.<\/p><\/dd>\n<dt id=\"does%20the%20ip%20detection%20work%20behind%20cloudflare%20or%20other%20reverse%20proxies%3F\"><h3>Does the IP detection work behind Cloudflare or other reverse proxies?<\/h3><\/dt>\n<dd><p>The plugin reads <code>REMOTE_ADDR<\/code> only and does not trust forwarded headers, which can be spoofed without a verified proxy. If your hosting puts the proxy IP in <code>REMOTE_ADDR<\/code> instead of the real client IP, all entries will record the proxy IP. Most WordPress-friendly hostings pass the real IP correctly.<\/p><\/dd>\n<dt id=\"what%20does%20%22tamper-evident%22%20mean%20here%3F\"><h3>What does \"Tamper-evident\" mean here?<\/h3><\/dt>\n<dd><p>When a consent is written, the plugin computes a SHA-256 hash of the exact accepted text and stores it alongside the record. On every read, the stored hash is compared against a freshly computed one \u2014 any difference is reported as TAMPERED in the records list, the order metabox and the certificate view. This is a cryptographic integrity check, not an electronic signature.<\/p><\/dd>\n<dt id=\"is%20the%20certificate%20a%20real%20pdf%3F\"><h3>Is the certificate a real PDF?<\/h3><\/dt>\n<dd><p>The plugin renders a one-page A4 view with print-optimised CSS and a \"Print \/ Save as PDF\" button. Modern browsers (Chrome, Safari, Firefox, Edge) export that view to a real PDF natively \u2014 same fidelity as a server-side library would produce, with the added benefit that it respects your site's language and fonts. No external library bundled, so the plugin stays small.<\/p>\n\n<p>To be clear: the plugin does NOT store any PDFs on disk and does NOT create an uploads folder of its own. The certificate is generated on demand as HTML each time you open it, and only becomes a PDF if you (or the customer) clicks \"Print \/ Save as PDF\" in the browser. There is nothing to clean up on the server. If the site has a Site Icon defined in Settings &gt; General (the same option block themes and classic themes share), it is shown in the certificate header next to the site name \u2014 including on certificates of consents recorded before this version, since the icon is added at render time, not at storage time.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>New: the site icon (Settings &gt; General &gt; 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 \u2014 the icon is rendered on demand each time the certificate is opened. Falls back gracefully when no site icon is set.<\/li>\n<li>Docs: clarified that [tccl_consent_box] is a self-contained form \u2014 not meant to be nested inside another form builder's form. Updated the FAQ and the source-file docblock.<\/li>\n<li>Docs: expanded the WPForms snippet in the FAQ \u2014 full example with the mandatory <code>accepted_args=4<\/code>, reading <code>consent_version<\/code> from the global setting, and the multi-form <code>id_form =&gt; [email, checkbox]<\/code> pattern. Same fix applied to the Gravity Forms snippet (now reads the version from the setting instead of hardcoding a date).<\/li>\n<li>Docs: clarified the version bump FAQ \u2014 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.<\/li>\n<li>Docs: clarified that the certificate is rendered as a print-optimised A4 view \u2014 no PDFs are stored on disk and the plugin owns no uploads folder.<\/li>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<li>Works with or without WooCommerce. Menu under WooCommerce &gt; Consent log when WC is active, otherwise under Users &gt; Consent log. Capability defaults to manage_woocommerce or manage_options accordingly. Filterable via tccl_admin_menu_parent and tccl_admin_capability.<\/li>\n<li>Activation notice on the plugins screen with quick links to the Records and the Settings tabs.<\/li>\n<li>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.<\/li>\n<li>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.<\/li>\n<li>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.<\/li>\n<li>[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 &gt; Integrations.<\/li>\n<li>Public tccl_save_consent() function (now accepting an optional source_url) for any other source (Gravity Forms, WPForms, custom flows\u2026).<\/li>\n<li>Source URL recorded with every acceptance and shown on the records list, the PDF certificate, the CSV export and the Privacy Tools export.<\/li>\n<li>SHA-256 integrity sealing per record.<\/li>\n<li>Printable A4 certificate per record (browser saves it as PDF).<\/li>\n<li>Native WordPress Privacy Tools integration (export and erase).<\/li>\n<li>Live partial-match filters (email, order, date range, type) with filtered CSV export.<\/li>\n<li>Optional opt-in deletion of plugin data on uninstall (off by default).<\/li>\n<\/ul>","raw_excerpt":"Tamper-evident GDPR consent log: WooCommerce checkout, CF7, WP comments and a [tccl_consent_box] shortcode. Works with or without WooCommerce.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/314214","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=314214"}],"author":[{"embeddable":true,"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/fernandot"}],"wp:attachment":[{"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=314214"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=314214"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=314214"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=314214"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=314214"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/es.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=314214"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}