{"id":318993,"date":"2026-06-04T02:05:50","date_gmt":"2026-06-04T02:05:50","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/warder-cookie-consent\/"},"modified":"2026-08-25T23:17:41","modified_gmt":"2026-08-25T23:17:41","slug":"warder-cookie-consent","status":"publish","type":"plugin","link":"https:\/\/frp.wordpress.org\/plugins\/warder-cookie-consent\/","author":32510,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.2.0","stable_tag":"2.2.0","tested":"7.1","requires":"5.0","requires_php":"8.0","requires_plugins":null,"header_name":"Warder Cookie Consent","header_author":"Jasper Frumau","header_description":"GDPR-compliant cookie consent banner with category management and floating preferences toggle.","assets_banners_color":"f7f9fa","last_updated":"2026-08-25 23:17:41","external_support_url":"","external_repository_url":"","donate_link":"https:\/\/imagewize.com","header_plugin_uri":"","header_author_uri":"https:\/\/imagewize.com","rating":0,"author_block_rating":0,"active_installs":0,"downloads":295,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"2.1.4":{"tag":"2.1.4","author":"Rhand","date":"2026-06-04 02:05:12"},"2.1.5":{"tag":"2.1.5","author":"Rhand","date":"2026-06-30 05:17:36"},"2.1.6":{"tag":"2.1.6","author":"Rhand","date":"2026-08-13 06:14:24"},"2.2.0":{"tag":"2.2.0","author":"Rhand","date":"2026-08-25 23:17:41"}},"upgrade_notice":{"2.2.0":"<p>Fixes a bug that stopped the consent banner from rendering at all on any site whose banner language was set to something other than English \u2014 if you use French, German, Spanish, Italian or Dutch, this release is the one that makes the banner work. Adds real translations for those five languages, and removes twelve debug console.log statements that ran on every page load.<\/p>","2.1.6":"<p>Confirms compatibility with WordPress 7.1. No functional changes.<\/p>","2.1.5":"<p>Fixes a visual regression when Astra Pro is active: the floating preferences toggle button was inheriting button padding from the theme, distorting its shape and hiding the icon.<\/p>","2.1.4":"<p>Documentation only: adds listing screenshots and captions. No functional changes.<\/p>","2.1.3":"<p>Build and documentation only: the compiled bundle now carries a source-link banner and the readme&#039;s Source Code section is more prominent. No functional changes.<\/p>","2.1.2":"<p>Documentation-only release: adds a <code>CONTRIBUTING.md<\/code> and slims the README to user-facing docs. No functional changes.<\/p>","2.1.1":"<p>Removes Slimstat from the default automatic script-blocking list (use the <code>warder_blocked_scripts<\/code> filter to add it back if needed). README updated to document WooCommerce cookie defaults and script-blocking examples.<\/p>","2.1.0":"<p>Adds automatic script blocking for SourceBuster.js, WooCommerce order attribution, and Slimstat before consent is given. Necessary cookie defaults now include the full WordPress and WooCommerce session cookie set. The <code>sbjs_*<\/code> pattern moves to analytics \u2014 if you manually placed it under necessary, you can remove the duplicate.<\/p>","2.0.2":"<p>Security and code-quality hardening: stronger input sanitization on settings save, nonce verification before delete actions, isset-guarded validation, and clearer source-code documentation. Recommended for all users.<\/p>","2.0.1":"<p>Fixes three data-corruption bugs: (1) Strictly Necessary category losing its locked state after every save; (2) all cookies being silently saved as regex patterns due to a hidden-input value bug; (3) non-necessary categories appearing locked and pre-selected in the frontend consent modal.<\/p>","2.0.0":"<p>Internal refactor only \u2014 plugin logic split into <code>inc\/<\/code> files for maintainability. Admin page title and Settings sidebar label updated to &quot;Warder Cookie Consent&quot; \/ &quot;Warder Consent&quot;. No settings migration required, no behaviour changes.<\/p>","1.5.2":"<p>Save All Settings now uses AJAX, so the page no longer jumps back to the top after saving. Add Cookie forms are no longer nested inside the main settings form, so the regex checkbox and the rest of the cookie inputs submit reliably.<\/p>","1.5.1":"<p>Fixes Add Cookie form positioning so it appears directly below the category button, and fixes a regression where submitting that form silently failed (nested form was being discarded by the browser).<\/p>","1.5.0":"<p>Adds Matomo cookie patterns to the default analytics category so new installs manage Matomo cookies out of the box.<\/p>","1.4.2":"<p>WordPress.org compliance fixes: register_setting() uses array format with sanitize_callback, privacy_policy_url uses esc_url_raw(), and src\/ ships in the build for human-readable source access.<\/p>","1.4.1":"<p>Replaces the 10up\/wpcs-action CI workflow with local PHPCS, adding strict i18n (text domain) and output escaping checks on pull requests.<\/p>","1.4.0":"<p>Restores the category\/cookie management buttons (add\/remove category and cookie) that were not working, with nonce-protected handlers. Adds minimum WordPress\/PHP version headers.<\/p>","1.3.2":"<p>WordPress.org compliance fixes: inline script moved to wp_add_inline_script, CSS output sanitized, source code documentation added to readme.<\/p>","1.3.0":"<p>Plugin renamed to Warder Cookie Consent; added languages\/ directory for translations; updated text domain, function prefixes, and Composer package name.<\/p>","1.2.1":"<p>Fixes composer.json license and support URLs; adds .gitattributes for Composer installs.<\/p>","1.2.0":"<p>Adds a floating preferences toggle button so visitors can revisit their cookie choices at any time.<\/p>","1.1.0":"<p>Adds &quot;Enable Plugin&quot; toggle; WordPress.org compliance (PHPCS, Plugin Check, readme.txt, workflows).<\/p>","1.0.0":"<p>Initial release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3560179,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3560179,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3560179,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3560179,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3560179,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["2.1.4","2.1.5","2.1.6","2.2.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3560179,"resolution":"1","location":"assets","locale":"","width":1600,"height":1068},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3560179,"resolution":"2","location":"assets","locale":"","width":1600,"height":953},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3560179,"resolution":"3","location":"assets","locale":"","width":1600,"height":1079},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3560179,"resolution":"4","location":"assets","locale":"","width":1600,"height":1081},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3560179,"resolution":"5","location":"assets","locale":"","width":1600,"height":845}},"screenshots":{"1":"Admin settings page","2":"Cookie consent banner frontend view","3":"Cookie category management interface","4":"Performance and analytics category, off by default for GDPR compliance","5":"Regex cookie matching and adding custom categories"}},"plugin_section":[],"plugin_tags":[216591,20272,16626,131785,396],"plugin_category":[54],"plugin_contributors":[269538,265722],"plugin_business_model":[],"class_list":["post-318993","plugin","type-plugin","status-publish","hentry","plugin_tags-consent-management","plugin_tags-cookie-banner","plugin_tags-cookie-consent","plugin_tags-gdpr","plugin_tags-privacy","plugin_category-security-and-spam-protection","plugin_contributors-gbogdan","plugin_contributors-rhand","plugin_committers-rhand"],"banners":{"banner":"https:\/\/ps.w.org\/warder-cookie-consent\/assets\/banner-772x250.png?rev=3560179","banner_2x":"https:\/\/ps.w.org\/warder-cookie-consent\/assets\/banner-1544x500.png?rev=3560179","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/warder-cookie-consent\/assets\/icon.svg?rev=3560179","icon":"https:\/\/ps.w.org\/warder-cookie-consent\/assets\/icon.svg?rev=3560179","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/warder-cookie-consent\/assets\/screenshot-1.png?rev=3560179","caption":"Admin settings page"},{"src":"https:\/\/ps.w.org\/warder-cookie-consent\/assets\/screenshot-2.png?rev=3560179","caption":"Cookie consent banner frontend view"},{"src":"https:\/\/ps.w.org\/warder-cookie-consent\/assets\/screenshot-3.png?rev=3560179","caption":"Cookie category management interface"},{"src":"https:\/\/ps.w.org\/warder-cookie-consent\/assets\/screenshot-4.png?rev=3560179","caption":"Performance and analytics category, off by default for GDPR compliance"},{"src":"https:\/\/ps.w.org\/warder-cookie-consent\/assets\/screenshot-5.png?rev=3560179","caption":"Regex cookie matching and adding custom categories"}],"raw_content":"<!--section=description-->\n<p>Warder Cookie Consent puts a consent banner on your WordPress site and holds your analytics scripts until a visitor opts in. Everything runs from your own server: there is no account to create, no external service to call, and no paid tier.<\/p>\n\n<p>It is built on the open-source <a href=\"https:\/\/github.com\/orestbida\/cookieconsent\">CookieConsent v3<\/a> library, and adds a WordPress settings screen, cookie defaults for WordPress and WooCommerce, and automatic blocking for scripts that would otherwise set cookies before consent.<\/p>\n\n<h4>Why you might pick this one<\/h4>\n\n<ul>\n<li><strong>Nothing leaves your server.<\/strong> The plugin makes no external HTTP requests \u2014 no CDN, no consent API, no phone-home. The script is served from your own domain.<\/li>\n<li><strong>No account, no upsell, no expiry.<\/strong> Every feature is in the free plugin. There is no pro tier, no per-domain limit and no trial.<\/li>\n<li><strong>Small.<\/strong> One deferred script of about 18KB gzipped, plus a little inline CSS for the floating button.<\/li>\n<li><strong>Opt in, not opt out.<\/strong> Non-necessary categories are unticked until the visitor chooses. That is what GDPR expects, and the plugin will not silently drift to something weaker.<\/li>\n<li><strong>Scripts are actually blocked.<\/strong> Known cookie-setting scripts are rewritten to <code>type=\"text\/plain\"<\/code> before the browser can run them, rather than being cleaned up afterwards.<\/li>\n<\/ul>\n\n<h4>What it does<\/h4>\n\n<ul>\n<li>A consent banner, and a preferences modal with a toggle per category<\/li>\n<li>Cookie categories you can add, rename and describe from the settings screen<\/li>\n<li>A cookie list per category, matched by exact name or by regular expression<\/li>\n<li>Automatic cookie clearing when a visitor withdraws consent for a category<\/li>\n<li>A floating cookie button, in any of four corners, so visitors can change their mind later<\/li>\n<li>Blocking for WooCommerce order attribution and SourceBuster out of the box<\/li>\n<li>Blocking for any script you mark with <code>data-category<\/code>, or register through a filter<\/li>\n<li>Editable banner title, description, button labels and privacy-policy link<\/li>\n<li>Interface strings in English, Dutch, German, French, Spanish and Italian<\/li>\n<li>Compatibility with page caching \u2014 the settings version is part of the script URL<\/li>\n<\/ul>\n\n<h4>What ships pre-configured<\/h4>\n\n<p><strong>Strictly Necessary<\/strong>, always on: <code>cc_cookie<\/code>, <code>wordpress_logged_in_*<\/code>, <code>wordpress_sec_*<\/code>, <code>wordpress_test_cookie<\/code>, <code>wp-settings-*<\/code>, <code>wp_woocommerce_session_*<\/code>, <code>woocommerce_cart_hash<\/code>, <code>woocommerce_items_in_cart<\/code>, <code>woocommerce_recently_viewed<\/code>, <code>PHPSESSID<\/code>.<\/p>\n\n<p><strong>Performance and Analytics<\/strong>, off until accepted: Google Analytics (<code>_ga*<\/code>, <code>_gid<\/code>, <code>_gat<\/code>), Matomo (<code>_pk_*<\/code>, <code>mtm_*<\/code>) and SourceBuster (<code>sbjs_*<\/code>).<\/p>\n\n<h4>What it does not do<\/h4>\n\n<p>Stated plainly, so you can rule it out in thirty seconds rather than after installing:<\/p>\n\n<ul>\n<li><strong>No consent log.<\/strong> The visitor's choice is stored in their own browser, in <code>cc_cookie<\/code>. Nothing is written to your database, so there is no proof-of-consent export.<\/li>\n<li><strong>No Google Consent Mode v2 signals.<\/strong><\/li>\n<li><strong>No automatic cookie scanner.<\/strong> You list the cookies you want managed.<\/li>\n<li><strong>No CCPA \"Do Not Sell\" flow.<\/strong><\/li>\n<li><strong>No automatic translation.<\/strong> Six languages ship for the interface strings. The text you write yourself \u2014 title, description, button labels, category names \u2014 is stored once, in whatever language you type it.<\/li>\n<\/ul>\n\n<h4>A note on compliance<\/h4>\n\n<p>This plugin gives you the mechanism: blocking before consent, granular categories, a choice the visitor controls, and a way to revisit it. Whether your site is compliant depends on how you configure it and what your site actually loads. It is a tool, not legal advice.<\/p>\n\n<h4>For developers<\/h4>\n\n<p>Hold any script until a category is accepted:<\/p>\n\n<pre><code>&lt;script type=\"text\/plain\" data-category=\"analytics\" src=\"...\"&gt;&lt;\/script&gt;\n<\/code><\/pre>\n\n<p>Or block a script that another plugin registered, by handle:<\/p>\n\n<pre><code>add_filter( 'warder_blocked_scripts', function ( $scripts ) {\n    $scripts['my-analytics-handle'] = 'analytics';\n    return $scripts;\n} );\n<\/code><\/pre>\n\n<h3>Source Code<\/h3>\n\n<p>This plugin ships no obfuscated or minified-only code. The only compiled asset is <code>dist\/cookieconsent.bundle.js<\/code>, bundled from human-readable source with webpack. Its first lines are a comment banner pointing back to the source. The uncompressed source (<code>src\/index.js<\/code> and <code>webpack.config.js<\/code>) is included in the plugin download, and the full development repository is public:<\/p>\n\n<p>https:\/\/github.com\/imagewize\/warder-cookie-consent<\/p>\n\n<pre><code>src\/index.js imports the [vanilla-cookieconsent v3](https:\/\/github.com\/orestbida\/cookieconsent) library. To build from source: run `npm install`, then `npx webpack` (or `npx webpack --watch` during development).\n<\/code><\/pre>\n\n<!--section=installation-->\n<ol>\n<li>In WordPress go to <strong>Plugins &gt; Add New<\/strong>, search for \"Warder Cookie Consent\", then Install and Activate. Or upload the plugin folder to <code>\/wp-content\/plugins\/<\/code> and activate it there.<\/li>\n<li>Open <strong>Settings &gt; Warder Consent<\/strong>.<\/li>\n<li>Set your <strong>Privacy Policy URL<\/strong>. It ships as a placeholder, so this is the one setting you should not skip.<\/li>\n<li>Review the <strong>Performance and Analytics<\/strong> category and add any cookies your site sets that are not already listed.<\/li>\n<li>Mark any third-party scripts you embed yourself with <code>data-category=\"analytics\"<\/code>.<\/li>\n<\/ol>\n\n<p>The banner appears straight away for visitors who have not yet made a choice. Nothing else is required.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20make%20my%20site%20gdpr%20compliant%3F\"><h3>Does this make my site GDPR compliant?<\/h3><\/dt>\n<dd><p>No plugin can promise that. This one gives you the parts a consent flow needs \u2014 scripts held until consent, categories the visitor chooses individually, and a way to change that choice later. Compliance still depends on configuring it for what your site actually loads, and on the rest of your privacy practices.<\/p><\/dd>\n<dt id=\"does%20it%20block%20google%20analytics%20before%20consent%3F\"><h3>Does it block Google Analytics before consent?<\/h3><\/dt>\n<dd><p>It clears Google Analytics cookies, and it will hold the GA script if that script carries <code>data-category=\"analytics\"<\/code> or if you register its handle through the <code>warder_blocked_scripts<\/code> filter. If you load GA through another plugin, add that plugin's script handle to the filter \u2014 see the developer section above. Only WooCommerce order attribution and SourceBuster are blocked automatically.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20google%20tag%20manager%3F\"><h3>Does it work with Google Tag Manager?<\/h3><\/dt>\n<dd><p>You can hold the GTM container itself the same way, with <code>data-category<\/code> on the snippet. The plugin does not emit Google Consent Mode v2 signals, so if your setup depends on those, this is not the right plugin for you.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20send%20any%20data%20anywhere%3F\"><h3>Does the plugin send any data anywhere?<\/h3><\/dt>\n<dd><p>No. It makes no external HTTP requests at all. The consent script is served from your own domain, and the visitor's choice is stored in their browser in the <code>cc_cookie<\/code> cookie. Nothing is transmitted to us or to anyone else.<\/p><\/dd>\n<dt id=\"does%20it%20keep%20a%20record%20of%20who%20consented%3F\"><h3>Does it keep a record of who consented?<\/h3><\/dt>\n<dd><p>Not on the server. The record lives in the visitor's own browser. If you need an auditable, exportable consent log stored in your database, use a plugin built for that.<\/p><\/dd>\n<dt id=\"will%20it%20slow%20my%20site%20down%3F\"><h3>Will it slow my site down?<\/h3><\/dt>\n<dd><p>The frontend is a single script, loaded deferred, about 18KB gzipped, plus a small inline stylesheet for the floating button. There are no external requests and no jQuery dependency.<\/p><\/dd>\n<dt id=\"can%20i%20use%20it%20in%20a%20language%20other%20than%20english%3F\"><h3>Can I use it in a language other than English?<\/h3><\/dt>\n<dd><p>Yes. Pick English, Dutch, German, French, Spanish or Italian under <strong>Settings &gt; Warder Consent<\/strong> and the interface strings \u2014 \"Manage preferences\", \"Accept current selection\", the close label and so on \u2014 follow. The text you write yourself, such as the banner title, description, button labels and category names, is stored as you type it, so you write that part in your own language.<\/p><\/dd>\n<dt id=\"is%20it%20compatible%20with%20caching%20plugins%3F\"><h3>Is it compatible with caching plugins?<\/h3><\/dt>\n<dd><p>Yes. Settings are versioned with a timestamp that becomes part of the script URL, so a cached page always pulls the matching configuration.<\/p><\/dd>\n<dt id=\"how%20do%20i%20add%20custom%20cookie%20categories%3F\"><h3>How do I add custom cookie categories?<\/h3><\/dt>\n<dd><p><strong>Settings &gt; Warder Consent<\/strong>, then \"Add New Category\" at the bottom of the page. Each category gets its own title, description and cookie list, and appears as a toggle in the preferences modal.<\/p><\/dd>\n<dt id=\"which%20cookies%20are%20managed%20by%20default%3F\"><h3>Which cookies are managed by default?<\/h3><\/dt>\n<dd><p>WordPress and WooCommerce session cookies are marked strictly necessary, and Google Analytics, Matomo and SourceBuster cookies sit in an analytics category that is off until accepted. The full list is in the description above, and all of it is editable.<\/p><\/dd>\n<dt id=\"can%20visitors%20change%20their%20mind%20after%20accepting%3F\"><h3>Can visitors change their mind after accepting?<\/h3><\/dt>\n<dd><p>Yes. The floating cookie button reopens the preferences modal at any time, and you can put it in any of the four corners or turn it off.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>2.2.0<\/h4>\n\n<p><em>2026-08-25<\/em><\/p>\n\n<ul>\n<li>Fixed: choosing any language other than English stopped the consent banner from appearing at all. <code>src\/index.js<\/code> set the banner language from the settings but only ever defined English strings, and the library throws from <code>run()<\/code> when the selected language has no translation. Because <code>run()<\/code> is async, that rejection escaped the surrounding try\/catch and surfaced as an unhandled promise rejection with no banner on the page. Reproduced in a browser before and after the fix.<\/li>\n<li>Added: real interface translations for Dutch, German, French, Spanish and Italian, so all six options in the language dropdown now work. The strings you write yourself \u2014 title, description, button labels, category names \u2014 continue to override the built-in text for whichever language is selected.<\/li>\n<li>Added: an English fallback. An unknown or stale language code now renders the banner in English instead of stopping it, so a bad stored value can never leave a site with no consent banner.<\/li>\n<li>Fixed: removed twelve <code>console.log<\/code> calls that shipped in the production bundle and ran for every visitor, including a dump of the plugin settings and the final consent configuration. Genuine error reporting is kept and now carries a \"Warder Cookie Consent:\" prefix.<\/li>\n<li>Fixed: <code>CookieConsent.run()<\/code> is awaited properly, so an initialisation failure is reported to the console instead of becoming a silent unhandled rejection.<\/li>\n<li>Listing: rewrote the plugin description around what the plugin does and who it suits, rather than which library it wraps. Adds the pre-configured WordPress\/WooCommerce and analytics cookie defaults, a developer section covering <code>data-category<\/code> and the <code>warder_blocked_scripts<\/code> filter, and an explicit \"What it does not do\" list \u2014 no consent log, no Consent Mode v2, no cookie scanner, no CCPA flow \u2014 so the plugin can be ruled in or out before installing.<\/li>\n<li>Listing: plugin title is now \"Warder Cookie Consent - GDPR Cookie Banner\", the short description leads with what it does instead of naming the bundled library, and the tags moved from single words to the phrases people search (<code>cookie banner<\/code>, <code>cookie consent<\/code>, <code>consent management<\/code>).<\/li>\n<li>Listing: expanded the FAQ from four questions to eleven, covering the ones that actually decide the install \u2014 whether it blocks Google Analytics and GTM, whether it sends data anywhere, whether it keeps a consent record, page weight, and language.<\/li>\n<li>Docs: <code>== Source Code ==<\/code> moved below the changelog. The directory concatenates unrecognised sections into the Details tab in file order, so keeping it directly under the feature list put build instructions ahead of the description for every visitor.<\/li>\n<\/ul>\n\n<h4>2.1.6<\/h4>\n\n<p><em>2026-08-13<\/em><\/p>\n\n<ul>\n<li>Confirmed compatibility with WordPress 7.1 and updated <code>Tested up to<\/code>.<\/li>\n<li>Build: updated webpack, css-loader, and PHP_CodeSniffer\/WPCS tooling to their latest in-range versions; no functional changes.<\/li>\n<\/ul>\n\n<h4>2.1.5<\/h4>\n\n<p><em>2026-06-26<\/em><\/p>\n\n<ul>\n<li>Fixed: added <code>padding: 0<\/code> to <code>.warder-preferences-toggle<\/code> to prevent Astra Pro theme button styles (padding: 15px 30px) from distorting the floating toggle button and hiding its icon.<\/li>\n<li>Props: gbogdan for reporting and fixing the Astra Pro CSS incompatibility.<\/li>\n<\/ul>\n\n<h4>2.1.4<\/h4>\n\n<p><em>2026-06-04<\/em><\/p>\n\n<ul>\n<li>Listing: added screenshots and captions for the admin settings page, frontend consent modal, cookie category management, the opt-in analytics defaults, and regex cookie matching. No functional changes.<\/li>\n<\/ul>\n\n<h4>2.1.3<\/h4>\n\n<p><em>2026-06-03<\/em><\/p>\n\n<ul>\n<li>Build: the compiled <code>dist\/cookieconsent.bundle.js<\/code> now begins with a comment banner (via webpack <code>BannerPlugin<\/code>) pointing to the uncompressed source and the public repository, so the source location is visible from within the compiled file itself. Comment extraction to a separate <code>.LICENSE.txt<\/code> is disabled so the banner stays inline.<\/li>\n<li>Docs: moved the <code>== Source Code ==<\/code> section higher in readme.txt (directly after the feature list) so the human-readable source reference is easy to find. No functional changes.<\/li>\n<\/ul>\n\n<h4>2.1.2<\/h4>\n\n<p><em>2026-05-30<\/em><\/p>\n\n<ul>\n<li>Docs: added <code>CONTRIBUTING.md<\/code> at the repository root with build-from-source, development setup, dependencies, and contribution guidelines.<\/li>\n<li>Docs: moved the developer-facing build\/development\/dependencies sections out of the README into <code>CONTRIBUTING.md<\/code>, leaving a short \"Contributing\" pointer; renumbered the Composer install method.<\/li>\n<\/ul>\n\n<h4>2.1.1<\/h4>\n\n<p><em>2026-05-30<\/em><\/p>\n\n<ul>\n<li>Changed: removed <code>wp_slimstat<\/code> from the default <code>warder_blocked_scripts<\/code> list \u2014 Slimstat is not bundled with WordPress or WooCommerce, so blocking it by default was dead code for most sites. Add it back via the <code>warder_blocked_scripts<\/code> filter if needed.<\/li>\n<li>Docs: updated README to document the necessary-category cookie defaults (WordPress core + WooCommerce) and analytics defaults (including <code>sbjs_*<\/code>); added common <code>warder_blocked_scripts<\/code> examples for Slimstat and MonsterInsights.<\/li>\n<\/ul>\n\n<h4>2.1.0<\/h4>\n\n<p><em>2026-05-30<\/em><\/p>\n\n<ul>\n<li>Added: <code>warder_block_script_until_consent()<\/code> \u2014 rewrites known analytics\/marketing script tags to <code>type=\"text\/plain\" data-category=\"&lt;category&gt;\"<\/code> so they are held by vanilla-cookieconsent until the user accepts the matching category. Covers <code>sourcebuster-js<\/code> (SourceBuster.js), <code>wc-order-attribution<\/code> (WooCommerce order attribution), and <code>wp_slimstat<\/code> (Slimstat Analytics) out of the box. Extend or replace the list with the <code>warder_blocked_scripts<\/code> filter.<\/li>\n<li>Added: expanded <code>necessary<\/code> category cookie defaults to cover the full WordPress and WooCommerce session surface: <code>cc_cookie<\/code>, <code>wordpress_logged_in_*<\/code>, <code>wordpress_sec_*<\/code>, <code>wordpress_test_cookie<\/code>, <code>wp-settings-*<\/code>, <code>wp_woocommerce_session_*<\/code>, <code>woocommerce_cart_hash<\/code>, <code>woocommerce_items_in_cart<\/code>, <code>woocommerce_recently_viewed<\/code>, <code>PHPSESSID<\/code>. Existing installs are unaffected (defaults only apply to new installs or when the necessary category has no cookies configured).<\/li>\n<li>Changed: <code>sbjs_*<\/code> (SourceBuster.js attribution cookies) moved from the <code>necessary<\/code> category to <code>analytics<\/code> in the default configuration \u2014 they are set by an optional tracking script, not by WordPress core.<\/li>\n<\/ul>\n\n<h4>2.0.2<\/h4>\n\n<p><em>2026-05-30<\/em><\/p>\n\n<ul>\n<li>Security: settings save handler now sanitizes the full <code>$_POST['warder_options']<\/code> array through a dedicated recursive sanitizer (<code>warder_sanitize_options_input<\/code>) before validation, instead of relying on a <code>phpcs:ignore<\/code> suppression. Description fields keep safe post HTML (<code>wp_kses_post<\/code>); all other fields are treated as plain text.<\/li>\n<li>Security: category and cookie delete handlers now verify the nonce before any request data is used to change state, and call <code>wp_die()<\/code> on a failed check.<\/li>\n<li>Hardening: <code>warder_validate_options()<\/code> now guards every field with <code>isset()<\/code> (no PHP warnings on partial submissions under <code>WP_DEBUG<\/code>) and constrains <code>current_lang<\/code> to the supported language whitelist.<\/li>\n<li>Fixed: removed the inappropriate <code>wp_strip_all_tags()<\/code> wrapper around the static preferences-toggle CSS (it is an HTML helper, not a CSS escaper).<\/li>\n<li>Refactored: the supported languages and preferences-toggle positions now come from two shared helpers (<code>warder_allowed_languages()<\/code>, <code>warder_allowed_toggle_positions()<\/code>) used by both validation and the admin dropdowns, instead of being hand-copied across three files. No change to available options or behaviour.<\/li>\n<li>Refactored: removed an unused internal function (<code>warder_render_category_title_field()<\/code>) that was dead code.<\/li>\n<li>Docs: clarified the \"Source Code\" section to state that uncompressed source ships in the plugin (<code>src\/index.js<\/code>, <code>webpack.config.js<\/code>) and in the public GitHub repository.<\/li>\n<li>Tooling: <code>phpcs.xml<\/code> now lints the <code>inc\/<\/code> directory (previously only the main file was scanned).<\/li>\n<\/ul>\n\n<h4>2.0.1<\/h4>\n\n<p><em>2026-05-28<\/em><\/p>\n\n<ul>\n<li>Fixed: necessary category enabled\/readonly values were silently overwritten as false on every admin settings save because the disabled checkboxes were not submitted by form.serialize(). The necessary category is now always forced to enabled=true and readonly=true in validation, regardless of form input.<\/li>\n<li>Fixed: is_regex was always saved as true for every cookie. The hidden input used value=\"\" for false, so PHP's isset() returned true for the empty string, silently marking non-regex cookies (_gid, _gat) as regex patterns and corrupting the autoClear cookie list. The hidden input now outputs '0' for false; validation uses !empty() with an explicit '0' check so only the string '1' is treated as true.<\/li>\n<li>Fixed: non-necessary categories (e.g. Analytics) appeared as locked and pre-selected in the frontend preferences modal. Validation now always saves enabled=false, readonly=false for non-necessary categories; src\/index.js derives enabled\/readOnly from the category id rather than DB values; admin UI replaces confusing enabled\/readonly checkboxes with descriptive lock\/unlock icons.<\/li>\n<li>Fixed: AJAX save no longer returns a misleading \"No changes detected\" message when a setting is toggled back to its current DB value \u2014 the response is always \"Settings saved successfully.\"<\/li>\n<\/ul>\n\n<h4>2.0.0<\/h4>\n\n<p><em>2026-05-28<\/em><\/p>\n\n<ul>\n<li>Changed: plugin logic split from one monolithic file into five focused files under <code>inc\/<\/code> \u2014 <code>defaults.php<\/code>, <code>settings.php<\/code>, <code>ajax.php<\/code>, <code>admin.php<\/code>, <code>frontend.php<\/code>. Main plugin file is now 26 lines (header, constants, requires). No behaviour changes.<\/li>\n<li>Fixed: admin page title renamed to \"Warder Cookie Consent\"; Settings sidebar label renamed to \"Warder Consent\"<\/li>\n<\/ul>\n\n<h4>1.5.2<\/h4>\n\n<p><em>2026-05-28<\/em><\/p>\n\n<ul>\n<li>Added: AJAX save for the Cookie Consent settings page \u2014 Save All Settings no longer reloads the page or scrolls back to the top; the success notice appears next to the form and the page scrolls it into view<\/li>\n<li>Fixed: setup welcome notice now self-suppresses once the plugin has been configured, instead of appearing on every admin page<\/li>\n<li>Fixed: Add Cookie forms are now rendered after the main settings form rather than nested inside it, so submitting an Add Cookie form no longer accidentally triggers the main settings save and no longer loses the regex checkbox state<\/li>\n<li>Fixed: success notice after adding\/deleting categories or cookies (<code>?warder_notice=saved<\/code> after the redirect)<\/li>\n<li>Changed: removed leftover <code>:not([form])<\/code> selectors and JS DOM repositioning that were workarounds for the old nested-form layout<\/li>\n<\/ul>\n\n<h4>1.5.1<\/h4>\n\n<p><em>2026-05-28<\/em><\/p>\n\n<ul>\n<li>Fixed: Add Cookie form now appears directly below the \"Add Cookie to this Category\" button instead of at the bottom of the page (below Save All Settings and Add New Category)<\/li>\n<li>Fixed: Add Cookie submissions were silently dropped because the relocated form ended up nested inside the main settings form, which browsers reject. The actual form element now lives outside the main settings form and the visible inputs reference it via the HTML5 <code>form<\/code> attribute<\/li>\n<\/ul>\n\n<h4>1.5.0<\/h4>\n\n<p><em>2026-05-28<\/em><\/p>\n\n<ul>\n<li>Added: Matomo cookie patterns (<code>\/^_pk_\/<\/code> and <code>\/^mtm_\/<\/code>) to the default analytics category, so new installs manage Matomo cookies out of the box alongside Google Analytics. Existing sites can add the same patterns under Settings &gt; Cookie Consent<\/li>\n<\/ul>\n\n<h4>1.4.2<\/h4>\n\n<p><em>2026-05-28<\/em><\/p>\n\n<ul>\n<li>Fixed: <code>register_setting()<\/code> updated to array format with explicit <code>sanitize_callback<\/code> key as required by WordPress.org guidelines<\/li>\n<li>Fixed: <code>privacy_policy_url<\/code> now sanitized with <code>esc_url_raw()<\/code> instead of <code>sanitize_text_field()<\/code> for proper URL sanitization<\/li>\n<li>Changed: <code>.distignore<\/code> updated to include <code>src\/<\/code>, <code>webpack.config.js<\/code>, and <code>package.json<\/code> in the WordPress.org build so the human-readable source is available to reviewers (guideline \u00a74)<\/li>\n<\/ul>\n\n<h4>1.4.1<\/h4>\n\n<p><em>2026-05-27<\/em><\/p>\n\n<ul>\n<li>Changed: Replaced the 10up\/wpcs-action workflow with a local PHPCS workflow using phpcs.xml, adding enforcement of WordPress.WP.I18n (text domain: warder-cookie-consent) and WordPress.Security.EscapeOutput on pull requests<\/li>\n<\/ul>\n\n<h4>1.4.0<\/h4>\n\n<p><em>2026-05-27<\/em><\/p>\n\n<ul>\n<li>Fixed: the \"Add New Category\", \"Add Cookie\", and \"Remove\" cookie controls rendered but had no backend handler after an earlier refactor \u2014 they now work again<\/li>\n<li>Added: restored category\/cookie management handlers (add category, add cookie, delete cookie, delete category) consolidated in <code>warder_handle_admin_actions()<\/code>, each with nonce verification and input sanitization<\/li>\n<li>Added: \"Delete Category\" link in each non-necessary category header<\/li>\n<li>Added: <code>Requires at least<\/code> and <code>Requires PHP<\/code> headers to the main plugin file<\/li>\n<li>Changed: wrapped all hardcoded admin settings-page strings in translation\/escaping functions (<code>esc_html_e<\/code>, <code>esc_attr_e<\/code>, <code>esc_html__<\/code>, <code>esc_js<\/code>) so admin UI text is translatable and escaped on output<\/li>\n<\/ul>\n\n<h4>1.3.2<\/h4>\n\n<p><em>2026-05-27<\/em><\/p>\n\n<ul>\n<li>Fixed: moved inline admin <code>&lt;script&gt;<\/code> to <code>wp_add_inline_script()<\/code> via <code>admin_enqueue_scripts<\/code> hook<\/li>\n<li>Fixed: sanitize CSS output with <code>wp_strip_all_tags()<\/code> before passing to <code>wp_add_inline_style()<\/code><\/li>\n<li>Added: <code>== Source Code &amp; Build Process ==<\/code> section to readme.txt documenting webpack build and GitHub source link<\/li>\n<li>Changed: Contributors field updated to WordPress.org username <code>rhand<\/code><\/li>\n<\/ul>\n\n<h4>1.3.1<\/h4>\n\n<p><em>2026-05-26<\/em><\/p>\n\n<ul>\n<li>Fixed .gitattributes so composer.json ships in Composer\/Packagist dist archives (still excluded from WordPress.org builds via .distignore)<\/li>\n<\/ul>\n\n<h4>1.3.0<\/h4>\n\n<p><em>2026-05-26<\/em><\/p>\n\n<ul>\n<li>Added languages\/ directory for translation files (WordPress 4.6+ auto-loads translations via the Text Domain header)<\/li>\n<li>Fixed Plugin Check workflow directory name to match text domain header (resolves textdomain_mismatch warnings)<\/li>\n<li>Renamed plugin to Warder Cookie Consent (Wheel of Time inspired, consistent with Elayne theme and Waygate pattern builder)<\/li>\n<li>Renamed main plugin file to warder-cookie-consent.php<\/li>\n<li>Updated text domain to warder-cookie-consent<\/li>\n<li>Updated all function prefixes from scc_ to warder_<\/li>\n<li>Updated Composer package name to imagewize\/warder-cookie-consent<\/li>\n<li>GitHub repository renamed to imagewize\/warder-cookie-consent<\/li>\n<\/ul>\n\n<h4>1.2.1<\/h4>\n\n<p><em>2026-05-26<\/em><\/p>\n\n<ul>\n<li>Fixed composer.json license field (MIT \u2192 GPL-2.0-or-later) to match plugin header<\/li>\n<li>Fixed composer.json support URLs pointing to wrong repository<\/li>\n<li>Added .gitattributes to exclude dev files from Composer installs and git archives<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<p><em>2026-05-26<\/em><\/p>\n\n<ul>\n<li>Added floating preferences toggle button \u2014 a cookie icon button rendered in the page footer that opens the preferences modal, letting users change their consent choices at any time<\/li>\n<li>Added \"Preferences Toggle Button\" setting in General Settings with a position dropdown (bottom-right, bottom-left, top-right, top-left)<\/li>\n<li>Toggle button can be enabled\/disabled independently of the main banner<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<p><em>2026-05-26<\/em><\/p>\n\n<ul>\n<li>Added \"Enable Plugin\" toggle to General Settings (disable the banner without deactivating the plugin)<\/li>\n<li>Added plugin header fields required by WordPress.org (Author URI, Text Domain, License, License URI)<\/li>\n<li>Added direct file access protection (ABSPATH check)<\/li>\n<li>Removed debug error_log and console.log statements<\/li>\n<li>Fixed output escaping throughout admin UI (esc_url, esc_attr, esc_html)<\/li>\n<li>Fixed Plugin Check workflow directory name (resolved textdomain_mismatch and trademarked_term warnings)<\/li>\n<li>Added strict comparison to in_array calls<\/li>\n<li>Full WordPress Coding Standards compliance (PHPCS 0 errors)<\/li>\n<li>Added PHPDoc blocks to all functions<\/li>\n<li>Updated license from MIT to GPLv2 or later<\/li>\n<li>Added readme.txt, phpcs.xml, .distignore, and GitHub Actions workflows (WPCS, Plugin Check, release zip)<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<p><em>2025-05-26<\/em><\/p>\n\n<ul>\n<li>Initial release<\/li>\n<\/ul>","raw_excerpt":"Self-hosted GDPR cookie consent banner. Blocks analytics scripts until visitors opt in. No account, no external service, no paid tier.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/318993","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=318993"}],"author":[{"embeddable":true,"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/rhand"}],"wp:attachment":[{"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=318993"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=318993"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=318993"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=318993"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=318993"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/frp.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=318993"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}