Skip to content

Commit 6a48636

Browse files
Tsvetan StoychevTsvetan Stoychev
authored andcommitted
Ship consent integration adapters
1 parent 08bb841 commit 6a48636

26 files changed

Lines changed: 806 additions & 151 deletions

‎.agents/skills/integrate-basicrum-consent/SKILL.md‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,17 @@ authoritative decision.
3434
- Keep opt-in loading idempotent. Keep opt-out idempotent, disable collection,
3535
and remove the documented Boomerang cookies for the current host and parent
3636
domains.
37-
- Put webmaster adapters in `examples/integrations/`, outside the installable
38-
plugin. Do not modify a production loader merely to accommodate a test.
37+
- Put canonical webmaster adapters in
38+
`plugins/basicrum/assets/js/integrations/`. Display the exact packaged files
39+
as escaped, copyable text in the settings page; never enqueue or execute them
40+
automatically. Do not modify a production loader merely to accommodate a
41+
test.
3942
- Document script ordering and cache/minification exclusions. Do not claim that
4043
an adapter replaces legal review or consent-tool configuration.
4144

4245
## Test the real boundary
4346

44-
- Execute the exact example adapter against a thin test double that models only
47+
- Execute the exact packaged adapter against a thin test double that models only
4548
the provider's documented public API, defaults, events, and payloads.
4649
- Cover adapter-before-provider and provider-before-adapter initialization,
4750
saved grant, saved denial, new grant, withdrawal, duplicate events, and
@@ -59,4 +62,5 @@ git diff --check
5962
```
6063

6164
Update `examples/integrations/README.md`, the main README, plugin readme, privacy
62-
guidance, and translations when the administrator-visible contract changes.
65+
guidance, release required-file checks, and translations when the
66+
administrator-visible contract changes.

‎AGENTS.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ in the matching skill.
5757
hard-coded script handles.
5858
- Boomerang lives in `assets/js/boomr/`; standard and consent loaders live in
5959
`assets/js/loaders/`.
60+
- Consent-tool adapters live in `assets/js/integrations/`. The settings page
61+
displays those exact files as escaped copyable text; never enqueue or execute
62+
them automatically. Browser tests must execute the same packaged files.
6063
- WooCommerce browser E2E tests are root-level test tooling. Keep the pinned
6164
WooCommerce version and checksum synchronized in `tools/setup-woocommerce-e2e.sh`.
6265
The setup disables WooCommerce Coming soon mode so anonymous storefront tests

‎README.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,11 @@ function reportBasicrumConsent(allowed) {
9797
```
9898

9999
Tested adapters for Borlabs Cookie 3.0.6+, WP Consent API with Complianz or
100-
CookieYes, and connected CookieYes without WP Consent API are available under
101-
[`examples/integrations/`](examples/integrations/).
100+
CookieYes, and connected CookieYes without WP Consent API are shipped under
101+
[`plugins/basicrum/assets/js/integrations/`](plugins/basicrum/assets/js/integrations/).
102+
The Basicrum settings page displays the exact packaged files in provider tabs so
103+
webmasters can copy them into their consent tool. A Generic tab provides
104+
separate allow and deny snippets for other consent tools.
102105

103106
Basicrum does not display a consent popup, select a legal basis, or make a site
104107
compliant by itself. The WordPress Privacy Policy Guide includes editable

