{"id":303333,"date":"2026-04-30T15:35:14","date_gmt":"2026-04-30T15:35:14","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/faz-cookie-manager\/"},"modified":"2026-05-25T11:51:39","modified_gmt":"2026-05-25T11:51:39","slug":"faz-cookie-manager","status":"publish","type":"plugin","link":"https:\/\/hu.wordpress.org\/plugins\/faz-cookie-manager\/","author":17687579,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.16.0","stable_tag":"1.16.0","tested":"7.0","requires":"5.0","requires_php":"7.4","requires_plugins":null,"header_name":"FAZ Cookie Manager","header_author":"Fabio D'Alessandro","header_description":"A comprehensive GDPR\/CCPA cookie consent manager with built-in cookie scanner, local consent logging, Google Consent Mode v2, and IAB TCF v2.3 support.","assets_banners_color":"c99196","last_updated":"2026-05-25 11:51:39","external_support_url":"","external_repository_url":"","donate_link":"https:\/\/buymeacoffee.com\/fabiodalez","header_plugin_uri":"https:\/\/github.com\/fabiodalez-dev\/faz-cookie-manager","header_author_uri":"https:\/\/fabiodalez.it\/","rating":5,"author_block_rating":0,"active_installs":200,"downloads":1324,"num_ratings":12,"support_threads":10,"support_threads_resolved":7,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.13.11":{"tag":"1.13.11","author":"fabiodalez","date":"2026-04-30 15:34:35"},"1.13.12":{"tag":"1.13.12","author":"fabiodalez","date":"2026-04-30 15:34:35"},"1.13.13":{"tag":"1.13.13","author":"fabiodalez","date":"2026-05-02 11:23:42"},"1.13.14":{"tag":"1.13.14","author":"fabiodalez","date":"2026-05-02 11:35:09"},"1.13.15":{"tag":"1.13.15","author":"fabiodalez","date":"2026-05-04 10:55:25"},"1.13.16":{"tag":"1.13.16","author":"fabiodalez","date":"2026-05-05 14:49:09"},"1.13.17":{"tag":"1.13.17","author":"fabiodalez","date":"2026-05-15 14:25:27"},"1.14.3":{"tag":"1.14.3","author":"fabiodalez","date":"2026-05-19 15:24:09"},"1.16.0":{"tag":"1.16.0","author":"fabiodalez","date":"2026-05-25 11:51:39"}},"upgrade_notice":{"1.13.11":"<p>wp.org round-2 compliance pass. Custom CSS field removed from the Banner editor (use Customizer \u2192 Additional CSS instead); $_SERVER sanitization, FS_CHMOD globals, WP-CLI export path, and ob_start callback all hardened.<\/p>","1.13.10":"<p>Final wp.org submission build. Plugin Check <code>library_core_files<\/code> ERROR on the ClassicPress polyfill resolved (file moved to GitHub-full ZIP only). <code>.distignore<\/code> realigned to release flow.<\/p>","1.13.9":"<p>Plugin Check ERROR resolved (ClassicPress wp.apiFetch polyfill ships as static JS file) plus automatic page-cache invalidation on upgrade for 11 cache plugins (LiteSpeed, WP Rocket, W3 Total Cache, etc.). Recommended for everyone.<\/p>","1.13.8":"<p>Bricks Builder support \u2014 Video element placeholder collapse fixed, Bricks Container Video Lightbox click intercepted before YouTube\/Vimeo opens without consent, banner suppressed in Bricks visual editor. Recommended for any site on Bricks.<\/p>","1.11.2":"<p>Fixes invisible preference center on dark presets, TypeError crash on ChromeOS\/PMP-exempt members, and High Contrast preset button colors. Recommended for all 1.11.x installations.<\/p>","1.11.3":"<p>WP 5.7+ inline script filter, Plugin Check compliance for wp.org submission, and returning-visitor unblock retry. Recommended before wp.org submission.<\/p>","1.11.1":"<p>Critical fix release \u2014 addresses two production-impacting bugs in 1.11.0 (banner reappearing on every page load, PMP <code>exempt_levels<\/code> setting not persisting) plus nine smaller fixes and a new Czech translation. Strongly recommended for all 1.11.0 installations.<\/p>","1.11.0":"<p>Consent versioning, non-personalized ads fallback for Google Consent Mode, and an optional Paid Memberships Pro integration. Review Settings \u2192 Force re-consent and GCM \u2192 Advanced after upgrade; existing cookies remain valid until you click &quot;Invalidate all consents&quot;.<\/p>","1.9.2":"<p>Fixes the &quot;English always comes back&quot; language bug. Clear caches after upgrading.<\/p>","1.9.1":"<p>Fixes default language fallback and theme color bleed on banner buttons. Clear caches after upgrading.<\/p>","1.9.0":"<p>WCAG 2.2 accessibility, CSS custom properties for CSP compatibility, Dutch language, admin UI refresh with live preview, security hardening, and 155+ E2E tests. Clear caches after upgrading.<\/p>","1.5.0":"<p>New link text colour picker for banner links. 21 new E2E tests. TinyMCE, accessibility, and output buffer fixes. Clear caches after upgrading.<\/p>","1.4.1":"<p>Fixes ClassicPress polyfill loading. Clear caches after upgrading.<\/p>","1.4.0":"<p>Major update: 5-layer script blocking with Known Providers database (147+ services), video\/social embed placeholders, cookie shredding on revocation, ClassicPress compatibility. Clear caches after upgrading.<\/p>","1.2.1":"<p>Fixes CSV export formatting, consent log accuracy (rejected now tracked), and CodeQL security alerts. Adds Composer\/Packagist support. Clear caches after upgrading.<\/p>","1.2.0":"<p>Security hardening (proxy trust filter, dual-throttle consent logging, TTL normalization). Improved necessary toggle UX. Clear caches after upgrading.<\/p>","1.1.0":"<p>Major update: IAB TCF v2.3 with full Global Vendor List integration. New GVL admin page for vendor management. 175 automated compliance tests. Clear caches after upgrading.<\/p>","1.0.5":"<p>Admin page URLs have changed. Update any bookmarks. Clear caches after upgrading.<\/p>"},"ratings":{"1":0,"2":0,"3":0,"4":0,"5":12},"assets_icons":{"icon-128x128.jpg":{"filename":"icon-128x128.jpg","revision":3519691,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.jpg":{"filename":"icon-256x256.jpg","revision":3519691,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.jpg":{"filename":"banner-1544x500.jpg","revision":3519691,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.jpg":{"filename":"banner-772x250.jpg","revision":3519691,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{"blueprint.json":{"filename":"blueprint.json","revision":3547537,"resolution":false,"location":"assets","locale":"","contents":"{\"$schema\":\"https:\\\/\\\/playground.wordpress.net\\\/blueprint-schema.json\",\"preferredVersions\":{\"php\":\"8.2\",\"wp\":\"latest\"},\"features\":{\"networking\":false},\"landingPage\":\"\\\/\",\"steps\":[{\"step\":\"login\",\"username\":\"admin\",\"password\":\"password\"},{\"step\":\"installPlugin\",\"options\":{\"activate\":true},\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"faz-cookie-manager\"}}]}"}},"all_blocks":{"faz\/cookie-table":{"name":"faz\/cookie-table","title":"Cookie Table"},"faz\/cookie-policy":{"name":"faz\/cookie-policy","title":"Cookie Policy"},"faz\/consent-button":{"name":"faz\/consent-button","title":"Manage Cookies Button"}},"tagged_versions":["1.13.11","1.13.12","1.13.13","1.13.14","1.13.15","1.13.16","1.13.17","1.14.3","1.16.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3519691,"resolution":"1","location":"assets","locale":"","width":2560,"height":1920},"screenshot-10.png":{"filename":"screenshot-10.png","revision":3519691,"resolution":"10","location":"assets","locale":"","width":2560,"height":1920},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3519691,"resolution":"2","location":"assets","locale":"","width":2560,"height":1920},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3519691,"resolution":"3","location":"assets","locale":"","width":2560,"height":1920},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3519691,"resolution":"4","location":"assets","locale":"","width":2560,"height":1920},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3519691,"resolution":"5","location":"assets","locale":"","width":2560,"height":1920},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3519691,"resolution":"6","location":"assets","locale":"","width":2560,"height":1920},"screenshot-7.png":{"filename":"screenshot-7.png","revision":3519691,"resolution":"7","location":"assets","locale":"","width":2560,"height":1920},"screenshot-8.png":{"filename":"screenshot-8.png","revision":3519691,"resolution":"8","location":"assets","locale":"","width":2560,"height":1920},"screenshot-9.png":{"filename":"screenshot-9.png","revision":3519691,"resolution":"9","location":"assets","locale":"","width":2560,"height":1920}},"screenshots":{"1":"<strong>Cookie consent banner on the frontend<\/strong> -- GDPR-ready banner in the bottom-left corner with \"Customize\", \"Reject All\" and equal-weight \"Accept All\" buttons. Shown only on the first visit until the visitor makes a choice.","2":"<strong>Preference center<\/strong> -- Category-level opt-in modal. Necessary cookies are always active; every other category (Functional, Analytics, Uncategorized, Marketing) is opt-in by default, with a clear description for each.","3":"<strong>Admin dashboard<\/strong> -- Overview of pageviews, banner impressions, accept rate and reject rate, with a 7\/30\/365-day pageviews chart and consent distribution.","4":"<strong>Banner editor<\/strong> -- Configure layout, position, colours, copy and behaviour with a live in-iframe preview. Ships with GDPR Strict, High Contrast and Light Minimal design presets.","5":"<strong>Cookies management<\/strong> -- Review and edit cookie categories, run the built-in scanner, and browse the bundled Open Cookie Database with 1,000+ definitions.","6":"<strong>IAB TCF v2.3 Global Vendor List<\/strong> -- Browse the bundled GVL, filter by purpose, and select which vendors your site works with. Full Transparency and Consent Framework v2.3 support, no cloud required.","7":"<strong>Consent logs<\/strong> -- Local, tamper-resistant audit trail of every visitor consent: status, categories, hashed IP, URL and timestamp. Filter, search and export to CSV for DPIA \/ audits.","8":"<strong>Google Consent Mode v2<\/strong> -- Default vs. granted state for <code>ad_storage<\/code>, <code>analytics_storage<\/code>, <code>ad_user_data<\/code>, <code>ad_personalization<\/code>, <code>functionality_storage<\/code>, <code>personalization_storage<\/code> and <code>security_storage<\/code>. Works with GTM and gtag.","9":"<strong>Languages<\/strong> -- Manage active languages and the default banner language. Works alongside WPML \/ Polylang; Italian, Dutch, German, French and Czech translations ship out of the box.","10":"<strong>Settings<\/strong> -- Global controls: enable\/disable the banner, exclude specific pages, cross-domain consent forwarding, hide from bots, GTM dataLayer events, consent log retention and scanner limits."}},"plugin_section":[262246],"plugin_tags":[166295,20011,388,131785,396],"plugin_category":[54],"plugin_contributors":[261568],"plugin_business_model":[],"class_list":["post-303333","plugin","type-plugin","status-publish","hentry","plugin_section-dashboard-widgets","plugin_tags-ccpa","plugin_tags-consent","plugin_tags-cookie","plugin_tags-gdpr","plugin_tags-privacy","plugin_category-security-and-spam-protection","plugin_contributors-fabiodalez","plugin_committers-fabiodalez"],"banners":{"banner":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/banner-772x250.jpg?rev=3519691","banner_2x":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/banner-1544x500.jpg?rev=3519691","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/icon-128x128.jpg?rev=3519691","icon_2x":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/icon-256x256.jpg?rev=3519691","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-1.png?rev=3519691","caption":"<strong>Cookie consent banner on the frontend<\/strong> -- GDPR-ready banner in the bottom-left corner with \"Customize\", \"Reject All\" and equal-weight \"Accept All\" buttons. Shown only on the first visit until the visitor makes a choice."},{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-2.png?rev=3519691","caption":"<strong>Preference center<\/strong> -- Category-level opt-in modal. Necessary cookies are always active; every other category (Functional, Analytics, Uncategorized, Marketing) is opt-in by default, with a clear description for each."},{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-3.png?rev=3519691","caption":"<strong>Admin dashboard<\/strong> -- Overview of pageviews, banner impressions, accept rate and reject rate, with a 7\/30\/365-day pageviews chart and consent distribution."},{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-4.png?rev=3519691","caption":"<strong>Banner editor<\/strong> -- Configure layout, position, colours, copy and behaviour with a live in-iframe preview. Ships with GDPR Strict, High Contrast and Light Minimal design presets."},{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-5.png?rev=3519691","caption":"<strong>Cookies management<\/strong> -- Review and edit cookie categories, run the built-in scanner, and browse the bundled Open Cookie Database with 1,000+ definitions."},{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-6.png?rev=3519691","caption":"<strong>IAB TCF v2.3 Global Vendor List<\/strong> -- Browse the bundled GVL, filter by purpose, and select which vendors your site works with. Full Transparency and Consent Framework v2.3 support, no cloud required."},{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-7.png?rev=3519691","caption":"<strong>Consent logs<\/strong> -- Local, tamper-resistant audit trail of every visitor consent: status, categories, hashed IP, URL and timestamp. Filter, search and export to CSV for DPIA \/ audits."},{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-8.png?rev=3519691","caption":"<strong>Google Consent Mode v2<\/strong> -- Default vs. granted state for <code>ad_storage<\/code>, <code>analytics_storage<\/code>, <code>ad_user_data<\/code>, <code>ad_personalization<\/code>, <code>functionality_storage<\/code>, <code>personalization_storage<\/code> and <code>security_storage<\/code>. Works with GTM and gtag."},{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-9.png?rev=3519691","caption":"<strong>Languages<\/strong> -- Manage active languages and the default banner language. Works alongside WPML \/ Polylang; Italian, Dutch, German, French and Czech translations ship out of the box."},{"src":"https:\/\/ps.w.org\/faz-cookie-manager\/assets\/screenshot-10.png?rev=3519691","caption":"<strong>Settings<\/strong> -- Global controls: enable\/disable the banner, exclude specific pages, cross-domain consent forwarding, hide from bots, GTM dataLayer events, consent log retention and scanner limits."}],"raw_content":"<!--section=description-->\n<p><strong>Tired of cookie consent plugins that lock essential features behind paywalls, require cloud accounts, or send your visitors' data to third-party servers?<\/strong><\/p>\n\n<p>FAZ Cookie Manager is a WordPress plugin that helps you implement cookie consent and privacy workflows for international regulations -- completely free, with no strings attached.<\/p>\n\n<p>No account to create. The plugin requires no cloud service connection. Basic features like consent logging and geo-targeting are included -- no premium plan needed. Core consent features run on your own server, and you own all your data.<\/p>\n\n<h4>Why FAZ Cookie Manager?<\/h4>\n\n<p>Most cookie consent plugins follow the same pattern: a free version with crippled features, and a paid tier starting at $10-50\/month that unlocks what you actually need (cookie scanning, consent logs, Google Consent Mode, IAB TCF). FAZ Cookie Manager breaks that model:<\/p>\n\n<ul>\n<li><strong>Cookie scanner<\/strong> -- Scans your site directly from your browser. No external service, no API limits, no waiting.<\/li>\n<li><strong>Cookie Policy generator (NEW in 1.16.0)<\/strong> -- Build a jurisdiction-aware Cookie Policy page directly from your admin. Pick GDPR \/ CCPA \/ LGPD, fill in your company details, and publish via the <code>[faz_cookie_policy_complete]<\/code> shortcode. Output is multilingual (en, it, fr, de, es, pt-BR), pulls the live cookie inventory from the scanner, and ships with a non-removable disclaimer that the templates are starting points, not legal advice. The standalone <code>[faz_cookie_table]<\/code> shortcode (and the matching Gutenberg block) still works for embedding just the cookie list.<\/li>\n<li><strong>Consent logging with CSV export<\/strong> -- Every consent is recorded locally in your database. Export anytime for audits.<\/li>\n<li><strong>Google Consent Mode v2<\/strong> -- Sends all 7 consent signals to Google tags. No premium required.<\/li>\n<li><strong>IAB TCF v2.3<\/strong> -- Full Transparency and Consent Framework support, built in.<\/li>\n<li><strong>Geo-targeting<\/strong> -- Show banners only to visitors from regulated regions (EU, California, etc.).<\/li>\n<li><strong>180+ languages<\/strong> -- Translate every string in the banner, or use one of the built-in translations.<\/li>\n<li><strong>Script blocking<\/strong> -- Tag any script with <code>data-faz-tag<\/code> to block it until the right category is accepted.<\/li>\n<li><strong>Microsoft UET\/Clarity<\/strong> -- Consent integration for Microsoft advertising and analytics tools.<\/li>\n<li><strong>Revisit consent widget<\/strong> -- Floating button lets visitors change their preferences anytime.<\/li>\n<li><strong>Accessibility-focused<\/strong> -- Keyboard navigation (Tab, Enter, Escape), screen-reader support, mobile responsive.<\/li>\n<\/ul>\n\n<h4>Helps with these frameworks<\/h4>\n\n<p>This plugin assists consent and privacy workflows. It does not itself create, provide, or guarantee legal compliance, and you remain responsible for the final configuration for your site and jurisdiction.<\/p>\n\n<ul>\n<li><strong>GDPR<\/strong> (EU General Data Protection Regulation) -- Opt-in consent, granular categories, right to withdraw<\/li>\n<li><strong>CCPA \/ CPRA<\/strong> (California Consumer Privacy Act) -- \"Do Not Sell or Share\" opt-out link<\/li>\n<li><strong>ePrivacy Directive<\/strong> (EU Cookie Law) -- Consent-based script blocking support<\/li>\n<li><strong>Italian Garante Privacy<\/strong> -- 6-month consent expiry setting and consent logging controls<\/li>\n<li><strong>EDPB Guidelines<\/strong> -- No scroll-as-consent, no pre-checked categories, equal button prominence options<\/li>\n<li><strong>LGPD<\/strong> (Brazil General Data Protection Law) -- Consent-based model<\/li>\n<li><strong>POPIA<\/strong> (South Africa Protection of Personal Information Act) -- Opt-in consent<\/li>\n<\/ul>\n\n<h4>Try it Live<\/h4>\n\n<p><strong><a href=\"https:\/\/playground.wordpress.net\/?plugin=faz-cookie-manager\">Try FAZ Cookie Manager in WordPress Playground<\/a><\/strong> -- no account, no install, runs entirely in your browser.<\/p>\n\n<h4>How it works<\/h4>\n\n<ol>\n<li>Install and activate -- the cookie banner appears immediately with sensible defaults<\/li>\n<li>Scan your site to detect cookies automatically<\/li>\n<li>Customize the banner design, text, and colors to match your brand<\/li>\n<li>Enable Google Consent Mode or IAB TCF if you use advertising tools<\/li>\n<li>Monitor consent analytics on the dashboard<\/li>\n<\/ol>\n\n<p>Core banner functionality runs on your WordPress site. Optional update\/download features may contact GitHub, IAB Europe, MaxMind, ip-api.com, ipinfo.io (opt-in VPN detection), or the AMP CDN depending on which features you enable and use.<\/p>\n\n<h4>Cookie Policy generator (1.16.0+)<\/h4>\n\n<p>Need a Cookie Policy page that explains the cookies your site sets, the jurisdiction it operates under, and who the visitor should contact about their data? FAZ Cookie Manager 1.16.0 ships a dedicated <strong>Cookie Policy<\/strong> admin tab plus the <code>[faz_cookie_policy_complete]<\/code> shortcode.<\/p>\n\n<ul>\n<li><strong>Jurisdiction-aware<\/strong> -- pick GDPR (EU\/EEA\/UK), CCPA\/CPRA (California), or LGPD (Brazil). Each jurisdiction ships its own template scaffold with the legal references and required sections for that framework.<\/li>\n<li><strong>Multilingual (6 languages out of the box)<\/strong> -- en, it, fr, de, es, pt-BR. Override per render with <code>[faz_cookie_policy_complete lang=\"it\"]<\/code> or let the visitor's browser language pick.<\/li>\n<li><strong>Auto-populated cookie inventory<\/strong> -- the rendered policy pulls live from <code>wp_faz_cookies<\/code>, so any cookie discovered by the scanner shows up at the next render with its category, duration and description, in the active language.<\/li>\n<li><strong>Filled with your company data<\/strong> -- name, address, DPO email, third-party services, retention period: stored in <code>faz_cookie_policy_data<\/code> option, edited via the admin form, never seeded from <code>admin_email<\/code> or <code>blogname<\/code> (PII protection).<\/li>\n<li><strong>Non-removable legal disclaimer<\/strong> -- every generated policy ends with a footer making explicit that the templates are starting points, not legal advice. The disclaimer is hardcoded in the renderer (not in the template files) so section overrides cannot suppress it.<\/li>\n<li><strong>Versioning hash<\/strong> -- a <code>data-faz-policy-version<\/code> attribute on the rendered article tracks template + data drift over time. Display-only fields (the visible \"Last updated\" date) are excluded so the hash doesn't change daily.<\/li>\n<li><strong>Filter for site builders<\/strong> -- <code>faz_cookie_policy_data<\/code> lets you inject custom placeholders before template substitution.<\/li>\n<li><strong>Backwards compatible<\/strong> -- the long-standing <code>[faz_cookie_policy]<\/code> shortcode (with <code>site_name<\/code> \/ <code>contact<\/code> \/ <code>show_table<\/code> attributes from 1.7.0) is unchanged. The standalone <code>[faz_cookie_table]<\/code> shortcode and matching <code>faz\/cookie-table<\/code> Gutenberg block still work for embedding just the cookie inventory table.<\/li>\n<\/ul>\n\n<h4>Multi-banner geo-routing vs multilingual content (1.14.0+)<\/h4>\n\n<p>These are two <strong>orthogonal<\/strong> features that combine freely \u2014 multi-banner is per <strong>country<\/strong>, multilingual content is per <strong>language inside each banner<\/strong>.<\/p>\n\n<ul>\n<li><p><strong>Multi-banner geo-routing<\/strong> picks WHICH banner profile to serve based on the visitor's country. Typical setup: a strict GDPR banner for EU\/EEA\/UK and a CCPA opt-out banner for California (or any other per-region compliance profile). Country resolution chain: Cloudflare <code>CF-IPCountry<\/code> header (opt-in via the <code>faz_trust_cf_ipcountry_header<\/code> filter) \u2192 MaxMind GeoLite2 \u2192 ip-api.com fallback. Each banner row carries its own <code>target_countries<\/code> list and a <code>priority<\/code> integer for overlap resolution.<\/p><\/li>\n<li><p><strong>Multilingual content<\/strong> lives INSIDE each banner. A single banner stores translations of its title, description and button labels for as many languages as you enable on the Languages page. The language displayed to the visitor is resolved CLIENT-SIDE from <code>navigator.languages<\/code> so a country-targeted banner can still be served from a full-page cache (LiteSpeed \/ WP Rocket \/ Cloudflare APO) and the right language renders on hydration.<\/p><\/li>\n<\/ul>\n\n<p>Practical example: an install needs only TWO banner rows, not eight. One EU-targeted GDPR banner with English + Italian + German + French + Polish translations inside, and one US-targeted CCPA banner with English + Spanish translations inside. The country selects the banner; the browser selects the translation inside the banner. Visitors hitting the right cache key get the right banner + the right language.<\/p>\n\n<h3>External Services<\/h3>\n\n<h4>GitHub \/ Raw GitHubusercontent (Open Cookie Database)<\/h4>\n\n<p>Used to refresh the built-in cookie definitions snapshot for the optional auto-categorize feature.<\/p>\n\n<p>Triggered when: you click the definitions update action in the Cookies screen.<\/p>\n\n<p>Data sent: your server IP address and standard HTTP request headers.<\/p>\n\n<p>Service URLs:\n* https:\/\/raw.githubusercontent.com\/fabiodalez-dev\/Open-Cookie-Database\/master\/open-cookie-database.json<\/p>\n\n<p>Terms of Service \/ Privacy Policy:\n* https:\/\/docs.github.com\/en\/site-policy\/github-terms\/github-terms-of-service\n* https:\/\/docs.github.com\/en\/site-policy\/privacy-policies\/github-privacy-statement<\/p>\n\n<h4>IAB Europe \/ vendor-list.consensu.org<\/h4>\n\n<p>Used to download the Global Vendor List and purpose translations for the optional IAB TCF feature.<\/p>\n\n<p>Triggered when: you manually update the vendor list, and weekly while IAB TCF is enabled.<\/p>\n\n<p>Data sent: your server IP address and standard HTTP request headers.<\/p>\n\n<p>Service URLs:\n* https:\/\/vendor-list.consensu.org\/v3\/vendor-list.json\n* https:\/\/vendor-list.consensu.org\/v3\/purposes-en.json<\/p>\n\n<p>Privacy Policy:\n* https:\/\/iabeurope.eu\/privacy-policy\/<\/p>\n\n<h4>MaxMind<\/h4>\n\n<p>Used to download the GeoLite2 Country database for optional geo-targeting.<\/p>\n\n<p>Triggered when: you enter a MaxMind license key in Settings and start the database download.<\/p>\n\n<p>Data sent: your server IP address, the license key you provide, and standard HTTP request headers.<\/p>\n\n<p>Service URL:\n* https:\/\/download.maxmind.com\/app\/geoip_download<\/p>\n\n<p>Terms of Service \/ Privacy Policy:\n* https:\/\/www.maxmind.com\/en\/terms-of-use\n* https:\/\/www.maxmind.com\/en\/privacy-policy<\/p>\n\n<h4>ip-api.com<\/h4>\n\n<p>Used as a fallback geolocation lookup for the optional geo-targeting and multi-banner geo-routing features, only when MaxMind is unavailable.<\/p>\n\n<p>Triggered when: a frontend page renders the banner while geo-targeting \/ multi-banner geo-routing is enabled AND neither the Cloudflare CF-IPCountry header (opt-in) nor the MaxMind GeoLite2 database produces a result. The visitor's IP is sent to ip-api.com for country resolution; the resolved country code is cached in a transient (hash-keyed by IP) for one hour to avoid repeating the lookup.<\/p>\n\n<p>Data sent: the visitor's IP address and standard HTTP request headers.<\/p>\n\n<p>Service URL:\n* http:\/\/ip-api.com\/json\/{ip}?fields=countryCode<\/p>\n\n<p>Terms of Service \/ Privacy Policy:\n* https:\/\/ip-api.com\/docs\/legal<\/p>\n\n<h4>ipinfo.io (geo-routing v2 only)<\/h4>\n\n<p>Used for VPN\/proxy\/Tor detection when the admin opts in to enhanced geo detection via Settings \u2192 Geo-routing \u2192 ipinfo settings. The plugin sends the visitor IP to ipinfo.io to determine whether the visitor is masking their location; when VPN is detected, the most-protective rule-set is applied regardless of the visitor's apparent country.<\/p>\n\n<p>Triggered when: a frontend page renders the banner AND the admin has configured an ipinfo API key AND has explicitly attested to having a DPF \/ SCC \/ DPA agreement with ipinfo.io for cross-border data transfer of EU\/UK visitor IPs. Without the admin opt-in, ipinfo is NEVER called.<\/p>\n\n<p>Data sent: the visitor's IP address (in cleartext, as required by ipinfo's lookup contract), the configured API key, and standard HTTP request headers. The plugin caches the VPN classification locally for 24 hours hash-keyed by the IP (with monthly salt rotation) so repeat visitors do not trigger fresh calls.<\/p>\n\n<p>Service URL:\n* https:\/\/ipinfo.io\/{ip}\/privacy<\/p>\n\n<p>Terms of Service \/ Privacy Policy:\n* https:\/\/ipinfo.io\/terms-of-service\n* https:\/\/ipinfo.io\/privacy-policy\n* DPA (Data Processing Agreement) available on request: https:\/\/ipinfo.io\/contact<\/p>\n\n<h4>Plugin REST endpoint \/faz\/v1\/banner (public)<\/h4>\n\n<p>Used by the plugin's own front-end JavaScript (<code>script.js<\/code>) to fetch the per-language \/ per-country banner payload after the page has loaded. This is an INTERNAL endpoint hosted by the plugin on the same WordPress install \u2014 no third-party network call leaves the visitor's browser to a remote service. It is documented here only because the response carries <code>bannerSlug<\/code> and <code>activeLaw<\/code>, two strings that describe which banner profile and which legal regime (gdpr \/ ccpa) currently applies to the visitor.<\/p>\n\n<p>Triggered when: the front-end banner script bootstraps on a page that has multi-banner geo-routing active.<\/p>\n\n<p>Data sent: only what the visitor's browser already sends with any page request to the same origin. The plugin does not forward the request to any remote service.<\/p>\n\n<p>Service URL:\n* https:\/\/{your-site}\/wp-json\/faz\/v1\/banner<\/p>\n\n<h4>AMP Project CDN<\/h4>\n\n<p>Used only on AMP pages when the AMP consent integration is active, to load the official <code>amp-consent<\/code> component required by AMP.<\/p>\n\n<p>Triggered when: an AMP page renders the AMP consent banner.<\/p>\n\n<p>Data sent: the visitor IP address and standard browser request data to the AMP CDN.<\/p>\n\n<p>Service URL:\n* https:\/\/cdn.ampproject.org\/v0\/amp-consent-0.1.js<\/p>\n\n<p>Documentation \/ Privacy:\n* https:\/\/amp.dev\/documentation\/components\/amp-consent\n* https:\/\/policies.google.com\/privacy<\/p>\n\n<h4>Note on third-party domain strings inside the plugin codebase<\/h4>\n\n<p>The plugin source includes several third-party domain names (e.g. <code>js.stripe.com<\/code>, <code>connect.facebook.net<\/code>, <code>cdn.jsdelivr.net<\/code>, <code>unpkg.com<\/code>, <code>googletagmanager.com<\/code>, etc.) as <strong>string patterns<\/strong> for two purposes:<\/p>\n\n<ol>\n<li><strong>Script-blocking detection patterns<\/strong> \u2014 used to identify analytics, advertising, and tracking scripts that the <em>site administrator's other plugins<\/em> may inject, so we can block them until the visitor has given consent. The plugin itself does <strong>not<\/strong> load any of these scripts.<\/li>\n<li><strong>Whitelist defaults<\/strong> \u2014 domains such as <code>unpkg.com\/<\/code>, <code>cdn.jsdelivr.net\/<\/code>, <code>fonts.googleapis.com\/<\/code>, <code>www.google.com\/recaptcha\/api<\/code>, etc. are seeded as default <em>whitelist<\/em> entries so the script blocker leaves them alone unless the admin explicitly removes them. They are configuration data, not outbound HTTP calls.<\/li>\n<\/ol>\n\n<p>The only outbound HTTP requests this plugin makes are the six documented above (Open Cookie Database, IAB GVL, MaxMind, ip-api.com fallback, ipinfo.io VPN detection (opt-in), AMP CDN). All six are gated behind explicit administrator action or an enabled feature. The internal <code>\/faz\/v1\/banner<\/code> endpoint described above is hosted by this plugin on the same site \u2014 no third-party network call leaves the visitor's browser to a remote service.<\/p>\n\n<h3>Cache Plugin Compatibility<\/h3>\n\n<p>When multi-banner geo-routing (1.14.0+) is active, the rendered HTML can legitimately vary by visitor country. This plugin asks the page-cache layer to bypass caching on those requests by emitting:<\/p>\n\n<ul>\n<li><code>Cache-Control: no-store, no-cache, must-revalidate, max-age=0<\/code><\/li>\n<li><code>Pragma: no-cache<\/code><\/li>\n<li><code>X-LiteSpeed-Cache-Control: no-cache<\/code><\/li>\n<li><code>Vary: CF-IPCountry<\/code> (when the trust filter <code>faz_trust_cf_ipcountry_header<\/code> is enabled)<\/li>\n<li><code>DONOTCACHEPAGE<\/code>, <code>DONOTCACHEOBJECT<\/code>, <code>DONOTCACHEDB<\/code> PHP constants (industry-standard bypass hints)<\/li>\n<li><code>do_action( 'litespeed_control_set_nocache', ... )<\/code> when LiteSpeed Cache is installed<\/li>\n<\/ul>\n\n<h4>Verified compatible (no extra configuration needed)<\/h4>\n\n<ul>\n<li><strong>LiteSpeed Cache<\/strong> \u2014 uses the explicit <code>litespeed_control_set_nocache<\/code> action + <code>X-LiteSpeed-Cache-Control<\/code> header.<\/li>\n<li><strong>WP Rocket<\/strong> \u2014 honors <code>DONOTCACHEPAGE<\/code> natively.<\/li>\n<li><strong>W3 Total Cache<\/strong> \u2014 honors <code>DONOTCACHEPAGE<\/code> \/ <code>DONOTCACHEOBJECT<\/code> natively.<\/li>\n<li><strong>WP Super Cache<\/strong> \u2014 honors <code>DONOTCACHEPAGE<\/code> natively.<\/li>\n<li><strong>Hummingbird (WPMU DEV)<\/strong> \u2014 honors <code>DONOTCACHEPAGE<\/code> natively.<\/li>\n<li><strong>Cloudflare APO<\/strong> \u2014 honors the <code>Cache-Control: no-store<\/code> header. With CF in front, also enable the trust filter so the <code>Vary: CF-IPCountry<\/code> header is emitted and CF caches per-country variants instead of bypassing entirely.<\/li>\n<\/ul>\n\n<h4>Known limitations<\/h4>\n\n<ul>\n<li><strong>CDNs without origin Cache-Control honoring<\/strong> (e.g. some legacy CloudFront configurations) \u2014 verify the response Cache-Control header reaches the edge. If not, add a CF-IPCountry or country-based cache key rule at the CDN level.<\/li>\n<li><strong>Minor \/ regional cache plugins<\/strong> (Comet Cache, Cachify, Swift Performance Lite) \u2014 not formally tested. Most still honor <code>DONOTCACHEPAGE<\/code>; verify by inspecting the response Cache-Control on a country-targeted page.<\/li>\n<\/ul>\n\n<p>Override the bypass logic per request via the <code>faz_country_dependent_banner_output<\/code> filter (return false to force the cache to ignore the country dimension on a specific URL).<\/p>\n\n<!--section=installation-->\n<h4>From the WordPress.org plugin directory (recommended)<\/h4>\n\n<ol>\n<li>In your WordPress dashboard go to <strong>Plugins &gt; Add New Plugin<\/strong><\/li>\n<li>Search for <strong>FAZ Cookie Manager<\/strong><\/li>\n<li>Click <strong>Install Now<\/strong>, then <strong>Activate<\/strong><\/li>\n<li>Go to <strong>FAZ Cookie<\/strong> in the admin sidebar to configure your banner<\/li>\n<\/ol>\n\n<h4>Manual installation<\/h4>\n\n<ol>\n<li>Download the ZIP from <a href=\"https:\/\/wordpress.org\/plugins\/faz-cookie-manager\/\">wordpress.org\/plugins\/faz-cookie-manager<\/a><\/li>\n<li>In your WordPress dashboard go to <strong>Plugins &gt; Add New Plugin &gt; Upload Plugin<\/strong><\/li>\n<li>Upload the ZIP and click <strong>Install Now<\/strong>, then <strong>Activate<\/strong><\/li>\n<li>Go to <strong>FAZ Cookie<\/strong> in the admin sidebar to configure your banner<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20plugin%20require%20a%20cloud%20account%20or%20subscription%3F\"><h3>Does this plugin require a cloud account or subscription?<\/h3><\/dt>\n<dd><p>No required cloud account or subscription is needed. Core consent features run locally, while some optional refresh\/download features can contact documented third-party services such as GitHub, IAB Europe, MaxMind, or AMP infrastructure.<\/p><\/dd>\n<dt id=\"is%20it%20really%20free%3F%20what%27s%20the%20catch%3F\"><h3>Is it really free? What's the catch?<\/h3><\/dt>\n<dd><p>It's free and open source (GPL-3.0). There are no premium upgrades, no feature gates, and no upsells. The plugin is based on the GPL-licensed CookieYes v3.4.0 codebase, with cloud dependencies removed and all included features running locally.<\/p><\/dd>\n<dt id=\"is%20it%20compatible%20with%20google%20consent%20mode%20v2%3F\"><h3>Is it compatible with Google Consent Mode v2?<\/h3><\/dt>\n<dd><p>Yes. The plugin sends all 7 consent signals (<code>ad_storage<\/code>, <code>analytics_storage<\/code>, <code>ad_user_data<\/code>, <code>ad_personalization<\/code>, <code>functionality_storage<\/code>, <code>personalization_storage<\/code>, <code>security_storage<\/code>) and supports Google Additional Consent Mode (GACM) for ad technology providers.<\/p><\/dd>\n<dt id=\"does%20the%20banner%20block%20cookies%20before%20consent%3F\"><h3>Does the banner block cookies before consent?<\/h3><\/dt>\n<dd><p>Yes. Any script tagged with <code>data-faz-tag=\"category-name\"<\/code> is blocked until the visitor grants consent for that category. This helps you implement consent-based blocking for ePrivacy\/GDPR workflows.<\/p><\/dd>\n<dt id=\"how%20does%20the%20cookie%20scanner%20work%3F\"><h3>How does the cookie scanner work?<\/h3><\/dt>\n<dd><p>Go to <strong>FAZ Cookie &gt; Cookies<\/strong> and click <strong>Scan Site<\/strong>. The scanner runs in your browser using iframes, crawling your site's pages to detect all cookies. Choose from quick scan (10 pages), standard (100), deep (1000), or full scan. No external service involved.<\/p><\/dd>\n<dt id=\"can%20i%20log%20consent%20for%20gdpr%20accountability%3F\"><h3>Can I log consent for GDPR accountability?<\/h3><\/dt>\n<dd><p>Yes. Every consent action (accept, reject, customize) is recorded in a local database table with timestamp, consent ID, categories chosen, anonymized IP, and page URL. Export to CSV anytime from the Consent Logs page.<\/p><\/dd>\n<dt id=\"does%20it%20support%20multiple%20languages%3F\"><h3>Does it support multiple languages?<\/h3><\/dt>\n<dd><p>Yes. The Languages page lets you select from 180+ available languages. Each banner you create carries its own translations for every language you enable \u2014 the banner text (title, description, button labels) is stored per-language inside the banner row, and the language displayed to the visitor is resolved client-side from <code>navigator.languages<\/code>. WPML \/ Polylang URL-based language switching is auto-detected and always cache-safe.<\/p><\/dd>\n<dt id=\"does%20multi-banner%20mean%20one%20banner%20per%20language%3F\"><h3>Does multi-banner mean one banner per language?<\/h3><\/dt>\n<dd><p>No \u2014 multi-banner routing is per visitor <strong>country<\/strong> (e.g. GDPR vs CCPA, EU vs US), not per language. Each banner row carries its OWN multilingual content: title, description and button labels translated for every language you support. The visitor's country selects the banner; the visitor's browser language then selects which translated strings to render inside that banner. So an install with one EU-targeted GDPR banner (carrying English + Italian + German + French translations) and one US-targeted CCPA banner (carrying English + Spanish translations) needs only TWO banner rows, not eight. See the \"Multi-banner geo-routing vs multilingual content\" section in the Description for the full architecture.<\/p><\/dd>\n<dt id=\"can%20users%20change%20their%20consent%20after%20accepting%3F\"><h3>Can users change their consent after accepting?<\/h3><\/dt>\n<dd><p>Yes. A floating revisit widget appears on every page, letting visitors reopen the preference center and change their choices at any time.<\/p><\/dd>\n<dt id=\"is%20the%20banner%20accessible%3F\"><h3>Is the banner accessible?<\/h3><\/dt>\n<dd><p>Yes. The banner supports full keyboard navigation (Tab, Enter, Escape), proper ARIA labels, and is responsive down to 375px viewports. Buttons have equal visual prominence to avoid dark patterns.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20caching%20plugins%3F\"><h3>Does it work with caching plugins?<\/h3><\/dt>\n<dd><p>Yes. The consent banner is rendered via JavaScript from a cached template, so it works with all major caching plugins (WP Super Cache, W3 Total Cache, LiteSpeed Cache, etc.).<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20send%20any%20data%20home%20or%20collect%20telemetry%3F\"><h3>Does the plugin send any data home or collect telemetry?<\/h3><\/dt>\n<dd><p>No. The plugin contains no telemetry, no analytics beacon, and no \"phone home\". Dashboard numbers are computed locally from your own <code>wp_faz_pageviews<\/code> and <code>wp_faz_consent_logs<\/code> tables. Every outbound request that <em>can<\/em> happen is documented in the \"External services\" section and is gated behind an explicit admin action.<\/p><\/dd>\n<dt id=\"where%20is%20the%20source%20of%20the%20bundled%20minified%20javascript%3F\"><h3>Where is the source of the bundled minified JavaScript?<\/h3><\/dt>\n<dd><p>The only minified files we ship are <code>frontend\/js\/gcm.min.js<\/code> and <code>frontend\/js\/tcf-cmp.min.js<\/code>. The full, unminified sources live next to them as <code>gcm.js<\/code> and <code>tcf-cmp.js<\/code>, and the build command <code>npm run build:min<\/code> rebuilds them with <code>terser<\/code>. No obfuscation is used.<\/p><\/dd>\n<dt id=\"does%20uninstalling%20the%20plugin%20remove%20my%20data%3F\"><h3>Does uninstalling the plugin remove my data?<\/h3><\/dt>\n<dd><p>By default, no -- your consent logs, banner configuration and categories stay in the database so you can reinstall without losing work. To wipe everything on uninstall, enable <strong>Settings \u2192 General \u2192 Remove all data on uninstall<\/strong> or define <code>FAZ_REMOVE_ALL_DATA<\/code> as <code>true<\/code> in <code>wp-config.php<\/code> before deleting the plugin.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20include%20a%20ccpa%20%22do%20not%20sell%22%20opt-out%20form%3F\"><h3>Does the plugin include a CCPA \"Do Not Sell\" opt-out form?<\/h3><\/dt>\n<dd><p>Yes. Place <code>[faz_do_not_sell]<\/code> on any page (e.g. your Privacy Policy) to show a California Consumer Privacy Act opt-out form. When a visitor submits the form, the opt-out is logged in the local consent table with a hashed IP address, a long-lived cookie is set so the visitor sees a confirmation on subsequent visits, and the site admin receives a notification email. Optional attributes: <code>title<\/code> (heading text) and <code>button<\/code> (submit label). No external service is involved.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20include%20a%20gdpr%20data%20subject%20access%20request%20%28dsar%29%20form%3F\"><h3>Does the plugin include a GDPR Data Subject Access Request (DSAR) form?<\/h3><\/dt>\n<dd><p>Yes. Place <code>[faz_dsar_form]<\/code> on any page to show a GDPR-compliant request form covering six rights: Access (Art. 15), Erasure (Art. 17), Data Portability (Art. 20), Rectification (Art. 16), Restriction (Art. 18), and the Right to Object (Art. 21). On submission, the request is stored as a private post in the WordPress database (so it survives email failures), a notification is sent to the admin with a direct link to the record, and a confirmation is sent to the requester. The form includes a honeypot field and nonce verification to block spam bots. Optional attributes: <code>button<\/code> (submit label).<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<p>The full changelog (every release back to 1.0.0) lives at:\nhttps:\/\/github.com\/fabiodalez-dev\/FAZ-Cookie-Manager\/blob\/main\/CHANGELOG.md\nand on the GitHub Releases page:\nhttps:\/\/github.com\/fabiodalez-dev\/FAZ-Cookie-Manager\/releases<\/p>\n\n<h4>1.16.0<\/h4>\n\n<ul>\n<li>Feature: Cookie Policy Generator (Spec 002). New admin tab \"Cookie Policy\" + new <code>[faz_cookie_policy_complete]<\/code> shortcode renders a jurisdiction-aware, multi-language Cookie Policy from a template scaffold filled with the admin's company data. Covers GDPR (EU\/EEA\/UK), CCPA\/CPRA (California), LGPD (Brazil) in six languages (en, it, fr, de, es, pt-BR) \u2014 18 scaffolds total. Auto-populated cookie inventory pulled live from <code>wp_faz_cookies<\/code> so additions via the scanner reflect at the next render. Non-removable disclaimer at the bottom of every generated policy makes explicit that templates are starting points, not legal advice.<\/li>\n<li>Feature: REST API <code>faz\/v1\/cookie-policy\/*<\/code> (<code>\/settings<\/code> GET\/POST, <code>\/preview<\/code> POST) \u2014 <code>manage_options<\/code> + nonce. Preview renders without persisting so admins can iterate inside a sandboxed-iframe modal.<\/li>\n<li>Feature: Policy version hash emitted as <code>&lt;meta name=\"faz-policy-version\"&gt;<\/code> for future material-change re-prompt detection (display-only fields excluded from the hash so the version doesn't drift daily).<\/li>\n<li>Filter: <code>faz_cookie_policy_data<\/code> lets site builders inject custom placeholders before template substitution.<\/li>\n<li>Compatibility: long-standing <code>[faz_cookie_policy]<\/code> shortcode (with <code>site_name<\/code> \/ <code>contact<\/code> \/ <code>show_table<\/code> attributes) and the standalone <code>[faz_cookie_table]<\/code> shortcode both unchanged and supported. The new generator is opt-in via the <code>_complete<\/code> suffix.<\/li>\n<li>Fix: Frontend focus-trap listener accumulation (issue #124). Reopening the preference center repeatedly no longer stacks keydown handlers; <code>_fazAttachFocusLoop<\/code> now tracks attached handlers per <code>(element, direction)<\/code> slot in a module-scope WeakMap and replaces previous listeners before attaching new ones.<\/li>\n<li>Fix: Plugin Check compatibility \u2014 removed <code>wp_cache_flush_group()<\/code> \/ <code>wp_cache_supports()<\/code> fast-path on cache invalidation (both require WP 6.1+, plugin minimum stays at WP 5.0). Manual <code>wp_cache_delete<\/code> loop replaces it; cache epoch bump on the line above is what actually invalidates live reads.<\/li>\n<li>Compatibility: verified against WordPress 7.0 (May 2026 final). No code changes required: plugin does not use <code>the_author_meta<\/code>\/<code>get_the_author_link<\/code>, all three Gutenberg blocks already declare <code>api_version: 3<\/code>, plugin does not bundle CodeMirror nor use the Interactivity API. <code>Tested up to<\/code> bumped to 7.0.<\/li>\n<\/ul>\n\n<h4>1.15.0<\/h4>\n\n<ul>\n<li>Feature: Geo-routing v2 \u2014 jurisdictional rule-sets. 47 ruleset JSON files cover EU (gdpr-strict + 7 country-specific), UK (uk-gdpr-pecr), 19 US states with privacy law (CCPA + CPA + CTDPA + VCDPA + UCPA + ICDPA + TIPA + MCDPA + TDPSA + OCPA + FDBR + Delaware + NJDPL + NHPL + KCDPA + MODPA + MCDPA + RIDTPPA + ICDPA), 18 international (LGPD\/PIPL\/APPI\/PIPA\/POPIA\/PDPA-Singapore\/Thailand\/Vietnam\/India\/Malaysia\/AU\/NZ\/UAE\/KSA\/Israel\/Turkey\/Canada\/Quebec Law 25), plus most-protective fallback for unknown \/ VPN visitors.<\/li>\n<li>Feature: New <code>admin\/modules\/geo-routing\/<\/code> module with REST API (<code>\/faz\/v1\/geo\/*<\/code>), admin tab UI (status \/ coverage \/ overrides \/ preview \/ ipinfo \/ PIPL), field-by-field per-country override editor using dot-notation deltas.<\/li>\n<li>Feature: VPN\/proxy\/Tor detection via ipinfo.io (opt-in, gated by admin DPF\/SCC attestation). When VPN detected, the most-protective ruleset is forced regardless of apparent country. API key encrypted at rest.<\/li>\n<li>Feature: PIPL cross-border attestation UI (audit-trail only).<\/li>\n<li>Migration: <code>wp_faz_consent_logs<\/code> schema gains 7 NULL-default columns (<code>country_at_consent<\/code>, <code>region_at_consent<\/code>, <code>ruleset_id_at_consent<\/code>, <code>signal_gpc_received<\/code>, <code>signal_dnt_received<\/code>, <code>tc_string<\/code>, <code>gpp_string<\/code>). Online DDL on MySQL 5.7.6+ \/ MariaDB 10.3+.<\/li>\n<li>External Services: new <code>ipinfo.io<\/code> entry documents the opt-in VPN detection lookup.<\/li>\n<\/ul>\n\n<h4>1.14.3<\/h4>\n\n<ul>\n<li>Filter: new <code>faz_country_detection_consensus<\/code> filter, introduced with <strong>2 arguments<\/strong> (<code>$require_consensus<\/code>, <code>$votes<\/code>). When the filter resolves to <code>true<\/code> AND at least two detection sources disagree on the visitor country, <code>Geolocation::detect_country()<\/code> returns an empty string (fail-open \u2014 banner is shown to everyone). Off by default to preserve the CF-first priority order. Plugins that legitimately need the visitor IP for their own logic should hook <code>faz_visitor_country<\/code> instead, which exposes it for trusted overrides and test fixtures.<\/li>\n<li>Fix (F101\u2013F112, adamsreview review#2): transactional delete with InnoDB row-lock-promoted fallback default; multisite-aware uninstall sweep that honours per-site opt-in and the <code>FAZ_REMOVE_ALL_DATA<\/code> constant; banner_id pollution fixed by removing the post-save <code>$wpdb-&gt;insert_id<\/code> re-read; LSCache <code>Vary: CF-IPCountry<\/code> emitted only when the <code>faz_trust_cf_ipcountry_header<\/code> filter opts in; AMP geo-resolution no longer buckets GB into the <code>eu<\/code> regional set.<\/li>\n<li>Fix (F301\u2013F308 + CodeRabbit#1#2, adamsreview review#3): cache-poisoning race in <code>promote_fallback_default<\/code> closed by moving <code>delete_cache()<\/code> to callers (post-COMMIT); both fallback SELECTs now use <code>FOR UPDATE<\/code> and prefer non-default active peers; <code>faz_cookies<\/code> + <code>faz_cookie_categories<\/code> enforced to InnoDB on install AND migrated on upgrade so settings-import START TRANSACTION calls are no longer silent no-ops on legacy MyISAM hosts; uninstall network sweep now also fires under bare <code>FAZ_REMOVE_ALL_DATA<\/code> when <code>get_sites()<\/code> is empty; cache epoch generation switched to <code>sprintf('%.6F', microtime(true))<\/code> for true microsecond resolution; create_item slug-probe now attempts a focused UPDATE retry before falling back to the cache-invalidate tail.<\/li>\n<li>Fix (R4-S001\u2013S004, adamsreview review#4): <code>update_item()<\/code> now wraps its UPDATE + invariant section in a <code>START TRANSACTION<\/code> so the <code>FOR UPDATE<\/code> lock in <code>promote_fallback_default<\/code> actually serializes (was a no-op under autocommit, leaving the update path with the same race F302 closed for delete); create_item slug-probe runs the invariant tail unconditionally on slug mismatch so a successful focused-UPDATE retry no longer bypasses the at-most-one-default invariant; <code>clear_default_on_others()<\/code> no longer self-flushes the cache (caller's responsibility, mirrors F301); F303 ALTER ENGINE loop now records partial-migration failures in <code>faz_innodb_migration_pending<\/code> instead of silently bumping <code>db_version<\/code> past 1.14.3.<\/li>\n<li>Fix: missing-banner notice in admin when <code>?banner_id=\u2026<\/code> does not resolve (deleted row, stale bookmark, phantom redirect) \u2014 the editor body is hidden and a recovery CTA points at the install's default banner.<\/li>\n<li>Fix: prefcenter renders every visible category even when its cookie list is empty (regression introduced by the audit-list refactor that early-returned on empty cookies).<\/li>\n<li>Fix: empty-state preference-center category wrapper now matches the populated-state DOM shape (<code>&lt;div class=\"faz-table-wrapper\"&gt;<\/code>) so CSS targeting the table wrapper applies uniformly across empty and populated categories.<\/li>\n<\/ul>\n\n<h4>1.14.0<\/h4>\n\n<ul>\n<li>Feature: Multi-banner geo-routing (refs #103). New schema columns <code>target_countries<\/code> and <code>priority<\/code> on <code>wp_faz_banners<\/code> let admins serve different banners per visitor country \u2014 e.g. a Reject-mandatory GDPR banner to EU\/EEA\/UK and a CCPA-style banner with the close (X) button to US visitors, picked automatically by <code>Controller::get_active_banner_for_country()<\/code> from the Cloudflare CF-IPCountry header (opt-in) or MaxMind\/ip-api.com fallback.<\/li>\n<li>Feature: Per-banner override of the EDPB\/Garante close-button dark-pattern auto-hide (<code>settings.allowCloseButtonWithReject<\/code>). Default false preserves the compliance behaviour; opting in is documented as an EU\/EEA\/UK violation but unblocks non-EU jurisdictions where Accept + Reject + X is legal.<\/li>\n<li>Feature: Cache busting for country-dependent output via <code>DONOTCACHEPAGE<\/code>\/<code>DONOTCACHEOBJECT<\/code>\/<code>DONOTCACHEDB<\/code> constants + <code>Cache-Control: no-store<\/code> + <code>Vary: CF-IPCountry<\/code> (with the trust filter on) so CDNs and full-page caches do not serve the wrong banner to the wrong country.<\/li>\n<li>Feature: AMP <code>&lt;amp-consent&gt;<\/code> resolver is now country-aware via <code>Geolocation::get_visitor_country()<\/code> with the same geo guards as the classic JS flow.<\/li>\n<li>Feature: Scope-change consent invalidation. Consent cookies now carry <code>__scope.banner<\/code> and <code>__scope.law<\/code> so a visitor that crosses a jurisdiction (CCPA \u2192 GDPR) re-prompts instead of inheriting consent from a different legal regime.<\/li>\n<li>Fix: <code>banner_default<\/code> mutual-exclusion finally enforced server-side \u2014 saving a banner with the default flag clears it on every peer row (matches the admin help text). Without this, more than one banner could simultaneously hold the flag and the fallback picker was non-deterministic.<\/li>\n<li>Fix: <code>Controller::get_active_banner()<\/code> preserves its pre-1.14.0 contract for third-party callers. An install with a single status=1, country-targeted, non-default banner now still receives that banner back when the call passes no country.<\/li>\n<li>Fix: <code>has_country_dependent_banners()<\/code> and <code>Frontend::is_geo_blocked()<\/code> iterate the entire <code>ruleSet<\/code>, not just the first entry. A ruleSet like <code>[{code:ALL}, {code:US}]<\/code> is now consistently classified between the cache-vary headers and the runtime show\/block decision.<\/li>\n<li>Fix: <code>Frontend::send_geo_cache_headers()<\/code> gates on <code>faz_is_front_end_request()<\/code> so REST API \/ heartbeat \/ sitemap \/ robots requests no longer trigger the country-dependent DB chain on every poll.<\/li>\n<li>Fix: <code>is_country_dependent_output()<\/code> also marks IAB-TCF output as country-dependent (TCF <code>gdprApplies<\/code> is derived from visitor country at render time and must not be served from a shared page cache).<\/li>\n<li>Fix: <code>update_db_350<\/code> clears <code>faz_banners_table_version<\/code> before re-running <code>install_tables()<\/code>, so dbDelta actually adds the new <code>target_countries<\/code> + <code>priority<\/code> columns on upgrade.<\/li>\n<li>Fix: Geolocation rejects the Cloudflare 'XX' (anonymous proxy \/ unknown) sentinel both on the CF-IPCountry branch and after the <code>faz_visitor_country<\/code> filter \u2014 a third-party filter implementer reintroducing 'XX' no longer leaks it into geo-routing.<\/li>\n<li>Fix: <code>_fazConsentScopeChanged()<\/code> no longer invalidates valid pre-1.14.0 consent on the first page load after upgrade. Absent scope keys are treated as \"upgrade case, no scope info known\" and the existing consent stands.<\/li>\n<li>Compatibility: New <code>Banner::set_target_countries()<\/code> \/ <code>set_priority()<\/code> \/ <code>get_target_countries()<\/code> \/ <code>get_priority()<\/code> accessors with normalisation (upper-case, dedup, <code>^[A-Z]{2}$<\/code> validation, non-negative integer clamping). REST schema exposes both fields on <code>\/faz\/v1\/banners\/{id}<\/code> with <code>[A-Z]{2}<\/code> pattern validation.<\/li>\n<\/ul>\n\n<h4>1.13.18<\/h4>\n\n<ul>\n<li>Fix: <code>wp_localize_script<\/code> and <code>wp_set_script_translations<\/code> payloads (inline <code>&lt;script id=\"*-js-extra\"&gt;<\/code> and <code>&lt;script id=\"*-js-translations\"&gt;<\/code>) are no longer false-positively blocked when their body contains a substring that matches a provider pattern. These ID shapes carry only data\/i18n strings, never executable tracker code \u2014 the prior content-substring matcher would crash third-party plugins whose config keys happened to mention a provider (e.g. trx_addons emits the key <code>animate_to_mc4wp_form_submitted<\/code>, which matched MailChimp's <code>mc4wp<\/code> and broke the page with <code>ReferenceError: TRX_ADDONS_STORAGE is not defined<\/code>). <code>-js-before<\/code> and <code>-js-after<\/code> payloads stay on the regular blocking path.<\/li>\n<\/ul>\n\n<h4>1.13.17<\/h4>\n\n<ul>\n<li>Fix: <code>dataLayer is not defined<\/code> when third-party trackers emit a bare <code>dataLayer.push()<\/code> before GTM bootstraps. Pre-init via <code>wp_add_inline_script('before')<\/code>. Closes wp.org thread \"bug-report-datalayer-is-not-defined\".<\/li>\n<li>Fix: cookie category counts stay stale after scan + auto-categorise \u2014 every cookie create\/update\/delete now invalidates Category controller cache, banner template, IAB unmatched-vendors transient, and 10 page-cache adapters. Closes wp.org thread \"bug-report-cookie-categories-not-populated\".<\/li>\n<li>Fix: REST <code>bulk_update<\/code> was silently dropping <code>opt_in_script<\/code> \/ <code>opt_out_script<\/code>. Now iterates schema editable fields through the same <code>sanitize_script_field<\/code> capability gate as single-cookie updates.<\/li>\n<li>Fix: <code>_cookieScripts<\/code> no longer truncates at 500 cookies (paged query, JSON-key-anchored LIKE, 10000-row ceiling).<\/li>\n<li>Fix: <code>sanitize_meta_for_current_user<\/code> intercepts every write path into <code>wp_faz_cookies.meta<\/code>. Closes a stored-XSS surface for multisite Site Administrators without <code>unfiltered_html<\/code>.<\/li>\n<li>Fix: own <code>wp_localize_script<\/code> payloads (<code>{handle}-js-extra<\/code>) can no longer be classified as analytics by the output-buffer blocker. Closes #99 and #101 (reported independently by @Myblueroom).<\/li>\n<li>Fix: WP Rocket \"Load JavaScript deferred\" no longer wraps our <code>_fazConfig<\/code> bootstrap payload in a <code>DOMContentLoaded<\/code> callback (which would scope <code>var _fazConfig<\/code> to the callback and break <code>script.js<\/code> with <code>Cannot set properties of undefined<\/code>). New <code>rocket_defer_inline_exclusions<\/code> filter excludes <code>_fazConfig<\/code>, <code>_fazCfg<\/code>, <code>_fazGcm<\/code>, <code>_fazTcfConfig<\/code> from DeferJS wrapping. Closes #95 (thanks @dominikkucharski for the diagnosis and reference patch).<\/li>\n<li>Fix: <code>&lt;noscript&gt;<\/code>-wrapped iframes injected by page builders (Bricks\/Elementor\/Divi) no longer become 0x0 phantom placeholders.<\/li>\n<li>Fix: Escape key no longer dismisses the consent banner without a recorded decision (EDPB dark-pattern). Preference center close-on-Escape preserved.<\/li>\n<li>Feature: <code>Necessary<\/code> selectable in Custom Blocking Rules dropdown. Closes wp.org thread \"feature-request-add-necessary-category-to-script-blocker\".<\/li>\n<li>Feature: Banner-status toggle now also appears at the top of the Cookie Banner admin page (mirrors Settings -&gt; Banner Control).<\/li>\n<li>Compliance: CCPA 1798.135(c) - <code>[faz_do_not_sell]<\/code> renders a Withdraw opt-out button + <code>dns_rescinded<\/code> log entry.<\/li>\n<li>A11y: DSAR validation announces errors via <code>role=alert<\/code>, <code>aria-invalid<\/code> per field, focus on first invalid. <code>.faz-dsar-btn<\/code> \/ <code>.faz-dnsmpi-btn<\/code> carry a contrasting focus indicator (WCAG 1.4.11). DNSMPI error notice switches to <code>role=alert<\/code> on failure.<\/li>\n<li>Release: scripted 3-way ZIP builder (<code>scripts\/build-release.sh<\/code>) for wp.org \/ GitHub \/ ClassicPress Directory. Refs #20.<\/li>\n<\/ul>\n\n<h4>1.13.16<\/h4>\n\n<ul>\n<li>Fix: Plugins like Rank Math include tracker domain names inside inline JavaScript config. Tracker-domain patterns now match only against a script's <code>src<\/code> URL, not its inline content.<\/li>\n<li>Fix: <code>faz-skip<\/code> CSS class was matched as a plain substring (<code>faz-skipper<\/code> also exempted). Fixed to exact whitespace-delimited token match.<\/li>\n<li>Fix: Global variables in <code>uninstall.php<\/code> renamed to carry the <code>faz_<\/code> prefix.<\/li>\n<\/ul>\n\n<h4>1.13.15<\/h4>\n\n<ul>\n<li>Fix: TinyMCE editors restored for Notice \/ Preference Description in banner admin.<\/li>\n<li>Fix: REST DELETE category was a silent no-op when the row was not loaded first; REST PUT wiped unspecified fields when starting from a blank object.<\/li>\n<li>Fix: Dynamic video placeholder (<code>_fazAddPlaceholder<\/code>) did not call <code>_fazSetPlaceHolder()<\/code> for non-YouTube providers.<\/li>\n<li>Fix: <code>faz_get_cookie_domain()<\/code> returned malformed IP suffix for IP-addressed sites; now returns <code>''<\/code> (host-only cookie) per RFC 6265.<\/li>\n<\/ul>\n\n<h4>1.13.14<\/h4>\n\n<ul>\n<li>Fix: Fatal error on WordPress Playground - <code>maybe_create_table()<\/code> was called synchronously from a controller constructor during plugin loading. Deferred to <code>plugins_loaded<\/code> and guarded <code>wp_salt()<\/code> with <code>function_exists()<\/code>.<\/li>\n<\/ul>\n\n<h4>1.13.13<\/h4>\n\n<ul>\n<li>Fix: Fatal error on fresh install - <code>wp_salt()<\/code> called without <code>\\<\/code> prefix inside a namespaced class resolved as a non-existent namespaced function.<\/li>\n<li>Added: WordPress Playground Live Preview on the plugin directory page.<\/li>\n<\/ul>","raw_excerpt":"Free cookie consent with GDPR, CCPA, ePrivacy, Google Consent Mode v2, IAB TCF v2.3, and built-in Cookie Policy generator. No cloud required.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/303333","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=303333"}],"author":[{"embeddable":true,"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/fabiodalez"}],"wp:attachment":[{"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=303333"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=303333"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=303333"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=303333"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=303333"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/hu.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=303333"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}