‎docs/audits/checklist.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ operator-journey defect, P3 = minor/polish.
4848
plugins_loaded). CDN/edge, host Varnish, static exports, and visitors'
4949
browser cache of HTML cannot be purged by the plugin - document as
5050
operator responsibility (see C). (privacy 3 / BR-WP-15)
51-
- [ ] P2 Ship the consent adapters inside the plugin (distribution model C):
51+
- [x] P2 Ship the consent adapters inside the plugin (distribution model C):
5252
move `examples/integrations/*.js` to
5353
`plugins/basicrum/assets/js/integrations/`, render each as copyable text
5454
in `render_consent_info()` (webmasters paste TEXT into their consent
@@ -58,6 +58,9 @@ operator-journey defect, P3 = minor/polish.
5858
entries. Add the required-category warning header to `cookieyes.js`
5959
(wrong category = silent no-data; tested distinction) and equivalents to
6060
the other adapters. (UX 1+2 / CI-01, CI-05, BR-DOC-12)
61+
Completed with four accessible settings tabs, including separate generic
62+
opt-in and opt-out snippets, copy fallback guidance, exact packaged-adapter
63+
browser coverage, and release ZIP required-file checks.
6164
- [ ] P2 Replace the proposed "persistent consent warning" with contextual
6265
detection hints (design analysis): in `render_consent_info()` only -
6366
never a site-wide notice - detect Borlabs / Complianz / CookieYes / WP

‎docs/audits/evidence.md‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ and a deterministic race reproduction in an isolated worktree.
1414
Confidence: high = upheld by 3 adversarial refuters and/or direct runtime
1515
observation; medium = static evidence with 2-refuter verification.
1616

17+
Post-audit update: CI-01/BR-DOC-12 and CI-05 are resolved by the current
18+
consent-adapter distribution work. The canonical adapters now live under
19+
`plugins/basicrum/assets/js/integrations/`, the settings page displays the
20+
exact packaged files, and the release verifier requires them. Reproduction
21+
commands below that reference `examples/integrations/*.js` describe the
22+
audited `0ec392e` tree and must be run against that ref, not current HEAD.
23+
1724
## Corrections to the original reports (capability vs transmission)
1825

1926
The lab distinguished code bundled in Boomerang from features enabled by
@@ -69,15 +76,15 @@ to the original privacy report:
6976
| DISC-09 (privacy 7) | low | `plugins/basicrum/README.md` | static | `sed -n '4p;14p;51p;86p' plugins/basicrum/README.md` | Stock template stub with placeholder text and fake security-fix changelog; excluded from ZIP by verify-release.sh | high |
7077
| BR-WP-14 (privacy 8) | low | `uninstall.php:13-20` | static | `grep -rn is_multisite plugins/basicrum/uninstall.php plugins/basicrum/src; echo exit=$?` | Exit 1 (no match): no multisite iteration; per-site options persist on other subsites | medium |
7178
| BR-WP-06 (privacy 10, OVERTURNED to PASS) | minor hardening | `Validate.php:110`; bundle `beacon_url_force_https:!0` | static | `grep -o 'beacon_url_force_https..' plugins/basicrum/assets/js/boomr/*.min.js` | Protocol-relative input evades the storage-layer upgrade, but the runtime force-https default prevents plaintext beacons; input hardening optional | medium |
72-
| CI-01/BR-DOC-12 (UX 1) | blocker | `examples/integrations/` outside plugin; readme/Page.php silent | static | `grep -rni 'borlabs\|complianz\|cookieyes\|consent api' plugins/basicrum/readme.txt plugins/basicrum/src; echo exit=$?` | Exit 1: the three tested adapters are unreachable from every operator surface and absent from the ZIP (build-release.sh packages plugins/basicrum only) | medium |
79+
| CI-01/BR-DOC-12 (UX 1, RESOLVED AFTER AUDIT) | blocker | `examples/integrations/` outside plugin; readme/Page.php silent | static | `grep -rni 'borlabs\|complianz\|cookieyes\|consent api' plugins/basicrum/readme.txt plugins/basicrum/src; echo exit=$?` | At audited ref: exit 1; the three tested adapters were unreachable from every operator surface and absent from the ZIP. Current tree packages and displays the canonical adapters. | medium |
7380
| BR-DOC-07/walkthrough-07 (UX 1) | major (verifier-corrected from blocker) | `Helpers.php:38`; `Page.php:457-488` | both | `make woocommerce-e2e-up`; set consent mode; `curl -s http://localhost:9081/ \| grep -o 'consent-boomerang[^\"]*'` then observe zero collector requests in an anonymous browser | Only the consent loader is fetched; Boomerang never loads; no beacon, no cookie, no console output; wp-admin shows only "Settings saved." | high |
7481
| walkthrough-05 (UX 6) | major | `Page.php:176`; single internal href on the page | both | On the settings page run `document.querySelectorAll('#wpbody a[href^="http"]')` | Zero external links; "Basicrum backoffice" named but never linked; account requirement stated only in readme FAQ | high |
7582
| walkthrough-06/BR-DOC-09 (UX 3) | major | `Assets.php:80-83`; `Page.php:192` | both | `curl -s http://localhost:9081/ \| grep -c basicrum` (nonzero anonymously) vs view-source in a logged-in admin session (zero) | Admin's own pages contain no Basicrum markup with default track_admins='0'; no copy anywhere explains it | high |
7683
| BR-DOC-08 (UX 3) | major | readme.txt FAQ 46-74 | static | `grep -rniE 'verify\|devtools\|health' plugins/basicrum/src plugins/basicrum/readme.txt` | No verification story on any surface | medium |
7784
| BR-DOC-10/CI-03 (UX 4) | major | `Validate.php:26,140-158`; zero console statements | static | `grep -rn 'console\.' plugins/basicrum/assets/js/loaders examples/integrations; echo exit=$?` | Exit 1: all integration failure modes silent; Site ID validated by regex shape only | medium |
7885
| CI-02 (UX 2) | major | `Page.php:699-726` | static | `sed -n '699,726p' plugins/basicrum/src/Admin/Settings/Page.php` | Inert `reportBasicrumConsent()` skeleton; no placement instructions, no event wiring | medium |
7986
| CI-04 (UX 2) | major | `Page.php:720`; `readme.txt:66` | static | `sed -n '720p' plugins/basicrum/src/Admin/Settings/Page.php` | Abstract load-order rule with no concrete recipe | medium |
80-
| CI-05 (UX 2) | major | `examples/integrations/cookieyes.js:4` | static | `sed -n '1,6p' examples/integrations/cookieyes.js` | `consentCategory='analytics'` hard-coded with no warning in the file; wrong-category symptom (silent no-data) documented only in the non-shipped README | medium |
87+
| CI-05 (UX 2, RESOLVED AFTER AUDIT) | major | `examples/integrations/cookieyes.js:4` | static | At audited ref: `sed -n '1,6p' examples/integrations/cookieyes.js` | At audited ref: `consentCategory='analytics'` was hard-coded with no warning in the file. Current packaged adapter includes the wrong-category warning. | medium |
8188
| BR-DOC-13 (UX 8) | major | no purge caveat on any shipped surface | static | `grep -ni 'purge\|cached' plugins/basicrum/readme.txt; echo exit=$?` | Exit 1; the non-shipped examples README does warn about cache clearing, the shipped surfaces do not | medium |
8289
| issue-http-strictness-inverted (UX 5, verifier-downgraded to minor) | minor | `Page.php:384,390`; `readme.txt:72-74` | static | `sed -n '384p;390p' plugins/basicrum/src/Admin/Settings/Page.php` | Label "HTTP Strictness" on a checkbox that relaxes strictness; readme FAQ repeats the inversion | medium |
8390
| BR-DOC-11 (UX 7, verifier-downgraded to minor) | minor | `readme.txt:76-81`; `wordpress-org-assets/` | static | `sed -n '76,81p' plugins/basicrum/readme.txt; ls wordpress-org-assets/` | Four screenshot captions, zero screenshot files in the repo | high |

‎docs/audits/operator-experience-audit.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@ surface tells the webmaster how to notice or fix that.
2323
New installs default to consent-controlled loading. Without a wired consent
2424
tool the consent loader waits forever: zero data, no warning, indefinitely.
2525
The enabled-but-inactive notice only checks Beacon URL and Brum Site ID. The
26-
three tested copy-paste adapters in `examples/integrations/` are outside the
27-
release ZIP and are never mentioned by readme.txt or the settings page, so the
28-
likely escape hatch for a stuck webmaster is switching to Load immediately,
26+
three tested copy-paste adapters in `examples/integrations/` were outside the
27+
release ZIP and were never mentioned by readme.txt or the settings page, so the
28+
likely escape hatch for a stuck webmaster was switching to Load immediately,
2929
which the plugin itself warns may be unlawful. (CI-01, BR-DOC-07, BR-DOC-12,
3030
walkthrough-07)
3131

32+
Post-audit update: the current consent-adapter distribution work resolves the
33+
adapter-availability portion of this finding. The packaged adapters now appear
34+
as copyable settings tabs and are required in the release ZIP. Contextual
35+
detection and monitoring-start guidance remain separate open checklist items.
36+
3237
- [ ] Add a plain-language consequence sentence to the consent panel: until
3338
the consent tool calls the opt-in callback, Basicrum will not load and no
3439
data will be collected.

‎examples/integrations/README.md‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Consent Tool Integration Examples
22

3-
These examples are copy-and-paste adapters for webmasters. They are development
4-
and documentation files outside the installable Basicrum plugin. Select
5-
**Follow external consent tool** in Basicrum before using an adapter.
3+
These examples document the copy-and-paste adapters shipped with the installable
4+
Basicrum plugin. Select **Follow external consent tool** in Basicrum, then use
5+
the matching tab under **Basicrum > Visitor Privacy > Consent Tool
6+
Integration** to copy the packaged adapter.
67

78
Load the Basicrum consent loader before the selected adapter. Load the adapter
89
as unblocked site code on every frontend page and call only one adapter. The
@@ -11,7 +12,7 @@ consent decision.
1112

1213
## Borlabs Cookie 3.0.6+
1314

14-
The [`borlabs-cookie-v3.js`](borlabs-cookie-v3.js) adapter follows the public
15+
The [`borlabs-cookie-v3.js`](../../plugins/basicrum/assets/js/integrations/borlabs-cookie-v3.js) adapter follows the public
1516
Borlabs Cookie JavaScript contract:
1617

1718
- It reads `BorlabsCookie.Consents.hasConsent('basicrum')`.
@@ -51,7 +52,7 @@ References:
5152

5253
## WP Consent API with Complianz or CookieYes
5354

54-
The [`wp-consent-api.js`](wp-consent-api.js) adapter uses WordPress's shared
55+
The [`wp-consent-api.js`](../../plugins/basicrum/assets/js/integrations/wp-consent-api.js) adapter uses WordPress's shared
5556
consent contract. Install and activate the separate WP Consent API plugin plus a
5657
supported consent-management plugin. The adapter was reviewed against Complianz
5758
7.5.0, CookieYes 3.5.3, and WP Consent API 2.0.1; both consent tools publish
@@ -94,7 +95,7 @@ References:
9495

9596
## Connected CookieYes fallback
9697

97-
Use [`cookieyes.js`](cookieyes.js) only when the CookieYes WordPress plugin is
98+
Use [`cookieyes.js`](../../plugins/basicrum/assets/js/integrations/cookieyes.js) only when the CookieYes WordPress plugin is
9899
connected to the CookieYes web app and WP Consent API is not being used. The
99100
direct adapter follows only CookieYes's Analytics category; Performance or any
100101
other category cannot enable Basicrum.

‎plugins/basicrum/assets/css/admin/settings.css‎

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,49 @@
5555
max-width: 760px;
5656
}
5757

58-
.basicrum-consent-info pre {
58+
.basicrum-consent-tabs {
59+
margin: 16px 0 8px;
60+
}
61+
62+
.basicrum-consent-tabs .nav-tab-wrapper {
63+
padding-left: 0;
64+
}
65+
66+
.basicrum-consent-tabs .nav-tab {
67+
cursor: pointer;
68+
margin-left: 0;
69+
margin-right: 4px;
70+
}
71+
72+
.basicrum-consent-panel {
73+
border-bottom: 1px solid #c3c4c7;
74+
padding: 8px 0 16px;
75+
}
76+
77+
.basicrum-consent-tabs:not(.is-initialized) .basicrum-consent-panel + .basicrum-consent-panel {
78+
border-top: 1px solid #c3c4c7;
79+
margin-top: 16px;
80+
}
81+
82+
.basicrum-consent-snippet {
83+
margin-top: 16px;
84+
}
85+
86+
.basicrum-consent-snippet textarea {
5987
background: #fff;
60-
border: 1px solid #ddd;
61-
overflow-x: auto;
62-
padding: 8px;
88+
margin-top: 6px;
89+
max-width: 100%;
90+
resize: vertical;
91+
}
92+
93+
.basicrum-consent-snippet-actions {
94+
display: flex;
95+
align-items: center;
96+
gap: 8px;
97+
margin: 6px 0 0;
98+
}
99+
100+
.basicrum-copy-status {
101+
color: #008a20;
102+
font-weight: 600;
63103
}

‎plugins/basicrum/assets/js/admin/settings.js‎

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
'use strict';
33

44
document.addEventListener( 'DOMContentLoaded', function() {
5+
var consentTabs = document.querySelectorAll( '.basicrum-consent-tabs' );
56
var enabled = document.getElementById( 'basicrum_enabled' );
67
var waitAfterOnload = document.getElementById( 'basicrum_wait_after_onload' );
78
var delay = document.getElementById( 'basicrum_delay_ms' );
@@ -15,6 +16,123 @@
1516
} ) : [];
1617
var preservedValues = form ? Array.from( form.querySelectorAll( '.basicrum-disabled-setting-value' ) ) : [];
1718

19+
/**
20+
* Activate one consent integration tab.
21+
*
22+
* @param {HTMLElement} tabContainer Tab container.
23+
* @param {HTMLButtonElement} activeTab Tab to activate.
24+
* @return {void}
25+
*/
26+
function activateConsentTab( tabContainer, activeTab ) {
27+
var tabs = Array.from( tabContainer.querySelectorAll( '[role="tab"]' ) );
28+
var panels = Array.from( tabContainer.querySelectorAll( '[role="tabpanel"]' ) );
29+
30+
tabs.forEach( function( tab ) {
31+
var isActive = tab === activeTab;
32+
33+
tab.classList.toggle( 'nav-tab-active', isActive );
34+
tab.setAttribute( 'aria-selected', isActive ? 'true' : 'false' );
35+
tab.tabIndex = isActive ? 0 : -1;
36+
} );
37+
38+
panels.forEach( function( panel ) {
39+
var isActive = panel.id === activeTab.getAttribute( 'aria-controls' );
40+
41+
panel.hidden = ! isActive;
42+
panel.classList.toggle( 'is-active', isActive );
43+
} );
44+
}
45+
46+
/**
47+
* Initialize accessible tabs and copy buttons for consent snippets.
48+
*
49+
* @param {HTMLElement} tabContainer Tab container.
50+
* @return {void}
51+
*/
52+
function initializeConsentTabs( tabContainer ) {
53+
var tabs = Array.from( tabContainer.querySelectorAll( '[role="tab"]' ) );
54+
55+
if ( ! tabs.length ) {
56+
return;
57+
}
58+
59+
tabs.forEach( function( tab, tabIndex ) {
60+
tab.addEventListener( 'click', function() {
61+
activateConsentTab( tabContainer, tab );
62+
} );
63+
64+
tab.addEventListener( 'keydown', function( event ) {
65+
var targetIndex = tabIndex;
66+
67+
if ( 'ArrowLeft' === event.key ) {
68+
targetIndex = 0 === tabIndex ? tabs.length - 1 : tabIndex - 1;
69+
} else if ( 'ArrowRight' === event.key ) {
70+
targetIndex = tabIndex === tabs.length - 1 ? 0 : tabIndex + 1;
71+
} else if ( 'Home' === event.key ) {
72+
targetIndex = 0;
73+
} else if ( 'End' === event.key ) {
74+
targetIndex = tabs.length - 1;
75+
} else {
76+
return;
77+
}
78+
79+
event.preventDefault();
80+
activateConsentTab( tabContainer, tabs[ targetIndex ] );
81+
tabs[ targetIndex ].focus();
82+
} );
83+
} );
84+
85+
tabContainer.querySelectorAll( '.basicrum-copy-consent-snippet' ).forEach( function( button ) {
86+
button.addEventListener( 'click', function() {
87+
var target = document.getElementById( button.dataset.copyTarget );
88+
var status = button.parentElement.querySelector( '.basicrum-copy-status' );
89+
90+
if ( ! target ) {
91+
return;
92+
}
93+
94+
function reportCopied() {
95+
if ( status ) {
96+
status.textContent = button.dataset.copiedLabel;
97+
}
98+
}
99+
100+
function reportCopyFallback() {
101+
if ( status ) {
102+
status.textContent = button.dataset.copyFallbackLabel;
103+
}
104+
}
105+
106+
function copyWithSelection() {
107+
target.focus();
108+
target.select();
109+
110+
if ( 'function' === typeof document.execCommand && document.execCommand( 'copy' ) ) {
111+
reportCopied();
112+
return;
113+
}
114+
115+
reportCopyFallback();
116+
}
117+
118+
if ( navigator.clipboard && 'function' === typeof navigator.clipboard.writeText ) {
119+
navigator.clipboard.writeText( target.value ).then( reportCopied, copyWithSelection );
120+
return;
121+
}
122+
123+
copyWithSelection();
124+
} );
125+
} );
126+
127+
tabContainer.classList.add( 'is-initialized' );
128+
activateConsentTab(
129+
tabContainer,
130+
tabContainer.querySelector( '[role="tab"][aria-selected="true"]' ) || tabs[0]
131+
);
132+
}
133+
134+
consentTabs.forEach( initializeConsentTabs );
135+
18136
if ( ! enabled || ! form || ! requiredFields.length ) {
19137
return;
20138
}

examples/integrations/borlabs-cookie-v3.js renamed to plugins/basicrum/assets/js/integrations/borlabs-cookie-v3.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
/*
2+
* Basicrum consent adapter for Borlabs Cookie v3.0.6 or newer.
3+
*
4+
* Create an enabled Borlabs service with the ID "basicrum", normally in the
5+
* Statistics service group. Paste this adapter as unblocked site code that runs
6+
* on every page after the Basicrum consent loader. A different service ID will
7+
* silently prevent Basicrum from receiving the intended consent decision.
8+
*/
19
( function( window ) {
210
'use strict';
311

0 commit comments

Comments
 (0)