Changeset 3771537
- Timestamp:
- 10/28/2026 08:11:59 PM (3 days ago)
- Location:
- cookieflame
- Files:
-
- 11 added
- 16 edited
- 1 copied
-
assets/screenshot-1.png (added)
-
assets/screenshot-2.png (added)
-
assets/screenshot-3.png (added)
-
assets/screenshot-4.png (added)
-
assets/screenshot-5.png (added)
-
assets/screenshot-6.png (added)
-
assets/screenshot-7.png (added)
-
tags/5.4.0 (copied) (copied from cookieflame/trunk )
-
tags/5.4.0/assets/css/admin.css (modified) (3 diffs)
-
tags/5.4.0/assets/css/cookieconsent.css (modified) (2 diffs)
-
tags/5.4.0/assets/js/admin-preview.js (added)
-
tags/5.4.0/assets/js/admin.js (modified) (5 diffs)
-
tags/5.4.0/assets/js/cookieconsent.js (modified) (36 diffs)
-
tags/5.4.0/assets/js/cookieconsent.min.js (modified) (2 diffs)
-
tags/5.4.0/changelog.txt (added)
-
tags/5.4.0/cookieflame.php (modified) (53 diffs)
-
tags/5.4.0/readme.txt (modified) (2 diffs)
-
tags/5.4.0/uninstall.php (modified) (1 diff)
-
trunk/assets/css/admin.css (modified) (3 diffs)
-
trunk/assets/css/cookieconsent.css (modified) (2 diffs)
-
trunk/assets/js/admin-preview.js (added)
-
trunk/assets/js/admin.js (modified) (5 diffs)
-
trunk/assets/js/cookieconsent.js (modified) (36 diffs)
-
trunk/assets/js/cookieconsent.min.js (modified) (2 diffs)
-
trunk/changelog.txt (added)
-
trunk/cookieflame.php (modified) (53 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/uninstall.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
cookieflame/tags/5.4.0/assets/css/admin.css
r3769546 r3771537 625 625 border: 1px solid #dcdcde; 626 626 border-radius: 4px; 627 } 628 .cf-admin-manual-row select { 629 padding-right: 28px; 627 630 } 628 631 .cf-admin-manual-row .button-link-delete { … … 1017 1020 color: #c2410c; 1018 1021 } 1022 .cf-admin-upgrade-banner-dismiss { 1023 margin: 0; 1024 flex-shrink: 0; 1025 align-self: flex-start; 1026 } 1027 .cf-admin-upgrade-banner-close { 1028 background: none; 1029 border: none; 1030 color: #8c8f94; 1031 font-size: 20px; 1032 line-height: 1; 1033 padding: 2px 4px; 1034 cursor: pointer; 1035 border-radius: 4px; 1036 } 1037 .cf-admin-upgrade-banner-close:hover, 1038 .cf-admin-upgrade-banner-close:focus { 1039 color: #1d2327; 1040 background: rgba(0, 0, 0, 0.05); 1041 } 1019 1042 1020 1043 /* Licence tab Pro features card */ … … 1475 1498 transition: border-color .15s; 1476 1499 } 1500 /* Room for WordPress's select arrow, drawn about 5-21px from the right edge. */ 1501 .cf-admin-wrap .form-table select { 1502 padding-right: 32px; 1503 } 1477 1504 .cf-admin-wrap .form-table input:focus, 1478 1505 .cf-admin-wrap .form-table select:focus, -
cookieflame/tags/5.4.0/assets/css/cookieconsent.css
r3766806 r3771537 1 1 /** 2 * CookieFlame v 4.41.0 — Consent Banner Stylesheet2 * CookieFlame v5.4.0 — Consent Banner Stylesheet 3 3 * ───────────────────────────────────────────────────────────── 4 4 * Loaded on every front-end page via cookieflame_enqueue_assets(). 5 * Before v4.41.0 these rules were injected inline by6 * cookieconsent.js at runtime; they are now an external7 * stylesheet so the main JS bundle is ~5.6 KB smaller and8 * the CSS can be cached independently by the browser.9 5 * 10 6 * Dark mode: JS adds `.cc-dark` to <html> when theme=dark. 11 7 * Icon position: JS adds `.cc-icon-right` to <html> when 12 8 * iconPosition=right (default is left). 9 * 10 * Custom properties (v5.4.0): the banner's colours, corner radius and 11 * font come from the --cc-* properties below. Override them on :root 12 * (and :root.cc-dark for the dark theme) to restyle the banner. 13 13 */ 14 15 :root{--cc-font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;--cc-radius:20px;--cc-btn-radius:12px;--cc-bg:#fff;--cc-text:#1d1d1f;--cc-text-muted:#555;--cc-link:#0071e3;--cc-focus:#0071e3;--cc-banner-shadow:rgba(0,0,0,.15);--cc-accent:#0071e3;--cc-accent-hover:#005bb5;--cc-accent-text:#fff;--cc-accent-shadow:rgba(0,113,227,.3);--cc-accent-shadow-hover:rgba(0,113,227,.35);--cc-secondary-bg:#e8e8ed;--cc-secondary-hover:#d2d2d7;--cc-secondary-text:#1d1d1f;--cc-save-bg:#34c759;--cc-save-hover:#28a745;--cc-save-text:#fff;--cc-save-shadow:rgba(52,199,89,.3);--cc-save-shadow-hover:rgba(52,199,89,.35);--cc-details:#555;--cc-details-hover:#555;--cc-border:#e8e8ed;--cc-divider:#f0f0f2;--cc-cat-desc:#646970;--cc-always-on:#767676;--cc-toggle-off:#ccc;--cc-toggle-on:#0071e3;--cc-knob:#fff;--cc-powered:#999;--cc-powered-hover:#666;--cc-icon-bg:#1d1d1f;--cc-icon-text:#fff;--cc-icon-shadow:rgba(0,0,0,.25);--cc-icon-shadow-hover:rgba(0,0,0,.35)} 16 :root.cc-dark{--cc-bg:#1c1c1e;--cc-text:#f5f5f7;--cc-text-muted:#98989d;--cc-link:#2997ff;--cc-banner-shadow:rgba(0,0,0,.5);--cc-accent:#0a84ff;--cc-accent-hover:#0071e3;--cc-accent-shadow:rgba(10,132,255,.35);--cc-accent-shadow-hover:rgba(10,132,255,.4);--cc-secondary-bg:#2c2c2e;--cc-secondary-hover:#3a3a3c;--cc-secondary-text:#f5f5f7;--cc-save-bg:#30d158;--cc-save-hover:#28a745;--cc-save-shadow:rgba(48,209,88,.3);--cc-save-shadow-hover:rgba(48,209,88,.35);--cc-details:#98989d;--cc-details-hover:#c7c7cc;--cc-border:#2c2c2e;--cc-divider:#2c2c2e;--cc-cat-desc:#8e8e93;--cc-always-on:#8e8e93;--cc-toggle-off:#3a3a3c;--cc-toggle-on:#0a84ff;--cc-knob:#f5f5f7;--cc-powered:#aaa;--cc-powered-hover:#ddd;--cc-icon-bg:#f5f5f7;--cc-icon-text:#1d1d1f;--cc-icon-shadow:rgba(255,255,255,.15);--cc-icon-shadow-hover:rgba(255,255,255,.25)} 14 17 15 18 #cc-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:999998;opacity:0;pointer-events:none;transition:opacity .3s;backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)} 16 19 #cc-overlay.cc-show{opacity:1;pointer-events:auto} 17 #cc-banner{position:fixed;bottom:0;left:0;right:0;z-index:999999;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5;color:#1d1d1f;background:#fff;box-shadow:0 -8px 40px rgba(0,0,0,.15);transform:translateY(100%);transition:transform .4s cubic-bezier(.4,0,.2,1);border-radius:20px 20px0 0;max-height:90vh;overflow-y:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}20 #cc-banner{position:fixed;bottom:0;left:0;right:0;z-index:999999;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);background:var(--cc-bg);box-shadow:0 -8px 40px var(--cc-banner-shadow);transform:translateY(100%);transition:transform .4s cubic-bezier(.4,0,.2,1);border-radius:var(--cc-radius) var(--cc-radius) 0 0;max-height:90vh;overflow-y:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} 18 21 #cc-banner.cc-show{transform:translateY(0)} 19 22 #cc-banner,#cc-banner *{box-sizing:border-box;-webkit-tap-highlight-color:transparent} … … 21 24 .cc-inner{max-width:720px;margin:0 auto;padding:32px 32px 20px} 22 25 .cc-title{font-size:20px !important;font-weight:800 !important;margin:0 0 8px !important;letter-spacing:-.3px;line-height:1.3 !important;color:inherit !important;background:none !important;border:none !important;padding:0 !important;text-transform:none !important} 23 .cc-text{margin:0 0 20px !important;color: #555!important;font-size:14px !important;font-weight:400 !important;line-height:1.6 !important;padding:0 !important;border:none !important;background:none !important;text-transform:none !important}24 .cc-text a{color: #0071e3!important;text-decoration:none !important;font-weight:500 !important;background:none !important;border:none !important;box-shadow:none !important}26 .cc-text{margin:0 0 20px !important;color:var(--cc-text-muted) !important;font-size:14px !important;font-weight:400 !important;line-height:1.6 !important;padding:0 !important;border:none !important;background:none !important;text-transform:none !important} 27 .cc-text a{color:var(--cc-link) !important;text-decoration:none !important;font-weight:500 !important;background:none !important;border:none !important;box-shadow:none !important} 25 28 .cc-text a:hover{text-decoration:underline !important} 26 29 .cc-buttons{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:16px} 27 .cc-btn{display:inline-flex !important;align-items:center !important;justify-content:center !important;border-radius: 12px!important;border:none !important;font-weight:700 !important;cursor:pointer !important;letter-spacing:-.1px !important;text-transform:none !important;text-decoration:none !important;line-height:1 !important;white-space:nowrap !important;-webkit-appearance:none !important;appearance:none !important;outline:none !important;box-shadow:none !important;text-shadow:none !important;float:none !important;margin:0 !important}28 .cc-btn:focus{outline:2px solid #0071e3!important;outline-offset:2px !important}30 .cc-btn{display:inline-flex !important;align-items:center !important;justify-content:center !important;border-radius:var(--cc-btn-radius) !important;border:none !important;font-weight:700 !important;cursor:pointer !important;letter-spacing:-.1px !important;text-transform:none !important;text-decoration:none !important;line-height:1 !important;white-space:nowrap !important;-webkit-appearance:none !important;appearance:none !important;outline:none !important;box-shadow:none !important;text-shadow:none !important;float:none !important;margin:0 !important} 31 .cc-btn:focus{outline:2px solid var(--cc-focus) !important;outline-offset:2px !important} 29 32 .cc-btn:active{transform:scale(.97) !important} 30 .cc-btn-accept{background:#0071e3 !important;color:#fff !important;font-size:15px !important;padding:14px 32px !important;box-shadow:0 2px 8px rgba(0,113,227,.3) !important;transition:background .15s,box-shadow .15s,transform .1s !important} 31 .cc-btn-accept:hover{background:#005bb5 !important;box-shadow:0 4px 16px rgba(0,113,227,.35) !important;color:#fff !important} 32 .cc-btn-decline{background:#e8e8ed !important;color:#1d1d1f !important;font-size:13px !important;padding:11px 22px !important;box-shadow:none !important;transition:background .15s,transform .1s !important} 33 .cc-btn-decline:hover{background:#d2d2d7 !important;color:#1d1d1f !important} 34 .cc-btn-save{background:#34c759 !important;color:#fff !important;font-size:14px !important;padding:13px 28px !important;box-shadow:0 2px 8px rgba(52,199,89,.3) !important;transition:background .15s,box-shadow .15s,transform .1s !important} 35 .cc-btn-save:hover{background:#28a745 !important;box-shadow:0 4px 16px rgba(52,199,89,.35) !important;color:#fff !important} 36 .cc-btn-details{background:transparent !important;color:#555 !important;padding:11px 4px;font-weight:500;font-size:13px;text-decoration:none !important;border:none !important;box-shadow:none !important;cursor:pointer;transition:color .15s !important} 37 .cc-btn-details:hover{color:#555 !important;text-decoration:none !important;background:transparent !important;box-shadow:none !important} 38 .cc-categories{margin:0 0 16px !important;border:1px solid #e8e8ed !important;border-radius:14px !important;overflow:hidden !important;display:none} 33 .cc-btn-accept{background:var(--cc-accent) !important;color:var(--cc-accent-text) !important;font-size:15px !important;padding:14px 32px !important;box-shadow:0 2px 8px var(--cc-accent-shadow) !important;transition:background .15s,box-shadow .15s,transform .1s !important} 34 .cc-btn-accept:hover{background:var(--cc-accent-hover) !important;box-shadow:0 4px 16px var(--cc-accent-shadow-hover) !important;color:var(--cc-accent-text) !important} 35 .cc-btn-decline{background:var(--cc-secondary-bg) !important;color:var(--cc-secondary-text) !important;font-size:13px !important;padding:11px 22px !important;box-shadow:none !important;transition:background .15s,transform .1s !important} 36 .cc-btn-decline:hover{background:var(--cc-secondary-hover) !important;color:var(--cc-secondary-text) !important} 37 .cc-btn-reject{background:var(--cc-secondary-bg) !important;color:var(--cc-secondary-text) !important;font-size:15px !important;padding:14px 32px !important;transition:background .15s,transform .1s !important} 38 .cc-btn-reject:hover{background:var(--cc-secondary-hover) !important;color:var(--cc-secondary-text) !important} 39 .cc-btn-save{background:var(--cc-save-bg) !important;color:var(--cc-save-text) !important;font-size:14px !important;padding:13px 28px !important;box-shadow:0 2px 8px var(--cc-save-shadow) !important;transition:background .15s,box-shadow .15s,transform .1s !important} 40 .cc-btn-save:hover{background:var(--cc-save-hover) !important;box-shadow:0 4px 16px var(--cc-save-shadow-hover) !important;color:var(--cc-save-text) !important} 41 .cc-btn-details{background:transparent !important;color:var(--cc-details) !important;padding:11px 4px;font-weight:500;font-size:13px;text-decoration:none !important;border:none !important;box-shadow:none !important;cursor:pointer;transition:color .15s !important} 42 .cc-btn-details:hover{color:var(--cc-details-hover) !important;text-decoration:none !important;background:transparent !important;box-shadow:none !important} 43 .cc-categories{margin:0 0 16px !important;border:1px solid var(--cc-border) !important;border-radius:14px !important;overflow:hidden !important;display:none} 39 44 .cc-categories.cc-open{display:block !important} 40 .cc-cat{padding:16px 20px !important;border-bottom:1px solid #f0f0f2!important;display:flex !important;justify-content:space-between !important;align-items:center !important;gap:16px !important;background:none !important}45 .cc-cat{padding:16px 20px !important;border-bottom:1px solid var(--cc-divider) !important;display:flex !important;justify-content:space-between !important;align-items:center !important;gap:16px !important;background:none !important} 41 46 .cc-cat:last-child{border-bottom:none !important} 42 47 .cc-cat-label{font-weight:700 !important;font-size:14px !important;line-height:1.3 !important;color:inherit !important;text-transform:none !important} 43 .cc-cat-desc{font-size:12px !important;color:#646970 !important;margin-top:3px !important;font-weight:400 !important;line-height:1.4 !important;text-transform:none !important} 44 .cc-always-on{font-weight:400 !important;font-size:11px !important;color:#767676 !important} 48 .cc-cat-desc{font-size:12px !important;color:var(--cc-cat-desc) !important;margin-top:3px !important;font-weight:400 !important;line-height:1.4 !important;text-transform:none !important} 49 .cc-always-on{font-weight:400 !important;font-size:11px !important;color:var(--cc-always-on) !important} 50 .cc-gpc-note{font-size:12px !important;color:var(--cc-text) !important;margin-top:6px !important;font-weight:600 !important;line-height:1.4 !important;text-transform:none !important} 45 51 .cc-toggle{position:relative !important;width:46px !important;height:26px !important;flex-shrink:0 !important;display:block !important} 46 52 .cc-toggle input{opacity:0 !important;width:0 !important;height:0 !important;position:absolute !important} 47 .cc-toggle .cc-slider{position:absolute !important;inset:0 !important;background: #ccc!important;border-radius:26px !important;cursor:pointer !important;transition:background .2s !important;border:none !important;box-shadow:none !important}48 .cc-toggle .cc-slider:before{content:"" !important;position:absolute !important;width:20px !important;height:20px !important;left:3px !important;bottom:3px !important;background: #fff!important;border-radius:50% !important;transition:transform .2s !important;box-shadow:0 1px 3px rgba(0,0,0,.15) !important}49 .cc-toggle input:checked+.cc-slider{background: #0071e3!important}53 .cc-toggle .cc-slider{position:absolute !important;inset:0 !important;background:var(--cc-toggle-off) !important;border-radius:26px !important;cursor:pointer !important;transition:background .2s !important;border:none !important;box-shadow:none !important} 54 .cc-toggle .cc-slider:before{content:"" !important;position:absolute !important;width:20px !important;height:20px !important;left:3px !important;bottom:3px !important;background:var(--cc-knob) !important;border-radius:50% !important;transition:transform .2s !important;box-shadow:0 1px 3px rgba(0,0,0,.15) !important} 55 .cc-toggle input:checked+.cc-slider{background:var(--cc-toggle-on) !important} 50 56 .cc-toggle input:checked+.cc-slider:before{transform:translateX(20px) !important} 51 .cc-toggle input:disabled+.cc-slider{background: #0071e3!important;opacity:.5 !important;cursor:default !important}52 .cc-toggle input:focus-visible+.cc-slider{outline:2px solid #0071e3!important;outline-offset:2px !important}53 .cc-powered{text-align:center !important;padding:0 0 16px !important;font-size:11px !important;color: #999!important;font-weight:400 !important;background:none !important;border:none !important;margin:0 !important}57 .cc-toggle input:disabled+.cc-slider{background:var(--cc-toggle-on) !important;opacity:.5 !important;cursor:default !important} 58 .cc-toggle input:focus-visible+.cc-slider{outline:2px solid var(--cc-focus) !important;outline-offset:2px !important} 59 .cc-powered{text-align:center !important;padding:0 0 16px !important;font-size:11px !important;color:var(--cc-powered) !important;font-weight:400 !important;background:none !important;border:none !important;margin:0 !important} 54 60 .cc-pref-buttons{display:none !important;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:8px} 55 61 .cc-pref-buttons.cc-open{display:flex !important} 56 .cc-powered a{color: #999!important;text-decoration:none !important;font-weight:400 !important;background:none !important;border:none !important;box-shadow:none !important}57 .cc-powered a:hover{color: #666!important;text-decoration:none !important}58 #cc-icon{position:fixed !important;bottom:18px !important;left:18px !important;z-index:999997 !important;width:48px !important;height:48px !important;border-radius:50% !important;background: #1d1d1f !important;color:#fff !important;border:none !important;cursor:pointer !important;box-shadow:0 2px 12px rgba(0,0,0,.25) !important;display:none;align-items:center !important;justify-content:center !important;padding:0 !important;transition:transform .15s,box-shadow .15s !important;-webkit-appearance:none !important;appearance:none !important;margin:0 !important;text-decoration:none !important;outline:none !important}59 #cc-icon:hover{transform:scale(1.1) !important;box-shadow:0 4px 20px rgba(0,0,0,.35) !important}60 #cc-icon:focus-visible{outline:2px solid #0071e3!important;outline-offset:2px !important}62 .cc-powered a{color:var(--cc-powered) !important;text-decoration:none !important;font-weight:400 !important;background:none !important;border:none !important;box-shadow:none !important} 63 .cc-powered a:hover{color:var(--cc-powered-hover) !important;text-decoration:none !important} 64 #cc-icon{position:fixed !important;bottom:18px !important;left:18px !important;z-index:999997 !important;width:48px !important;height:48px !important;border-radius:50% !important;background:var(--cc-icon-bg) !important;color:var(--cc-icon-text) !important;border:none !important;cursor:pointer !important;box-shadow:0 2px 12px var(--cc-icon-shadow) !important;display:none;align-items:center !important;justify-content:center !important;padding:0 !important;transition:transform .15s,box-shadow .15s !important;-webkit-appearance:none !important;appearance:none !important;margin:0 !important;text-decoration:none !important;outline:none !important} 65 #cc-icon:hover{transform:scale(1.1) !important;box-shadow:0 4px 20px var(--cc-icon-shadow-hover) !important} 66 #cc-icon:focus-visible{outline:2px solid var(--cc-focus) !important;outline-offset:2px !important} 61 67 #cc-icon.cc-show{display:flex !important} 62 68 .cc-icon-right #cc-icon{left:auto !important;right:18px !important} 63 #cc-ccpa-banner{position:fixed;bottom:0;left:0;right:0;z-index:999999;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5;color:#1d1d1f;background:#fff;box-shadow:0 -8px 40px rgba(0,0,0,.15);transform:translateY(100%);transition:transform .4s cubic-bezier(.4,0,.2,1);border-radius:20px 20px 0 0}69 #cc-ccpa-banner{position:fixed;bottom:0;left:0;right:0;z-index:999999;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);background:var(--cc-bg);box-shadow:0 -8px 40px var(--cc-banner-shadow);transform:translateY(100%);transition:transform .4s cubic-bezier(.4,0,.2,1);border-radius:var(--cc-radius) var(--cc-radius) 0 0;max-height:90vh;overflow-y:auto} 64 70 #cc-ccpa-banner.cc-show{transform:translateY(0)} 65 71 #cc-ccpa-banner *{box-sizing:border-box} 66 72 .cc-ccpa-inner{max-width:720px;margin:0 auto;padding:20px 32px;display:flex;align-items:center;gap:20px;flex-wrap:wrap} 67 73 .cc-ccpa-text{flex:1;min-width:280px} 68 .cc-ccpa-text p{margin:0;color:#555} 74 .cc-ccpa-text p{margin:0;color:var(--cc-text-muted)} 75 .cc-ccpa-text p.cc-ccpa-gpc{margin:8px 0 0;color:var(--cc-text);font-weight:600} 76 .cc-ccpa-gpc:before{content:"\2713\00a0"} 69 77 .cc-ccpa-actions{display:flex;gap:10px;align-items:center;flex-shrink:0} 70 .cc-ccpa-link{color:#0071e3;text-decoration:underline;font-size:13px;font-weight:500;white-space:nowrap} 71 .cc-dark #cc-banner,.cc-dark #cc-ccpa-banner{background:#1c1c1e !important;color:#f5f5f7 !important;box-shadow:0 -8px 40px rgba(0,0,0,.5) !important} 72 .cc-dark .cc-text{color:#98989d !important} 73 .cc-dark .cc-text a,.cc-dark .cc-ccpa-link{color:#2997ff !important} 74 .cc-dark .cc-btn-details{color:#98989d !important} 75 .cc-dark .cc-btn-details:hover{color:#c7c7cc !important} 76 .cc-dark .cc-btn-decline{background:#2c2c2e !important;color:#f5f5f7 !important} 77 .cc-dark .cc-btn-decline:hover{background:#3a3a3c !important;color:#f5f5f7 !important} 78 .cc-dark .cc-categories{border-color:#2c2c2e !important} 79 .cc-dark .cc-cat{border-bottom-color:#2c2c2e !important} 80 .cc-dark .cc-cat-desc{color:#8e8e93 !important} 81 .cc-dark .cc-always-on{color:#8e8e93 !important} 82 .cc-dark .cc-toggle .cc-slider{background:#3a3a3c !important} 83 .cc-dark .cc-toggle .cc-slider:before{background:#f5f5f7 !important} 84 .cc-dark .cc-toggle input:checked+.cc-slider{background:#0a84ff !important} 85 .cc-dark .cc-toggle input:disabled+.cc-slider{background:#0a84ff !important} 86 .cc-dark .cc-btn-accept{background:#0a84ff !important;box-shadow:0 2px 8px rgba(10,132,255,.35) !important} 87 .cc-dark .cc-btn-accept:hover{background:#0071e3 !important;box-shadow:0 4px 16px rgba(10,132,255,.4) !important} 88 .cc-dark .cc-btn-save{background:#30d158 !important;box-shadow:0 2px 8px rgba(48,209,88,.3) !important} 89 .cc-dark .cc-btn-save:hover{background:#28a745 !important;box-shadow:0 4px 16px rgba(48,209,88,.35) !important} 90 .cc-dark .cc-ccpa-text p{color:#98989d !important} 91 .cc-dark .cc-powered{color:#aaa !important} 92 .cc-dark .cc-powered a{color:#aaa !important} 93 .cc-dark .cc-powered a:hover{color:#ddd !important} 94 .cc-dark .cc-cat-label{color:#f5f5f7 !important} 95 .cc-dark .cc-title{color:#f5f5f7 !important} 96 .cc-dark #cc-icon{background:#f5f5f7 !important;color:#1d1d1f !important;box-shadow:0 2px 12px rgba(255,255,255,.15) !important} 97 .cc-dark #cc-icon:hover{box-shadow:0 4px 20px rgba(255,255,255,.25) !important} 98 @media(max-width:480px){.cc-inner{padding:24px 20px 16px}.cc-title{font-size:18px}.cc-buttons{gap:8px}.cc-btn-accept{font-size:14px;padding:12px 24px}.cc-btn-decline{font-size:12px;padding:10px 18px}} 78 .cc-ccpa-link{color:var(--cc-link);text-decoration:underline;font-size:13px;font-weight:500;white-space:nowrap} 79 #cc-ccpa-banner .cc-btn-details{padding:11px 0} 80 .cc-ccpa-choice{display:none} 81 .cc-ccpa-choice.cc-open{display:block} 82 .cc-ccpa-choice-inner{max-width:720px;margin:0 auto;padding:20px 32px} 83 .cc-ccpa-choice-title{font-size:16px;font-weight:800;line-height:1.3;margin:0 0 6px;color:inherit} 84 .cc-ccpa-choice-text,.cc-ccpa-choice-note{margin:0 0 14px;color:var(--cc-text-muted)} 85 .cc-ccpa-choice-status{margin:0 0 14px;font-weight:600;color:var(--cc-text)} 86 .cc-ccpa-choice-note{font-size:12px;margin:10px 0 0} 87 .cc-ccpa-choice-buttons{display:flex;gap:10px;flex-wrap:wrap;align-items:center} 88 @media(max-width:480px){.cc-inner{padding:24px 20px 16px}.cc-title{font-size:18px}.cc-buttons{gap:8px}.cc-btn-accept{font-size:14px;padding:12px 24px}.cc-btn-decline{font-size:12px;padding:10px 18px}.cc-ccpa-inner{padding:16px 20px;gap:12px}.cc-ccpa-text{min-width:0;flex:1 1 100%}.cc-ccpa-actions{flex-wrap:wrap;flex-shrink:1;width:100%;justify-content:space-between}.cc-ccpa-link{white-space:normal}.cc-ccpa-choice-inner{padding:16px 20px}} -
cookieflame/tags/5.4.0/assets/js/admin.js
r3769546 r3771537 10 10 cc_anon_id: { vendor: "CookieFlame", category: "necessary", purpose: "Anonymous ID for consent logging" }, 11 11 cookieflame_dnt: { vendor: "CookieFlame", category: "necessary", purpose: "Do-not-track opt-out flag" }, 12 "wp-settings-": { vendor: "WordPress", category: "necessary", purpose: "Admin interface preferences", prefix: true }, 13 "wp-settings-time-": { vendor: "WordPress", category: "necessary", purpose: "Admin settings timestamp", prefix: true }, 14 wordpress_logged_in_: { vendor: "WordPress", category: "necessary", purpose: "Login session", prefix: true }, 15 wordpress_sec_: { vendor: "WordPress", category: "necessary", purpose: "Secure login session", prefix: true }, 16 wordpress_test_cookie: { vendor: "WordPress", category: "necessary", purpose: "Checks cookie support" }, 17 wp_lang: { vendor: "WordPress", category: "necessary", purpose: "Language preference" }, 12 "wp-settings-": { vendor: "WordPress", category: "necessary", purpose: "Remembers dashboard layout settings", prefix: true }, 13 "wp-settings-time-": { vendor: "WordPress", category: "necessary", purpose: "Records when dashboard settings were last saved", prefix: true }, 14 wordpress_logged_in_: { vendor: "WordPress", category: "necessary", purpose: "Keeps you logged in", prefix: true }, 15 wordpress_sec_: { vendor: "WordPress", category: "necessary", purpose: "Confirms your login when you open the dashboard", prefix: true }, 16 wordpress_rec_: { vendor: "WordPress", category: "necessary", purpose: "Recovery mode after a site error (administrators only)", prefix: true }, 17 wordpress_test_cookie: { vendor: "WordPress", category: "necessary", purpose: "Checks your browser accepts cookies before you log in" }, 18 wp_lang: { vendor: "WordPress", category: "necessary", purpose: "Remembers the language picked on the login page" }, 19 comment_author_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your name for comments, if you ask it to", prefix: true }, 20 comment_author_email_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your email for comments, if you ask it to", prefix: true }, 21 comment_author_url_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your website for comments, if you ask it to", prefix: true }, 22 "wp-postpass_": { vendor: "WordPress", category: "necessary", purpose: "Remembers the password entered for a protected page", prefix: true }, 18 23 woocommerce_cart_hash: { vendor: "WooCommerce", category: "necessary", purpose: "Shopping cart contents" }, 19 24 woocommerce_items_in_cart: { vendor: "WooCommerce", category: "necessary", purpose: "Cart item count" }, … … 181 186 function identifyBuiltIn(name) { 182 187 if (KNOWN[name]) return KNOWN[name]; 183 var match = null; 188 // Longest prefix wins (wp-settings-time- over wp-settings-). 189 var match = null, matchLen = 0; 184 190 var keys = Object.keys(KNOWN); 185 191 for (var i = 0; i < keys.length; i++) { 186 if (KNOWN[keys[i]].prefix && name.indexOf(keys[i]) === 0) { match = KNOWN[keys[i]]; break; }192 if (KNOWN[keys[i]].prefix && keys[i].length > matchLen && name.indexOf(keys[i]) === 0) { match = KNOWN[keys[i]]; matchLen = keys[i].length; } 187 193 } 188 194 return match; … … 320 326 } 321 327 322 // WordPress core vendors that are always necessary 323 var WP_CORE_VENDORS = ["WordPress", "WooCommerce", "PHP"]; 328 // Vendors listed under WordPress Core Cookies (PHP sessions and 329 // WooCommerce are not core, so they appear in the main table). 330 var WP_CORE_VENDORS = ["WordPress"]; 324 331 var PLUGIN_VENDOR = "CookieFlame"; 325 332 … … 2354 2361 .then(function (data) { 2355 2362 if (!data.success) { 2356 if (startBtn) { startBtn.disabled = false; startBtn.textContent = "Start Cookie Discovery "; }2363 if (startBtn) { startBtn.disabled = false; startBtn.textContent = "Start Cookie Discovery (uses your own browser)"; } 2357 2364 return; 2358 2365 } … … 2405 2412 }) 2406 2413 .catch(function (err) { 2407 if (startBtn) { startBtn.disabled = false; startBtn.textContent = "Start Cookie Discovery "; }2414 if (startBtn) { startBtn.disabled = false; startBtn.textContent = "Start Cookie Discovery (uses your own browser)"; } 2408 2415 cookieflameNotice("Error: " + err.message, "error"); 2409 2416 }); -
cookieflame/tags/5.4.0/assets/js/cookieconsent.js
r3769546 r3771537 1 1 /** 2 * CookieFlame v5. 3.02 * CookieFlame v5.4.0 3 3 * ───────────────────────────────────────────────────────────── 4 4 * Self-hosted cookie consent manager with Google Consent Mode v2, 5 * IAB TCF v2.2,automatic script blocking, iframe facades with5 * automatic script blocking, iframe facades with 6 6 * thumbnails, Elementor video widget support, multi-region 7 7 * compliance (GDPR/CCPA), and passive cookie monitoring. … … 40 40 } catch (e) {} 41 41 42 var PLUGIN_VERSION = "5. 3.0";42 var PLUGIN_VERSION = "5.4.0"; 43 43 44 44 /* ═══════════════════════════════════════════ … … 49 49 cookieExpiry: 365, 50 50 privacyUrl: "/privacy-policy", 51 ccpaUrl: "/privacy-policy#do-not-sell",52 51 geoApi: "", 53 52 logEndpoint: "", 54 53 reconsentDays: 365, 55 54 cspSafe: false, 56 tcfEnabled: true, 55 // IAB TCF v2.2 stub. Off since v5.4.0 (it never carried a TC string); 56 // the code is kept and can be switched on through the 57 // cookieflame_frontend_config filter (tcfEnabled: true). 58 tcfEnabled: false, 57 59 cmpId: 1, 58 60 cmpVersion: 1, … … 65 67 iconPosition: "left", 66 68 consentVersion: "", 69 // Show "Decline All" next to "Accept All" on the GDPR banner's first 70 // screen (Settings > Show "Decline all" on the first screen). 71 rejectFirstScreen: false, 72 // List WordPress login cookies in the declaration under "If you log 73 // in" (Settings > Visitor logins). Login cookies are never listed 74 // with the others, so the declaration is the same for every visitor. 75 visitorLogins: false, 67 76 68 77 autoBlockPatterns: [ … … 122 131 description: 'We use cookies to improve your experience, analyse traffic and personalise content. You can choose which categories to allow.', 123 132 acceptAllButton: "Accept All", 133 rejectAllButton: "Decline All", 124 134 declineAllButton: "Decline All", 125 135 saveButton: "Save Preferences", … … 128 138 alwaysOnLabel: "(always on)", 129 139 ccpaTitle: "Your Privacy Choices", 130 ccpaDescription: 'We use cookies and similar technologies. Under California law, you have the right to opt out of the sale or sharing of your personal information.',140 ccpaDescription: "We use cookies and similar technologies. Under US state privacy laws, such as California's, you have the right to opt out of the sale or sharing of your personal information.", 131 141 ccpaAcceptButton: "OK", 132 ccpaDnsmpiLink: "Do Not Sell My Personal Information" 142 ccpaDnsmpiLink: "Do Not Sell or Share My Personal Information", 143 ccpaGpcNotice: "Your browser asked us not to sell or share your information. We're honouring that.", 144 ccpaChoiceTitle: "Do Not Sell or Share My Personal Information", 145 ccpaChoiceText: "Information about your visit may be used by advertising partners whose cookies run on this site. Under US state privacy laws that can count as selling or sharing. You can opt out in this browser.", 146 ccpaOptOutButton: "Opt out", 147 ccpaCancelButton: "Cancel", 148 ccpaOptedOut: "You've opted out of the sale or sharing of your personal information in this browser.", 149 ccpaGpcOptedOut: "Your browser's Global Privacy Control signal asked us not to sell or share your information, so you're opted out in this browser.", 150 ccpaDoneButton: "Done", 151 ccpaOptInButton: "Allow selling or sharing", 152 ccpaOptInGpcNote: "Allowing it overrides your browser's Global Privacy Control signal on this site only.", 153 ccpaMoreSettings: "More cookie settings", 154 gpcPrefNote: "Your browser's Global Privacy Control signal is on, so Marketing stays off unless you switch it on here.", 155 changePreferences: "Change cookie preferences", 156 loginCookiesTitle: "If you log in", 157 loginCookiesIntro: "These cookies are only set if you log in to an account on this site. They keep you signed in and remember your account settings. They're needed for logging in to work, so they don't ask for your consent." 133 158 }, 134 159 … … 181 206 var ext = window.__ccConfig; 182 207 // shallow merge top-level primitives 183 var keys = ["cookieName","cookieExpiry","privacyUrl"," ccpaUrl","geoApi",208 var keys = ["cookieName","cookieExpiry","privacyUrl","geoApi", 184 209 "logEndpoint","reconsentDays","cspSafe","tcfEnabled","cmpId", 185 "cmpVersion","vendorListVersion","storageMethod","cookieDomain","shareSubdomains","theme","showPoweredBy","iconPosition","consentVersion"]; 210 "cmpVersion","vendorListVersion","storageMethod","cookieDomain","shareSubdomains","theme","showPoweredBy","iconPosition","consentVersion", 211 "rejectFirstScreen","visitorLogins"]; 186 212 keys.forEach(function (k) { 187 213 if (ext[k] !== undefined) CONFIG[k] = ext[k]; … … 207 233 // visitor's browser is sending the GPC signal. 208 234 var gpcActive = !!window.__ccGPC; 235 236 /* GPC rules (Cal. Code Regs. tit. 11 §7025, applied in every region): 237 - A GPC signal is a valid opt-out of selling and sharing: Marketing 238 stays off and Google's ad signals are denied, with no extra steps. 239 - The opt-out is saved in the consent cookie (state.gpc), so a later 240 visit without GPC keeps it. 241 - The visitor can opt back in only through a separate choice: the 242 US notice's "Allow selling or sharing" button, or switching 243 Marketing on and saving in the preferences. That sets 244 state.gpcOptIn. OK and Accept All never opt back in. */ 245 function applyGpc(state) { 246 if (gpcActive && state && !state.gpcOptIn) { 247 state.marketing = false; 248 state.gpc = 1; 249 } 250 return state; 251 } 209 252 210 253 // Merged blocking rules from PHP (built-in defaults + auto-generated … … 227 270 cc_anon_id: { vendor: "CookieFlame", category: "necessary", purpose: "Anonymous ID for consent logging", expiry: "1 year" }, 228 271 cookieflame_dnt: { vendor: "CookieFlame", category: "necessary", purpose: "Do-not-track opt-out flag", expiry: "1 year" }, 272 // PHP sessions (started by some plugins and hosts, not by WordPress) 273 PHPSESSID: { vendor: "PHP", category: "necessary", purpose: "Server-side session identifier", expiry: "Session" }, 229 274 // WordPress core 230 PHPSESSID: { vendor: "PHP", category: "necessary", purpose: "Server-side session identifier", expiry: "Session" }, 231 wp_settings_: { vendor: "WordPress", category: "necessary", purpose: "Admin interface preferences", prefix: true, expiry: "1 year" }, 232 "wp-settings-": { vendor: "WordPress", category: "necessary", purpose: "Admin interface preferences", prefix: true, expiry: "1 year" }, 233 "wp-settings-time-": { vendor: "WordPress", category: "necessary", purpose: "Admin settings timestamp", prefix: true, expiry: "1 year" }, 234 wordpress_logged_in_: { vendor: "WordPress", category: "necessary", purpose: "Login session", prefix: true, expiry: "Session" }, 235 wordpress_sec_: { vendor: "WordPress", category: "necessary", purpose: "Secure login session", prefix: true, expiry: "Session" }, 236 wordpress_test_cookie: { vendor: "WordPress", category: "necessary", purpose: "Checks cookie support", expiry: "Session" }, 237 wp_lang: { vendor: "WordPress", category: "necessary", purpose: "Language preference", expiry: "Session" }, 275 "wp-settings-": { vendor: "WordPress", category: "necessary", purpose: "Remembers dashboard layout settings", prefix: true, expiry: "1 year" }, 276 "wp-settings-time-": { vendor: "WordPress", category: "necessary", purpose: "Records when dashboard settings were last saved", prefix: true, expiry: "1 year" }, 277 wordpress_logged_in_: { vendor: "WordPress", category: "necessary", purpose: "Keeps you logged in", prefix: true, expiry: "Session, or 14 days with Remember Me" }, 278 wordpress_sec_: { vendor: "WordPress", category: "necessary", purpose: "Confirms your login when you open the dashboard", prefix: true, expiry: "Session, or 14 days with Remember Me" }, 279 wordpress_rec_: { vendor: "WordPress", category: "necessary", purpose: "Recovery mode after a site error (administrators only)", prefix: true, expiry: "1 week" }, 280 wordpress_test_cookie: { vendor: "WordPress", category: "necessary", purpose: "Checks your browser accepts cookies before you log in", expiry: "Session" }, 281 wp_lang: { vendor: "WordPress", category: "necessary", purpose: "Remembers the language picked on the login page", expiry: "Session" }, 282 comment_author_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your name for comments, if you ask it to", prefix: true, expiry: "1 year" }, 283 comment_author_email_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your email for comments, if you ask it to", prefix: true, expiry: "1 year" }, 284 comment_author_url_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your website for comments, if you ask it to", prefix: true, expiry: "1 year" }, 285 "wp-postpass_": { vendor: "WordPress", category: "necessary", purpose: "Remembers the password entered for a protected page", prefix: true, expiry: "10 days" }, 238 286 // WooCommerce 239 287 woocommerce_cart_hash: { vendor: "WooCommerce", category: "necessary", purpose: "Shopping cart contents", expiry: "Session" }, … … 272 320 if (match) return match; 273 321 274 // Prefix match in built-in database 322 // Prefix match in built-in database: the longest prefix wins, so 323 // wp-settings-time-1 is not taken for a wp-settings- cookie. 324 var matchLen = 0; 275 325 Object.keys(KNOWN_COOKIES).forEach(function (key) { 276 if ( !match && KNOWN_COOKIES[key].prefix&& name.indexOf(key) === 0) {326 if (KNOWN_COOKIES[key].prefix && key.length > matchLen && name.indexOf(key) === 0) { 277 327 match = KNOWN_COOKIES[key]; 328 matchLen = key.length; 278 329 } 279 330 }); 280 331 281 332 return match || null; 333 } 334 335 // Cookies WordPress sets when someone logs in. The declaration never 336 // lists the ones in the viewer's own browser; with visitorLogins on it 337 // lists all of them under "If you log in" instead. wordpress_[hash] is 338 // the login cookie on sites without HTTPS, wordpress_rec_ is admin-only. 339 var LOGIN_COOKIE_RE = /^(?:wordpress_(?:logged_in_|sec_|rec_|test_cookie$|[0-9a-f]{32}$)|wp-settings-|wp_lang$)/; 340 341 function isLoginCookie(name) { 342 return LOGIN_COOKIE_RE.test(name); 343 } 344 345 function loginCookieRows() { 346 var keep = "Session, or 14 days with Remember Me"; 347 return [ 348 { name: "wordpress_logged_in_[hash]", purpose: "Keeps you logged in", expiry: keep }, 349 { name: location.protocol === "https:" ? "wordpress_sec_[hash]" : "wordpress_[hash]", purpose: "Confirms your login when you open your account dashboard", expiry: keep }, 350 { name: "wordpress_test_cookie", purpose: "Checks your browser accepts cookies before you log in", expiry: "Session" }, 351 { name: "wp_lang", purpose: "Remembers the language you picked on the login page", expiry: "Session" }, 352 { name: "wp-settings-[user ID]", purpose: "Remembers your dashboard layout settings", expiry: "1 year" }, 353 { name: "wp-settings-time-[user ID]", purpose: "Records when your dashboard settings were last saved", expiry: "1 year" } 354 ]; 282 355 } 283 356 … … 410 483 // Connecticut, Montana, Oregon, Texas, Delaware, Iowa, Nebraska, 411 484 // New Hampshire, New Jersey, Minnesota, plus others pending). 412 if (gpcActive ) {485 if (gpcActive && !state.gpcOptIn) { 413 486 update.ad_storage = "denied"; 414 487 update.ad_user_data = "denied"; … … 666 739 ]; 667 740 741 // Patterns match whole hostnames (the host itself or a subdomain of 742 // it), plus an optional path prefix: "x.com" matches x.com and 743 // www.x.com but not netflix.com or box.com. 744 function iframePatternMatches(src, pattern) { 745 var host, path; 746 try { 747 var u = new URL(src, location.href); 748 host = u.hostname.toLowerCase(); 749 path = u.pathname.toLowerCase(); 750 } catch (e) { return false; } 751 var slash = pattern.indexOf("/"); 752 var pHost = slash === -1 ? pattern : pattern.slice(0, slash); 753 var pPath = slash === -1 ? "" : pattern.slice(slash); 754 if (host !== pHost && host.slice(-pHost.length - 1) !== "." + pHost) return false; 755 return !pPath || path.indexOf(pPath) === 0; 756 } 757 668 758 function getIframeBlockCategory(src) { 669 759 if (!src) return null; 670 var lower = src.toLowerCase();671 760 for (var i = 0; i < IFRAME_BLOCK_PATTERNS.length; i++) { 672 if ( lower.indexOf(IFRAME_BLOCK_PATTERNS[i].pattern) !== -1) {761 if (iframePatternMatches(src, IFRAME_BLOCK_PATTERNS[i].pattern)) { 673 762 return IFRAME_BLOCK_PATTERNS[i].category; 674 763 } … … 838 927 var src = iframe.src || ""; 839 928 if (!src) continue; 840 for (var j = 0; j < IFRAME_BLOCK_PATTERNS.length; j++) { 841 if (src.indexOf(IFRAME_BLOCK_PATTERNS[j].pattern) !== -1) { 842 iframe.src = src; 843 break; 844 } 845 } 929 if (getIframeBlockCategory(src)) iframe.src = src; 846 930 } 847 931 } … … 896 980 function saveState(state, action) { 897 981 var prev = consentState; 982 // A click on an embed's placeholder loads that embed now; the GPC 983 // opt-out is saved again on the next page view (boot()). 984 if (!(action && action.indexOf("facade_accept_") === 0)) applyGpc(state); 898 985 consentState = state; 899 986 storageSet(CONFIG.cookieName, JSON.stringify(state), CONFIG.cookieExpiry); … … 958 1045 if (!s || typeof s !== "object" || Array.isArray(s)) return null; 959 1046 if (!s.ts || isNaN(new Date(s.ts).getTime())) return null; 960 // Check re-consent expiry 1047 // Check re-consent expiry. A period below 1 day means the default. 1048 var days = Number(CONFIG.reconsentDays); 1049 if (!(days >= 1)) days = 365; 961 1050 var age = (Date.now() - new Date(s.ts).getTime()) / 864e5; 962 if (age > CONFIG.reconsentDays) return null;1051 if (age > days) return null; 963 1052 // Consent version check: if the site owner has clicked 964 1053 // "Invalidate All Consent", the version string will not … … 1183 1272 var alwaysOn = t.required 1184 1273 ? ' <span class="cc-always-on">' + escHtml(txt.alwaysOnLabel) + '</span>' : ""; 1274 var gpcNote = gpcActive && t.id === "marketing" 1275 ? '<div class="cc-gpc-note">' + escHtml(txt.gpcPrefNote) + '</div>' : ""; 1185 1276 return '<div class="cc-cat">' + 1186 1277 '<div><div class="cc-cat-label">' + escHtml(t.name) + alwaysOn + '</div>' + 1187 '<div class="cc-cat-desc">' + escHtml(t.description || "") + '</div> </div>' +1278 '<div class="cc-cat-desc">' + escHtml(t.description || "") + '</div>' + gpcNote + '</div>' + 1188 1279 '<label class="cc-toggle"><input type="checkbox" id="cc-cb-' + escHtml(t.id) + '"' + 1189 1280 ' aria-label="' + escHtml(t.name) + '"' + … … 1200 1291 '<div class="cc-buttons">' + 1201 1292 '<button class="cc-btn cc-btn-accept" id="cc-accept">' + escHtml(txt.acceptAllButton) + '</button>' + 1293 (CONFIG.rejectFirstScreen ? '<button class="cc-btn cc-btn-reject" id="cc-reject">' + escHtml(txt.rejectAllButton) + '</button>' : '') + 1202 1294 '<button class="cc-btn cc-btn-details" id="cc-details" aria-expanded="false">' + escHtml(txt.showDetailsButton) + '</button>' + 1203 1295 '</div>' + … … 1221 1313 document.getElementById("cc-accept").addEventListener("click", acceptAll); 1222 1314 document.getElementById("cc-decline").addEventListener("click", declineAll); 1315 var rejectBtn = document.getElementById("cc-reject"); 1316 if (rejectBtn) rejectBtn.addEventListener("click", declineAll); 1223 1317 document.getElementById("cc-save").addEventListener("click", savePrefs); 1224 1318 document.getElementById("cc-details").addEventListener("click", toggleDetails); 1225 1319 icon.addEventListener("click", function () { showBanner("gdpr"); }); 1226 1320 // No overlay click handler — banner must be dismissed via buttons only 1321 emitRendered("gdpr", banner); 1322 } 1323 1324 // cc:banner-rendered fires once each banner's markup is in the page 1325 // (before it is shown), so other code can adjust it. 1326 // detail: { banner: "gdpr" | "ccpa", element, state } 1327 function emitRendered(type, el) { 1328 emit("cc:banner-rendered", { banner: type, element: el, state: getPublicState() }); 1227 1329 } 1228 1330 … … 1231 1333 ═══════════════════════════════════════════ */ 1232 1334 function buildCcpaBanner() { 1335 if (document.getElementById("cc-ccpa-banner")) return; 1233 1336 var txt = CONFIG.text; 1234 1337 var banner = document.createElement("div"); … … 1240 1343 1241 1344 banner.innerHTML = 1242 '<div class="cc-ccpa-inner">' + 1243 '<div class="cc-ccpa-text"><p>' + escHtml(txt.ccpaDescription) + '</p></div>' + 1345 '<div class="cc-ccpa-inner" id="cc-ccpa-notice">' + 1346 '<div class="cc-ccpa-text"><p>' + escHtml(txt.ccpaDescription) + '</p>' + 1347 (gpcActive ? '<p class="cc-ccpa-gpc">' + escHtml(txt.ccpaGpcNotice) + '</p>' : '') + 1348 '</div>' + 1244 1349 '<div class="cc-ccpa-actions">' + 1245 '<a href=" ' + escHtml(CONFIG.ccpaUrl) + '" class="cc-ccpa-link">' + escHtml(txt.ccpaDnsmpiLink) + '</a>' +1350 '<a href="#" class="cc-ccpa-link" id="cc-ccpa-dns" role="button">' + escHtml(txt.ccpaDnsmpiLink) + '</a>' + 1246 1351 '<button class="cc-btn cc-btn-accept" id="cc-ccpa-ok">' + escHtml(txt.ccpaAcceptButton) + '</button>' + 1247 1352 '</div>' + 1248 '</div>'; 1353 '</div>' + 1354 '<div class="cc-ccpa-choice" id="cc-ccpa-choice"></div>' + 1355 (CONFIG.showPoweredBy ? '<div class="cc-powered"><a href="https://www.cookieflame.com" target="_blank" rel="noopener">Powered by CookieFlame</a></div>' : ''); 1249 1356 1250 1357 document.body.appendChild(banner); 1251 1358 1359 // OK only closes the notice. It never changes the visitor's choices: 1360 // US visitors start with everything allowed, and a GPC visitor's 1361 // opt-out was saved before the notice appeared. 1252 1362 document.getElementById("cc-ccpa-ok").addEventListener("click", function () { 1253 var s = buildStateAllGranted(); 1254 saveState(s, "ccpa_acknowledge"); 1255 banner.classList.remove("cc-show"); 1363 if (consentState) logConsent("ccpa_acknowledge", consentState); 1364 closeCcpaBanner(); 1365 }); 1366 document.getElementById("cc-ccpa-dns").addEventListener("click", function (e) { 1367 e.preventDefault(); 1368 showCcpaChoice(true); 1369 }); 1370 emitRendered("ccpa", banner); 1371 } 1372 1373 // The "Do Not Sell or Share" choice, drawn from the current state. 1374 function renderCcpaChoice() { 1375 var el = document.getElementById("cc-ccpa-choice"); 1376 if (!el) return; 1377 var txt = CONFIG.text; 1378 var optedOut = !(consentState && consentState.marketing); 1379 var html = '<div class="cc-ccpa-choice-inner">' + 1380 '<p class="cc-ccpa-choice-title">' + escHtml(txt.ccpaChoiceTitle) + '</p>'; 1381 if (optedOut) { 1382 html += '<p class="cc-ccpa-choice-status">' + escHtml(gpcActive ? txt.ccpaGpcOptedOut : txt.ccpaOptedOut) + '</p>' + 1383 '<div class="cc-ccpa-choice-buttons">' + 1384 '<button class="cc-btn cc-btn-accept" id="cc-ccpa-done">' + escHtml(txt.ccpaDoneButton) + '</button>' + 1385 '<button class="cc-btn cc-btn-decline" id="cc-ccpa-optin">' + escHtml(txt.ccpaOptInButton) + '</button>' + 1386 '</div>' + 1387 (gpcActive ? '<p class="cc-ccpa-choice-note">' + escHtml(txt.ccpaOptInGpcNote) + '</p>' : ''); 1388 } else { 1389 html += '<p class="cc-ccpa-choice-text">' + escHtml(txt.ccpaChoiceText) + '</p>' + 1390 '<div class="cc-ccpa-choice-buttons">' + 1391 '<button class="cc-btn cc-btn-accept" id="cc-ccpa-optout">' + escHtml(txt.ccpaOptOutButton) + '</button>' + 1392 '<button class="cc-btn cc-btn-decline" id="cc-ccpa-cancel">' + escHtml(txt.ccpaCancelButton) + '</button>' + 1393 '</div>'; 1394 } 1395 html += '<button class="cc-btn cc-btn-details" id="cc-ccpa-more">' + escHtml(txt.ccpaMoreSettings) + '</button></div>'; 1396 el.innerHTML = html; 1397 1398 var bind = function (id, fn) { var b = document.getElementById(id); if (b) b.addEventListener("click", fn); }; 1399 bind("cc-ccpa-done", closeCcpaBanner); 1400 bind("cc-ccpa-cancel", closeCcpaBanner); 1401 bind("cc-ccpa-optout", function () { 1402 var s = copyState(consentState); 1403 s.marketing = false; 1404 saveState(s, "ccpa_opt_out"); 1405 renderCcpaChoice(); 1406 focusFirst("cc-ccpa-choice"); 1256 1407 emit("cc:update"); 1257 1408 }); 1409 bind("cc-ccpa-optin", function () { 1410 var s = copyState(consentState); 1411 s.marketing = true; 1412 if (gpcActive) s.gpcOptIn = 1; 1413 delete s.gpc; 1414 saveState(s, "ccpa_opt_in"); 1415 renderCcpaChoice(); 1416 focusFirst("cc-ccpa-choice"); 1417 emit("cc:update"); 1418 }); 1419 bind("cc-ccpa-more", function () { 1420 closeCcpaBanner(); 1421 openPreferences(); 1422 }); 1423 } 1424 1425 function showCcpaChoice(open) { 1426 var notice = document.getElementById("cc-ccpa-notice"); 1427 var choice = document.getElementById("cc-ccpa-choice"); 1428 if (!notice || !choice) return; 1429 if (open) renderCcpaChoice(); 1430 notice.style.display = open ? "none" : ""; 1431 choice.classList.toggle("cc-open", !!open); 1432 if (open) focusFirst("cc-ccpa-choice"); 1433 } 1434 1435 function closeCcpaBanner() { 1436 var b = document.getElementById("cc-ccpa-banner"); 1437 if (b) b.classList.remove("cc-show"); 1438 showCcpaChoice(false); 1439 document.removeEventListener("keydown", _trapFocus); 1440 if (_preFocusEl && typeof _preFocusEl.focus === "function") { 1441 try { _preFocusEl.focus(); } catch (e) {} 1442 } 1443 _preFocusEl = null; 1444 } 1445 1446 function focusFirst(id) { 1447 var el = document.getElementById(id); 1448 var b = el && el.querySelector("button"); 1449 if (b) { try { b.focus(); } catch (e) {} } 1450 } 1451 1452 // A fresh state with the same choices as src (everything allowed when 1453 // there is none yet), keeping an earlier GPC opt-in. 1454 function copyState(src) { 1455 var s = { ts: new Date().toISOString(), cv: CONFIG.consentVersion, cc: detectedCountry }; 1456 CONFIG.cookieTypes.forEach(function (t) { s[t.id] = src ? !!src[t.id] : true; }); 1457 if (src && src.gpcOptIn) s.gpcOptIn = 1; 1458 return s; 1258 1459 } 1259 1460 … … 1268 1469 function _trapFocus(e) { 1269 1470 if (e.key !== "Tab") return; 1270 var banner = document. getElementById("cc-banner") || document.getElementById("cc-ccpa-banner");1271 if (!banner || !banner.classList.contains("cc-show")) return;1471 var banner = document.querySelector("#cc-banner.cc-show, #cc-ccpa-banner.cc-show"); 1472 if (!banner) return; 1272 1473 var focusable = []; 1273 1474 var all = banner.querySelectorAll('button, a[href], input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'); … … 1278 1479 var p = el.parentElement; 1279 1480 while (p && p !== banner) { 1280 if ( p.style &&window.getComputedStyle(p).display === "none") { hidden = true; break; }1481 if (window.getComputedStyle(p).display === "none") { hidden = true; break; } 1281 1482 p = p.parentElement; 1282 1483 } … … 1295 1496 function showBanner(mode) { 1296 1497 if (mode === "ccpa") { 1498 _preFocusEl = document.activeElement; 1297 1499 requestAnimationFrame(function () { 1298 1500 var b = document.getElementById("cc-ccpa-banner"); 1299 1501 if (b) { 1300 1502 b.classList.add("cc-show"); 1301 _preFocusEl = document.activeElement; 1302 b.focus(); 1503 var choice = document.getElementById("cc-ccpa-choice"); 1504 if (choice && choice.classList.contains("cc-open")) focusFirst("cc-ccpa-choice"); 1505 else b.focus(); 1303 1506 document.addEventListener("keydown", _trapFocus); 1304 1507 } … … 1307 1510 } 1308 1511 _preFocusEl = document.activeElement; 1512 // "Decline All" on the first screen is for the GDPR banner only, never 1513 // when the preferences are opened by a US or other visitor. 1514 var rj = document.getElementById("cc-reject"); 1515 if (rj) { 1516 if (complianceMode === "gdpr") rj.style.removeProperty("display"); 1517 else rj.style.setProperty("display", "none", "important"); // beats .cc-btn's !important 1518 } 1309 1519 requestAnimationFrame(function () { 1310 1520 var ov = document.getElementById("cc-overlay"); … … 1354 1564 ═══════════════════════════════════════════ */ 1355 1565 function acceptAll() { 1356 saveState(buildStateAllGranted(), "accept_all"); 1566 var s = buildStateAllGranted(); 1567 if (consentState && consentState.gpcOptIn) s.gpcOptIn = 1; 1568 saveState(s, "accept_all"); 1357 1569 closeBanner(); 1358 1570 emit("cc:accept-all"); emit("cc:update"); … … 1366 1578 1367 1579 function savePrefs() { 1368 saveState(buildStateFromCheckboxes(), "save_preferences"); 1580 var s = buildStateFromCheckboxes(); 1581 // Switching Marketing on here is the separate choice that opts a GPC 1582 // visitor back in. 1583 if (s.marketing && (gpcActive || (consentState && consentState.gpcOptIn))) s.gpcOptIn = 1; 1584 saveState(s, "save_preferences"); 1369 1585 closeBanner(); 1370 1586 emit("cc:save"); emit("cc:update"); … … 1383 1599 } 1384 1600 } 1601 1602 /* ═══════════════════════════════════════════ 1603 COOKIE SETTINGS LINK 1604 [cookieflame_settings_link], the declaration's button and any 1605 element with the class cc-open-settings reopen the visitor's 1606 choices: the "Do Not Sell or Share" choice for US visitors, the 1607 preferences for everyone else. 1608 ═══════════════════════════════════════════ */ 1609 function openPreferences() { 1610 if (!document.getElementById("cc-banner")) { 1611 applyThemeClasses(); 1612 buildGdprBanner(); 1613 hideAllBanners(); 1614 if (consentState) syncCheckboxes(consentState); 1615 } 1616 showBanner("gdpr"); 1617 } 1618 1619 function openUsChoices() { 1620 applyThemeClasses(); 1621 buildCcpaBanner(); 1622 showBanner("ccpa"); 1623 showCcpaChoice(true); 1624 } 1625 1626 function openSettings() { 1627 if (complianceMode === "ccpa") openUsChoices(); 1628 else openPreferences(); 1629 } 1630 1631 document.addEventListener("click", function (e) { 1632 var t = e.target && e.target.closest ? e.target.closest(".cc-open-settings") : null; 1633 if (!t) return; 1634 e.preventDefault(); 1635 openSettings(); 1636 }); 1385 1637 1386 1638 /* ═══════════════════════════════════════════ … … 1435 1687 getComplianceMode: function () { return complianceMode; }, 1436 1688 getCountry: function () { return detectedCountry; }, 1437 show: function () { showBanner("gdpr"); }, // always open preference manager 1689 show: openPreferences, // always open preference manager 1690 showSettings: openSettings, // what the Cookie settings link opens 1438 1691 acceptAll: acceptAll, 1439 1692 declineAll: declineAll, … … 1461 1714 // Add browser cookies 1462 1715 var browserCookies = document.cookie.split(";").map(function (c) { return c.trim().split("=")[0]; }).filter(Boolean); 1716 browserCookies = browserCookies.filter(function (n) { return !isLoginCookie(n); }); 1463 1717 for (var b = 0; b < browserCookies.length; b++) { 1464 1718 var bInfo = identifyCookie(browserCookies[b]); … … 1473 1727 for (var c = 0; c < customCookies.length; c++) { 1474 1728 var cc = customCookies[c]; 1475 if (cc.name && cc.vendor && cc.category && cc.category !== "unclassified" && !allCookies[cc.name] ) {1729 if (cc.name && cc.vendor && cc.category && cc.category !== "unclassified" && !allCookies[cc.name] && !isLoginCookie(cc.name)) { 1476 1730 allCookies[cc.name] = { 1477 1731 name: cc.name, vendor: cc.vendor, category: cc.category, … … 1562 1816 } 1563 1817 1818 if (CONFIG.visitorLogins) { 1819 var loginRows = loginCookieRows(); 1820 html += '<div class="cc-declaration-login">'; 1821 html += '<p><b>' + escHtml(CONFIG.text.loginCookiesTitle) + '</b></p>'; 1822 html += '<p>' + escHtml(CONFIG.text.loginCookiesIntro) + '</p>'; 1823 html += '<table><thead><tr>'; 1824 html += '<th>Name</th><th>Provider</th><th>Purpose</th><th>Expiry</th>'; 1825 html += '</tr></thead><tbody>'; 1826 for (var li = 0; li < loginRows.length; li++) { 1827 html += '<tr>'; 1828 html += '<td><code>' + escHtml(loginRows[li].name) + '</code></td>'; 1829 html += '<td>WordPress</td>'; 1830 html += '<td>' + escHtml(loginRows[li].purpose) + '</td>'; 1831 html += '<td>' + escHtml(loginRows[li].expiry) + '</td>'; 1832 html += '</tr>'; 1833 } 1834 html += '</tbody></table></div>'; 1835 } 1836 1837 html += '<p><button type="button" class="cc-open-settings cc-declaration-change">' + escHtml(CONFIG.text.changePreferences) + '</button></p>'; 1564 1838 html += '</div>'; 1565 1839 el.innerHTML = html; … … 1581 1855 var preloadedConsent = loadState(); 1582 1856 if (preloadedConsent) { 1857 // Marketing scripts created before boot() runs stay blocked for GPC. 1858 if (gpcActive && !preloadedConsent.gpcOptIn) preloadedConsent.marketing = false; 1583 1859 consentState = preloadedConsent; 1584 1860 updateConsentMode(preloadedConsent); … … 1649 1925 var existing = loadState(); 1650 1926 1927 var gpcNewOptOut = false; 1651 1928 if (existing) { 1652 // GPC override: when Global Privacy Control is active, suppress 1653 // marketing consent in the effective state. The stored cookie 1654 // is not modified — if the visitor later disables GPC, their 1655 // original consent choice is restored. This ensures marketing 1656 // scripts and ad signals stay blocked while GPC is active. 1657 if (gpcActive) existing.marketing = false; 1929 // GPC: a signal that arrives after the visitor's choice is 1930 // processed as an opt-out and saved, so it holds on later visits 1931 // without GPC (§7025(c)(5)). An earlier opt-in made while GPC was 1932 // on (gpcOptIn) is kept. 1933 if (gpcActive && !existing.gpcOptIn && (existing.marketing || !existing.gpc)) { 1934 gpcNewOptOut = !!existing.marketing; 1935 applyGpc(existing); 1936 storageSet(CONFIG.cookieName, JSON.stringify(existing), CONFIG.cookieExpiry); 1937 } 1658 1938 consentState = existing; 1659 1939 updateConsentMode(existing); … … 1681 1961 if (ic) ic.classList.add("cc-show"); 1682 1962 } 1963 if (gpcNewOptOut) logConsent("gpc_optout", existing); 1683 1964 emit("cc:ready"); 1684 1965 renderDeclaration(); … … 1691 1972 if (ic) ic.classList.add("cc-show"); 1692 1973 } 1974 if (gpcNewOptOut) logConsent("gpc_optout", existing); 1693 1975 emit("cc:ready"); 1694 1976 renderDeclaration(); … … 1714 1996 startIframeObserver(); 1715 1997 if (gpcActive) { 1716 // GPC active: grant analytics and preferences but deny marketing. 1717 // This fulfills the CCPA/CPRA requirement to honor Global Privacy 1718 // Control as a valid opt-out of the sale or sharing of personal 1719 // information. Fines have been issued for ignoring GPC (Sephora 1720 // $1.2M, DoorDash $375K, Tractor Supply $1.3M). 1721 var gpcState = buildStateAllGranted(); 1722 gpcState.marketing = false; 1723 saveState(gpcState, "ccpa_gpc_optout"); 1998 // GPC active: allow analytics and preferences but opt out of 1999 // selling and sharing (Marketing off), saved before the notice 2000 // appears. The notice says the signal was honoured. 2001 saveState(applyGpc(buildStateAllGranted()), "ccpa_gpc_optout"); 1724 2002 } else { 1725 2003 saveState(buildStateAllGranted(), "ccpa_auto_grant"); -
cookieflame/tags/5.4.0/assets/js/cookieconsent.min.js
r3769546 r3771537 1 1 /** 2 * CookieFlame v5. 3.02 * CookieFlame v5.4.0 3 3 * ───────────────────────────────────────────────────────────── 4 4 * Self-hosted cookie consent manager with Google Consent Mode v2, 5 * IAB TCF v2.2,automatic script blocking, iframe facades with5 * automatic script blocking, iframe facades with 6 6 * thumbnails, Elementor video widget support, multi-region 7 7 * compliance (GDPR/CCPA), and passive cookie monitoring. … … 17 17 * • Timezone-based geo heuristic with server-side fallback 18 18 */ 19 !function(){"use strict";var e={};try{for(var t=document.cookie.split(";"),n=0;n<t.length;n++){var o=t[n].trim().split("=")[0];o&&(e[o]=!0)}}catch(e){}var c="5.3.0",r={cookieName:"cc_consent",cookieExpiry:365,privacyUrl:"/privacy-policy",ccpaUrl:"/privacy-policy#do-not-sell",geoApi:"",logEndpoint:"",reconsentDays:365,cspSafe:!1,tcfEnabled:!0,cmpId:1,cmpVersion:1,vendorListVersion:2,storageMethod:"cookie",cookieDomain:"",shareSubdomains:!1,theme:"light",showPoweredBy:!1,iconPosition:"left",consentVersion:"",autoBlockPatterns:[{pattern:"googletagmanager.com/gtag",category:"analytics"},{pattern:"google-analytics.com",category:"analytics"},{pattern:"analytics.google.com",category:"analytics"},{pattern:"hotjar.com",category:"analytics"},{pattern:"mixpanel.com",category:"analytics"},{pattern:"segment.com/analytics",category:"analytics"},{pattern:"segment.io",category:"analytics"},{pattern:"clarity.ms",category:"analytics"},{pattern:"plausible.io",category:"analytics"},{pattern:"js.hs-analytics.net",category:"analytics"},{pattern:"connect.facebook.net",category:"marketing"},{pattern:"snap.licdn.com",category:"marketing"},{pattern:"ads.linkedin.com",category:"marketing"},{pattern:"googleads.g.doubleclick.net",category:"marketing"},{pattern:"googlesyndication.com",category:"marketing"},{pattern:"googleadservices.com",category:"marketing"},{pattern:"ads-twitter.com",category:"marketing"},{pattern:"analytics.tiktok.com",category:"marketing"},{pattern:"pintrk",category:"marketing"},{pattern:"s.pinimg.com/ct",category:"marketing"},{pattern:"widget.intercom.io",category:"preferences"},{pattern:"js.intercomcdn.com",category:"preferences"}],regionRules:{gdpr:["AT","BE","BG","CY","CZ","DE","DK","EE","ES","FI","FR","GR","HR","HU","IE","IT","LT","LU","LV","MT","NL","PL","PT","RO","SE","SI","SK","IS","LI","NO","GB","GG","JE","IM","CH","BR","ZA"],ccpa:["US"]},text:{title:"We value your privacy",description:"We use cookies to improve your experience, analyse traffic and personalise content. You can choose which categories to allow.",acceptAllButton:"Accept All",declineAllButton:"Decline All",saveButton:"Save Preferences",showDetailsButton:"Manage preferences",hideDetailsButton:"Hide preferences",alwaysOnLabel:"(always on)",ccpaTitle:"Your Privacy Choices",ccpaDescription:"We use cookies and similar technologies. Under California law, you have the right to opt out of the sale or sharing of your personal information.",ccpaAcceptButton:"OK",ccpaDnsmpiLink:"Do Not Sell My Personal Information"},cookieTypes:[{id:"necessary",name:"Necessary",description:"Required for the site to function. Cannot be disabled.",required:!0,consentMode:["security_storage","functionality_storage"],tcfPurposes:[1],onAccept:function(){}},{id:"preferences",name:"Preferences",description:"Remember your settings and personalisation choices.",required:!1,defaultValue:!1,consentMode:["functionality_storage","personalization_storage"],tcfPurposes:[2,3],onAccept:function(){},onReject:function(){}},{id:"analytics",name:"Analytics",description:"Help us understand how visitors use the site.",required:!1,defaultValue:!1,consentMode:["analytics_storage"],tcfPurposes:[4,5,6],onAccept:function(){},onReject:function(){}},{id:"marketing",name:"Marketing",description:"Used to deliver relevant ads and track campaigns.",required:!1,defaultValue:!1,consentMode:["ad_storage","ad_user_data","ad_personalization"],tcfPurposes:[7,8,9,10],onAccept:function(){},onReject:function(){}}]};if(window.__ccConfig&&"object"==typeof window.__ccConfig){var a=window.__ccConfig;["cookieName","cookieExpiry","privacyUrl","ccpaUrl","geoApi","logEndpoint","reconsentDays","cspSafe","tcfEnabled","cmpId","cmpVersion","vendorListVersion","storageMethod","cookieDomain","shareSubdomains","theme","showPoweredBy","iconPosition","consentVersion"].forEach(function(e){void 0!==a[e]&&(r[e]=a[e])}),a.text&&"object"==typeof a.text&&Object.keys(a.text).forEach(function(e){r.text[e]=a.text[e]}),Array.isArray(a.cookieTypes)&&(r.cookieTypes=a.cookieTypes),Array.isArray(a.autoBlockPatterns)&&(r.autoBlockPatterns=a.autoBlockPatterns),a.regionRules&&(r.regionRules=a.regionRules)}window.__ccConsentVersion&&(r.consentVersion=window.__ccConsentVersion);var i=!!window.__ccGPC;window.__ccBlockRules&&Array.isArray(window.__ccBlockRules)&&window.__ccBlockRules.length&&(r.autoBlockPatterns=window.__ccBlockRules);var s={cc_consent:{vendor:"CookieFlame",category:"necessary",purpose:"Stores your consent preferences",expiry:"1 year"},cc_anon_id:{vendor:"CookieFlame",category:"necessary",purpose:"Anonymous ID for consent logging",expiry:"1 year"},cookieflame_dnt:{vendor:"CookieFlame",category:"necessary",purpose:"Do-not-track opt-out flag",expiry:"1 year"},PHPSESSID:{vendor:"PHP",category:"necessary",purpose:"Server-side session identifier",expiry:"Session"},wp_settings_:{vendor:"WordPress",category:"necessary",purpose:"Admin interface preferences",prefix:!0,expiry:"1 year"},"wp-settings-":{vendor:"WordPress",category:"necessary",purpose:"Admin interface preferences",prefix:!0,expiry:"1 year"},"wp-settings-time-":{vendor:"WordPress",category:"necessary",purpose:"Admin settings timestamp",prefix:!0,expiry:"1 year"},wordpress_logged_in_:{vendor:"WordPress",category:"necessary",purpose:"Login session",prefix:!0,expiry:"Session"},wordpress_sec_:{vendor:"WordPress",category:"necessary",purpose:"Secure login session",prefix:!0,expiry:"Session"},wordpress_test_cookie:{vendor:"WordPress",category:"necessary",purpose:"Checks cookie support",expiry:"Session"},wp_lang:{vendor:"WordPress",category:"necessary",purpose:"Language preference",expiry:"Session"},woocommerce_cart_hash:{vendor:"WooCommerce",category:"necessary",purpose:"Shopping cart contents",expiry:"Session"},woocommerce_items_in_cart:{vendor:"WooCommerce",category:"necessary",purpose:"Cart item count",expiry:"Session"},wp_woocommerce_session_:{vendor:"WooCommerce",category:"necessary",purpose:"Shopping session",prefix:!0,expiry:"2 days"},_ga:{vendor:"Google Analytics",category:"analytics",purpose:"Distinguishes visitors",expiry:"2 years"},_ga_:{vendor:"Google Analytics",category:"analytics",purpose:"Stores session state",prefix:!0,expiry:"2 years"},_gid:{vendor:"Google Analytics",category:"analytics",purpose:"Distinguishes visitors (24h)",expiry:"24 hours"},_gat:{vendor:"Google Analytics",category:"analytics",purpose:"Throttles request rate",expiry:"1 minute"},wp_wk_ga_untrack_:{vendor:"Google Analytics",category:"analytics",purpose:"GA opt-out flag",prefix:!0,expiry:"Persistent"},_gcl_au:{vendor:"Google Ads",category:"marketing",purpose:"Conversion linker",expiry:"90 days"},_gcl_aw:{vendor:"Google Ads",category:"marketing",purpose:"Stores ad click info",expiry:"90 days"},_gac_:{vendor:"Google Ads",category:"marketing",purpose:"Campaign information",prefix:!0,expiry:"90 days"},IDE:{vendor:"Google DoubleClick",category:"marketing",purpose:"Ad serving and frequency",expiry:"1 year"},test_cookie:{vendor:"Google DoubleClick",category:"marketing",purpose:"Checks cookie support",expiry:"15 minutes"},NID:{vendor:"Google",category:"preferences",purpose:"Stores preferences",expiry:"6 months"}},d=window.__ccCustomCookies||[];function u(e){for(var t=0;t<d.length;t++){var n=d[t];if("exact"===n.match&&e===n.name)return n;if("prefix"===n.match&&0===e.indexOf(n.name))return n;if("contains"===n.match&&-1!==e.indexOf(n.name))return n}var o=s[e];return o||(Object.keys(s).forEach(function(t){!o&&s[t].prefix&&0===e.indexOf(t)&&(o=s[t])}),o||null)}var l=null,p="none",f=null,m=null,g=!1,y=0;function v(e){return"string"!=typeof e&&(e=String(e)),e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function h(e){if("localStorage"===r.storageMethod)try{return localStorage.getItem(e)}catch(e){return null}var t=document.cookie.match(new RegExp("(?:^|;\\s*)"+e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));if(!t)return null;try{return decodeURIComponent(t[1])}catch(e){return null}}function w(e,t,n){if("localStorage"!==r.storageMethod){var o=new Date;o.setTime(o.getTime()+864e5*n);var c=e+"="+encodeURIComponent(t)+";expires="+o.toUTCString()+";path=/;SameSite=Lax;Secure";r.cookieDomain&&(c+=";domain="+r.cookieDomain),document.cookie=c}else try{localStorage.setItem(e,t)}catch(e){}}function b(){if(m)return m;var e=h("cc_anon_id");return e?(m=e,e):(w("cc_anon_id",m="cc_"+function(){if("undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID)try{return crypto.randomUUID()}catch(e){}return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}(),730),m)}function k(){var e={timestamp:l?l.ts:null,complianceMode:p,country:f,gpc:i,consentVersion:r.consentVersion};return r.cookieTypes.forEach(function(t){e[t.id]=l?!!l[t.id]:!!t.required}),e}function _(e,t){window.dispatchEvent(new CustomEvent(e,{detail:t||k()}))}function x(){window.dataLayer=window.dataLayer||[],window.dataLayer.push(arguments)}function E(e){var t={};r.cookieTypes.forEach(function(n){n.consentMode&&n.consentMode.forEach(function(o){t[o]=e[n.id]?"granted":"denied"})}),i&&(t.ad_storage="denied",t.ad_user_data="denied",t.ad_personalization="denied"),x("consent","update",t)}var A=new Map,S=!1;function L(e){var t={};return r.cookieTypes.forEach(function(n){n.tcfPurposes&&n.tcfPurposes.forEach(function(o){t[o]=!!e[n.id]})}),{tcString:"",tcfPolicyVersion:4,cmpId:r.cmpId,cmpVersion:r.cmpVersion,gdprApplies:"gdpr"===p,eventStatus:"useractioncomplete",cmpStatus:"loaded",listenerId:void 0,isServiceSpecific:!0,useNonStandardTexts:!1,publisherCC:"GB",purposeOneTreatment:!1,purpose:{consents:t,legitimateInterests:{}},vendor:{consents:{},legitimateInterests:{}},specialFeatureOptins:{},publisher:{consents:{},legitimateInterests:{},customPurpose:{consents:{},legitimateInterests:{}},restrictions:{}}}}function C(e,t,n,o){if("function"==typeof n)if("addEventListener"===e){var c=++y;if(A.set(c,n),l){var a=L(l);a.listenerId=c,n(a,!0)}}else"removeEventListener"===e?n({listenerId:o},A.delete(o)):"getTCData"===e?n(l?L(l):null,!!l):"ping"===e?n({gdprApplies:"gdpr"===p,cmpLoaded:S,cmpStatus:S?"loaded":"stub",displayStatus:"hidden",apiVersion:"2.2",cmpVersion:r.cmpVersion,cmpId:r.cmpId,gvlVersion:r.vendorListVersion,tcfPolicyVersion:4},!0):n(null,!1)}function B(e){A.forEach(function(t,n){var o=L(e);o.listenerId=n;try{t(o,!0)}catch(e){}})}function I(e){if(!e)return null;for(var t=e.toLowerCase(),n=0;n<r.autoBlockPatterns.length;n++){var o=r.autoBlockPatterns[n];if(o.pattern instanceof RegExp){if(o.pattern.test(t))return o.category}else if(-1!==t.indexOf(o.pattern.toLowerCase()))return o.category}return null}function T(e){document.querySelectorAll('script[type="text/plain"][data-cc]').forEach(function(t){var n=(t.getAttribute("data-cc")||"").toLowerCase();if(e[n]&&!t.getAttribute("data-cc-activated")){t.setAttribute("data-cc-activated","1");var o=document.createElement("script");o.isWPRocket=!0,Array.from(t.attributes).forEach(function(e){"type"!==e.name&&"data-cc"!==e.name&&"data-cc-activated"!==e.name&&"data-cc-auto"!==e.name&&o.setAttribute(e.name,e.value)}),!t.src&&t.textContent&&(o.textContent=t.textContent),t.parentNode.insertBefore(o,t.nextSibling),t.parentNode.removeChild(t)}}),["img","iframe","link"].forEach(function(t){document.querySelectorAll(t+"[data-cc][data-cc-src]").forEach(function(n){var o=(n.getAttribute("data-cc")||"").toLowerCase();e[o]&&!n.getAttribute("data-cc-activated")&&(n.setAttribute("data-cc-activated","1"),"link"===t?n.href=n.getAttribute("data-cc-src"):n.src=n.getAttribute("data-cc-src"))})})}var O=[{pattern:"youtube.com",category:"marketing"},{pattern:"youtube-nocookie.com",category:"marketing"},{pattern:"youtu.be",category:"marketing"},{pattern:"player.vimeo.com",category:"marketing"},{pattern:"vimeo.com",category:"marketing"},{pattern:"dailymotion.com",category:"marketing"},{pattern:"open.spotify.com",category:"preferences"},{pattern:"soundcloud.com",category:"preferences"},{pattern:"google.com/maps",category:"preferences"},{pattern:"maps.google.com",category:"preferences"},{pattern:"facebook.com/plugins",category:"marketing"},{pattern:"instagram.com",category:"marketing"},{pattern:"tiktok.com/embed",category:"marketing"},{pattern:"twitter.com",category:"marketing"},{pattern:"x.com",category:"marketing"}];function D(){return!(!document.body||!(document.body.classList.contains("elementor-editor-active")||document.body.classList.contains("fl-builder-edit")||document.body.classList.contains("et-fb-root-ancestor")))}var P=[],M=0,R=null,q=!1;function V(){if(R&&P.length){var e=P;P=[];try{R(e)}catch(e){}}}function N(){0!==P.length&&(2!==M?1!==M&&3!==M&&(M=1,function(){var e=window.__ccConfig&&window.__ccConfig.facadesUrl||null;if(!e)return void(M=3);var t=document.createElement("script");t.src=e,t.async=!0,t.onload=function(){M=2,V()},t.onerror=function(){M=3},(document.head||document.documentElement).appendChild(t)}()):V())}function U(e){var t=e.src||e.getAttribute("data-src")||"";if(t&&!e.getAttribute("data-cc-facade")&&!D()){var n=function(e){if(!e)return null;for(var t=e.toLowerCase(),n=0;n<O.length;n++)if(-1!==t.indexOf(O[n].pattern))return O[n].category;return null}(t);if(n){var o=l||Z();o&&o[n]||(e.setAttribute("data-cc-facade","1"),e.setAttribute("data-cc-iframe-cat",n),e.setAttribute("data-cc-original-src",t),e.removeAttribute("src"),e.style.display="none",P.push({type:"iframe",node:e,category:n,src:t}),N())}}}function G(e){if(!e.getAttribute("data-cc-facade")&&!D()){var t=e.getAttribute("data-settings");if(t){var n;try{n=JSON.parse(t)}catch(e){return}var o=n.youtube_url||n.vimeo_url||"";if(o){var c=o.toLowerCase(),r=null;if((-1!==c.indexOf("youtube")||-1!==c.indexOf("youtu.be")||-1!==c.indexOf("vimeo")||-1!==c.indexOf("dailymotion"))&&(r="marketing"),r&&(!l||!l[r])){if(!l){var a=Z();if(a&&a[r])return}e.setAttribute("data-cc-facade","1"),q=!0,P.push({type:"elementor",node:e,category:r,url:o}),N()}}}}}function j(){for(var e=document.querySelectorAll("iframe[src]"),t=0;t<e.length;t++)U(e[t]);for(var n=document.querySelectorAll(".elementor-widget-video"),o=0;o<n.length;o++)G(n[o]);for(var c=document.querySelectorAll(".wp-block-embed__wrapper iframe[src]"),r=0;r<c.length;r++)U(c[r])}function J(){new MutationObserver(function(e){e.forEach(function(e){e.addedNodes.forEach(function(e){if(1===e.nodeType){if("IFRAME"===e.tagName&&U(e),e.querySelectorAll){for(var t=e.querySelectorAll("iframe[src]"),n=0;n<t.length;n++)U(t[n]);for(var o=e.querySelectorAll(".elementor-widget-video"),c=0;c<o.length;c++)G(o[c])}e.classList&&e.classList.contains("elementor-widget-video")&&G(e)}})})}).observe(document.documentElement,{childList:!0,subtree:!0})}function W(){for(var e=document.querySelectorAll("iframe[src]"),t=0;t<e.length;t++){var n=e[t];if(!n.getAttribute("data-cc-facade")){var o=n.src||"";if(o)for(var c=0;c<O.length;c++)if(-1!==o.indexOf(O[c].pattern)){n.src=o;break}}}}function H(e,t){r.cookieTypes.forEach(function(n){var o=!!e[n.id];if((t?!!t[n.id]:null)!==o||null===t)if(o&&"function"==typeof n.onAccept)try{n.onAccept()}catch(e){console.warn("[CookieConsent] onAccept error for "+n.id,e)}else if(!o&&"function"==typeof n.onReject)try{n.onReject()}catch(e){console.warn("[CookieConsent] onReject error for "+n.id,e)}})}function F(e,t){var n=l;if(l=e,w(r.cookieName,JSON.stringify(e),r.cookieExpiry),E(e),q&&t)for(var o=document.querySelectorAll(".cc-iframe-facade[data-cc-elementor-widget]"),a=0;a<o.length;a++){var s=o[a].getAttribute("data-cc-iframe-cat");if(s&&e[s])return void window.location.reload()}K(e,n),T(e),function(e){for(var t=document.querySelectorAll(".cc-iframe-facade"),n=0;n<t.length;n++)if(!t[n].getAttribute("data-cc-elementor-widget")&&e[t[n].getAttribute("data-cc-iframe-cat")]){var o=t[n].nextElementSibling;if(o&&"IFRAME"===o.tagName&&o.getAttribute("data-cc-facade")){var c=o.getAttribute("data-cc-original-src");c&&(o.src=c,o.style.display="",o.removeAttribute("data-cc-facade"))}t[n].parentNode.removeChild(t[n])}}(e),t&&"auto_grant_non_eu"!==t&&"ccpa_auto_grant"!==t&&setTimeout(W,300),H(e,n),$(e),r.tcfEnabled&&B(e),t&&function(e,t){if(r.logEndpoint){var n={};r.cookieTypes.forEach(function(e){n[e.id]=!!t[e.id]});var o={anonymousId:b(),timestamp:(new Date).toISOString(),action:e,consent:n,complianceMode:p,country:f,url:location.href,userAgent:navigator.userAgent,tcfEnabled:r.tcfEnabled,gpc:i,consentVersion:r.consentVersion,version:c};try{var a=JSON.stringify(o);navigator.sendBeacon?navigator.sendBeacon(r.logEndpoint,new Blob([a],{type:"application/json"})):fetch(r.logEndpoint,{method:"POST",body:a,headers:{"Content-Type":"application/json"},keepalive:!0})}catch(e){}}}(t,e)}function $(e){r.cookieTypes.forEach(function(t){if(!t.required){var n=document.getElementById("cc-cb-"+t.id);n&&(n.checked=!!e[t.id])}});var t=document.getElementById("cc-cats"),n=document.getElementById("cc-details"),o=document.getElementById("cc-pref-buttons");t&&t.classList.remove("cc-open"),n&&(n.textContent=r.text.showDetailsButton),o&&o.classList.remove("cc-open")}function Z(){var e=h(r.cookieName);if(!e)return null;try{var t=JSON.parse(e);return!t||"object"!=typeof t||Array.isArray(t)||(!t.ts||isNaN(new Date(t.ts).getTime())||(Date.now()-new Date(t.ts).getTime())/864e5>r.reconsentDays||r.consentVersion&&t.cv!==r.consentVersion)?null:t}catch(e){return null}}function Y(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:f};return r.cookieTypes.forEach(function(t){e[t.id]=!0}),e}function z(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:f};return r.cookieTypes.forEach(function(t){e[t.id]=!!t.required}),e}function K(e,t){if(t){var n=[];if(r.cookieTypes.forEach(function(o){o.required||t[o.id]&&!e[o.id]&&n.push(o.id)}),n.length)document.cookie.split(";").map(function(e){return e.trim().split("=")[0]}).filter(Boolean).forEach(function(e){var t=u(e);if(t&&-1!==n.indexOf(t.category)){var o=location.hostname,c="."+o;document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/",document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT",document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+c,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+o,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; Secure",r.cookieDomain&&(document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+r.cookieDomain,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+r.cookieDomain+"; Secure");var a=o.split(".");if(a.length>2){var i="."+a.slice(1).join(".");document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+i,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+i+"; Secure"}}})}}function Q(e){return(f=e?e.toUpperCase():null)?r.regionRules.gdpr&&-1!==r.regionRules.gdpr.indexOf(f)?"gdpr":r.regionRules.ccpa&&-1!==r.regionRules.ccpa.indexOf(f)?"ccpa":"none":"gdpr"}function X(e){if(window.__ccGeo)return e(Q(window.__ccGeo));r.geoApi?fetch(r.geoApi).then(function(e){return e.json()}).then(function(t){var n=t.country_code||t.country||"";n?e(Q(n)):ee(e)}).catch(function(){ee(e)}):ee(e)}function ee(e){var t="";try{t=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(e){}return 0===t.indexOf("Europe/")||"Atlantic/Reykjavik"===t?(f=null,e("gdpr")):0===t.indexOf("US/")||0===t.indexOf("America/")&&["New_York","Chicago","Denver","Los_Angeles","Phoenix","Anchorage","Boise","Detroit","Indianapolis","Louisville","Menominee","Nome","Adak","Juneau","Sitka","Yakutat","Metlakatla","Kentucky","Indiana","North_Dakota"].some(function(e){return-1!==t.indexOf(e)})||"Pacific/Honolulu"===t?(f="US",e("ccpa")):(f=null,void e("gdpr"))}function te(){"dark"===r.theme&&document.documentElement.classList.add("cc-dark"),"right"===r.iconPosition&&document.documentElement.classList.add("cc-icon-right")}function ne(){var e=r.text,t=document.createElement("div");t.id="cc-overlay",document.body.appendChild(t);var n=document.createElement("div");n.id="cc-banner",n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),n.setAttribute("aria-label","Cookie consent"),n.setAttribute("tabindex","-1");var o=r.cookieTypes.map(function(t){var n=t.required?' <span class="cc-always-on">'+v(e.alwaysOnLabel)+"</span>":"";return'<div class="cc-cat"><div><div class="cc-cat-label">'+v(t.name)+n+'</div><div class="cc-cat-desc">'+v(t.description||"")+'</div></div><label class="cc-toggle"><input type="checkbox" id="cc-cb-'+v(t.id)+'" aria-label="'+v(t.name)+'"'+(t.required||t.defaultValue?" checked":"")+(t.required?" disabled":"")+'><span class="cc-slider"></span></label></div>'}).join("");n.innerHTML='<div class="cc-inner"><p class="cc-title">'+v(e.title)+'</p><p class="cc-text">'+v(e.description)+' <a href="'+v(r.privacyUrl)+'">Privacy Policy</a></p><div class="cc-buttons"><button class="cc-btn cc-btn-accept" id="cc-accept">'+v(e.acceptAllButton)+'</button><button class="cc-btn cc-btn-details" id="cc-details" aria-expanded="false">'+v(e.showDetailsButton)+'</button></div><div class="cc-categories" id="cc-cats">'+o+'</div><div class="cc-pref-buttons" id="cc-pref-buttons"><button class="cc-btn cc-btn-save" id="cc-save">'+v(e.saveButton)+'</button><button class="cc-btn cc-btn-decline" id="cc-decline">'+v(e.declineAllButton)+"</button></div></div>"+(r.showPoweredBy?'<div class="cc-powered"><a href="https://www.cookieflame.com" target="_blank" rel="noopener">Powered by CookieFlame</a></div>':""),document.body.appendChild(n);var c=document.createElement("button");c.id="cc-icon",c.setAttribute("aria-label","Cookie settings"),c.innerHTML='<svg width="24" height="24" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19.1 1.16c-.06-.42-.38-.77-.8-.83A11.8 11.8 0 0 0 13.28 1.2L7.5 4.3A11.8 11.8 0 0 0 3.95 7.98L1.09 13.9A11.8 11.8 0 0 0 .38 18.96l1.13 6.47a11.8 11.8 0 0 0 3.53 4.99l4.71 4.56a11.8 11.8 0 0 0 4.59 2.24l6.48.91a11.8 11.8 0 0 0 5.52-.89l5.78-3.09a11.8 11.8 0 0 0 3.55-3.69l2.87-5.9a11.8 11.8 0 0 0 .71-5.08c-.07-.41-.42-.73-.83-.79a14.8 14.8 0 0 1-8.16-7.61c-.14-.59-.63-1.05-1.23-1.14A14.8 14.8 0 0 1 19.1 1.16ZM15.25 10.25a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Zm-5 15a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0Zm17.5-5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z" fill="currentColor"/></svg>',document.body.appendChild(c),document.getElementById("cc-accept").addEventListener("click",se),document.getElementById("cc-decline").addEventListener("click",de),document.getElementById("cc-save").addEventListener("click",ue),document.getElementById("cc-details").addEventListener("click",le),c.addEventListener("click",function(){re("gdpr")})}var oe=null;function ce(e){if("Tab"===e.key){var t=document.getElementById("cc-banner")||document.getElementById("cc-ccpa-banner");if(t&&t.classList.contains("cc-show")){for(var n=[],o=t.querySelectorAll('button, a[href], input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'),c=0;c<o.length;c++){for(var r=o[c],a=!1,i=r.parentElement;i&&i!==t;){if(i.style&&"none"===window.getComputedStyle(i).display){a=!0;break}i=i.parentElement}a||n.push(r)}if(0!==n.length){var s=n[0],d=n[n.length-1];e.shiftKey?document.activeElement===s&&(e.preventDefault(),d.focus()):document.activeElement===d&&(e.preventDefault(),s.focus())}}}}function re(e){"ccpa"!==e?(oe=document.activeElement,requestAnimationFrame(function(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");if(e&&e.classList.add("cc-show"),t){t.classList.add("cc-show");var o=t.querySelector(".cc-btn-accept");o&&o.focus(),document.addEventListener("keydown",ce)}n&&n.classList.remove("cc-show")})):requestAnimationFrame(function(){var e=document.getElementById("cc-ccpa-banner");e&&(e.classList.add("cc-show"),oe=document.activeElement,e.focus(),document.addEventListener("keydown",ce))})}function ae(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");if(e&&e.classList.remove("cc-show"),t&&t.classList.remove("cc-show"),n&&"gdpr"===p&&n.classList.add("cc-show"),document.removeEventListener("keydown",ce),oe&&"function"==typeof oe.focus)try{oe.focus()}catch(e){}oe=null}function ie(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");e&&e.classList.remove("cc-show"),t&&t.classList.remove("cc-show"),n&&n.classList.remove("cc-show"),document.removeEventListener("keydown",ce)}function se(){F(Y(),"accept_all"),ae(),_("cc:accept-all"),_("cc:update")}function de(){F(z(),"decline_all"),ae(),_("cc:decline-all"),_("cc:update")}function ue(){var e;F((e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:f},r.cookieTypes.forEach(function(t){if(t.required)e[t.id]=!0;else{var n=document.getElementById("cc-cb-"+t.id);e[t.id]=!!n&&n.checked}}),e),"save_preferences"),ae(),_("cc:save"),_("cc:update")}function le(){var e=document.getElementById("cc-cats"),t=document.getElementById("cc-details"),n=document.getElementById("cc-pref-buttons"),o=e.classList.toggle("cc-open");t.textContent=o?r.text.hideDetailsButton:r.text.showDetailsButton,t.setAttribute("aria-expanded",o?"true":"false"),n&&(o?n.classList.add("cc-open"):n.classList.remove("cc-open"))}function pe(){var e=document.getElementById("cc-declaration");if(e){for(var t=k(),n={},o=document.cookie.split(";").map(function(e){return e.trim().split("=")[0]}).filter(Boolean),c=0;c<o.length;c++){var a=u(o[c]);a&&(n[o[c]]={name:o[c],vendor:a.vendor,category:a.category,purpose:a.purpose,expiry:a.expiry||"",active:!0})}for(var i=0;i<d.length;i++){var s=d[i];s.name&&s.vendor&&s.category&&"unclassified"!==s.category&&!n[s.name]&&(n[s.name]={name:s.name,vendor:s.vendor,category:s.category,purpose:s.purpose||"",expiry:s.expiry||"",active:!1})}for(var l=0;l<o.length;l++)n[o[l]]||(n[o[l]]={name:o[l],vendor:"",category:"unclassified",purpose:"",expiry:"",active:!0});var p=["necessary","preferences","analytics","marketing"],f={};r.cookieTypes.forEach(function(e){f[e.id]=e.name});var m={};p.forEach(function(e){m[e]=[]}),Object.keys(n).forEach(function(e){var t=n[e],o=t.category;m[o]||(m[o]=[]),m[o].push(t)}),p.forEach(function(e){m[e]&&m[e].sort(function(e,t){return e.name.localeCompare(t.name)})});var g="<div>";if(t.timestamp){var y=new Date(t.timestamp);g+="<p>"+v("Your consent was last updated on "+y.toLocaleDateString(void 0,{year:"numeric",month:"long",day:"numeric"})+" at "+y.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"})+".")+"</p>"}else g+="<p>"+v("You have not yet made a cookie preference choice on this site.")+"</p>";for(var h=0;h<p.length;h++){var w=p[h],b=m[w];if(b&&b.length){var _,x=f[w]||w,E=r.cookieTypes.filter(function(e){return e.id===w})[0],A=E&&E.description||"";_=!!E&&E.required?"Always active":t[w]?"Active":"Inactive",g+="<div>",g+="<p><b>"+v(x)+"</b> ("+v(_)+")</p>",A&&(g+="<p>"+v(A)+"</p>"),g+="<table><thead><tr>",g+="<th>Name</th><th>Provider</th><th>Purpose</th><th>Expiry</th>",g+="</tr></thead><tbody>";for(var S=0;S<b.length;S++){var L=b[S];g+="<tr>",g+="<td><code>"+v(L.name)+"</code></td>",g+="<td>"+v(L.vendor||"Unknown")+"</td>",g+="<td>"+v(L.purpose||"Not specified")+"</td>",g+="<td>"+v(L.expiry||"—")+"</td>",g+="</tr>"}g+="</tbody></table></div>"}}g+="</div>",e.innerHTML=g}}window.__ccInternal={getResourceSnapshot:ye,scriptLoaded:function(e){for(var t=ge||ye(),n=0;n<t.allUrls.length;n++)if(-1!==t.allUrls[n].indexOf(e))return!0;return!1},scriptLoadedUrls:function(e){for(var t=ge||ye(),n=[],o=0;o<t.allUrls.length;o++)-1!==t.allUrls[o].indexOf(e)&&n.push(t.allUrls[o]);return n},hasCookie:function(e){return document.cookie.split(";").some(function(t){return t.trim().split("=")[0]===e})},hasCookiePrefix:function(e){return document.cookie.split(";").some(function(t){return 0===t.trim().split("=")[0].indexOf(e)})},setResourceSnapshot:function(e){ge=e},visitorCookies:function(e){for(var t=ye().thirdPartyDomains,n={},o=0;o<e.length;o++)we(e[o],t,he(e[o]))&&(n[e[o]]=!0);return n},facadeBridge:{getConfig:function(){return r},getConsentState:function(){return l},loadConsent:Z,saveConsent:function(e,t){F(e,t)},hideAllBanners:ie,registerRenderer:function(e){R=e,M=2,V()}}},window.CookieConsent={getState:k,getConfig:function(){return r},getComplianceMode:function(){return p},getCountry:function(){return f},show:function(){re("gdpr")},acceptAll:se,declineAll:de,getGPC:function(){return i},getConsentVersion:function(){return r.consentVersion},on:function(e,t){window.addEventListener(e,t)},off:function(e,t){window.removeEventListener(e,t)},VERSION:c},window.addEventListener("cc:ready",pe),window.addEventListener("cc:update",pe),function(){if(!window.__ccDefaultsSet){var e={wait_for_update:500};r.cookieTypes.forEach(function(t){t.consentMode&&t.consentMode.forEach(function(n){e[n]=t.required?"granted":"denied"})}),x("consent","default",e)}}(),function(){if(r.tcfEnabled){var e=window.__tcfapi&&window.__tcfapi.queue?window.__tcfapi.queue:[];window.__tcfapi=C,e.forEach(function(e){C.apply(null,e)}),S=!0,window===window.top&&window.addEventListener("message",function(e){var t;try{t="string"==typeof e.data?JSON.parse(e.data):e.data}catch(e){return}if(t&&t.__tcfapiCall){var n=t.__tcfapiCall;C(n.command,n.version,function(t,o){var c={__tcfapiReturn:{returnValue:t,success:o,callId:n.callId}};e.source&&e.source.postMessage("string"==typeof e.data?JSON.stringify(c):c,"*")},n.parameter)}})}}();var fe=Z();function me(){var e=window.__ccDiscovery&&window.__ccDiscovery.active;if(!e&&(window.__ccDNT||document.cookie.split(";").some(function(e){return 0===e.trim().indexOf("cookieflame_dnt=")})))return E(l=z()),K(l,Y()),J(),j(),p="dnt",te(),ne(),ie(),$(l),_("cc:ready"),void pe();if(e){E(l=Y()),T(l),J(),H(l,null),r.tcfEnabled&&B(l),window.dataLayer=window.dataLayer||[];for(var t=["consent_accepted_analytics","consent_accepted_marketing","consent_accepted_preferences","consent_accepted_necessary","consent_update","consent_given","cookie_consent_given","cookie_consent_update","consent_accepted"],n=0;n<t.length;n++)window.dataLayer.push({event:t[n]});return p="discovery",te(),ne(),ie(),_("cc:ready"),_("cc:update"),void pe()}var o=Z();if(o)if(i&&(o.marketing=!1),l=o,E(o),T(o),J(),j(),H(o,null),r.tcfEnabled&&B(o),te(),ne(),ie(),$(o),o.cc){if("gdpr"===(p=Q(o.cc))){var c=document.getElementById("cc-icon");c&&c.classList.add("cc-show")}_("cc:ready"),pe()}else X(function(e){if(p=e,"gdpr"===e){var t=document.getElementById("cc-icon");t&&t.classList.add("cc-show")}_("cc:ready"),pe()});else X(function(e){if(p=e,"none"===e)return J(),F(Y(),"auto_grant_non_eu"),_("cc:ready"),_("cc:update"),void pe();if("ccpa"===e){if(J(),i){var t=Y();t.marketing=!1,F(t,"ccpa_gpc_optout")}else F(Y(),"ccpa_auto_grant");return te(),n=r.text,(o=document.createElement("div")).id="cc-ccpa-banner",o.setAttribute("role","dialog"),o.setAttribute("aria-modal","true"),o.setAttribute("aria-label","Privacy notice"),o.setAttribute("tabindex","-1"),o.innerHTML='<div class="cc-ccpa-inner"><div class="cc-ccpa-text"><p>'+v(n.ccpaDescription)+'</p></div><div class="cc-ccpa-actions"><a href="'+v(r.ccpaUrl)+'" class="cc-ccpa-link">'+v(n.ccpaDnsmpiLink)+'</a><button class="cc-btn cc-btn-accept" id="cc-ccpa-ok">'+v(n.ccpaAcceptButton)+"</button></div></div>",document.body.appendChild(o),document.getElementById("cc-ccpa-ok").addEventListener("click",function(){F(Y(),"ccpa_acknowledge"),o.classList.remove("cc-show"),_("cc:update")}),re("ccpa"),_("cc:ready"),_("cc:update"),void pe()}var n,o;te(),J(),j(),ne(),re("gdpr"),_("cc:ready"),pe()})}fe&&(l=fe,E(fe)),function(){if(!g&&r.autoBlockPatterns&&r.autoBlockPatterns.length){g=!0;var e=document.createElement.bind(document),t=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype,"src");t&&t.set&&(document.createElement=function(){var n=e.apply(document,arguments);if("script"!==(arguments[0]||"").toLowerCase())return n;function o(e,t){if(t&&"string"==typeof t&&!e.getAttribute("data-cc")&&!e.getAttribute("data-cc-auto")&&-1===t.indexOf("cookieconsent")){var n=I(t);!n||l&&l[n]||(e.type="text/plain",e.setAttribute("data-cc",n),e.setAttribute("data-cc-auto","1"))}}Object.defineProperty(n,"src",{configurable:!0,enumerable:!0,get:function(){return t.get.call(this)},set:function(e){return o(this,e),t.set.call(this,e)}});var c=n.setAttribute.bind(n);return n.setAttribute=function(e,t){return"src"===e.toLowerCase()&&o(n,t),c(e,t)},n}),new MutationObserver(function(e){e.forEach(function(e){e.addedNodes.forEach(function(e){if(1===e.nodeType){var t=[];"SCRIPT"===e.tagName&&t.push(e),e.querySelectorAll&&e.querySelectorAll("script[src]").forEach(function(e){t.push(e)}),t.forEach(function(e){if(!(e.getAttribute("data-cc-auto")||e.getAttribute("data-cc")||e.src&&-1!==e.src.indexOf("cookieconsent"))){var t=I(e.src);t&&(l&&l[t]||(e.type="text/plain",e.setAttribute("data-cc",t),e.setAttribute("data-cc-auto","1")))}})}})})}).observe(document.documentElement,{childList:!0,subtree:!0})}}(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",me):me();var ge=null;function ye(){for(var e=[],t=[],n=[],o={},c={},r=document.querySelectorAll("script[src]"),a=0;a<r.length;a++)r[a].src&&e.push(r[a].src);for(var i=document.querySelectorAll("iframe[src]"),s=0;s<i.length;s++)i[s].src&&e.push(i[s].src);if(window.performance&&performance.getEntriesByType)for(var d=performance.getEntriesByType("resource"),u=0;u<d.length;u++){var l=d[u].name||"";if(l){-1===e.indexOf(l)&&e.push(l);var p=d[u].initiatorType||"";if("script"===p||"iframe"===p||"xmlhttprequest"===p||"fetch"===p||-1!==l.indexOf(".js"))try{var f=new URL(l),m=f.hostname;if(m===location.hostname||m==="www."+location.hostname||"www."+m===location.hostname){var g=f.pathname.match(/\/wp-content\/plugins\/([^\/]+)\//);g&&!c[g[1]]&&(c[g[1]]=!0,n.push({slug:g[1],url:l}))}else o[m]||(o[m]=!0,t.push(m))}catch(e){}}}return{allUrls:e,thirdPartyDomains:t,pluginScripts:n}}var ve={};function he(e){var t={set_by:"",set_by_plugin:""},n=window.__ccCookieSetters;if(!n||!Object.prototype.hasOwnProperty.call(n,e)||!n[e])return t;try{var o=new URL(String(n[e]).replace(/:\d+(:\d+)?$/,""));if(/^(chrome-extension|moz-extension|safari-web-extension|safari-extension):$/.test(o.protocol))return t.set_by="browser-extension",t;var c=o.hostname;if(c===location.hostname||c==="www."+location.hostname||"www."+c===location.hostname){t.set_by="first-party";var r=o.pathname.match(/\/wp-content\/(?:plugins|themes)\/([^\/]+)\//);r&&(t.set_by_plugin=r[1].toLowerCase())}else t.set_by=c.toLowerCase()}catch(e){}return t}function we(e,t,n){if("browser-extension"===n.set_by)return!0;var o=window.__ccVisitorCookieRules;if(!o||!o.tools)return!1;if(o.allow&&-1!==o.allow.indexOf(e))return!1;if(n.set_by)return!1;for(var c=0;c<o.tools.length;c++){for(var r=o.tools[c],a=-1!==(r.n||[]).indexOf(e),i=0;!a&&i<(r.p||[]).length;i++)0===e.indexOf(r.p[i])&&(a=!0);if(a){for(var s=0;s<t.length;s++)for(var d=0;d<(r.d||[]).length;d++){var u=String(t[s]).toLowerCase(),l=r.d[d];if(u===l||u.slice(-l.length-1)==="."+l)return!1}return!0}}return!1}function be(){var t=window.__ccClassified||[],n=window.__ccUnclassified||[],o=window.__ccBeaconUrl;if(o){var c,r={};for(c=0;c<t.length;c++)r[t[c]]=!0;for(c=0;c<n.length;c++)r[n[c]]=!0;var a=Object.keys(ve);for(c=0;c<a.length;c++)r[a[c]]=!0;var i=Object.keys(s);for(c=0;c<i.length;c++)r[i[c]]=!0;var d={},u=[],l=document.cookie.split(";");for(c=0;c<l.length;c++){var p=l[c].indexOf("="),f=(p>-1?l[c].substring(0,p):l[c]).trim(),m=p>-1?l[c].substring(p+1).trim():"";f&&(u.push(f),d[f]=m)}var g=[];for(c=0;c<u.length;c++)if(!r[u[c]]){for(var y=!1,v=Object.keys(s),h=0;h<v.length;h++)if(s[v[h]].prefix&&0===u[c].indexOf(v[h])){y=!0;break}y||g.push(u[c])}if(0!==g.length){for(var w=ye(),b=w.thirdPartyDomains,k=[],_=0;_<w.pluginScripts.length;_++)k.push(w.pluginScripts[_].slug);for(var x=location.pathname,E=0;E<g.length;E++){var A=g[E],S=!!e[A]&&!r[A],L=ke(d[A]||""),C=he(A);if(!we(A,b,C)||window.__ccDiscovery&&window.__ccDiscovery.active){var B=JSON.stringify({cookie_name:A,script_domains:b,plugin_scripts:k,page_path:x,pre_existing:S,value_shape:L,set_by:C.set_by,set_by_plugin:C.set_by_plugin});ve[A]=!0,navigator.sendBeacon&&navigator.sendBeacon(o,new Blob([B],{type:"application/json"}))}else ve[A]=!0}}}}function ke(e){if(!e||0===e.length)return"empty";if("true"===e||"false"===e||"1"===e||"0"===e)return"boolean";if(/^\d+$/.test(e))return e.length>12?"long-numeric":"numeric";if(/^\d+\.\d+$/.test(e))return"decimal";if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e))return"uuid";if(/^[A-Za-z0-9+\/]{20,}={0,2}$/.test(e))return"base64";if(/^(GA\d+\.\d+\.\d+\.\d+|G-[A-Z0-9]+)/.test(e))return"ga-id";try{if("object"==typeof JSON.parse(decodeURIComponent(e)))return"json"}catch(e){}return/^[a-f0-9]{24,}$/i.test(e)?"hex-id":/^\d{10,13}$/.test(e)?"timestamp":e.length<=20?"short-string":e.length<=100?"medium-string":"long-string"}setTimeout(be,3e3),setTimeout(be,15e3),setTimeout(be,45e3),window.addEventListener("cc:update",function(){setTimeout(be,3e3)}),window.addEventListener("pagehide",be)}();19 !function(){"use strict";var e={};try{for(var t=document.cookie.split(";"),n=0;n<t.length;n++){var c=t[n].trim().split("=")[0];c&&(e[c]=!0)}}catch(e){}var o="5.4.0",r={cookieName:"cc_consent",cookieExpiry:365,privacyUrl:"/privacy-policy",geoApi:"",logEndpoint:"",reconsentDays:365,cspSafe:!1,tcfEnabled:!1,cmpId:1,cmpVersion:1,vendorListVersion:2,storageMethod:"cookie",cookieDomain:"",shareSubdomains:!1,theme:"light",showPoweredBy:!1,iconPosition:"left",consentVersion:"",rejectFirstScreen:!1,visitorLogins:!1,autoBlockPatterns:[{pattern:"googletagmanager.com/gtag",category:"analytics"},{pattern:"google-analytics.com",category:"analytics"},{pattern:"analytics.google.com",category:"analytics"},{pattern:"hotjar.com",category:"analytics"},{pattern:"mixpanel.com",category:"analytics"},{pattern:"segment.com/analytics",category:"analytics"},{pattern:"segment.io",category:"analytics"},{pattern:"clarity.ms",category:"analytics"},{pattern:"plausible.io",category:"analytics"},{pattern:"js.hs-analytics.net",category:"analytics"},{pattern:"connect.facebook.net",category:"marketing"},{pattern:"snap.licdn.com",category:"marketing"},{pattern:"ads.linkedin.com",category:"marketing"},{pattern:"googleads.g.doubleclick.net",category:"marketing"},{pattern:"googlesyndication.com",category:"marketing"},{pattern:"googleadservices.com",category:"marketing"},{pattern:"ads-twitter.com",category:"marketing"},{pattern:"analytics.tiktok.com",category:"marketing"},{pattern:"pintrk",category:"marketing"},{pattern:"s.pinimg.com/ct",category:"marketing"},{pattern:"widget.intercom.io",category:"preferences"},{pattern:"js.intercomcdn.com",category:"preferences"}],regionRules:{gdpr:["AT","BE","BG","CY","CZ","DE","DK","EE","ES","FI","FR","GR","HR","HU","IE","IT","LT","LU","LV","MT","NL","PL","PT","RO","SE","SI","SK","IS","LI","NO","GB","GG","JE","IM","CH","BR","ZA"],ccpa:["US"]},text:{title:"We value your privacy",description:"We use cookies to improve your experience, analyse traffic and personalise content. You can choose which categories to allow.",acceptAllButton:"Accept All",rejectAllButton:"Decline All",declineAllButton:"Decline All",saveButton:"Save Preferences",showDetailsButton:"Manage preferences",hideDetailsButton:"Hide preferences",alwaysOnLabel:"(always on)",ccpaTitle:"Your Privacy Choices",ccpaDescription:"We use cookies and similar technologies. Under US state privacy laws, such as California's, you have the right to opt out of the sale or sharing of your personal information.",ccpaAcceptButton:"OK",ccpaDnsmpiLink:"Do Not Sell or Share My Personal Information",ccpaGpcNotice:"Your browser asked us not to sell or share your information. We're honouring that.",ccpaChoiceTitle:"Do Not Sell or Share My Personal Information",ccpaChoiceText:"Information about your visit may be used by advertising partners whose cookies run on this site. Under US state privacy laws that can count as selling or sharing. You can opt out in this browser.",ccpaOptOutButton:"Opt out",ccpaCancelButton:"Cancel",ccpaOptedOut:"You've opted out of the sale or sharing of your personal information in this browser.",ccpaGpcOptedOut:"Your browser's Global Privacy Control signal asked us not to sell or share your information, so you're opted out in this browser.",ccpaDoneButton:"Done",ccpaOptInButton:"Allow selling or sharing",ccpaOptInGpcNote:"Allowing it overrides your browser's Global Privacy Control signal on this site only.",ccpaMoreSettings:"More cookie settings",gpcPrefNote:"Your browser's Global Privacy Control signal is on, so Marketing stays off unless you switch it on here.",changePreferences:"Change cookie preferences",loginCookiesTitle:"If you log in",loginCookiesIntro:"These cookies are only set if you log in to an account on this site. They keep you signed in and remember your account settings. They're needed for logging in to work, so they don't ask for your consent."},cookieTypes:[{id:"necessary",name:"Necessary",description:"Required for the site to function. Cannot be disabled.",required:!0,consentMode:["security_storage","functionality_storage"],tcfPurposes:[1],onAccept:function(){}},{id:"preferences",name:"Preferences",description:"Remember your settings and personalisation choices.",required:!1,defaultValue:!1,consentMode:["functionality_storage","personalization_storage"],tcfPurposes:[2,3],onAccept:function(){},onReject:function(){}},{id:"analytics",name:"Analytics",description:"Help us understand how visitors use the site.",required:!1,defaultValue:!1,consentMode:["analytics_storage"],tcfPurposes:[4,5,6],onAccept:function(){},onReject:function(){}},{id:"marketing",name:"Marketing",description:"Used to deliver relevant ads and track campaigns.",required:!1,defaultValue:!1,consentMode:["ad_storage","ad_user_data","ad_personalization"],tcfPurposes:[7,8,9,10],onAccept:function(){},onReject:function(){}}]};if(window.__ccConfig&&"object"==typeof window.__ccConfig){var a=window.__ccConfig;["cookieName","cookieExpiry","privacyUrl","geoApi","logEndpoint","reconsentDays","cspSafe","tcfEnabled","cmpId","cmpVersion","vendorListVersion","storageMethod","cookieDomain","shareSubdomains","theme","showPoweredBy","iconPosition","consentVersion","rejectFirstScreen","visitorLogins"].forEach(function(e){void 0!==a[e]&&(r[e]=a[e])}),a.text&&"object"==typeof a.text&&Object.keys(a.text).forEach(function(e){r.text[e]=a.text[e]}),Array.isArray(a.cookieTypes)&&(r.cookieTypes=a.cookieTypes),Array.isArray(a.autoBlockPatterns)&&(r.autoBlockPatterns=a.autoBlockPatterns),a.regionRules&&(r.regionRules=a.regionRules)}window.__ccConsentVersion&&(r.consentVersion=window.__ccConsentVersion);var i=!!window.__ccGPC;function s(e){return i&&e&&!e.gpcOptIn&&(e.marketing=!1,e.gpc=1),e}window.__ccBlockRules&&Array.isArray(window.__ccBlockRules)&&window.__ccBlockRules.length&&(r.autoBlockPatterns=window.__ccBlockRules);var d={cc_consent:{vendor:"CookieFlame",category:"necessary",purpose:"Stores your consent preferences",expiry:"1 year"},cc_anon_id:{vendor:"CookieFlame",category:"necessary",purpose:"Anonymous ID for consent logging",expiry:"1 year"},cookieflame_dnt:{vendor:"CookieFlame",category:"necessary",purpose:"Do-not-track opt-out flag",expiry:"1 year"},PHPSESSID:{vendor:"PHP",category:"necessary",purpose:"Server-side session identifier",expiry:"Session"},"wp-settings-":{vendor:"WordPress",category:"necessary",purpose:"Remembers dashboard layout settings",prefix:!0,expiry:"1 year"},"wp-settings-time-":{vendor:"WordPress",category:"necessary",purpose:"Records when dashboard settings were last saved",prefix:!0,expiry:"1 year"},wordpress_logged_in_:{vendor:"WordPress",category:"necessary",purpose:"Keeps you logged in",prefix:!0,expiry:"Session, or 14 days with Remember Me"},wordpress_sec_:{vendor:"WordPress",category:"necessary",purpose:"Confirms your login when you open the dashboard",prefix:!0,expiry:"Session, or 14 days with Remember Me"},wordpress_rec_:{vendor:"WordPress",category:"necessary",purpose:"Recovery mode after a site error (administrators only)",prefix:!0,expiry:"1 week"},wordpress_test_cookie:{vendor:"WordPress",category:"necessary",purpose:"Checks your browser accepts cookies before you log in",expiry:"Session"},wp_lang:{vendor:"WordPress",category:"necessary",purpose:"Remembers the language picked on the login page",expiry:"Session"},comment_author_:{vendor:"WordPress",category:"necessary",purpose:"Remembers your name for comments, if you ask it to",prefix:!0,expiry:"1 year"},comment_author_email_:{vendor:"WordPress",category:"necessary",purpose:"Remembers your email for comments, if you ask it to",prefix:!0,expiry:"1 year"},comment_author_url_:{vendor:"WordPress",category:"necessary",purpose:"Remembers your website for comments, if you ask it to",prefix:!0,expiry:"1 year"},"wp-postpass_":{vendor:"WordPress",category:"necessary",purpose:"Remembers the password entered for a protected page",prefix:!0,expiry:"10 days"},woocommerce_cart_hash:{vendor:"WooCommerce",category:"necessary",purpose:"Shopping cart contents",expiry:"Session"},woocommerce_items_in_cart:{vendor:"WooCommerce",category:"necessary",purpose:"Cart item count",expiry:"Session"},wp_woocommerce_session_:{vendor:"WooCommerce",category:"necessary",purpose:"Shopping session",prefix:!0,expiry:"2 days"},_ga:{vendor:"Google Analytics",category:"analytics",purpose:"Distinguishes visitors",expiry:"2 years"},_ga_:{vendor:"Google Analytics",category:"analytics",purpose:"Stores session state",prefix:!0,expiry:"2 years"},_gid:{vendor:"Google Analytics",category:"analytics",purpose:"Distinguishes visitors (24h)",expiry:"24 hours"},_gat:{vendor:"Google Analytics",category:"analytics",purpose:"Throttles request rate",expiry:"1 minute"},wp_wk_ga_untrack_:{vendor:"Google Analytics",category:"analytics",purpose:"GA opt-out flag",prefix:!0,expiry:"Persistent"},_gcl_au:{vendor:"Google Ads",category:"marketing",purpose:"Conversion linker",expiry:"90 days"},_gcl_aw:{vendor:"Google Ads",category:"marketing",purpose:"Stores ad click info",expiry:"90 days"},_gac_:{vendor:"Google Ads",category:"marketing",purpose:"Campaign information",prefix:!0,expiry:"90 days"},IDE:{vendor:"Google DoubleClick",category:"marketing",purpose:"Ad serving and frequency",expiry:"1 year"},test_cookie:{vendor:"Google DoubleClick",category:"marketing",purpose:"Checks cookie support",expiry:"15 minutes"},NID:{vendor:"Google",category:"preferences",purpose:"Stores preferences",expiry:"6 months"}},u=window.__ccCustomCookies||[];function l(e){for(var t=0;t<u.length;t++){var n=u[t];if("exact"===n.match&&e===n.name)return n;if("prefix"===n.match&&0===e.indexOf(n.name))return n;if("contains"===n.match&&-1!==e.indexOf(n.name))return n}var c=d[e];if(c)return c;var o=0;return Object.keys(d).forEach(function(t){d[t].prefix&&t.length>o&&0===e.indexOf(t)&&(c=d[t],o=t.length)}),c||null}var p=/^(?:wordpress_(?:logged_in_|sec_|rec_|test_cookie$|[0-9a-f]{32}$)|wp-settings-|wp_lang$)/;function f(e){return p.test(e)}var g=null,m="none",y=null,v=null,h=!1,w=0;function b(e){return"string"!=typeof e&&(e=String(e)),e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function k(e){if("localStorage"===r.storageMethod)try{return localStorage.getItem(e)}catch(e){return null}var t=document.cookie.match(new RegExp("(?:^|;\\s*)"+e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));if(!t)return null;try{return decodeURIComponent(t[1])}catch(e){return null}}function _(e,t,n){if("localStorage"!==r.storageMethod){var c=new Date;c.setTime(c.getTime()+864e5*n);var o=e+"="+encodeURIComponent(t)+";expires="+c.toUTCString()+";path=/;SameSite=Lax;Secure";r.cookieDomain&&(o+=";domain="+r.cookieDomain),document.cookie=o}else try{localStorage.setItem(e,t)}catch(e){}}function x(){if(v)return v;var e=k("cc_anon_id");return e?(v=e,e):(_("cc_anon_id",v="cc_"+function(){if("undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID)try{return crypto.randomUUID()}catch(e){}return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}(),730),v)}function E(){var e={timestamp:g?g.ts:null,complianceMode:m,country:y,gpc:i,consentVersion:r.consentVersion};return r.cookieTypes.forEach(function(t){e[t.id]=g?!!g[t.id]:!!t.required}),e}function A(e,t){window.dispatchEvent(new CustomEvent(e,{detail:t||E()}))}function S(){window.dataLayer=window.dataLayer||[],window.dataLayer.push(arguments)}function C(e){var t={};r.cookieTypes.forEach(function(n){n.consentMode&&n.consentMode.forEach(function(c){t[c]=e[n.id]?"granted":"denied"})}),i&&!e.gpcOptIn&&(t.ad_storage="denied",t.ad_user_data="denied",t.ad_personalization="denied"),S("consent","update",t)}var I=new Map,L=!1;function B(e){var t={};return r.cookieTypes.forEach(function(n){n.tcfPurposes&&n.tcfPurposes.forEach(function(c){t[c]=!!e[n.id]})}),{tcString:"",tcfPolicyVersion:4,cmpId:r.cmpId,cmpVersion:r.cmpVersion,gdprApplies:"gdpr"===m,eventStatus:"useractioncomplete",cmpStatus:"loaded",listenerId:void 0,isServiceSpecific:!0,useNonStandardTexts:!1,publisherCC:"GB",purposeOneTreatment:!1,purpose:{consents:t,legitimateInterests:{}},vendor:{consents:{},legitimateInterests:{}},specialFeatureOptins:{},publisher:{consents:{},legitimateInterests:{},customPurpose:{consents:{},legitimateInterests:{}},restrictions:{}}}}function O(e,t,n,c){if("function"==typeof n)if("addEventListener"===e){var o=++w;if(I.set(o,n),g){var a=B(g);a.listenerId=o,n(a,!0)}}else"removeEventListener"===e?n({listenerId:c},I.delete(c)):"getTCData"===e?n(g?B(g):null,!!g):"ping"===e?n({gdprApplies:"gdpr"===m,cmpLoaded:L,cmpStatus:L?"loaded":"stub",displayStatus:"hidden",apiVersion:"2.2",cmpVersion:r.cmpVersion,cmpId:r.cmpId,gvlVersion:r.vendorListVersion,tcfPolicyVersion:4},!0):n(null,!1)}function T(e){I.forEach(function(t,n){var c=B(e);c.listenerId=n;try{t(c,!0)}catch(e){}})}function P(e){if(!e)return null;for(var t=e.toLowerCase(),n=0;n<r.autoBlockPatterns.length;n++){var c=r.autoBlockPatterns[n];if(c.pattern instanceof RegExp){if(c.pattern.test(t))return c.category}else if(-1!==t.indexOf(c.pattern.toLowerCase()))return c.category}return null}function D(e){document.querySelectorAll('script[type="text/plain"][data-cc]').forEach(function(t){var n=(t.getAttribute("data-cc")||"").toLowerCase();if(e[n]&&!t.getAttribute("data-cc-activated")){t.setAttribute("data-cc-activated","1");var c=document.createElement("script");c.isWPRocket=!0,Array.from(t.attributes).forEach(function(e){"type"!==e.name&&"data-cc"!==e.name&&"data-cc-activated"!==e.name&&"data-cc-auto"!==e.name&&c.setAttribute(e.name,e.value)}),!t.src&&t.textContent&&(c.textContent=t.textContent),t.parentNode.insertBefore(c,t.nextSibling),t.parentNode.removeChild(t)}}),["img","iframe","link"].forEach(function(t){document.querySelectorAll(t+"[data-cc][data-cc-src]").forEach(function(n){var c=(n.getAttribute("data-cc")||"").toLowerCase();e[c]&&!n.getAttribute("data-cc-activated")&&(n.setAttribute("data-cc-activated","1"),"link"===t?n.href=n.getAttribute("data-cc-src"):n.src=n.getAttribute("data-cc-src"))})})}var R=[{pattern:"youtube.com",category:"marketing"},{pattern:"youtube-nocookie.com",category:"marketing"},{pattern:"youtu.be",category:"marketing"},{pattern:"player.vimeo.com",category:"marketing"},{pattern:"vimeo.com",category:"marketing"},{pattern:"dailymotion.com",category:"marketing"},{pattern:"open.spotify.com",category:"preferences"},{pattern:"soundcloud.com",category:"preferences"},{pattern:"google.com/maps",category:"preferences"},{pattern:"maps.google.com",category:"preferences"},{pattern:"facebook.com/plugins",category:"marketing"},{pattern:"instagram.com",category:"marketing"},{pattern:"tiktok.com/embed",category:"marketing"},{pattern:"twitter.com",category:"marketing"},{pattern:"x.com",category:"marketing"}];function M(e,t){var n,c;try{var o=new URL(e,location.href);n=o.hostname.toLowerCase(),c=o.pathname.toLowerCase()}catch(e){return!1}var r=t.indexOf("/"),a=-1===r?t:t.slice(0,r),i=-1===r?"":t.slice(r);return(n===a||n.slice(-a.length-1)==="."+a)&&(!i||0===c.indexOf(i))}function N(e){if(!e)return null;for(var t=0;t<R.length;t++)if(M(e,R[t].pattern))return R[t].category;return null}function q(){return!(!document.body||!(document.body.classList.contains("elementor-editor-active")||document.body.classList.contains("fl-builder-edit")||document.body.classList.contains("et-fb-root-ancestor")))}var G=[],j=0,V=null,U=!1;function J(){if(V&&G.length){var e=G;G=[];try{V(e)}catch(e){}}}function W(){0!==G.length&&(2!==j?1!==j&&3!==j&&(j=1,function(){var e=window.__ccConfig&&window.__ccConfig.facadesUrl||null;if(!e)return void(j=3);var t=document.createElement("script");t.src=e,t.async=!0,t.onload=function(){j=2,J()},t.onerror=function(){j=3},(document.head||document.documentElement).appendChild(t)}()):J())}function F(e){var t=e.src||e.getAttribute("data-src")||"";if(t&&!e.getAttribute("data-cc-facade")&&!q()){var n=N(t);if(n){var c=g||ee();c&&c[n]||(e.setAttribute("data-cc-facade","1"),e.setAttribute("data-cc-iframe-cat",n),e.setAttribute("data-cc-original-src",t),e.removeAttribute("src"),e.style.display="none",G.push({type:"iframe",node:e,category:n,src:t}),W())}}}function H(e){if(!e.getAttribute("data-cc-facade")&&!q()){var t=e.getAttribute("data-settings");if(t){var n;try{n=JSON.parse(t)}catch(e){return}var c=n.youtube_url||n.vimeo_url||"";if(c){var o=c.toLowerCase(),r=null;if((-1!==o.indexOf("youtube")||-1!==o.indexOf("youtu.be")||-1!==o.indexOf("vimeo")||-1!==o.indexOf("dailymotion"))&&(r="marketing"),r&&(!g||!g[r])){if(!g){var a=ee();if(a&&a[r])return}e.setAttribute("data-cc-facade","1"),U=!0,G.push({type:"elementor",node:e,category:r,url:c}),W()}}}}}function $(){for(var e=document.querySelectorAll("iframe[src]"),t=0;t<e.length;t++)F(e[t]);for(var n=document.querySelectorAll(".elementor-widget-video"),c=0;c<n.length;c++)H(n[c]);for(var o=document.querySelectorAll(".wp-block-embed__wrapper iframe[src]"),r=0;r<o.length;r++)F(o[r])}function Y(){new MutationObserver(function(e){e.forEach(function(e){e.addedNodes.forEach(function(e){if(1===e.nodeType){if("IFRAME"===e.tagName&&F(e),e.querySelectorAll){for(var t=e.querySelectorAll("iframe[src]"),n=0;n<t.length;n++)F(t[n]);for(var c=e.querySelectorAll(".elementor-widget-video"),o=0;o<c.length;o++)H(c[o])}e.classList&&e.classList.contains("elementor-widget-video")&&H(e)}})})}).observe(document.documentElement,{childList:!0,subtree:!0})}function Z(){for(var e=document.querySelectorAll("iframe[src]"),t=0;t<e.length;t++){var n=e[t];if(!n.getAttribute("data-cc-facade")){var c=n.src||"";c&&N(c)&&(n.src=c)}}}function K(e,t){r.cookieTypes.forEach(function(n){var c=!!e[n.id];if((t?!!t[n.id]:null)!==c||null===t)if(c&&"function"==typeof n.onAccept)try{n.onAccept()}catch(e){console.warn("[CookieConsent] onAccept error for "+n.id,e)}else if(!c&&"function"==typeof n.onReject)try{n.onReject()}catch(e){console.warn("[CookieConsent] onReject error for "+n.id,e)}})}function z(e,t){if(r.logEndpoint){var n={};r.cookieTypes.forEach(function(e){n[e.id]=!!t[e.id]});var c={anonymousId:x(),timestamp:(new Date).toISOString(),action:e,consent:n,complianceMode:m,country:y,url:location.href,userAgent:navigator.userAgent,tcfEnabled:r.tcfEnabled,gpc:i,consentVersion:r.consentVersion,version:o};try{var a=JSON.stringify(c);navigator.sendBeacon?navigator.sendBeacon(r.logEndpoint,new Blob([a],{type:"application/json"})):fetch(r.logEndpoint,{method:"POST",body:a,headers:{"Content-Type":"application/json"},keepalive:!0})}catch(e){}}}function Q(e,t){var n=g;if(t&&0===t.indexOf("facade_accept_")||s(e),g=e,_(r.cookieName,JSON.stringify(e),r.cookieExpiry),C(e),U&&t)for(var c=document.querySelectorAll(".cc-iframe-facade[data-cc-elementor-widget]"),o=0;o<c.length;o++){var a=c[o].getAttribute("data-cc-iframe-cat");if(a&&e[a])return void window.location.reload()}ce(e,n),D(e),function(e){for(var t=document.querySelectorAll(".cc-iframe-facade"),n=0;n<t.length;n++)if(!t[n].getAttribute("data-cc-elementor-widget")&&e[t[n].getAttribute("data-cc-iframe-cat")]){var c=t[n].nextElementSibling;if(c&&"IFRAME"===c.tagName&&c.getAttribute("data-cc-facade")){var o=c.getAttribute("data-cc-original-src");o&&(c.src=o,c.style.display="",c.removeAttribute("data-cc-facade"))}t[n].parentNode.removeChild(t[n])}}(e),t&&"auto_grant_non_eu"!==t&&"ccpa_auto_grant"!==t&&setTimeout(Z,300),K(e,n),X(e),r.tcfEnabled&&T(e),t&&z(t,e)}function X(e){r.cookieTypes.forEach(function(t){if(!t.required){var n=document.getElementById("cc-cb-"+t.id);n&&(n.checked=!!e[t.id])}});var t=document.getElementById("cc-cats"),n=document.getElementById("cc-details"),c=document.getElementById("cc-pref-buttons");t&&t.classList.remove("cc-open"),n&&(n.textContent=r.text.showDetailsButton),c&&c.classList.remove("cc-open")}function ee(){var e=k(r.cookieName);if(!e)return null;try{var t=JSON.parse(e);if(!t||"object"!=typeof t||Array.isArray(t))return null;if(!t.ts||isNaN(new Date(t.ts).getTime()))return null;var n=Number(r.reconsentDays);return n>=1||(n=365),(Date.now()-new Date(t.ts).getTime())/864e5>n||r.consentVersion&&t.cv!==r.consentVersion?null:t}catch(e){return null}}function te(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:y};return r.cookieTypes.forEach(function(t){e[t.id]=!0}),e}function ne(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:y};return r.cookieTypes.forEach(function(t){e[t.id]=!!t.required}),e}function ce(e,t){if(t){var n=[];if(r.cookieTypes.forEach(function(c){c.required||t[c.id]&&!e[c.id]&&n.push(c.id)}),n.length)document.cookie.split(";").map(function(e){return e.trim().split("=")[0]}).filter(Boolean).forEach(function(e){var t=l(e);if(t&&-1!==n.indexOf(t.category)){var c=location.hostname,o="."+c;document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/",document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT",document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+o,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+c,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; Secure",r.cookieDomain&&(document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+r.cookieDomain,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+r.cookieDomain+"; Secure");var a=c.split(".");if(a.length>2){var i="."+a.slice(1).join(".");document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+i,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+i+"; Secure"}}})}}function oe(e){return(y=e?e.toUpperCase():null)?r.regionRules.gdpr&&-1!==r.regionRules.gdpr.indexOf(y)?"gdpr":r.regionRules.ccpa&&-1!==r.regionRules.ccpa.indexOf(y)?"ccpa":"none":"gdpr"}function re(e){if(window.__ccGeo)return e(oe(window.__ccGeo));r.geoApi?fetch(r.geoApi).then(function(e){return e.json()}).then(function(t){var n=t.country_code||t.country||"";n?e(oe(n)):ae(e)}).catch(function(){ae(e)}):ae(e)}function ae(e){var t="";try{t=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(e){}return 0===t.indexOf("Europe/")||"Atlantic/Reykjavik"===t?(y=null,e("gdpr")):0===t.indexOf("US/")||0===t.indexOf("America/")&&["New_York","Chicago","Denver","Los_Angeles","Phoenix","Anchorage","Boise","Detroit","Indianapolis","Louisville","Menominee","Nome","Adak","Juneau","Sitka","Yakutat","Metlakatla","Kentucky","Indiana","North_Dakota"].some(function(e){return-1!==t.indexOf(e)})||"Pacific/Honolulu"===t?(y="US",e("ccpa")):(y=null,void e("gdpr"))}function ie(){"dark"===r.theme&&document.documentElement.classList.add("cc-dark"),"right"===r.iconPosition&&document.documentElement.classList.add("cc-icon-right")}function se(){var e=r.text,t=document.createElement("div");t.id="cc-overlay",document.body.appendChild(t);var n=document.createElement("div");n.id="cc-banner",n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),n.setAttribute("aria-label","Cookie consent"),n.setAttribute("tabindex","-1");var c=r.cookieTypes.map(function(t){var n=t.required?' <span class="cc-always-on">'+b(e.alwaysOnLabel)+"</span>":"",c=i&&"marketing"===t.id?'<div class="cc-gpc-note">'+b(e.gpcPrefNote)+"</div>":"";return'<div class="cc-cat"><div><div class="cc-cat-label">'+b(t.name)+n+'</div><div class="cc-cat-desc">'+b(t.description||"")+"</div>"+c+'</div><label class="cc-toggle"><input type="checkbox" id="cc-cb-'+b(t.id)+'" aria-label="'+b(t.name)+'"'+(t.required||t.defaultValue?" checked":"")+(t.required?" disabled":"")+'><span class="cc-slider"></span></label></div>'}).join("");n.innerHTML='<div class="cc-inner"><p class="cc-title">'+b(e.title)+'</p><p class="cc-text">'+b(e.description)+' <a href="'+b(r.privacyUrl)+'">Privacy Policy</a></p><div class="cc-buttons"><button class="cc-btn cc-btn-accept" id="cc-accept">'+b(e.acceptAllButton)+"</button>"+(r.rejectFirstScreen?'<button class="cc-btn cc-btn-reject" id="cc-reject">'+b(e.rejectAllButton)+"</button>":"")+'<button class="cc-btn cc-btn-details" id="cc-details" aria-expanded="false">'+b(e.showDetailsButton)+'</button></div><div class="cc-categories" id="cc-cats">'+c+'</div><div class="cc-pref-buttons" id="cc-pref-buttons"><button class="cc-btn cc-btn-save" id="cc-save">'+b(e.saveButton)+'</button><button class="cc-btn cc-btn-decline" id="cc-decline">'+b(e.declineAllButton)+"</button></div></div>"+(r.showPoweredBy?'<div class="cc-powered"><a href="https://www.cookieflame.com" target="_blank" rel="noopener">Powered by CookieFlame</a></div>':""),document.body.appendChild(n);var o=document.createElement("button");o.id="cc-icon",o.setAttribute("aria-label","Cookie settings"),o.innerHTML='<svg width="24" height="24" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19.1 1.16c-.06-.42-.38-.77-.8-.83A11.8 11.8 0 0 0 13.28 1.2L7.5 4.3A11.8 11.8 0 0 0 3.95 7.98L1.09 13.9A11.8 11.8 0 0 0 .38 18.96l1.13 6.47a11.8 11.8 0 0 0 3.53 4.99l4.71 4.56a11.8 11.8 0 0 0 4.59 2.24l6.48.91a11.8 11.8 0 0 0 5.52-.89l5.78-3.09a11.8 11.8 0 0 0 3.55-3.69l2.87-5.9a11.8 11.8 0 0 0 .71-5.08c-.07-.41-.42-.73-.83-.79a14.8 14.8 0 0 1-8.16-7.61c-.14-.59-.63-1.05-1.23-1.14A14.8 14.8 0 0 1 19.1 1.16ZM15.25 10.25a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Zm-5 15a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0Zm17.5-5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z" fill="currentColor"/></svg>',document.body.appendChild(o),document.getElementById("cc-accept").addEventListener("click",ke),document.getElementById("cc-decline").addEventListener("click",_e);var a=document.getElementById("cc-reject");a&&a.addEventListener("click",_e),document.getElementById("cc-save").addEventListener("click",xe),document.getElementById("cc-details").addEventListener("click",Ee),o.addEventListener("click",function(){he("gdpr")}),de("gdpr",n)}function de(e,t){A("cc:banner-rendered",{banner:e,element:t,state:E()})}function ue(){if(!document.getElementById("cc-ccpa-banner")){var e=r.text,t=document.createElement("div");t.id="cc-ccpa-banner",t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-label","Privacy notice"),t.setAttribute("tabindex","-1"),t.innerHTML='<div class="cc-ccpa-inner" id="cc-ccpa-notice"><div class="cc-ccpa-text"><p>'+b(e.ccpaDescription)+"</p>"+(i?'<p class="cc-ccpa-gpc">'+b(e.ccpaGpcNotice)+"</p>":"")+'</div><div class="cc-ccpa-actions"><a href="#" class="cc-ccpa-link" id="cc-ccpa-dns" role="button">'+b(e.ccpaDnsmpiLink)+'</a><button class="cc-btn cc-btn-accept" id="cc-ccpa-ok">'+b(e.ccpaAcceptButton)+'</button></div></div><div class="cc-ccpa-choice" id="cc-ccpa-choice"></div>'+(r.showPoweredBy?'<div class="cc-powered"><a href="https://www.cookieflame.com" target="_blank" rel="noopener">Powered by CookieFlame</a></div>':""),document.body.appendChild(t),document.getElementById("cc-ccpa-ok").addEventListener("click",function(){g&&z("ccpa_acknowledge",g),fe()}),document.getElementById("cc-ccpa-dns").addEventListener("click",function(e){e.preventDefault(),pe(!0)}),de("ccpa",t)}}function le(){var e=document.getElementById("cc-ccpa-choice");if(e){var t=r.text,n=!(g&&g.marketing),c='<div class="cc-ccpa-choice-inner"><p class="cc-ccpa-choice-title">'+b(t.ccpaChoiceTitle)+"</p>";c+=n?'<p class="cc-ccpa-choice-status">'+b(i?t.ccpaGpcOptedOut:t.ccpaOptedOut)+'</p><div class="cc-ccpa-choice-buttons"><button class="cc-btn cc-btn-accept" id="cc-ccpa-done">'+b(t.ccpaDoneButton)+'</button><button class="cc-btn cc-btn-decline" id="cc-ccpa-optin">'+b(t.ccpaOptInButton)+"</button></div>"+(i?'<p class="cc-ccpa-choice-note">'+b(t.ccpaOptInGpcNote)+"</p>":""):'<p class="cc-ccpa-choice-text">'+b(t.ccpaChoiceText)+'</p><div class="cc-ccpa-choice-buttons"><button class="cc-btn cc-btn-accept" id="cc-ccpa-optout">'+b(t.ccpaOptOutButton)+'</button><button class="cc-btn cc-btn-decline" id="cc-ccpa-cancel">'+b(t.ccpaCancelButton)+"</button></div>",c+='<button class="cc-btn cc-btn-details" id="cc-ccpa-more">'+b(t.ccpaMoreSettings)+"</button></div>",e.innerHTML=c;var o=function(e,t){var n=document.getElementById(e);n&&n.addEventListener("click",t)};o("cc-ccpa-done",fe),o("cc-ccpa-cancel",fe),o("cc-ccpa-optout",function(){var e=me(g);e.marketing=!1,Q(e,"ccpa_opt_out"),le(),ge("cc-ccpa-choice"),A("cc:update")}),o("cc-ccpa-optin",function(){var e=me(g);e.marketing=!0,i&&(e.gpcOptIn=1),delete e.gpc,Q(e,"ccpa_opt_in"),le(),ge("cc-ccpa-choice"),A("cc:update")}),o("cc-ccpa-more",function(){fe(),Ae()})}}function pe(e){var t=document.getElementById("cc-ccpa-notice"),n=document.getElementById("cc-ccpa-choice");t&&n&&(e&&le(),t.style.display=e?"none":"",n.classList.toggle("cc-open",!!e),e&&ge("cc-ccpa-choice"))}function fe(){var e=document.getElementById("cc-ccpa-banner");if(e&&e.classList.remove("cc-show"),pe(!1),document.removeEventListener("keydown",ve),ye&&"function"==typeof ye.focus)try{ye.focus()}catch(e){}ye=null}function ge(e){var t=document.getElementById(e),n=t&&t.querySelector("button");if(n)try{n.focus()}catch(e){}}function me(e){var t={ts:(new Date).toISOString(),cv:r.consentVersion,cc:y};return r.cookieTypes.forEach(function(n){t[n.id]=!e||!!e[n.id]}),e&&e.gpcOptIn&&(t.gpcOptIn=1),t}var ye=null;function ve(e){if("Tab"===e.key){var t=document.querySelector("#cc-banner.cc-show, #cc-ccpa-banner.cc-show");if(t){for(var n=[],c=t.querySelectorAll('button, a[href], input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'),o=0;o<c.length;o++){for(var r=c[o],a=!1,i=r.parentElement;i&&i!==t;){if("none"===window.getComputedStyle(i).display){a=!0;break}i=i.parentElement}a||n.push(r)}if(0!==n.length){var s=n[0],d=n[n.length-1];e.shiftKey?document.activeElement===s&&(e.preventDefault(),d.focus()):document.activeElement===d&&(e.preventDefault(),s.focus())}}}}function he(e){if("ccpa"===e)return ye=document.activeElement,void requestAnimationFrame(function(){var e=document.getElementById("cc-ccpa-banner");if(e){e.classList.add("cc-show");var t=document.getElementById("cc-ccpa-choice");t&&t.classList.contains("cc-open")?ge("cc-ccpa-choice"):e.focus(),document.addEventListener("keydown",ve)}});ye=document.activeElement;var t=document.getElementById("cc-reject");t&&("gdpr"===m?t.style.removeProperty("display"):t.style.setProperty("display","none","important")),requestAnimationFrame(function(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");if(e&&e.classList.add("cc-show"),t){t.classList.add("cc-show");var c=t.querySelector(".cc-btn-accept");c&&c.focus(),document.addEventListener("keydown",ve)}n&&n.classList.remove("cc-show")})}function we(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");if(e&&e.classList.remove("cc-show"),t&&t.classList.remove("cc-show"),n&&"gdpr"===m&&n.classList.add("cc-show"),document.removeEventListener("keydown",ve),ye&&"function"==typeof ye.focus)try{ye.focus()}catch(e){}ye=null}function be(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");e&&e.classList.remove("cc-show"),t&&t.classList.remove("cc-show"),n&&n.classList.remove("cc-show"),document.removeEventListener("keydown",ve)}function ke(){var e=te();g&&g.gpcOptIn&&(e.gpcOptIn=1),Q(e,"accept_all"),we(),A("cc:accept-all"),A("cc:update")}function _e(){Q(ne(),"decline_all"),we(),A("cc:decline-all"),A("cc:update")}function xe(){var e=function(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:y};return r.cookieTypes.forEach(function(t){if(t.required)e[t.id]=!0;else{var n=document.getElementById("cc-cb-"+t.id);e[t.id]=!!n&&n.checked}}),e}();e.marketing&&(i||g&&g.gpcOptIn)&&(e.gpcOptIn=1),Q(e,"save_preferences"),we(),A("cc:save"),A("cc:update")}function Ee(){var e=document.getElementById("cc-cats"),t=document.getElementById("cc-details"),n=document.getElementById("cc-pref-buttons"),c=e.classList.toggle("cc-open");t.textContent=c?r.text.hideDetailsButton:r.text.showDetailsButton,t.setAttribute("aria-expanded",c?"true":"false"),n&&(c?n.classList.add("cc-open"):n.classList.remove("cc-open"))}function Ae(){document.getElementById("cc-banner")||(ie(),se(),be(),g&&X(g)),he("gdpr")}function Se(){"ccpa"===m?(ie(),ue(),he("ccpa"),pe(!0)):Ae()}function Ce(){var e=document.getElementById("cc-declaration");if(e){var t=E(),n={},c=document.cookie.split(";").map(function(e){return e.trim().split("=")[0]}).filter(Boolean);c=c.filter(function(e){return!f(e)});for(var o=0;o<c.length;o++){var a=l(c[o]);a&&(n[c[o]]={name:c[o],vendor:a.vendor,category:a.category,purpose:a.purpose,expiry:a.expiry||"",active:!0})}for(var i=0;i<u.length;i++){var s=u[i];s.name&&s.vendor&&s.category&&"unclassified"!==s.category&&!n[s.name]&&!f(s.name)&&(n[s.name]={name:s.name,vendor:s.vendor,category:s.category,purpose:s.purpose||"",expiry:s.expiry||"",active:!1})}for(var d=0;d<c.length;d++)n[c[d]]||(n[c[d]]={name:c[d],vendor:"",category:"unclassified",purpose:"",expiry:"",active:!0});var p=["necessary","preferences","analytics","marketing"],g={};r.cookieTypes.forEach(function(e){g[e.id]=e.name});var m={};p.forEach(function(e){m[e]=[]}),Object.keys(n).forEach(function(e){var t=n[e],c=t.category;m[c]||(m[c]=[]),m[c].push(t)}),p.forEach(function(e){m[e]&&m[e].sort(function(e,t){return e.name.localeCompare(t.name)})});var y,v="<div>";if(t.timestamp){var h=new Date(t.timestamp);v+="<p>"+b("Your consent was last updated on "+h.toLocaleDateString(void 0,{year:"numeric",month:"long",day:"numeric"})+" at "+h.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"})+".")+"</p>"}else v+="<p>"+b("You have not yet made a cookie preference choice on this site.")+"</p>";for(var w=0;w<p.length;w++){var k=p[w],_=m[k];if(_&&_.length){var x,A=g[k]||k,S=r.cookieTypes.filter(function(e){return e.id===k})[0],C=S&&S.description||"";x=!!S&&S.required?"Always active":t[k]?"Active":"Inactive",v+="<div>",v+="<p><b>"+b(A)+"</b> ("+b(x)+")</p>",C&&(v+="<p>"+b(C)+"</p>"),v+="<table><thead><tr>",v+="<th>Name</th><th>Provider</th><th>Purpose</th><th>Expiry</th>",v+="</tr></thead><tbody>";for(var I=0;I<_.length;I++){var L=_[I];v+="<tr>",v+="<td><code>"+b(L.name)+"</code></td>",v+="<td>"+b(L.vendor||"Unknown")+"</td>",v+="<td>"+b(L.purpose||"Not specified")+"</td>",v+="<td>"+b(L.expiry||"—")+"</td>",v+="</tr>"}v+="</tbody></table></div>"}}if(r.visitorLogins){var B=[{name:"wordpress_logged_in_[hash]",purpose:"Keeps you logged in",expiry:y="Session, or 14 days with Remember Me"},{name:"https:"===location.protocol?"wordpress_sec_[hash]":"wordpress_[hash]",purpose:"Confirms your login when you open your account dashboard",expiry:y},{name:"wordpress_test_cookie",purpose:"Checks your browser accepts cookies before you log in",expiry:"Session"},{name:"wp_lang",purpose:"Remembers the language you picked on the login page",expiry:"Session"},{name:"wp-settings-[user ID]",purpose:"Remembers your dashboard layout settings",expiry:"1 year"},{name:"wp-settings-time-[user ID]",purpose:"Records when your dashboard settings were last saved",expiry:"1 year"}];v+='<div class="cc-declaration-login">',v+="<p><b>"+b(r.text.loginCookiesTitle)+"</b></p>",v+="<p>"+b(r.text.loginCookiesIntro)+"</p>",v+="<table><thead><tr>",v+="<th>Name</th><th>Provider</th><th>Purpose</th><th>Expiry</th>",v+="</tr></thead><tbody>";for(var O=0;O<B.length;O++)v+="<tr>",v+="<td><code>"+b(B[O].name)+"</code></td>",v+="<td>WordPress</td>",v+="<td>"+b(B[O].purpose)+"</td>",v+="<td>"+b(B[O].expiry)+"</td>",v+="</tr>";v+="</tbody></table></div>"}v+='<p><button type="button" class="cc-open-settings cc-declaration-change">'+b(r.text.changePreferences)+"</button></p>",v+="</div>",e.innerHTML=v}}document.addEventListener("click",function(e){(e.target&&e.target.closest?e.target.closest(".cc-open-settings"):null)&&(e.preventDefault(),Se())}),window.__ccInternal={getResourceSnapshot:Oe,scriptLoaded:function(e){for(var t=Be||Oe(),n=0;n<t.allUrls.length;n++)if(-1!==t.allUrls[n].indexOf(e))return!0;return!1},scriptLoadedUrls:function(e){for(var t=Be||Oe(),n=[],c=0;c<t.allUrls.length;c++)-1!==t.allUrls[c].indexOf(e)&&n.push(t.allUrls[c]);return n},hasCookie:function(e){return document.cookie.split(";").some(function(t){return t.trim().split("=")[0]===e})},hasCookiePrefix:function(e){return document.cookie.split(";").some(function(t){return 0===t.trim().split("=")[0].indexOf(e)})},setResourceSnapshot:function(e){Be=e},visitorCookies:function(e){for(var t=Oe().thirdPartyDomains,n={},c=0;c<e.length;c++)De(e[c],t,Pe(e[c]))&&(n[e[c]]=!0);return n},facadeBridge:{getConfig:function(){return r},getConsentState:function(){return g},loadConsent:ee,saveConsent:function(e,t){Q(e,t)},hideAllBanners:be,registerRenderer:function(e){V=e,j=2,J()}}},window.CookieConsent={getState:E,getConfig:function(){return r},getComplianceMode:function(){return m},getCountry:function(){return y},show:Ae,showSettings:Se,acceptAll:ke,declineAll:_e,getGPC:function(){return i},getConsentVersion:function(){return r.consentVersion},on:function(e,t){window.addEventListener(e,t)},off:function(e,t){window.removeEventListener(e,t)},VERSION:o},window.addEventListener("cc:ready",Ce),window.addEventListener("cc:update",Ce),function(){if(!window.__ccDefaultsSet){var e={wait_for_update:500};r.cookieTypes.forEach(function(t){t.consentMode&&t.consentMode.forEach(function(n){e[n]=t.required?"granted":"denied"})}),S("consent","default",e)}}(),function(){if(r.tcfEnabled){var e=window.__tcfapi&&window.__tcfapi.queue?window.__tcfapi.queue:[];window.__tcfapi=O,e.forEach(function(e){O.apply(null,e)}),L=!0,window===window.top&&window.addEventListener("message",function(e){var t;try{t="string"==typeof e.data?JSON.parse(e.data):e.data}catch(e){return}if(t&&t.__tcfapiCall){var n=t.__tcfapiCall;O(n.command,n.version,function(t,c){var o={__tcfapiReturn:{returnValue:t,success:c,callId:n.callId}};e.source&&e.source.postMessage("string"==typeof e.data?JSON.stringify(o):o,"*")},n.parameter)}})}}();var Ie=ee();function Le(){var e=window.__ccDiscovery&&window.__ccDiscovery.active;if(!e&&(window.__ccDNT||document.cookie.split(";").some(function(e){return 0===e.trim().indexOf("cookieflame_dnt=")})))return C(g=ne()),ce(g,te()),Y(),$(),m="dnt",ie(),se(),be(),X(g),A("cc:ready"),void Ce();if(e){C(g=te()),D(g),Y(),K(g,null),r.tcfEnabled&&T(g),window.dataLayer=window.dataLayer||[];for(var t=["consent_accepted_analytics","consent_accepted_marketing","consent_accepted_preferences","consent_accepted_necessary","consent_update","consent_given","cookie_consent_given","cookie_consent_update","consent_accepted"],n=0;n<t.length;n++)window.dataLayer.push({event:t[n]});return m="discovery",ie(),se(),be(),A("cc:ready"),A("cc:update"),void Ce()}var c=ee(),o=!1;if(c)if(!i||c.gpcOptIn||!c.marketing&&c.gpc||(o=!!c.marketing,s(c),_(r.cookieName,JSON.stringify(c),r.cookieExpiry)),g=c,C(c),D(c),Y(),$(),K(c,null),r.tcfEnabled&&T(c),ie(),se(),be(),X(c),c.cc){if("gdpr"===(m=oe(c.cc))){var a=document.getElementById("cc-icon");a&&a.classList.add("cc-show")}o&&z("gpc_optout",c),A("cc:ready"),Ce()}else re(function(e){if(m=e,"gdpr"===e){var t=document.getElementById("cc-icon");t&&t.classList.add("cc-show")}o&&z("gpc_optout",c),A("cc:ready"),Ce()});else re(function(e){return m=e,"none"===e?(Y(),Q(te(),"auto_grant_non_eu"),A("cc:ready"),A("cc:update"),void Ce()):"ccpa"===e?(Y(),i?Q(s(te()),"ccpa_gpc_optout"):Q(te(),"ccpa_auto_grant"),ie(),ue(),he("ccpa"),A("cc:ready"),A("cc:update"),void Ce()):(ie(),Y(),$(),se(),he("gdpr"),A("cc:ready"),void Ce())})}Ie&&(i&&!Ie.gpcOptIn&&(Ie.marketing=!1),g=Ie,C(Ie)),function(){if(!h&&r.autoBlockPatterns&&r.autoBlockPatterns.length){h=!0;var e=document.createElement.bind(document),t=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype,"src");t&&t.set&&(document.createElement=function(){var n=e.apply(document,arguments);if("script"!==(arguments[0]||"").toLowerCase())return n;function c(e,t){if(t&&"string"==typeof t&&!e.getAttribute("data-cc")&&!e.getAttribute("data-cc-auto")&&-1===t.indexOf("cookieconsent")){var n=P(t);!n||g&&g[n]||(e.type="text/plain",e.setAttribute("data-cc",n),e.setAttribute("data-cc-auto","1"))}}Object.defineProperty(n,"src",{configurable:!0,enumerable:!0,get:function(){return t.get.call(this)},set:function(e){return c(this,e),t.set.call(this,e)}});var o=n.setAttribute.bind(n);return n.setAttribute=function(e,t){return"src"===e.toLowerCase()&&c(n,t),o(e,t)},n}),new MutationObserver(function(e){e.forEach(function(e){e.addedNodes.forEach(function(e){if(1===e.nodeType){var t=[];"SCRIPT"===e.tagName&&t.push(e),e.querySelectorAll&&e.querySelectorAll("script[src]").forEach(function(e){t.push(e)}),t.forEach(function(e){if(!(e.getAttribute("data-cc-auto")||e.getAttribute("data-cc")||e.src&&-1!==e.src.indexOf("cookieconsent"))){var t=P(e.src);t&&(g&&g[t]||(e.type="text/plain",e.setAttribute("data-cc",t),e.setAttribute("data-cc-auto","1")))}})}})})}).observe(document.documentElement,{childList:!0,subtree:!0})}}(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",Le):Le();var Be=null;function Oe(){for(var e=[],t=[],n=[],c={},o={},r=document.querySelectorAll("script[src]"),a=0;a<r.length;a++)r[a].src&&e.push(r[a].src);for(var i=document.querySelectorAll("iframe[src]"),s=0;s<i.length;s++)i[s].src&&e.push(i[s].src);if(window.performance&&performance.getEntriesByType)for(var d=performance.getEntriesByType("resource"),u=0;u<d.length;u++){var l=d[u].name||"";if(l){-1===e.indexOf(l)&&e.push(l);var p=d[u].initiatorType||"";if("script"===p||"iframe"===p||"xmlhttprequest"===p||"fetch"===p||-1!==l.indexOf(".js"))try{var f=new URL(l),g=f.hostname;if(g===location.hostname||g==="www."+location.hostname||"www."+g===location.hostname){var m=f.pathname.match(/\/wp-content\/plugins\/([^\/]+)\//);m&&!o[m[1]]&&(o[m[1]]=!0,n.push({slug:m[1],url:l}))}else c[g]||(c[g]=!0,t.push(g))}catch(e){}}}return{allUrls:e,thirdPartyDomains:t,pluginScripts:n}}var Te={};function Pe(e){var t={set_by:"",set_by_plugin:""},n=window.__ccCookieSetters;if(!n||!Object.prototype.hasOwnProperty.call(n,e)||!n[e])return t;try{var c=new URL(String(n[e]).replace(/:\d+(:\d+)?$/,""));if(/^(chrome-extension|moz-extension|safari-web-extension|safari-extension):$/.test(c.protocol))return t.set_by="browser-extension",t;var o=c.hostname;if(o===location.hostname||o==="www."+location.hostname||"www."+o===location.hostname){t.set_by="first-party";var r=c.pathname.match(/\/wp-content\/(?:plugins|themes)\/([^\/]+)\//);r&&(t.set_by_plugin=r[1].toLowerCase())}else t.set_by=o.toLowerCase()}catch(e){}return t}function De(e,t,n){if("browser-extension"===n.set_by)return!0;var c=window.__ccVisitorCookieRules;if(!c||!c.tools)return!1;if(c.allow&&-1!==c.allow.indexOf(e))return!1;if(n.set_by)return!1;for(var o=0;o<c.tools.length;o++){for(var r=c.tools[o],a=-1!==(r.n||[]).indexOf(e),i=0;!a&&i<(r.p||[]).length;i++)0===e.indexOf(r.p[i])&&(a=!0);if(a){for(var s=0;s<t.length;s++)for(var d=0;d<(r.d||[]).length;d++){var u=String(t[s]).toLowerCase(),l=r.d[d];if(u===l||u.slice(-l.length-1)==="."+l)return!1}return!0}}return!1}function Re(){var t=window.__ccClassified||[],n=window.__ccUnclassified||[],c=window.__ccBeaconUrl;if(c){var o,r={};for(o=0;o<t.length;o++)r[t[o]]=!0;for(o=0;o<n.length;o++)r[n[o]]=!0;var a=Object.keys(Te);for(o=0;o<a.length;o++)r[a[o]]=!0;var i=Object.keys(d);for(o=0;o<i.length;o++)r[i[o]]=!0;var s={},u=[],l=document.cookie.split(";");for(o=0;o<l.length;o++){var p=l[o].indexOf("="),f=(p>-1?l[o].substring(0,p):l[o]).trim(),g=p>-1?l[o].substring(p+1).trim():"";f&&(u.push(f),s[f]=g)}var m=[];for(o=0;o<u.length;o++)if(!r[u[o]]){for(var y=!1,v=Object.keys(d),h=0;h<v.length;h++)if(d[v[h]].prefix&&0===u[o].indexOf(v[h])){y=!0;break}y||m.push(u[o])}if(0!==m.length){for(var w=Oe(),b=w.thirdPartyDomains,k=[],_=0;_<w.pluginScripts.length;_++)k.push(w.pluginScripts[_].slug);for(var x=location.pathname,E=0;E<m.length;E++){var A=m[E],S=!!e[A]&&!r[A],C=Me(s[A]||""),I=Pe(A);if(!De(A,b,I)||window.__ccDiscovery&&window.__ccDiscovery.active){var L=JSON.stringify({cookie_name:A,script_domains:b,plugin_scripts:k,page_path:x,pre_existing:S,value_shape:C,set_by:I.set_by,set_by_plugin:I.set_by_plugin});Te[A]=!0,navigator.sendBeacon&&navigator.sendBeacon(c,new Blob([L],{type:"application/json"}))}else Te[A]=!0}}}}function Me(e){if(!e||0===e.length)return"empty";if("true"===e||"false"===e||"1"===e||"0"===e)return"boolean";if(/^\d+$/.test(e))return e.length>12?"long-numeric":"numeric";if(/^\d+\.\d+$/.test(e))return"decimal";if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e))return"uuid";if(/^[A-Za-z0-9+\/]{20,}={0,2}$/.test(e))return"base64";if(/^(GA\d+\.\d+\.\d+\.\d+|G-[A-Z0-9]+)/.test(e))return"ga-id";try{if("object"==typeof JSON.parse(decodeURIComponent(e)))return"json"}catch(e){}return/^[a-f0-9]{24,}$/i.test(e)?"hex-id":/^\d{10,13}$/.test(e)?"timestamp":e.length<=20?"short-string":e.length<=100?"medium-string":"long-string"}setTimeout(Re,3e3),setTimeout(Re,15e3),setTimeout(Re,45e3),window.addEventListener("cc:update",function(){setTimeout(Re,3e3)}),window.addEventListener("pagehide",Re)}(); -
cookieflame/tags/5.4.0/cookieflame.php
r3769546 r3771537 3 3 * Plugin Name: CookieFlame 4 4 * Plugin URI: https://cookieflame.com 5 * Description: A lightweight, self-hosted cookie consent manager with Google Consent Mode v2, IAB TCF v2.2,automatic script blocking, multi-region compliance (GDPR/CCPA/LGPD/POPIA), passive cookie monitoring, and server-side geo detection via Cloudflare.6 * Version: 5. 3.05 * Description: A lightweight, self-hosted cookie consent manager with Google Consent Mode v2, automatic script blocking, multi-region compliance (GDPR/CCPA/LGPD/POPIA), passive cookie monitoring, and server-side geo detection via Cloudflare. 6 * Version: 5.4.0 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 8.0 … … 19 19 } 20 20 21 define( 'COOKIEFLAME_VERSION', '5. 3.0' );21 define( 'COOKIEFLAME_VERSION', '5.4.0' ); 22 22 define( 'COOKIEFLAME_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 23 23 define( 'COOKIEFLAME_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); … … 27 27 28 28 /* ═══════════════════════════════════════════════════════════ 29 MONITORING INTERVAL BUCKETS (in minutes) 30 A single 'digest_interval' option drives both beacon 31 processing cadence and email digest cadence. Value 0 = off. 32 Free tier: off, daily, weekly. 33 Pro tier: free buckets + 15m, 1h, 4h, monthly (added via the 34 cookieflame_interval_buckets filter by the Pro plugin). 29 COOKIE MONITORING SETTINGS (v5.4.0) 30 Two separate settings on the Cookie Monitoring tab: 31 - Watch for new cookies: On/Off. Visitors' browsers report 32 new cookies as they browse, processed straight away. 33 Stored as the inverse of 'monitoring_paused'. 34 - Email me about changes: 'weekly' (default), 'daily', 35 'instant' or 'off'. Stored as 'email_frequency'. 36 Before v5.4.0 a single 'digest_interval' (minutes, 0 = off) 37 drove both. cookieflame_migrate_monitoring_settings() converts 38 it once; until then the getters derive the same answer from it. 39 40 The cookieflame_interval_buckets filter is kept: a plugin that 41 adds a bucket shorter than a day (Pro 1.5.x adds 15 minutes) 42 makes "Instantly" emails possible, sent after that delay. 35 43 ═══════════════════════════════════════════════════════════ */ 36 44 … … 53 61 */ 54 62 function cookieflame_interval_buckets_free() { 63 // v5.4.0: the 15-minute, 1-hour and 4-hour entries are no longer 64 // declared here. Pro 1.5.x still contributes them through the filter. 55 65 return array( 56 66 array( 'minutes' => 0, 'tier' => 'free' ), 57 array( 'minutes' => 15, 'tier' => 'pro' ),58 array( 'minutes' => 60, 'tier' => 'pro' ),59 array( 'minutes' => 240, 'tier' => 'pro' ),60 67 array( 'minutes' => 1440, 'tier' => 'free' ), 61 68 array( 'minutes' => 10080, 'tier' => 'free' ), … … 159 166 160 167 /** 161 * Return the currently configured monitoring interval in minutes, 162 * normalised to a valid bucket for the current tier. Defaults to 163 * daily (1440) on new installs with no value set. 168 * Delay before the next email summary, in minutes (0 = no email). 169 * Kept under its pre-5.4.0 name for any code that still calls it. 164 170 */ 165 171 function cookieflame_current_interval_minutes() { 166 $settings = get_option( 'cookieflame_settings', array() ); 167 $raw = isset( $settings['digest_interval'] ) ? intval( $settings['digest_interval'] ) : 1440; 168 return cookieflame_normalise_interval( $raw ); 169 } 170 172 return cookieflame_email_delay_minutes(); 173 } 174 175 /** 176 * Whether "Watch for new cookies" is off. Every beacon, queue and 177 * digest path gates on this, so turning it off silences them all. 178 */ 171 179 function cookieflame_monitoring_is_off() { 172 if ( cookieflame_monitoring_is_paused() ) { 173 return true; 174 } 175 return 0 === cookieflame_current_interval_minutes(); 176 } 177 178 /** 179 * Whether monitoring is currently paused. Paused is a temporary 180 * suppression state — distinct from "Off" — that keeps the chosen 181 * update interval configured but silences beacons and digests while 182 * the admin does site surgery. Admin toggles this from the Cookie 183 * Monitoring tab. cookieflame_monitoring_is_off() folds this into the same 184 * gate so every downstream path inherits pause automatically. 180 return cookieflame_monitoring_is_paused(); 181 } 182 183 /** 184 * Whether "Watch for new cookies" is off (stored as 'monitoring_paused', 185 * the flag behind the On/Off switch on the Cookie Monitoring tab). A 186 * pre-5.4.0 'digest_interval' of 0 also means off until migrated. 185 187 */ 186 188 function cookieflame_monitoring_is_paused() { 187 189 $settings = get_option( 'cookieflame_settings', array() ); 188 return ! empty( $settings['monitoring_paused'] ); 189 } 190 if ( ! empty( $settings['monitoring_paused'] ) ) { 191 return true; 192 } 193 if ( ! isset( $settings['email_frequency'] ) && isset( $settings['digest_interval'] ) && intval( $settings['digest_interval'] ) <= 0 ) { 194 return true; 195 } 196 return false; 197 } 198 199 /** 200 * The saved "Email me about changes" choice: 'weekly', 'daily', 201 * 'instant' or 'off'. Sites not yet migrated get the answer their old 202 * 'digest_interval' maps to (15/60/240 minutes map to 'instant'). 203 */ 204 function cookieflame_email_frequency() { 205 $settings = get_option( 'cookieflame_settings', array() ); 206 if ( isset( $settings['email_frequency'] ) ) { 207 return cookieflame_sanitize_email_frequency( $settings['email_frequency'] ); 208 } 209 if ( ! isset( $settings['digest_interval'] ) ) { 210 return 'weekly'; 211 } 212 return cookieflame_email_frequency_from_interval( $settings['digest_interval'], true ); 213 } 214 215 /** 216 * Map a pre-5.4.0 'digest_interval' to an email frequency. Intervals 217 * under a day become 'instant' when $instant_ok, otherwise 'daily'. 218 */ 219 function cookieflame_email_frequency_from_interval( $minutes, $instant_ok ) { 220 $minutes = intval( $minutes ); 221 if ( $minutes <= 0 ) { 222 return 'off'; 223 } 224 if ( 10080 === $minutes ) { 225 return 'weekly'; 226 } 227 if ( $minutes < 1440 ) { 228 return $instant_ok ? 'instant' : 'daily'; 229 } 230 return 'daily'; 231 } 232 233 function cookieflame_sanitize_email_frequency( $value ) { 234 $value = is_string( $value ) ? $value : ''; 235 return in_array( $value, array( 'weekly', 'daily', 'instant', 'off' ), true ) ? $value : 'weekly'; 236 } 237 238 /** 239 * Shortest sub-daily bucket another plugin added through the 240 * cookieflame_interval_buckets filter, in minutes, or 0 when none. 241 */ 242 function cookieflame_instant_bucket_minutes() { 243 $shortest = 0; 244 foreach ( cookieflame_interval_buckets() as $bucket ) { 245 $minutes = intval( $bucket['minutes'] ); 246 if ( $minutes > 0 && $minutes < 1440 && ( 0 === $shortest || $minutes < $shortest ) ) { 247 $shortest = $minutes; 248 } 249 } 250 return $shortest; 251 } 252 253 /** 254 * Whether "Instantly" can be chosen on this site: CookieFlame Pro is 255 * active and a plugin supplies a delay shorter than a day. 256 */ 257 function cookieflame_instant_email_available() { 258 return cookieflame_is_premium() && cookieflame_instant_bucket_minutes() > 0; 259 } 260 261 /** 262 * Minutes from the first new cookie to the email summary, or 0 when 263 * emails are off. 'instant' uses the shortest contributed bucket and 264 * falls back to daily when there is none. 265 * 266 * Filter: cookieflame_email_delay_minutes ( int $minutes, string $frequency ). 267 */ 268 function cookieflame_email_delay_minutes() { 269 $frequency = cookieflame_email_frequency(); 270 $map = array( 'off' => 0, 'daily' => 1440, 'weekly' => 10080 ); 271 if ( 'instant' === $frequency ) { 272 $minutes = cookieflame_instant_bucket_minutes(); 273 $minutes = $minutes > 0 ? $minutes : 1440; 274 } else { 275 $minutes = $map[ $frequency ]; 276 } 277 return max( 0, intval( apply_filters( 'cookieflame_email_delay_minutes', $minutes, $frequency ) ) ); 278 } 279 280 /** 281 * One-time conversion of the pre-5.4.0 'digest_interval' setting: 282 * 0 -> watch Off, email Off 283 * daily, weekly -> watch On, email unchanged 284 * 15m, 1h, 4h -> 'instant' when Pro is active, 'daily' otherwise 285 * 'digest_interval' itself is left in place. 286 */ 287 function cookieflame_migrate_monitoring_settings() { 288 $settings = get_option( 'cookieflame_settings', false ); 289 if ( ! is_array( $settings ) || isset( $settings['email_frequency'] ) ) { 290 return; 291 } 292 if ( isset( $settings['digest_interval'] ) ) { 293 $minutes = intval( $settings['digest_interval'] ); 294 $settings['email_frequency'] = cookieflame_email_frequency_from_interval( $minutes, $minutes > 0 && $minutes < 1440 && cookieflame_is_premium() ); 295 if ( $minutes <= 0 ) { 296 $settings['monitoring_paused'] = 1; 297 } 298 } else { 299 $settings['email_frequency'] = 'weekly'; 300 } 301 update_option( 'cookieflame_settings', $settings ); 302 } 303 add_action( 'admin_init', 'cookieflame_migrate_monitoring_settings', 5 ); 190 304 191 305 /** … … 209 323 $host = is_string( $host ) ? strtolower( $host ) : ''; 210 324 return substr( sha1( $host . '|' . $page_path ), 0, 16 ); 325 } 326 327 /** 328 * Version string for one of the plugin's own scripts or stylesheets: the 329 * plugin version plus the file's modification time, so a changed file 330 * always gets a new URL and no browser or CDN cache serves an old copy. 331 */ 332 function cookieflame_asset_version( $relative_path ) { 333 $file = COOKIEFLAME_PLUGIN_DIR . $relative_path; 334 $mtime = file_exists( $file ) ? filemtime( $file ) : false; 335 return $mtime ? COOKIEFLAME_VERSION . '.' . $mtime : COOKIEFLAME_VERSION; 211 336 } 212 337 … … 337 462 add_action( 'admin_post_cookieflame_save_notifications', 'cookieflame_save_notifications' ); 338 463 add_action( 'admin_post_cookieflame_toggle_pause', 'cookieflame_toggle_monitoring_pause' ); 464 add_action( 'admin_post_cookieflame_dismiss_upgrade_banner', 'cookieflame_dismiss_upgrade_banner' ); 465 466 /** 467 * Hide the "Upgrade to Pro" banner at the top of the CookieFlame tabs 468 * for the current user. 469 */ 470 function cookieflame_dismiss_upgrade_banner() { 471 if ( ! current_user_can( 'manage_options' ) ) { 472 wp_die( esc_html__( 'You do not have permission to do this.', 'cookieflame' ) ); 473 } 474 check_admin_referer( 'cookieflame_dismiss_upgrade_banner' ); 475 update_user_meta( get_current_user_id(), 'cookieflame_upgrade_banner_dismissed', time() ); 476 $tab = sanitize_key( wp_unslash( $_POST['tab'] ?? 'cookies' ) ); 477 wp_safe_redirect( add_query_arg( array( 'page' => 'cookieflame', 'tab' => $tab ? $tab : 'cookies' ), admin_url( 'admin.php' ) ) ); 478 exit; 479 } 339 480 340 481 /** … … 1193 1334 // - The beacon carries a script_domain or plugin_script not yet 1194 1335 // recorded for this (URL, cookie) pair 1195 // - The configured update interval has elapsed since last_processed 1336 // - A day has passed since last_processed (heartbeat; this only 1337 // refreshes "last seen", which Tidy up relies on) 1196 1338 // 1197 1339 // Otherwise we silently drop the beacon. 1198 1340 // 1199 // Monitoring off (interval = 0)is handled at enqueue time — the1341 // Watch for new cookies Off is handled at enqueue time — the 1200 1342 // beacon URL is not injected so nothing fires. We defensively drop 1201 1343 // here too in case a stale page serves the URL from cache. … … 1208 1350 $url_hash = cookieflame_url_hash_for_path( $page_path ); 1209 1351 $now = time(); 1210 $interval_s = cookieflame_current_interval_minutes() * 60;1352 $interval_s = DAY_IN_SECONDS; // Heartbeat: fixed daily since v5.4.0, independent of the email setting. 1211 1353 1212 1354 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table; high-write-rate beacon storage where the object cache adds no value (writes invalidate cache on every visitor request, reads are admin-only and infrequent). … … 1404 1546 */ 1405 1547 function cookieflame_queue_notification( $cookie_name, $script_domains, $page_path, $pre_existing = false, $value_shape = '' ) { 1406 // Monitoringoff -> do not queue and do not schedule a digest.1407 if ( cookieflame_monitoring_is_off() ) {1548 // Watching off, or emails off -> do not queue and do not schedule a digest. 1549 if ( cookieflame_monitoring_is_off() || 0 === cookieflame_email_delay_minutes() ) { 1408 1550 return; 1409 1551 } … … 1422 1564 update_option( 'cookieflame_notification_queue', $queue ); 1423 1565 1424 // Start digest timer if not already running . The same update_interval1425 // setting drives both beacon filtering and digest cadence.1566 // Start digest timer if not already running, from the 1567 // "Email me about changes" setting. 1426 1568 $scheduled = get_option( 'cookieflame_digest_scheduled', 0 ); 1427 1569 if ( empty( $scheduled ) ) { 1428 $interval = cookieflame_ current_interval_minutes() * 60;1570 $interval = cookieflame_email_delay_minutes() * 60; 1429 1571 if ( $interval <= 0 ) { 1430 1572 return; … … 1601 1743 // invoking the hook directly. Silently clean up and return — the 1602 1744 // queue is preserved for resume. 1603 if ( cookieflame_monitoring_is_off() ) {1745 if ( cookieflame_monitoring_is_off() || 0 === cookieflame_email_delay_minutes() ) { 1604 1746 delete_option( 'cookieflame_digest_scheduled' ); 1605 1747 return; … … 1685 1827 1686 1828 // Header 1687 $body = "CookieFlame Cookie Discovery Report\n";1829 $body = "CookieFlame: new cookies found by Watch for new cookies\n"; 1688 1830 $body .= $site_name . " (" . $site_url . ")\n"; 1689 1831 $body .= wp_date( 'j M Y, H:i' ) . "\n"; … … 1813 1955 $body .= "-- \n"; 1814 1956 $body .= "CookieFlame v" . COOKIEFLAME_VERSION . " on " . $site_name . "\n"; 1815 $body .= "You are receiving this because Cookie Monitoring is enabled.\n";1957 $body .= "You are receiving this because \"Email me about changes\" is on in Cookie Monitoring.\n"; 1816 1958 $body .= "Change settings: " . admin_url( 'admin.php?page=cookieflame&tab=notifications' ) . "\n"; 1817 1959 1818 wp_mail( $to, $subject, $body ); 1960 /** 1961 * Filter the email summary before it is sent. 1962 * 1963 * Return an array with 'to', 'subject', 'body' and 'headers' to change 1964 * the email, or false to skip sending it (the queue is still cleared). 1965 * 1966 * @param array $email { to, subject, body, headers }. 1967 * @param array $actionable Queue entries that need a decision. 1968 * @param array $residual Queue entries from plugins no longer active. 1969 */ 1970 $email = apply_filters( 'cookieflame_digest_email', array( 1971 'to' => $to, 1972 'subject' => $subject, 1973 'body' => $body, 1974 'headers' => array(), 1975 ), $actionable, $residual ); 1976 1977 if ( is_array( $email ) && ! empty( $email['to'] ) ) { 1978 wp_mail( $email['to'], (string) ( $email['subject'] ?? $subject ), (string) ( $email['body'] ?? $body ), $email['headers'] ?? array() ); 1979 } 1819 1980 1820 1981 // Clear the queue and timer … … 2218 2379 COOKIEFLAME_PLUGIN_URL . 'assets/css/admin.css', 2219 2380 array(), 2220 COOKIEFLAME_VERSION2381 cookieflame_asset_version( 'assets/css/admin.css' ) 2221 2382 ); 2222 2383 wp_enqueue_script( … … 2224 2385 COOKIEFLAME_PLUGIN_URL . 'assets/js/admin.js', 2225 2386 array(), 2226 COOKIEFLAME_VERSION,2387 cookieflame_asset_version( 'assets/js/admin.js' ), 2227 2388 true 2228 2389 ); … … 2253 2414 $cookieflame_js_data = apply_filters( 'cookieflame_admin_js_data', $cookieflame_js_data ); 2254 2415 wp_localize_script( 'cookieflame-admin', 'cookieflameAdmin', $cookieflame_js_data ); 2416 2417 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab navigation, no data modification. 2418 if ( isset( $_GET['tab'] ) && 'preview' === sanitize_key( wp_unslash( $_GET['tab'] ) ) ) { 2419 wp_enqueue_script( 2420 'cookieflame-admin-preview', 2421 COOKIEFLAME_PLUGIN_URL . 'assets/js/admin-preview.js', 2422 array(), 2423 cookieflame_asset_version( 'assets/js/admin-preview.js' ), 2424 true 2425 ); 2426 wp_localize_script( 'cookieflame-admin-preview', 'cookieflamePreview', array( 2427 'privacyUrl' => $opts['privacy_url'] ?? '/privacy-policy', 2428 'theme' => $opts['theme'] ?? 'light', 2429 'rejectFirstScreen' => ! empty( $opts['show_reject_first'] ), 2430 'showPoweredBy' => ! empty( $opts['show_powered_by'] ), 2431 ) ); 2432 } 2255 2433 } 2256 2434 … … 2305 2483 } 2306 2484 2485 /** 2486 * [cookieflame_settings_link text="Cookie settings"]: a link that reopens 2487 * the visitor's cookie choices (the US "Do Not Sell or Share" choice for 2488 * US visitors). Any element with the class cc-open-settings does the same, 2489 * for example a menu item. 2490 */ 2491 add_shortcode( 'cookieflame_settings_link', 'cookieflame_settings_link_shortcode' ); 2492 2493 function cookieflame_settings_link_shortcode( $atts ) { 2494 $atts = shortcode_atts( array( 2495 'text' => __( 'Cookie settings', 'cookieflame' ), 2496 'class' => '', 2497 ), $atts, 'cookieflame_settings_link' ); 2498 $class = trim( 'cc-open-settings ' . implode( ' ', array_map( 'sanitize_html_class', preg_split( '/\s+/', (string) $atts['class'], -1, PREG_SPLIT_NO_EMPTY ) ) ) ); 2499 return '<a href="#" class="' . esc_attr( $class ) . '" role="button">' . esc_html( $atts['text'] ) . '</a>'; 2500 } 2501 2307 2502 /* ═══════════════════════════════════════════════════════════ 2308 2503 REST API — GEO ENDPOINT … … 2339 2534 update_option( 'cookieflame_settings', array( 2340 2535 'privacy_url' => '/privacy-policy', 2341 'ccpa_url' => '/privacy-policy#do-not-sell',2342 2536 'log_endpoint' => '', 2343 2537 'gtm_id' => '', … … 2350 2544 'track_logged_in' => 1, 2351 2545 'extra_patterns' => '', 2352 ' digest_interval' => 1440, // daily2546 'email_frequency' => 'weekly', 2353 2547 'monitoring_paused' => 0, 2354 2548 ) ); … … 2540 2734 . "if(raw.cv!=={$cv_js})return;" 2541 2735 . "var gpc=window.__ccGPC;" 2542 . "var mkt=raw.marketing&& !gpc;"2736 . "var mkt=raw.marketing&&(!gpc||raw.gpcOptIn);" 2543 2737 . "gtag('consent','update',{" 2544 2738 . "analytics_storage:raw.analytics?'granted':'denied'," … … 2629 2823 // blockable iframe or Elementor video widget is present on the 2630 2824 // page). Versioned so cache busts cleanly on plugin update. 2631 $overrides['facadesUrl'] = COOKIEFLAME_PLUGIN_URL . 'assets/js/cookieconsent-facades.min.js?ver=' . COOKIEFLAME_VERSION;2825 $overrides['facadesUrl'] = COOKIEFLAME_PLUGIN_URL . 'assets/js/cookieconsent-facades.min.js?ver=' . cookieflame_asset_version( 'assets/js/cookieconsent-facades.min.js' ); 2632 2826 2633 2827 if ( ! empty( $options['privacy_url'] ) ) $overrides['privacyUrl'] = $options['privacy_url']; 2634 // CCPA "Do Not Sell" link points to the same privacy policy page with an anchor.2635 $cookieflame_privacy = ! empty( $options['privacy_url'] ) ? $options['privacy_url'] : '/privacy-policy';2636 $overrides['ccpaUrl'] = $cookieflame_privacy . '#do-not-sell';2637 2828 if ( ! empty( $options['log_endpoint'] ) ) $overrides['logEndpoint'] = $options['log_endpoint']; 2638 2829 if ( ! empty( $options['storage_method'] ) ) $overrides['storageMethod'] = $options['storage_method']; … … 2648 2839 $overrides['shareSubdomains'] = ! empty( $options['share_subdomains'] ) || ! empty( $options['cookie_domain'] ); 2649 2840 if ( isset( $options['reconsent_days'] ) && $options['reconsent_days'] !== '' ) { 2650 $overrides['reconsentDays'] = intval( $options['reconsent_days'] ); 2841 $overrides['reconsentDays'] = cookieflame_reconsent_days( $options['reconsent_days'] ); 2842 } 2843 if ( ! empty( $options['show_reject_first'] ) ) { 2844 $overrides['rejectFirstScreen'] = true; 2845 } 2846 if ( ! empty( $options['visitor_logins'] ) ) { 2847 $overrides['visitorLogins'] = true; 2651 2848 } 2652 2849 if ( ! empty( $options['show_powered_by'] ) ) { … … 2655 2852 if ( ! empty( $options['icon_position'] ) && 'right' === $options['icon_position'] ) { 2656 2853 $overrides['iconPosition'] = 'right'; 2854 } 2855 2856 /** 2857 * Filter the banner configuration sent to the front end as 2858 * window.__ccConfig. Keys the banner script knows are applied over 2859 * its defaults; any other keys are left on window.__ccConfig for the 2860 * code that added them. 2861 * 2862 * @param array $overrides Front-end config. 2863 * @param array $options The cookieflame_settings option. 2864 */ 2865 $filtered = apply_filters( 'cookieflame_frontend_config', $overrides, $options ); 2866 if ( is_array( $filtered ) ) { 2867 $overrides = $filtered; 2657 2868 } 2658 2869 … … 2797 3008 COOKIEFLAME_PLUGIN_URL . 'assets/js/cookieconsent' . $suffix . '.js', 2798 3009 array(), 2799 COOKIEFLAME_VERSION,3010 cookieflame_asset_version( 'assets/js/cookieconsent' . $suffix . '.js' ), 2800 3011 false // Load in head, not footer. 2801 3012 ); … … 2811 3022 COOKIEFLAME_PLUGIN_URL . 'assets/js/cookieconsent-discovery' . $suffix . '.js', 2812 3023 array( 'cookieflame-consent' ), 2813 COOKIEFLAME_VERSION,3024 cookieflame_asset_version( 'assets/js/cookieconsent-discovery' . $suffix . '.js' ), 2814 3025 true // Footer is fine -- discovery runs after DOM is ready. 2815 3026 ); … … 2847 3058 COOKIEFLAME_PLUGIN_URL . 'assets/css/cookieconsent.css', 2848 3059 array(), 2849 COOKIEFLAME_VERSION3060 cookieflame_asset_version( 'assets/css/cookieconsent.css' ) 2850 3061 ); 2851 3062 } … … 3378 3589 array( 'key' => 'privacy_url', 3379 3590 'wp_default_page' => 'wp_page_for_privacy_policy', 3380 'desc' => __( 'The page linked from the consent banner and the CCPA "Do Not Sell My Personal Information" notice. Defaults to the page set under Settings > Privacy. Add the [cookieflame_declaration] shortcode to this page to show visitors their consent choices.', 'cookieflame' ) ) ); 3591 'desc' => __( 'The page linked from the consent banner. Defaults to the page set under Settings > Privacy. Add the [cookieflame_declaration] shortcode to this page to show visitors their consent choices.', 'cookieflame' ) ) ); 3592 3593 add_settings_field( 'cookieflame_visitor_logins', __( 'Visitor logins', 'cookieflame' ), 'cookieflame_field_checkbox', 'cookieflame', 'cookieflame_consent', 3594 array( 'key' => 'visitor_logins', 3595 'desc' => __( 'Visitors can log in to this site (for example, customer accounts or a members area)', 'cookieflame' ), 3596 'help' => __( 'Tick this if people other than you and your team can log in, for example to see their orders, download purchases or read members-only pages. Your cookie declaration will then also list the cookies WordPress sets when someone logs in. Leave it unticked if only you and your team log in.', 'cookieflame' ) ) ); 3381 3597 3382 3598 add_settings_field( 'cookieflame_reconsent', __( 'Re-consent Period', 'cookieflame' ), 'cookieflame_field_number', 'cookieflame', 'cookieflame_consent', … … 3386 3602 add_settings_field( 'cookieflame_theme', __( 'Banner Theme', 'cookieflame' ), 'cookieflame_field_theme', 'cookieflame', 'cookieflame_consent' ); 3387 3603 3604 add_settings_field( 'cookieflame_show_reject_first', __( 'Show "Decline all" on the first screen', 'cookieflame' ), 'cookieflame_field_checkbox', 'cookieflame', 'cookieflame_consent', 3605 array( 'key' => 'show_reject_first', 3606 'desc' => __( 'Adds a Decline All button next to Accept All on the banner for visitors in the UK, EU/EEA, Switzerland, Brazil and South Africa. Off by default: visitors can always decline everything under Manage preferences. Never shown on the US notice.', 'cookieflame' ) ) ); 3607 3388 3608 add_settings_field( 'cookieflame_share_subdomains', __( 'Share consent across subdomains', 'cookieflame' ), 'cookieflame_field_checkbox', 'cookieflame', 'cookieflame_consent', 3389 3609 array( 'key' => 'share_subdomains', … … 3428 3648 } 3429 3649 3650 /** 3651 * Re-consent period in days. Anything below 1 (blank, 0, negative or 3652 * junk) means the default of 365, so consent never expires on load. 3653 */ 3654 function cookieflame_reconsent_days( $value ) { 3655 $days = intval( $value ); 3656 return $days >= 1 ? $days : 365; 3657 } 3658 3430 3659 function cookieflame_sanitize_settings( $input ) { 3431 // Handle the DNT device cookie (not stored in DB, stored as browser cookie) 3432 cookieflame_handle_dnt_cookie( ! empty( $input['dnt_device'] ) ); 3660 $input = is_array( $input ) ? $input : array(); 3433 3661 3434 3662 // Preserve consent_bump from existing settings (managed by the 3435 3663 // Force Re-consent button, not by this form) 3436 3664 $existing = get_option( 'cookieflame_settings', array() ); 3437 3438 return array( 3439 'privacy_url' => esc_url_raw( $input['privacy_url'] ?? '' ), 3440 'ccpa_url' => esc_url_raw( $input['ccpa_url'] ?? '' ), 3441 'log_endpoint' => esc_url_raw( $input['log_endpoint'] ?? '' ), 3665 $existing = is_array( $existing ) ? $existing : array(); 3666 3667 // The Settings tab form posts a '_form' marker. Other saves (the 3668 // Cookie Monitoring and Tag Managers handlers, the pause switch, the 3669 // migration) pass the whole stored array back through this callback, 3670 // so an unticked checkbox there means "not in this form", not "off". 3671 $main_form = ! empty( $input['_form'] ); 3672 $flag = function ( $key, $default = 0 ) use ( $input, $existing, $main_form ) { 3673 if ( $main_form ) { 3674 return ! empty( $input[ $key ] ) ? 1 : 0; 3675 } 3676 $source = array_key_exists( $key, $input ) ? $input : $existing; 3677 return isset( $source[ $key ] ) ? ( ! empty( $source[ $key ] ) ? 1 : 0 ) : $default; 3678 }; 3679 3680 if ( $main_form ) { 3681 // Handle the DNT device cookie (not stored in DB, stored as browser cookie) 3682 cookieflame_handle_dnt_cookie( ! empty( $input['dnt_device'] ) ); 3683 $track_logged_in = empty( $input['exclude_staff'] ) ? 1 : 0; 3684 } else { 3685 $track_logged_in = $flag( 'track_logged_in', 1 ); 3686 } 3687 3688 $email_frequency = cookieflame_sanitize_email_frequency( $input['email_frequency'] ?? ( $existing['email_frequency'] ?? 'weekly' ) ); 3689 3690 $clean = array( 3691 'privacy_url' => esc_url_raw( $input['privacy_url'] ?? ( $main_form ? '' : ( $existing['privacy_url'] ?? '' ) ) ), 3692 'log_endpoint' => esc_url_raw( $input['log_endpoint'] ?? ( $main_form ? '' : ( $existing['log_endpoint'] ?? '' ) ) ), 3442 3693 'storage_method' => 'cookie', // hardcoded — localStorage removed 3443 'reconsent_days' => intval( $input['reconsent_days'] ?? 365 ), 3444 'cookie_domain' => cookieflame_sanitize_cookie_domain( $input['cookie_domain'] ?? '' ), 3445 'share_subdomains' => ! empty( $input['share_subdomains'] ) ? 1 : 0, 3446 'csp_safe' => ! empty( $input['csp_safe'] ) ? 1 : 0, 3447 'theme' => in_array( $input['theme'] ?? '', array( 'light', 'dark' ), true ) ? $input['theme'] : 'light', 3448 'show_powered_by' => ! empty( $input['show_powered_by'] ) ? 1 : 0, 3449 'icon_position' => in_array( $input['icon_position'] ?? '', array( 'left', 'right' ), true ) ? $input['icon_position'] : 'left', 3450 'track_logged_in' => empty( $input['exclude_staff'] ) ? 1 : 0, 3451 'extra_patterns' => sanitize_textarea_field( $input['extra_patterns'] ?? '' ), 3694 'reconsent_days' => cookieflame_reconsent_days( $input['reconsent_days'] ?? ( $existing['reconsent_days'] ?? 365 ) ), 3695 'cookie_domain' => cookieflame_sanitize_cookie_domain( $input['cookie_domain'] ?? ( $existing['cookie_domain'] ?? '' ) ), 3696 'share_subdomains' => $flag( 'share_subdomains' ), 3697 'csp_safe' => $flag( 'csp_safe' ), 3698 'theme' => in_array( $input['theme'] ?? ( $existing['theme'] ?? '' ), array( 'light', 'dark' ), true ) ? ( $input['theme'] ?? $existing['theme'] ) : 'light', 3699 'show_reject_first' => $flag( 'show_reject_first' ), 3700 'visitor_logins' => $flag( 'visitor_logins' ), 3701 'show_powered_by' => $flag( 'show_powered_by' ), 3702 'icon_position' => in_array( $input['icon_position'] ?? ( $existing['icon_position'] ?? '' ), array( 'left', 'right' ), true ) ? ( $input['icon_position'] ?? $existing['icon_position'] ) : 'left', 3703 'track_logged_in' => $track_logged_in, 3704 'extra_patterns' => sanitize_textarea_field( $input['extra_patterns'] ?? ( $main_form ? '' : ( $existing['extra_patterns'] ?? '' ) ) ), 3452 3705 'consent_bump' => intval( $existing['consent_bump'] ?? 0 ), 3453 'delete_on_uninstall' => ! empty( $input['delete_on_uninstall'] ) ? 1 : 0,3706 'delete_on_uninstall' => $flag( 'delete_on_uninstall' ), 3454 3707 // Separate-tab fields: use $input if present (handler included it), else preserve $existing 3455 3708 'gtm_id' => isset( $input['gtm_id'] ) ? cookieflame_sanitize_gtm_id( $input['gtm_id'] ) : ( $existing['gtm_id'] ?? '' ), 3456 3709 'uet_id' => isset( $input['uet_id'] ) ? cookieflame_sanitize_uet_id( $input['uet_id'] ) : ( $existing['uet_id'] ?? '' ), 3457 3710 'notification_email' => isset( $input['notification_email'] ) ? sanitize_email( $input['notification_email'] ) : ( $existing['notification_email'] ?? '' ), 3458 'digest_interval' => isset( $input['digest_interval'] ) ? cookieflame_normalise_interval( $input['digest_interval'] ) : cookieflame_normalise_interval( $existing['digest_interval'] ?? 1440 ), 3459 // monitoring_paused is toggled only via the dedicated 3460 // admin-post handler (cookieflame_toggle_monitoring_pause). When the 3461 // handler's update_option() fires, the sanitize_option filter 3462 // runs with $input containing the new value the handler wants 3463 // to commit. When the main Settings tab saves, $input does not 3464 // include this field, so we fall back to $existing to preserve 3465 // whatever pause state was previously set. 3711 'email_frequency' => $email_frequency, 3712 // "Watch for new cookies" Off. Toggled only via the dedicated 3713 // admin-post handler (cookieflame_toggle_monitoring_pause) or 3714 // the migration; the Settings tab preserves it. 3466 3715 'monitoring_paused' => isset( $input['monitoring_paused'] ) ? ( ! empty( $input['monitoring_paused'] ) ? 1 : 0 ) : ( ! empty( $existing['monitoring_paused'] ) ? 1 : 0 ), 3467 3716 ); 3717 // Pre-5.4.0 setting, kept as-is so a downgrade still finds it. 3718 if ( isset( $input['digest_interval'] ) || isset( $existing['digest_interval'] ) ) { 3719 $clean['digest_interval'] = intval( $input['digest_interval'] ?? $existing['digest_interval'] ); 3720 } 3721 return $clean; 3468 3722 } 3469 3723 … … 3532 3786 check_admin_referer( 'cookieflame_save_notifications' ); 3533 3787 3534 $opts = get_option( 'cookieflame_settings', array() ); 3535 $previous = cookieflame_normalise_interval( $opts['digest_interval'] ?? 1440 ); 3788 $opts = get_option( 'cookieflame_settings', array() ); 3789 $previous = cookieflame_email_frequency(); 3790 $frequency = cookieflame_sanitize_email_frequency( sanitize_key( wp_unslash( $_POST['email_frequency'] ?? 'weekly' ) ) ); 3791 if ( 'instant' === $frequency && ! cookieflame_instant_email_available() ) { 3792 $frequency = 'daily'; 3793 } 3536 3794 $opts['notification_email'] = sanitize_email( wp_unslash( $_POST['notification_email'] ?? '' ) ); 3537 $opts[' digest_interval'] = cookieflame_normalise_interval( intval( $_POST['digest_interval'] ?? 1440 ) );3795 $opts['email_frequency'] = $frequency; 3538 3796 update_option( 'cookieflame_settings', $opts ); 3539 3797 3540 // If the interval changed, clear any pending single-event digest so 3541 // the next queued cookie reschedules against the new cadence. If the 3542 // admin has just turned monitoring off, drop any pending digest too. 3543 if ( $previous !== $opts['digest_interval'] || 0 === $opts['digest_interval'] ) { 3798 // If the frequency changed, clear any pending single-event digest so 3799 // the next queued cookie reschedules against the new setting. Emails 3800 // turned off also drops the waiting list, so nothing stale is sent 3801 // when they are turned back on. 3802 if ( $previous !== $frequency ) { 3544 3803 wp_clear_scheduled_hook( 'cookieflame_send_digest' ); 3545 3804 delete_option( 'cookieflame_digest_scheduled' ); 3805 if ( 'off' === $frequency ) { 3806 update_option( 'cookieflame_notification_queue', array() ); 3807 } 3546 3808 } 3547 3809 … … 3554 3816 3555 3817 /** 3556 * Toggle the monitoring_paused flag from the Cookie Monitoring tab.3557 * Pausesuppresses beacons, beacon handling, digest queueing, and3818 * Toggle "Watch for new cookies" (the monitoring_paused flag) from the 3819 * Cookie Monitoring tab. Off suppresses beacons, beacon handling, digest queueing, and 3558 3820 * digest sending — all three paths gate on cookieflame_monitoring_is_off(), 3559 3821 * which now returns true when paused. On pause we also clear any … … 3571 3833 3572 3834 $opts = get_option( 'cookieflame_settings', array() ); 3573 $was_paused = ! empty( $opts['monitoring_paused']);3835 $was_paused = cookieflame_monitoring_is_paused(); 3574 3836 $now_paused = ! $was_paused; 3575 3837 … … 3588 3850 // next cadence window rather than immediately. 3589 3851 $queue = get_option( 'cookieflame_notification_queue', array() ); 3590 $interval = cookieflame_ current_interval_minutes();3852 $interval = cookieflame_email_delay_minutes(); 3591 3853 if ( ! empty( $queue ) && $interval > 0 && ! wp_next_scheduled( 'cookieflame_send_digest' ) ) { 3592 3854 wp_schedule_single_event( time() + ( $interval * MINUTE_IN_SECONDS ), 'cookieflame_send_digest' ); … … 3930 4192 esc_html( $a['desc'] ?? '' ) 3931 4193 ); 4194 if ( ! empty( $a['help'] ) ) { 4195 printf( '<p class="description">%s</p>', esc_html( $a['help'] ) ); 4196 } 3932 4197 } 3933 4198 … … 4192 4457 </div> 4193 4458 4194 <?php if ( ! cookieflame_is_premium() ) : ?>4459 <?php if ( ! cookieflame_is_premium() && ! get_user_meta( get_current_user_id(), 'cookieflame_upgrade_banner_dismissed', true ) ) : ?> 4195 4460 <div class="cf-admin-upgrade-banner"> 4196 4461 <div class="cf-admin-upgrade-banner-icon">✨</div> … … 4200 4465 </div> 4201 4466 <a href="https://cookieflame.com/pricing" target="_blank" class="cf-admin-btn-upgrade"><?php esc_html_e( 'Upgrade to Pro', 'cookieflame' ); ?> →</a> 4467 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="cf-admin-upgrade-banner-dismiss"> 4468 <?php wp_nonce_field( 'cookieflame_dismiss_upgrade_banner' ); ?> 4469 <input type="hidden" name="action" value="cookieflame_dismiss_upgrade_banner" /> 4470 <input type="hidden" name="tab" value="<?php echo esc_attr( $active_tab ); ?>" /> 4471 <button type="submit" class="cf-admin-upgrade-banner-close" aria-label="<?php esc_attr_e( 'Dismiss this message', 'cookieflame' ); ?>" title="<?php esc_attr_e( 'Dismiss this message', 'cookieflame' ); ?>">×</button> 4472 </form> 4202 4473 </div> 4203 4474 <?php endif; ?> … … 4209 4480 <p><?php esc_html_e( 'Add this shortcode to the page you have set as your Privacy Policy page (under Settings > Privacy). It shows visitors which cookie categories are active, what each cookie does, and a button to change their consent choices.', 'cookieflame' ); ?></p> 4210 4481 <pre class="cf-admin-code-block">[cookieflame_declaration]</pre> 4482 <h3><?php esc_html_e( 'Cookie Settings Link', 'cookieflame' ); ?></h3> 4483 <p><?php esc_html_e( 'Put this shortcode in your footer or anywhere else to give visitors a "Cookie settings" link that reopens their choices. US visitors get the "Do Not Sell or Share My Personal Information" choice. You can change the wording with text="...". To use a menu item or your own button instead, give it the CSS class cc-open-settings.', 'cookieflame' ); ?></p> 4484 <pre class="cf-admin-code-block">[cookieflame_settings_link]</pre> 4211 4485 </div> 4212 4486 … … 4255 4529 <?php 4256 4530 settings_fields( 'cookieflame_settings_group' ); 4531 ?> 4532 <input type="hidden" name="cookieflame_settings[_form]" value="main" /> 4533 <?php 4257 4534 do_settings_sections( 'cookieflame' ); 4258 4535 submit_button(); 4259 4536 ?> 4260 4537 </form> 4538 4539 <?php 4540 /** 4541 * Fires on the Settings tab after the main settings form, so other 4542 * plugins can add their own section (with its own form and option). 4543 */ 4544 do_action( 'cookieflame_settings_sections' ); 4545 ?> 4261 4546 4262 4547 <div class="cf-admin-card cf-admin-card-warning"> … … 4454 4739 4455 4740 <h4 style="margin-top:16px;margin-bottom:8px;"><?php esc_html_e( 'How the scan works', 'cookieflame' ); ?></h4> 4456 <p style="margin-bottom:12px;"><?php esc_html_e( ' The scan reads cookies from this browser, right now. It does not crawl your website or check other visitors. If a cookie does not exist in your browser at this moment, it will not appear in the scan. That means you need to visit your own website first and trigger all the scripts that set cookies.', 'cookieflame' ); ?></p>4741 <p style="margin-bottom:12px;"><?php esc_html_e( 'Cookie Discovery uses your own browser. It records the cookies your browser picks up while you browse your site with every cookie allowed. It does not crawl your website or check other visitors, so a cookie only appears if a page you visit sets it. Cookies your visitors pick up are found separately by Watch for new cookies on the Cookie Monitoring tab.', 'cookieflame' ); ?></p> 4457 4742 4458 4743 <h4 style="margin-bottom:8px;"><?php esc_html_e( 'Before you start', 'cookieflame' ); ?></h4> 4459 <p style="margin-bottom:4px; color:#d63638;"><strong><?php esc_html_e( 'Check your tracking settings first.', 'cookieflame' ); ?></strong></p>4460 <p style="margin-bottom:12px;"><?php esc_html_e( ' Go to the Settings tab. Make sure "Track logged-in users" is checked and "Do not track this device" is unchecked. If either of these is blocking tracking, scripts will be suppressed for you even after you accept cookies, and no third-party cookies will appear in the scan. Save the settings and clear your site cache if you make changes. You can turn these back on after you have finished scanning.', 'cookieflame' ); ?></p>4744 <p style="margin-bottom:4px;"><strong><?php esc_html_e( 'Clear your page cache if you run one.', 'cookieflame' ); ?></strong></p> 4745 <p style="margin-bottom:12px;"><?php esc_html_e( 'You do not need to change any CookieFlame settings. While Cookie Discovery runs, "Exclude staff from tracking" and "Do not track this device" are paused for your browser, and they are put back when you click Finish Discovery.', 'cookieflame' ); ?></p> 4461 4746 4462 4747 <h4 style="margin-bottom:8px;"><?php esc_html_e( 'Step by step', 'cookieflame' ); ?></h4> … … 4464 4749 <li> 4465 4750 <strong><?php esc_html_e( 'Start Cookie Discovery.', 'cookieflame' ); ?></strong> 4466 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( ' When you click the Start Cookie Discovery button above, your website opens in this tab with all cookies temporarily accepted, so every tracking script can run and set its cookies. You need to be in the same browser you are using right now for wp-admin, because cookies are stored per browser.', 'cookieflame' ); ?></span>4751 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( 'Click Start Cookie Discovery above, then Open my site. Your site opens in this tab with every cookie allowed for your browser only, and a green bar at the top counts what is found. Use the browser you are using for wp-admin right now, because cookies are stored per browser.', 'cookieflame' ); ?></span> 4467 4752 </li> 4468 4753 <li> 4469 <strong><?php esc_html_e( ' Click "Accept All" onthe consent banner.', 'cookieflame' ); ?></strong>4470 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( ' This is essential. The plugin blocks all tracking scripts until consent is given. If you only accept some categories, only those scripts will run. You need to accept everything so every script fires and every cookie gets created.', 'cookieflame' ); ?></span>4754 <strong><?php esc_html_e( 'You will not see the consent banner.', 'cookieflame' ); ?></strong> 4755 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( 'Cookie Discovery allows every cookie for your browser while it runs, so every script on your site can run and set its cookies. Your visitors still see the banner as normal.', 'cookieflame' ); ?></span> 4471 4756 </li> 4472 4757 <li> … … 4484 4769 </li> 4485 4770 <li> 4486 <strong><?php esc_html_e( 'C ome back to this tab to see your results.', 'cookieflame' ); ?></strong>4487 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( ' The table will update with every cookie now sitting in your browser. Known cookies will have their details filled in automatically. Unknown cookies will be highlighted in yellow.', 'cookieflame' ); ?></span>4771 <strong><?php esc_html_e( 'Click Finish Discovery on the green bar.', 'cookieflame' ); ?></strong> 4772 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( 'You come back here, and the table lists every cookie found. Known cookies have their details filled in automatically. Unknown cookies are highlighted in yellow.', 'cookieflame' ); ?></span> 4488 4773 </li> 4489 4774 <li> … … 4500 4785 </li> 4501 4786 <li> 4502 <strong><?php esc_html_e( ' Re-enable your tracking protection.', 'cookieflame' ); ?></strong>4503 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( ' If you turned off "Do not track this device" in step 1, go back to the Settings tab and turn it back on. You can also click "Delete Non-Essential Cookies" on the Settings tab to clean up the tracking cookies that were just created during your scan.', 'cookieflame' ); ?></span>4787 <strong><?php esc_html_e( 'Tidy up your browser if you like.', 'cookieflame' ); ?></strong> 4788 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( 'Your tracking settings are put back automatically. To remove the tracking cookies your browser picked up during the scan, click "Delete Non-Essential Cookies from This Browser" on the Settings tab.', 'cookieflame' ); ?></span> 4504 4789 </li> 4505 4790 </ol> … … 4510 4795 <h4 style="margin-bottom:4px;"><?php esc_html_e( 'Why are some cookies not showing up?', 'cookieflame' ); ?></h4> 4511 4796 <ul style="margin:0 0 12px 20px;line-height:1.8;"> 4512 <li><strong><?php esc_html_e( 'You did not click Accept All:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'If you only accepted some categories, scripts in the other categories never ran. Their cookies do not exist. Go back and accept all categories.', 'cookieflame' ); ?></li> 4513 <li><strong><?php esc_html_e( 'Your tracking protection is still on:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'If "Do not track this device" is enabled or "Track logged-in users" is unchecked on the Settings tab, tracking is suppressed for your browser even after you accept cookies. Temporarily disable these before scanning.', 'cookieflame' ); ?></li> 4797 <li><strong><?php esc_html_e( 'Your page cache served an old page:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'Clear your page cache and run Cookie Discovery again.', 'cookieflame' ); ?></li> 4514 4798 <li><strong><?php esc_html_e( 'The cookie is page-specific:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'Some cookies are only set on specific pages (e.g. Stripe only on checkout, a chat widget only on contact pages). Visit those pages before re-scanning.', 'cookieflame' ); ?></li> 4515 4799 <li><strong><?php esc_html_e( 'The cookie is on a subdomain:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'If you have subdomains (like app.yoursite.com), cookies locked to that subdomain are invisible from your main site. Enable "Share consent across subdomains" in Settings to share cookies, or use the "Add Cookie Manually" section below to register them by name.', 'cookieflame' ); ?></li> … … 4587 4871 <div id="cf-admin-discovery-idle"> 4588 4872 <div style="display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;"> 4589 <button type="button" id="cf-admin-discovery-start" class="cf-admin-btn-primary"><?php esc_html_e( 'Start Cookie Discovery ', 'cookieflame' ); ?></button>4590 <span style="font-size:13px;color:#50575e;flex:1;min-width:200px;"><?php esc_html_e( ' Temporarily disables tracking protection, opens your site in a new tab with all cookies accepted, and lets you browse a few pages. When you finish, it scans for cookies and restores your settings.', 'cookieflame' ); ?></span>4873 <button type="button" id="cf-admin-discovery-start" class="cf-admin-btn-primary"><?php esc_html_e( 'Start Cookie Discovery (uses your own browser)', 'cookieflame' ); ?></button> 4874 <span style="font-size:13px;color:#50575e;flex:1;min-width:200px;"><?php esc_html_e( 'Cookie Discovery uses your own browser: it allows every cookie for you only while you browse a few pages of your site in this tab. When you finish, it lists the cookies found and puts your settings back.', 'cookieflame' ); ?></span> 4591 4875 </div> 4592 4876 <p style="font-size:12px;color:#646970;margin:10px 0 0;"><?php esc_html_e( 'If you notice cookies you cannot explain after scanning, try running discovery in an incognito or private browser window. Browser extensions can occasionally inject tracking cookies that do not belong to your site, and incognito mode disables them by default. This is uncommon.', 'cookieflame' ); ?></p> … … 5002 5286 <?php if ( $cookieflame_paused ) : ?> 5003 5287 <div class="notice notice-warning is-dismissible" style="margin:0 0 16px;"> 5004 <p><strong><?php esc_html_e( ' Monitoring is now Off.', 'cookieflame' ); ?></strong> <?php esc_html_e( 'Cookie beacons and alert emails are suppressed. Your update interval is preserved for when you switch monitoringback on.', 'cookieflame' ); ?></p>5288 <p><strong><?php esc_html_e( 'Watch for new cookies is now Off.', 'cookieflame' ); ?></strong> <?php esc_html_e( 'Visitors\' browsers no longer report new cookies, and no emails are sent. Your email setting is kept for when you turn it back on.', 'cookieflame' ); ?></p> 5005 5289 </div> 5006 5290 <?php endif; ?> … … 5008 5292 <?php if ( $cookieflame_resumed ) : ?> 5009 5293 <div class="notice notice-success is-dismissible" style="margin:0 0 16px;"> 5010 <p><strong><?php esc_html_e( ' Monitoring is now On.', 'cookieflame' ); ?></strong> <?php esc_html_e( 'New cookies will be detected on the cadence you have configured.', 'cookieflame' ); ?></p>5294 <p><strong><?php esc_html_e( 'Watch for new cookies is now On.', 'cookieflame' ); ?></strong> <?php esc_html_e( 'New cookies appear in the Cookie Manager as soon as a visitor\'s browser reports them.', 'cookieflame' ); ?></p> 5011 5295 </div> 5012 5296 <?php endif; ?> 5013 5297 5014 <p class="cf-admin-tab-intro"><?php esc_html_e( ' Watch real visitor sessions in the background and surface new cookies as they appear.', 'cookieflame' ); ?></p>5298 <p class="cf-admin-tab-intro"><?php esc_html_e( 'Your visitors\' browsers report cookies as they browse your site, so new ones show up in the Cookie Manager without you scanning.', 'cookieflame' ); ?></p> 5015 5299 5016 5300 <?php $cookieflame_is_paused = cookieflame_monitoring_is_paused(); ?> 5017 5301 <div class="cf-admin-pause-card <?php echo $cookieflame_is_paused ? 'cf-admin-pause-card-paused' : 'cf-admin-pause-card-running'; ?>"> 5018 5302 <div class="cf-admin-pause-card-body"> 5019 <h3 class="cf-admin-pause-card-title"><?php esc_html_e( ' Visitor Monitoring', 'cookieflame' ); ?></h3>5020 <p class="cf-admin-pause-card-desc"><?php esc_html_e( ' Pause during site surgery (plugin swaps, theme changes, migrations). Your update interval is preserved.', 'cookieflame' ); ?></p>5303 <h3 class="cf-admin-pause-card-title"><?php esc_html_e( 'Watch for new cookies', 'cookieflame' ); ?></h3> 5304 <p class="cf-admin-pause-card-desc"><?php esc_html_e( 'Uses your visitors\' browsers. New cookies are recorded the moment a visitor\'s browser reports them. Turn it off for a while during big changes (plugin swaps, theme changes, migrations) if you prefer. Clear your page cache after switching, so cached pages pick up the change.', 'cookieflame' ); ?></p> 5021 5305 </div> 5022 5306 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="cf-admin-pause-card-action"> … … 5027 5311 <span class="cf-admin-toggle-label"><?php 5028 5312 echo $cookieflame_is_paused 5029 ? esc_html__( ' Monitoring: Off', 'cookieflame' )5030 : esc_html__( ' Monitoring: On', 'cookieflame' );5313 ? esc_html__( 'Watch for new cookies: Off', 'cookieflame' ) 5314 : esc_html__( 'Watch for new cookies: On', 'cookieflame' ); 5031 5315 ?></span> 5032 5316 </button> … … 5035 5319 5036 5320 <?php 5037 $notif_opts = get_option( 'cookieflame_settings', array() ); 5038 $notif_email = $notif_opts['notification_email'] ?? ''; 5039 $current_mins = cookieflame_normalise_interval( $notif_opts['digest_interval'] ?? 1440 ); 5040 $buckets = cookieflame_interval_buckets(); 5041 $bucket_label = array( 5042 0 => __( 'Off', 'cookieflame' ), 5043 15 => __( '15 minutes', 'cookieflame' ), 5044 60 => __( '1 hour', 'cookieflame' ), 5045 240 => __( '4 hours', 'cookieflame' ), 5046 1440 => __( 'Daily', 'cookieflame' ), 5047 10080 => __( 'Weekly', 'cookieflame' ), 5321 $notif_opts = get_option( 'cookieflame_settings', array() ); 5322 $notif_email = $notif_opts['notification_email'] ?? ''; 5323 $cf_frequency = cookieflame_email_frequency(); 5324 $cf_instant_ok = cookieflame_instant_email_available(); 5325 $cf_is_premium = cookieflame_is_premium(); 5326 if ( 'instant' === $cf_frequency && ! $cf_instant_ok ) { 5327 $cf_frequency = 'daily'; // What the site actually gets without Pro. 5328 } 5329 $cf_frequencies = array( 5330 'weekly' => __( 'Weekly', 'cookieflame' ), 5331 'daily' => __( 'Daily', 'cookieflame' ), 5332 'instant' => $cf_instant_ok ? __( 'Instantly', 'cookieflame' ) : __( 'Instantly (Pro)', 'cookieflame' ), 5333 'off' => __( 'Off', 'cookieflame' ), 5048 5334 ); 5049 // Dropdown ordering: fastest at top, then slower cadences,5050 // with Off at the very bottom as the "I don't want this"5051 // answer rather than a cadence choice. Off is excluded from5052 // the ordered list and re-appended at the end.5053 $cf_dropdown_order = array( 15, 60, 240, 1440, 10080 );5054 $cf_ordered_buckets = array();5055 $cf_buckets_by_minutes = array();5056 foreach ( $buckets as $cf_b ) {5057 $cf_buckets_by_minutes[ $cf_b['minutes'] ] = $cf_b;5058 }5059 foreach ( $cf_dropdown_order as $cf_m ) {5060 if ( isset( $cf_buckets_by_minutes[ $cf_m ] ) ) {5061 $cf_ordered_buckets[] = $cf_buckets_by_minutes[ $cf_m ];5062 }5063 }5064 if ( isset( $cf_buckets_by_minutes[ 0 ] ) ) {5065 $cf_ordered_buckets[] = $cf_buckets_by_minutes[ 0 ];5066 }5067 $cf_is_premium = cookieflame_is_premium();5068 5335 ?> 5069 5336 … … 5075 5342 <tr> 5076 5343 <th scope="row"> 5077 <label for="cf-admin- digest-interval" title="<?php esc_attr_e( 'How often CookieFlame refreshes the cookie list and sends digest alerts. Shorter cadences surface new cookies sooner; longer cadences group changes into a single summary.', 'cookieflame' ); ?>"><?php esc_html_e( 'Update interval', 'cookieflame' ); ?></label>5344 <label for="cf-admin-email-frequency"><?php esc_html_e( 'Email me about changes', 'cookieflame' ); ?></label> 5078 5345 </th> 5079 5346 <td> 5080 <select id="cf-admin-digest-interval" name="digest_interval"> 5081 <?php foreach ( $cf_ordered_buckets as $cf_b ) : 5082 $cf_min = intval( $cf_b['minutes'] ); 5083 $cf_tier = (string) ( $cf_b['tier'] ?? 'free' ); 5084 $cf_label = $bucket_label[ $cf_min ] ?? ( $cf_min . ' min' ); 5085 $cf_is_pro_tier = ( 'pro' === $cf_tier ); 5086 $cf_disabled = ( $cf_is_pro_tier && ! $cf_is_premium ); 5087 if ( $cf_is_pro_tier ) { 5088 /* translators: %s: human-readable interval label, e.g. "15 minutes" */ 5089 $cf_label = sprintf( __( '%s (Pro)', 'cookieflame' ), $cf_label ); 5090 } 5091 ?> 5092 <option value="<?php echo esc_attr( $cf_min ); ?>"<?php 5093 selected( $current_mins, $cf_min ); 5094 disabled( $cf_disabled, true ); 5347 <select id="cf-admin-email-frequency" name="email_frequency"> 5348 <?php foreach ( $cf_frequencies as $cf_value => $cf_label ) : ?> 5349 <option value="<?php echo esc_attr( $cf_value ); ?>"<?php 5350 selected( $cf_frequency, $cf_value ); 5351 disabled( 'instant' === $cf_value && ! $cf_instant_ok, true ); 5095 5352 ?>><?php echo esc_html( $cf_label ); ?></option> 5096 5353 <?php endforeach; ?> 5097 5354 </select> 5098 <p class="description"><?php esc_html_e( ' Pro intervals are visible above so you can see what an upgrade adds. They unlock when CookieFlame Pro is active.', 'cookieflame' ); ?></p>5355 <p class="description"><?php esc_html_e( 'One email lists every new cookie found since the last one. Nothing is sent when there is nothing new.', 'cookieflame' ); ?></p> 5099 5356 <?php if ( ! $cf_is_premium ) : ?> 5100 <p class="description">< a href="https://cookieflame.com/pricing" target="_blank"><?php esc_html_e( 'Upgrade to CookieFlame Pro', 'cookieflame' ); ?></a></p>5357 <p class="description"><?php esc_html_e( 'Instant emails come with CookieFlame Pro.', 'cookieflame' ); ?> <a href="https://cookieflame.com/pricing" target="_blank"><?php esc_html_e( 'Upgrade to CookieFlame Pro', 'cookieflame' ); ?></a></p> 5101 5358 <?php endif; ?> 5102 5359 </td> … … 5113 5370 <p style="display:flex;align-items:center;gap:14px;flex-wrap:wrap;"> 5114 5371 <?php submit_button( __( 'Save Cookie Monitoring Settings', 'cookieflame' ), 'primary', 'submit', false ); ?> 5115 <span class="description" style="margin:0;"><?php esc_html_e( 'Clear your page cache after saving so visitors pick up the new beacon configuration.', 'cookieflame' ); ?></span>5116 5372 </p> 5117 5373 </form> … … 5123 5379 <div class="cf-admin-card"> 5124 5380 <h2><?php esc_html_e( 'Banner Preview', 'cookieflame' ); ?></h2> 5125 <p><?php esc_html_e( ' Preview how the consent banner appears to visitors in different regions. Select a region and theme to see the exact banner they would see.', 'cookieflame' ); ?></p>5381 <p><?php esc_html_e( 'Choose a region and theme to see the banner those visitors get.', 'cookieflame' ); ?></p> 5126 5382 5127 5383 <div style="display:flex;gap:16px;flex-wrap:wrap;margin:20px 0;"> 5128 5384 <div> 5129 5385 <label style="font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:#50575e;display:block;margin-bottom:6px;"><?php esc_html_e( 'Region', 'cookieflame' ); ?></label> 5130 <select id="cf-admin-preview-region" style="min-width:200px;padding:8px 12px;border-radius:6px;border:1px solid #dcdcde;">5386 <select id="cf-admin-preview-region" style="min-width:200px;padding:8px 36px 8px 12px;border-radius:6px;border:1px solid #dcdcde;"> 5131 5387 <option value="gdpr"><?php esc_html_e( 'GDPR / Opt-in (EU, EEA, UK, Brazil, South Africa, Switzerland)', 'cookieflame' ); ?></option> 5132 5388 <option value="ccpa"><?php esc_html_e( 'CCPA / Opt-out (United States)', 'cookieflame' ); ?></option> … … 5136 5392 <div> 5137 5393 <label style="font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:#50575e;display:block;margin-bottom:6px;"><?php esc_html_e( 'Theme', 'cookieflame' ); ?></label> 5138 <select id="cf-admin-preview-theme" style="min-width:200px;padding:8px 12px;border-radius:6px;border:1px solid #dcdcde;">5394 <select id="cf-admin-preview-theme" style="min-width:200px;padding:8px 36px 8px 12px;border-radius:6px;border:1px solid #dcdcde;"> 5139 5395 <option value="light"><?php esc_html_e( 'Light', 'cookieflame' ); ?></option> 5140 5396 <option value="dark"><?php esc_html_e( 'Dark', 'cookieflame' ); ?></option> 5141 5397 </select> 5142 5398 </div> 5399 <div id="cf-admin-preview-reject-wrap" style="align-self:flex-end;padding-bottom:8px;"> 5400 <label><input type="checkbox" id="cf-admin-preview-reject" /> <?php esc_html_e( 'Show "Decline all" on the first screen', 'cookieflame' ); ?></label> 5401 </div> 5402 <div id="cf-admin-preview-gpc-wrap" style="align-self:flex-end;padding-bottom:8px;display:none;"> 5403 <label><input type="checkbox" id="cf-admin-preview-gpc" /> <?php esc_html_e( 'Visitor\'s browser sends Global Privacy Control', 'cookieflame' ); ?></label> 5404 </div> 5143 5405 </div> 5144 5145 <div style="margin-top:8px;font-size:12px;color:#888;line-height:1.8;"> 5146 <strong><?php esc_html_e( 'GDPR / Opt-in:', 'cookieflame' ); ?></strong> 5147 <?php esc_html_e( 'Full consent banner with Accept All, Decline All, and Manage Preferences. Visitors must opt in before non-essential cookies are set.', 'cookieflame' ); ?><br /> 5148 <span style="color:#aaa;"><?php esc_html_e( 'Countries: Austria, Belgium, Bulgaria, Croatia, Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Ireland, Italy, Latvia, Lithuania, Luxembourg, Malta, Netherlands, Poland, Portugal, Romania, Slovakia, Slovenia, Spain, Sweden (EU); Iceland, Liechtenstein, Norway (EEA); United Kingdom, Guernsey, Jersey, Isle of Man (UK); Switzerland (revFADP); Brazil (LGPD); South Africa (POPIA).', 'cookieflame' ); ?></span><br /><br /> 5149 <strong><?php esc_html_e( 'CCPA / Opt-out:', 'cookieflame' ); ?></strong> 5150 <?php esc_html_e( 'Compact notice bar with a "Do Not Sell My Personal Information" link. All cookies are granted by default. The visitor can opt out of the sale or sharing of personal data. If Global Privacy Control (GPC) is detected in the browser, marketing cookies are automatically suppressed.', 'cookieflame' ); ?><br /> 5151 <span style="color:#aaa;"><?php esc_html_e( 'Countries: United States (CCPA/CPRA applies to California residents; GPC is legally binding in 12+ states including California, Colorado, Connecticut, Texas, Montana, Oregon, and others).', 'cookieflame' ); ?></span><br /><br /> 5152 <strong><?php esc_html_e( 'No banner:', 'cookieflame' ); ?></strong> 5153 <?php esc_html_e( 'No consent banner is shown. All cookies are auto-granted. The cookie icon appears in the bottom-left corner so visitors can manage preferences if they choose. This applies to visitors in countries not covered by the regions above.', 'cookieflame' ); ?> 5406 <p class="description" id="cf-admin-preview-reject-note" style="margin-top:-8px;"><?php esc_html_e( 'The "Decline all" box here only changes the preview. To change your banner, use the setting on the Settings tab.', 'cookieflame' ); ?></p> 5407 5408 <div id="cf-admin-preview-container" style="position:relative;border-radius:12px;overflow:hidden;margin-top:16px;"></div> 5409 5410 <div id="cf-admin-preview-notes" style="margin-top:16px;font-size:12px;color:#888;line-height:1.8;"> 5411 <p data-region="gdpr" style="margin:0;"><strong><?php esc_html_e( 'GDPR / Opt-in:', 'cookieflame' ); ?></strong> 5412 <?php esc_html_e( 'Full consent banner with Accept All and Manage preferences. Decline All is under Manage preferences, and you can also show it next to Accept All with the setting on the Settings tab. Visitors must opt in before non-essential cookies are set.', 'cookieflame' ); ?><br /> 5413 <span style="color:#aaa;"><?php esc_html_e( 'Countries: Austria, Belgium, Bulgaria, Croatia, Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Ireland, Italy, Latvia, Lithuania, Luxembourg, Malta, Netherlands, Poland, Portugal, Romania, Slovakia, Slovenia, Spain, Sweden (EU); Iceland, Liechtenstein, Norway (EEA); United Kingdom, Guernsey, Jersey, Isle of Man (UK); Switzerland (revFADP); Brazil (LGPD); South Africa (POPIA).', 'cookieflame' ); ?></span></p> 5414 <p data-region="ccpa" style="margin:0;display:none;"><strong><?php esc_html_e( 'CCPA / Opt-out:', 'cookieflame' ); ?></strong> 5415 <?php esc_html_e( 'Compact notice with a "Do Not Sell or Share My Personal Information" link, which opens a real opt-out choice. Cookies are allowed by default. If the visitor\'s browser sends Global Privacy Control (GPC), they are opted out of selling and sharing straight away: Marketing stays off, the notice says the signal was honoured, and OK only closes the notice. They can opt back in only through a separate choice, and a later visit without GPC keeps the opt-out.', 'cookieflame' ); ?><br /> 5416 <span style="color:#aaa;"><?php esc_html_e( 'Countries: United States. The same notice covers California (CCPA/CPRA) and the other states with similar laws.', 'cookieflame' ); ?></span></p> 5417 <p data-region="none" style="margin:0;display:none;"><strong><?php esc_html_e( 'No banner:', 'cookieflame' ); ?></strong> 5418 <?php esc_html_e( 'No consent banner is shown and all cookies are allowed. Visitors can still change their choices from a [cookieflame_settings_link] link or your [cookieflame_declaration] page. This applies to visitors in countries not covered by the regions above.', 'cookieflame' ); ?></p> 5154 5419 </div> 5155 5420 </div> 5156 5157 <div id="cf-admin-preview-container" style="position:relative;border-radius:12px;overflow:hidden;min-height:400px;margin-top:4px;">5158 </div>5159 5421 </div> 5160 5422 5161 <?php5162 $opts = get_option( 'cookieflame_settings', array() );5163 $preview_config = array(5164 'privacyUrl' => $opts['privacy_url'] ?? '/privacy-policy',5165 'ccpaUrl' => ( $opts['privacy_url'] ?? '/privacy-policy' ) . '#do-not-sell',5166 );5167 $preview_cfg_json = wp_json_encode( $preview_config );5168 $preview_theme_json = wp_json_encode( $opts['theme'] ?? 'light' );5169 $preview_js = <<<'JS'5170 (function () {5171 var regionEl = document.getElementById("cf-admin-preview-region");5172 var themeEl = document.getElementById("cf-admin-preview-theme");5173 var container = document.getElementById("cf-admin-preview-container");5174 if (!regionEl || !themeEl || !container) return;5175 5176 var cfg = __COOKIEFLAME_PREVIEW_CFG__;5177 5178 var txt = {5179 title: "We value your privacy",5180 description: "We use cookies to improve your experience, analyse traffic and personalise content. You can choose which categories to allow.",5181 acceptAll: "Accept All",5182 declineAll: "Decline All",5183 savePrefs: "Save Preferences",5184 managePrefs: "Manage preferences",5185 hidePrefs: "Hide preferences",5186 ccpaText: "We use cookies and similar technologies. Under California law, you have the right to opt out of the sale of your personal information.",5187 ccpaDnsmpi: "Do Not Sell My Personal Information",5188 ccpaOk: "OK"5189 };5190 5191 var categories = [5192 { id: "necessary", name: "Necessary", desc: "Required for the site to function. Cannot be disabled.", required: true },5193 { id: "preferences", name: "Preferences", desc: "Remember your settings and personalisation choices.", required: false },5194 { id: "analytics", name: "Analytics", desc: "Help us understand how visitors use the site.", required: false },5195 { id: "marketing", name: "Marketing", desc: "Used to deliver relevant ads and track campaigns.", required: false }5196 ];5197 5198 function esc(s) { var d = document.createElement("div"); d.textContent = s; return d.innerHTML; }5199 5200 function render() {5201 var region = regionEl.value;5202 var theme = themeEl.value;5203 var isDark = theme === "dark";5204 var bg = isDark ? "#121212" : "#f5f5f5";5205 var bannerBg = isDark ? "#1c1c1e" : "#fff";5206 var textColor = isDark ? "#f5f5f7" : "#1d1d1f";5207 var mutedColor = isDark ? "#98989d" : "#555";5208 var borderColor = isDark ? "#2c2c2e" : "#e8e8ed";5209 var catDescColor = isDark ? "#636366" : "#888";5210 var acceptBg = isDark ? "#0a84ff" : "#0071e3";5211 var declineBg = isDark ? "#2c2c2e" : "#e8e8ed";5212 var declineColor = isDark ? "#f5f5f7" : "#1d1d1f";5213 var saveBg = isDark ? "#30d158" : "#34c759";5214 var detailsColor = isDark ? "#636366" : "#888";5215 var linkColor = isDark ? "#2997ff" : "#0071e3";5216 var toggleBg = isDark ? "#3a3a3c" : "#ccc";5217 var toggleActiveBg = isDark ? "#0a84ff" : "#0071e3";5218 var sliderBg = isDark ? "#f5f5f7" : "#fff";5219 var poweredColor = isDark ? "#888" : "#ccc";5220 5221 var html = '<div style="background:' + bg + ';padding:60px 20px 20px;min-height:400px;display:flex;align-items:flex-end;justify-content:center;border-radius:12px;">';5222 5223 if (region === "none") {5224 html += '<div style="text-align:center;padding:40px;color:' + mutedColor + ';font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;font-size:14px;align-self:center;">';5225 html += '<div style="font-size:48px;margin-bottom:16px;">🌍</div>';5226 html += '<div style="font-weight:700;font-size:16px;color:' + textColor + ';margin-bottom:8px;">No banner shown</div>';5227 html += '<div>Visitors outside the regulated regions (EU/EEA, UK, US,<br>Switzerland, Brazil, South Africa) see no consent banner.<br>All cookies are auto-granted. The cookie icon appears in the<br>bottom-left corner so they can manage preferences if they choose.</div>';5228 html += '<div style="margin-top:24px;width:48px;height:48px;border-radius:50%;background:' + (isDark ? "#f5f5f7" : "#1d1d1f") + ';display:inline-flex;align-items:center;justify-content:center;">';5229 html += '<svg width="24" height="24" viewBox="0 0 38 38" fill="none"><path d="M19.1 1.16c-.06-.42-.38-.77-.8-.83A11.8 11.8 0 0 0 13.28 1.2L7.5 4.3A11.8 11.8 0 0 0 3.95 7.98L1.09 13.9A11.8 11.8 0 0 0 .38 18.96l1.13 6.47a11.8 11.8 0 0 0 3.53 4.99l4.71 4.56a11.8 11.8 0 0 0 4.59 2.24l6.48.91a11.8 11.8 0 0 0 5.52-.89l5.78-3.09a11.8 11.8 0 0 0 3.55-3.69l2.87-5.9a11.8 11.8 0 0 0 .71-5.08c-.07-.41-.42-.73-.83-.79a14.8 14.8 0 0 1-8.16-7.61c-.14-.59-.63-1.05-1.23-1.14A14.8 14.8 0 0 1 19.1 1.16ZM15.25 10.25a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Zm-5 15a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0Zm17.5-5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z" fill="' + (isDark ? "#1d1d1f" : "#fff") + '"/></svg>';5230 html += '</div>';5231 html += '</div>';5232 html += '</div>';5233 container.innerHTML = html;5234 return;5235 }5236 5237 if (region === "ccpa") {5238 html += '<div style="position:relative;bottom:0;left:0;right:0;width:100%;background:' + bannerBg + ';box-shadow:0 -8px 40px rgba(0,0,0,.15);border-radius:20px 20px 0 0;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;font-size:14px;color:' + textColor + ';">';5239 html += '<div style="max-width:720px;margin:0 auto;padding:20px 32px;display:flex;align-items:center;gap:20px;flex-wrap:wrap;">';5240 html += '<div style="flex:1;min-width:280px;"><p style="margin:0;color:' + mutedColor + ';">' + esc(txt.ccpaText) + '</p></div>';5241 html += '<div style="display:flex;gap:10px;align-items:center;flex-shrink:0;">';5242 html += '<a href="' + esc(cfg.ccpaUrl) + '" style="color:' + linkColor + ';text-decoration:underline;font-size:13px;font-weight:500;white-space:nowrap;">' + esc(txt.ccpaDnsmpi) + '</a>';5243 html += '<button style="display:inline-flex;align-items:center;justify-content:center;border-radius:12px;border:none;font-weight:700;cursor:pointer;background:' + acceptBg + ';color:#fff;font-size:15px;padding:14px 32px;box-shadow:0 2px 8px rgba(0,113,227,.3);">' + esc(txt.ccpaOk) + '</button>';5244 html += '</div></div>';5245 html += '<div style="text-align:center;padding:0 0 12px;font-size:11px;color:' + poweredColor + ';"><a href="https://www.cookieflame.com" target="_blank" rel="noopener" style="color:' + poweredColor + ';text-decoration:none;">Powered by CookieFlame</a></div>';5246 html += '</div>';5247 html += '</div>';5248 container.innerHTML = html;5249 return;5250 }5251 5252 html += '<div style="position:relative;bottom:0;left:0;right:0;width:100%;background:' + bannerBg + ';box-shadow:0 -8px 40px rgba(0,0,0,.15);border-radius:20px 20px 0 0;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;font-size:14px;color:' + textColor + ';">';5253 html += '<div style="max-width:720px;margin:0 auto;padding:32px 32px 20px;">';5254 html += '<p style="font-size:20px;font-weight:800;margin:0 0 8px;letter-spacing:-.3px;">' + esc(txt.title) + '</p>';5255 html += '<p style="margin:0 0 20px;color:' + mutedColor + ';font-size:14px;line-height:1.6;">' + esc(txt.description) + ' <a href="' + esc(cfg.privacyUrl) + '" style="color:' + linkColor + ';text-decoration:none;font-weight:500;">Privacy Policy</a></p>';5256 5257 html += '<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:16px;">';5258 html += '<button style="display:inline-flex;align-items:center;justify-content:center;border-radius:12px;border:none;font-weight:700;cursor:pointer;background:' + acceptBg + ';color:#fff;font-size:15px;padding:14px 32px;box-shadow:0 2px 8px rgba(0,113,227,.3);">' + esc(txt.acceptAll) + '</button>';5259 html += '<button id="cf-admin-preview-manage" style="background:transparent;color:' + detailsColor + ';padding:11px 4px;font-weight:500;font-size:13px;border:none;cursor:pointer;">' + esc(txt.managePrefs) + '</button>';5260 html += '</div>';5261 5262 html += '<div id="cf-admin-preview-cats" style="display:none;margin:0 0 16px;border:1px solid ' + borderColor + ';border-radius:14px;overflow:hidden;">';5263 for (var i = 0; i < categories.length; i++) {5264 var cat = categories[i];5265 var isLast = i === categories.length - 1;5266 var checkedAttr = cat.required ? " checked disabled" : "";5267 var alwaysOn = cat.required ? ' <span style="font-weight:400;font-size:11px;color:' + (isDark ? "#48484a" : "#aaa") + ';">(always on)</span>' : "";5268 5269 html += '<div style="padding:16px 20px;' + (isLast ? "" : "border-bottom:1px solid " + borderColor + ";") + 'display:flex;justify-content:space-between;align-items:center;gap:16px;">';5270 html += '<div><div style="font-weight:700;font-size:14px;">' + esc(cat.name) + alwaysOn + '</div>';5271 html += '<div style="font-size:12px;color:' + catDescColor + ';margin-top:3px;">' + esc(cat.desc) + '</div></div>';5272 html += '<label style="position:relative;width:46px;height:26px;flex-shrink:0;display:block;">';5273 html += '<input type="checkbox"' + checkedAttr + ' style="opacity:0;width:0;height:0;position:absolute;" />';5274 html += '<span style="position:absolute;inset:0;background:' + (cat.required ? toggleActiveBg + ";opacity:.5" : toggleBg) + ';border-radius:26px;cursor:' + (cat.required ? "default" : "pointer") + ';transition:background .2s;"></span>';5275 html += '<span style="position:absolute;width:20px;height:20px;left:' + (cat.required ? "23px" : "3px") + ';bottom:3px;background:' + sliderBg + ';border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.15);transition:transform .2s;"></span>';5276 html += '</label></div>';5277 }5278 html += '</div>';5279 5280 html += '<div id="cf-admin-preview-pref-btns" style="display:none;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:8px;">';5281 html += '<button style="display:inline-flex;align-items:center;justify-content:center;border-radius:12px;border:none;font-weight:700;cursor:pointer;background:' + saveBg + ';color:#fff;font-size:14px;padding:13px 28px;box-shadow:0 2px 8px rgba(52,199,89,.3);">' + esc(txt.savePrefs) + '</button>';5282 html += '<button style="display:inline-flex;align-items:center;justify-content:center;border-radius:12px;border:none;font-weight:700;cursor:pointer;background:' + declineBg + ';color:' + declineColor + ';font-size:13px;padding:11px 22px;">' + esc(txt.declineAll) + '</button>';5283 html += '</div>';5284 5285 html += '</div>';5286 html += '<div style="text-align:center;padding:0 0 16px;font-size:11px;color:' + poweredColor + ';"><a href="https://www.cookieflame.com" target="_blank" rel="noopener" style="color:' + poweredColor + ';text-decoration:none;">Powered by CookieFlame</a></div>';5287 html += '</div>';5288 html += '</div>';5289 5290 container.innerHTML = html;5291 5292 var manageBtn = document.getElementById("cf-admin-preview-manage");5293 var catsEl = document.getElementById("cf-admin-preview-cats");5294 var prefBtnsEl = document.getElementById("cf-admin-preview-pref-btns");5295 if (manageBtn && catsEl) {5296 manageBtn.addEventListener("click", function () {5297 var open = catsEl.style.display === "none";5298 catsEl.style.display = open ? "block" : "none";5299 if (prefBtnsEl) prefBtnsEl.style.display = open ? "flex" : "none";5300 manageBtn.textContent = open ? txt.hidePrefs : txt.managePrefs;5301 });5302 }5303 }5304 5305 render();5306 regionEl.addEventListener("change", render);5307 themeEl.addEventListener("change", render);5308 5309 themeEl.value = __COOKIEFLAME_PREVIEW_THEME__;5310 render();5311 })();5312 JS;5313 $preview_js = str_replace(5314 array( '__COOKIEFLAME_PREVIEW_CFG__', '__COOKIEFLAME_PREVIEW_THEME__' ),5315 array( $preview_cfg_json, $preview_theme_json ),5316 $preview_js5317 );5318 wp_print_inline_script_tag( $preview_js );5319 ?>5320 5423 5321 5424 <?php endif; ?> -
cookieflame/tags/5.4.0/readme.txt
r3769546 r3771537 5 5 Tested up to: 7.1 6 6 Requires PHP: 8.0 7 Stable tag: 5. 3.07 Stable tag: 5.4.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 Cookie consent for WordPress that finds cookies automatically. Runs continuously, no external service, correct banner for GDPR, CCPA, LGPD, POPIA.11 Cookie consent for WordPress that finds cookies automatically. Self-hosted, with the right banner for GDPR, CCPA, LGPD and POPIA visitors. 12 12 13 13 == Description == 14 14 15 CookieFlame is a cookie consent plugin for WordPress that does three things most other free plugins do not. It finds the cookies on your site automatically by watching real visitor sessions. It runs entirely on your own server with no outside connections. And it shows the right banner for each visitor based on where they are, rather than showing a single GDPR banner to the whole world.15 CookieFlame is a cookie consent plugin that finds your site's cookies for you, runs on your own server, and shows each visitor the banner their region requires. Full details are at [cookieflame.com](https://cookieflame.com). 16 16 17 It is built by [Zoobu] and the full product site is at [cookieflame.com](https://cookieflame.com). 17 = Finds cookies for you = 18 18 19 = 🔍 Cookie discovery, continuously = 19 * **Watch for new cookies:** your visitors' browsers report cookies as they browse, so a new plugin's or tag's cookies appear in the Cookie Manager without you scanning. Get an email about changes weekly, daily, or not at all. 20 * **Cookie Discovery:** for a full list now, browse your own site with every cookie allowed for your browser only. It uses your own browser. 20 21 21 Most consent plugins expect you to know which cookies your site sets. CookieFlame does not. A lightweight beacon watches real visitor sessions in the background and reports any cookies it has not seen before. New plugin installed? Its cookies surface automatically. Tracking tag changed? Picked up next time a visitor triggers it. No scheduled scans, no headless browsers, no external scanning service. 22 = The right banner for each region = 22 23 23 For a one-off inventory you can also run an on-demand Discovery scan from the admin. Browse your own site as logged in, watch the discovery bar accumulate cookies and detected services, click Finish when done. 24 * UK, EU/EEA, Switzerland, Brazil and South Africa: an opt-in banner with Accept All and Manage preferences. You can also show Decline All on the first screen (off by default). 25 * United States: a short notice with a "Do Not Sell or Share My Personal Information" link that opens a real opt-out choice. 26 * Everywhere else: no banner. 24 27 25 = 🌍 Correct behaviour per region = 28 Country comes from CDN headers such as Cloudflare's, or a free MaxMind database you upload, with the time zone as a fallback. If none works, the opt-in banner is shown. 26 29 27 CookieFlame detects visitor country and shows the banner that region actually requires: 30 = Global Privacy Control = 28 31 29 * EU, UK, EEA, Switzerland: full two-step GDPR opt-in banner with Accept, Manage Preferences, and Decline 30 * California and other US states with similar laws: compact CCPA opt-out notice with a Do Not Sell link 31 * Brazil (LGPD) and South Africa (POPIA): GDPR-style opt-in banner 32 * Everywhere else: no banner, all cookies auto-granted 32 A browser that sends Global Privacy Control (GPC) is opted out of selling and sharing straight away: marketing cookies stay off and Google's ad signals are denied. US visitors see that the signal was honoured, and OK only closes the notice. The opt-out is saved, so a later visit without GPC keeps it. A visitor can opt back in only through a separate, clear choice. 33 33 34 Detection uses three layers in order: Cloudflare and other CDN country headers first (zero latency, works with every caching setup), then a local MaxMind GeoLite2 database if uploaded, then a browser timezone heuristic as a last-resort fallback. If all three fail, the plugin defaults to GDPR opt-in for safety rather than silently granting consent. 34 = Blocks trackers before consent = 35 35 36 = 🔒 No outside connections = 36 Tracking scripts are rewritten on your server before the page is sent, so blocking works with WP Rocket, LiteSpeed, Cloudflare APO and other caches. Built-in patterns cover Google Analytics, Google Ads, Google Tag Manager, Microsoft Ads, Meta, X (Twitter), LinkedIn, Pinterest, Reddit and TikTok. Cookies you classify get matching rules automatically. 37 37 38 CookieFlame does not load JavaScript from any content delivery network, does not call any geo API, does not phone home. Every part of the consent flow runs on your server and in the visitor's browser. The only optional outbound request is Google Tag Manager itself, if you enter a GTM container ID in the settings (see the External services section below).38 YouTube, Vimeo, Google Maps, Spotify, SoundCloud, Dailymotion and Elementor videos show a click-to-load placeholder until the visitor consents. 39 39 40 This matters because a consent tool that itself sets third-party cookies is an awkward contradiction, and because self-hosted plugins keep working during outages of services you did not choose to depend on. 40 = Google Consent Mode v2 = 41 41 42 = 🚫 Server-side script blocking = 42 All Google consent signals start as denied before any tag runs and update the moment a visitor chooses. Returning visitors' choices are restored before Google Tag Manager loads. 43 43 44 When a visitor has not granted consent, CookieFlame rewrites tracking scripts in the HTML output so the browser does not execute them. This happens before the page leaves your server, which means it works with any caching stack: WP Rocket, LiteSpeed, Cloudflare APO, W3 Total Cache, and others all hold the correct pre-modified HTML. A lighter JavaScript layer handles scripts injected later by GTM or other plugins. 44 = For developers = 45 45 46 Known trackers are blocked from the moment you install the plugin. Ten services ship with built-in patterns: Google Analytics, Google Ads, Google Tag Manager, Microsoft Ads, Meta and Facebook, Twitter and X, LinkedIn, Pinterest, Reddit, TikTok. Anything else you classify in the Cookie Manager gets a matching block rule generated automatically. 46 * Events: cc:ready, cc:update, cc:accept-all, cc:decline-all, cc:save and cc:banner-rendered. 47 * JavaScript API: CookieConsent.getState(), show(), showSettings() and more. 48 * [cookieflame_settings_link] shortcode, or the class cc-open-settings on any link, to reopen a visitor's choices. 49 * CSS custom properties (--cc-accent, --cc-bg, --cc-radius, --cc-font and others) to restyle the banner. 50 * Filters cookieflame_frontend_config and cookieflame_digest_email, and the action cookieflame_settings_sections. 51 * An optional consent log endpoint for your own audit trail. 47 52 48 = 🪟 Iframe facades=53 = CookieFlame Pro = 49 54 50 YouTube, Vimeo, Google Maps, Spotify, SoundCloud, Dailymotion, and Elementor video widgets are replaced with click-to-accept placeholders until the visitor grants consent. The iframe source is stripped synchronously before the embed has a chance to contact its origin.55 Pro identifies and classifies cookies automatically, creates the blocking rules, and adds instant emails. See [cookieflame.com](https://cookieflame.com). 51 56 52 = 📊 Google Consent Mode v2=57 = Compliance note = 53 58 54 All seven Google consent signals are managed automatically and set to denied by default before any tag runs. When a visitor grants consent, the correct signals update immediately. Returning visitors keep their stored choices, with the signals restored before GTM loads (so the first dataLayer push is already correct). 55 56 Works with a GTM container ID you paste into the settings. No separate GTM consent template, no additional configuration. The plugin also injects the body noscript tag in the correct position. 57 58 = 🛡️ Global Privacy Control = 59 60 GPC is detected automatically and suppresses marketing consent for CCPA visitors. Required by California, Colorado, Connecticut, Texas, and nine other US states. Turning GPC off later restores the visitor's original stored choice rather than overwriting it. 61 62 = 📜 IAB TCF v2.2 = 63 64 A TCF v2.2 stub is registered on every page so ad-tech vendors that need TCF signals can read them. 65 66 = 🚀 Lightweight and framework-free = 67 68 The frontend JavaScript is a single 37 KB file (12 KB gzipped) with no jQuery and no framework dependencies. A separate facades bundle is only loaded on pages that actually contain a blockable embed. The Discovery bundle only loads for administrators during an active scan. 69 70 = 👨💻 Developer-friendly = 71 72 * Event API with CustomEvents: cc:ready, cc:update, cc:accept-all, cc:decline-all, cc:save 73 * Public JavaScript API for reading consent state and showing the banner on demand 74 * Per-category onAccept and onReject callbacks 75 * Extension points via WordPress filters and actions 76 * Optional consent log endpoint for audit trails 77 78 = ✨ Want automatic classification? = 79 80 CookieFlame Pro adds automatic cookie classification using a central database and AI. You click Auto-Identify All, and the plugin classifies every unclassified cookie with vendor, category, and purpose, then generates the blocking rules automatically. Flat annual pricing by number of sites, no pageview limits. Learn more at [cookieflame.com](https://cookieflame.com). 81 82 = 📋 Compliance note = 83 84 CookieFlame provides tools that help support compliance with GDPR, CCPA, LGPD, POPIA and similar regulations. Compliance itself is the site owner's responsibility and depends on how the plugin is configured. For legal advice, consult a qualified professional. 59 CookieFlame helps with GDPR, CCPA, LGPD, POPIA and similar laws; how you set it up is your responsibility. For legal advice, ask a qualified professional. 85 60 86 61 == Installation == 87 62 88 1. Upload the `cookieflame` folder to `/wp-content/plugins/`, or install via Plugins > Add New in the WordPress admin89 2. Activate the plugin through the Plugins menu90 3. Go to **CookieFlame** in the admin menu to review settings63 1. Install from Plugins > Add New, or upload the `cookieflame` folder to `/wp-content/plugins/`. 64 2. Activate the plugin. 65 3. Open **CookieFlame** in the admin menu to check the settings. 91 66 92 That is it. The plugin works immediately with sensible defaults. The ten built-in tracking services are blocked before any visitor can trigger them, and the banner shows correctly for each region. 93 94 For best geo detection, either run Cloudflare (the plugin reads the CF-IPCountry header automatically) or upload a free MaxMind GeoLite2 database on the Geo Detection tab. Neither is required. 67 It works straight away. For the best country detection, use Cloudflare or upload a free MaxMind database on the Geo Detection tab. 95 68 96 69 == Frequently Asked Questions == … … 98 71 = Do I need to know which cookies my site sets? = 99 72 100 No. CookieFlame watches real visitor sessions and reports new cookies to the Cookie Manager automatically. You classify them when you are ready, and the plugin generates the blocking rules for you.73 No. Watch for new cookies lists them as your visitors' browsers report them, and Cookie Discovery finds them on demand. You classify them when you are ready. 101 74 102 = Does this work with Google Tag Manager? =75 = Does it work with Google Tag Manager and caching plugins? = 103 76 104 Yes. Enter your GTM container ID on the Tag Managers tab and the plugin injects both the head script and body noscript tag in the correct positions. All GTM tags that respect Google Consent Mode will see the denied state on load and wait for consent.77 Yes. Blocking happens in the HTML before it is cached. Turn off Cloudflare Rocket Loader; the plugin warns you if it is on. 105 78 106 = Does this work with Cloudflare, WP Rocket, and other caching setups? =79 = What do US visitors see? = 107 80 108 Yes. Script blocking runs server-side in the PHP output buffer, so the cached HTML already has the correct tags rewritten. Cloudflare is specifically supported for geo detection via the CF-IPCountry header on all plans including free.81 A short notice. Cookies are allowed by default because US laws use an opt-out model. The "Do Not Sell or Share My Personal Information" link opens a choice to opt out. Put [cookieflame_settings_link] in your footer so visitors can reach it again later. 109 82 110 One exception: Cloudflare Rocket Loader rewrites scripts at the edge after PHP has run, which defeats the blocking layer. Rocket Loader must be disabled when using CookieFlame. The plugin detects this and shows a warning if it is active. 83 = Can I show "Decline all" on the first screen? = 111 84 112 = What happens for US visitors? = 85 Yes. Turn on "Show 'Decline all' on the first screen" on the Settings tab. It appears only on the opt-in banner, never on the US notice. 113 86 114 US visitors see a compact CCPA opt-out notice at the bottom of the page with a Do Not Sell My Personal Information link. Cookies are auto-granted by default because CCPA uses an opt-out model, and the visitor can revoke consent through the notice. 87 = Can I change the banner text? = 115 88 116 = What happens for visitors outside the regulated regions? = 117 118 No banner is shown and all cookies are auto-granted. CookieFlame does not show GDPR banners to visitors whose region does not require them. 119 120 = Does the plugin contact any external service? = 121 122 By default, no. Geo detection reads CDN headers first, then a local MaxMind database if you have uploaded one, then the browser timezone as a last resort. No external geo API is called. The plugin does not load scripts from any CDN and does not phone home. 123 124 The one optional external request is Google Tag Manager, but only if you have entered a GTM container ID in the settings. See the External services section for full details. 125 126 = Does it handle Global Privacy Control (GPC)? = 127 128 Yes. GPC is required by twelve US states and the plugin detects the browser signal automatically. For CCPA visitors with GPC active, marketing consent is suppressed before any tag runs. Turning GPC off later restores the visitor's original choice. 129 130 = Can I customise the banner text and colours? = 131 132 Yes. Every string shown to visitors is editable through the Settings tab, and the banner supports both light and dark themes. For per-language versions, use WPML or Polylang with the standard WordPress translation pipeline. 133 134 = What is CookieFlame Pro? = 135 136 Pro adds automatic cookie classification. Instead of classifying each cookie manually, you click Auto-Identify All and the plugin uses a central database of known cookies plus AI to classify them with vendor, category, and purpose. Pro also adds tighter monitoring intervals, a more detailed digest email, and automatic MaxMind database updates. Pricing is flat annual by site count with no pageview limits. See [cookieflame.com](https://cookieflame.com) for details. 89 Not yet from the settings. The banner is in English, with light and dark themes. Developers can change the text and colours with the cookieflame_frontend_config filter and the --cc-* CSS properties. 137 90 138 91 = Is my visitors' data safe? = 139 92 140 CookieFlame does not collect visitor data. IP addresses are hashed for rate-limiting and never stored raw. Cookie values are never transmitted, only their name and a shape classification (uuid, timestamp, and similar) used for identification. The plugin stores consent decisions in a first-party cookie on the visitor's ownbrowser.93 CookieFlame collects no visitor data. Cookie values are never sent, only names. IP addresses are hashed for rate limiting and never stored. Choices are kept in a first-party cookie in the visitor's browser. 141 94 142 95 == External services == 143 96 144 CookieFlame is a self-hosted plugin. By default it does not contact any external service from the visitor's browser, and it does not phone home.97 CookieFlame does not phone home. These requests happen only in the cases described. 145 98 146 There is one optional external interaction, only if the site owner configures it.99 **Google Tag Manager (optional).** If you enter a container ID on the Tag Managers tab, Google's script (https://www.googletagmanager.com/gtm.js) loads on every page, except for visitors you exclude from tracking. Tags inside it follow the visitor's Consent Mode choices. Sent: the container ID and the browser's standard request data (IP address, user agent, page URL). [Terms](https://marketingplatform.google.com/about/analytics/tag-manager/use-policy/), [privacy policy](https://policies.google.com/privacy). 147 100 148 ** Google Tag Manager (optional)**101 **Microsoft UET (optional).** If you enter a UET tag ID, Microsoft's loader (https://bat.bing.com/bat.js) loads on every page, except for visitors you exclude from tracking. It records nothing until the visitor allows Marketing. Sent: the tag ID and the browser's standard request data. [Microsoft Advertising agreement](https://about.ads.microsoft.com/en/resources/policies/microsoft-advertising-agreement), [privacy statement](https://privacy.microsoft.com/en-gb/privacystatement). 149 102 150 If the site owner enters a Google Tag Manager container ID on the CookieFlame Tag Managers settings tab, CookieFlame injects the standard Google Tag Manager script (https://www.googletagmanager.com/gtm.js) into the site front-end under the visitor's consent. This script is provided by Google and is loaded from Google's servers when the page is viewed.103 **YouTube thumbnails.** When a page has a YouTube embed that is waiting for consent, its placeholder shows the video's thumbnail from img.youtube.com. Sent: the video ID and the browser's standard request data. [YouTube terms](https://www.youtube.com/t/terms), [Google privacy policy](https://policies.google.com/privacy). 151 104 152 * What is sent: standard Google Tag Manager request, including the container ID and whatever browser request data Google receives for any embed. 153 * When: only if the admin has set a GTM container ID, and only when a visitor loads a page and has consented. 154 * Google Tag Manager terms: https://marketingplatform.google.com/about/analytics/tag-manager/use-policy/ 155 * Google privacy policy: https://policies.google.com/privacy 105 **Consent log endpoint (optional).** If you enter a URL in the Consent Log Endpoint setting, each consent choice is sent there from the visitor's browser as JSON: an anonymous ID, time, action, the choice per category, region, country code, page URL, user agent, the GPC flag, consent version and plugin version. No cookie values or IP address are in it. You choose the destination and are responsible for it. 156 106 157 If no GTM container ID is set, no request is made. 158 159 **Microsoft UET (Bing Ads tag, optional)** 160 161 If the site owner enters a Microsoft UET tag ID on the CookieFlame Tag Managers settings tab, CookieFlame injects the standard Microsoft UET loader script (https://bat.bing.com/bat.js) into the site front-end. The loader is provided by Microsoft and is loaded from Microsoft's servers when the page is viewed. 162 163 The UET runtime reads Microsoft Consent Mode v2 signals from the dataLayer that CookieFlame already manages, so the loader honours visitor consent internally: tracking events do not record until the visitor has granted Marketing consent. The loader script itself is fetched on every page load while a UET tag ID is configured. 164 165 * What is sent: the UET tag ID, plus the standard browser request data Microsoft receives for any third-party script load (visitor IP, user agent, page URL, referer). 166 * When: every page load while a UET tag ID is configured, except for visitors whose browser sends the Do Not Track header in which case CookieFlame suppresses the injection entirely. 167 * Microsoft Advertising agreement: https://about.ads.microsoft.com/en/resources/policies/microsoft-advertising-agreement 168 * Microsoft privacy statement: https://privacy.microsoft.com/en-gb/privacystatement 169 170 If no UET tag ID is set, no request is made. 171 172 **Consent log endpoint (optional, site-owner-nominated webhook)** 173 174 If the site owner enters a URL in the Consent Log endpoint setting, CookieFlame sends a JSON record of each consent decision to that URL from the visitor's browser via navigator.sendBeacon (with a fetch fallback for browsers without sendBeacon). The default for this setting is empty, in which case nothing is sent. The destination URL is nominated by the site owner; terms and privacy obligations for that URL are the site owner's responsibility, not CookieFlame's. 175 176 * What is sent: a JSON payload containing an anonymous ID, an ISO-8601 timestamp, the action that produced the record (accept-all, decline-all, save, or one of the region-based auto-grants), the per-category consent decision (necessary, preferences, analytics, marketing), the compliance mode (gdpr, ccpa, none), the visitor country code, the page URL, the user agent string, the TCF v2.2 flag, the Global Privacy Control flag, the consent policy version, and the plugin version. No cookie values are included. No IP address is included in the payload, although the receiving endpoint will of course see the visitor IP via the underlying HTTP request, the same as any other request. 177 * When: every time the visitor's consent state is set. This includes banner actions (accept-all, decline-all, save) and region-based auto-grants for visitors outside GDPR regions and for CCPA opt-out visitors. 178 179 If no Consent Log endpoint URL is set, no request is made. 180 181 **Domain strings in the plugin code** 182 183 The plugin code contains many third-party domain names (for example, connect.facebook.net, googletagmanager.com, and similar). These are pattern strings used for local matching only. They identify scripts that may already be present on the admin's site so CookieFlame can detect them, show them to the admin in the Cookie Manager, and block them until the visitor has given consent. CookieFlame does not fetch from these domains. 107 Third-party domain names in the code (such as connect.facebook.net) are only used to recognise and block scripts; they are never contacted. 184 108 185 109 == Screenshots == 186 110 187 1. GDPR consent banner with category toggles (two-step flow)188 2. CCPA opt-out notice for US visitors189 3. Cookie Manager showing discovered cookies with their origin190 4. Cookie Discovery in progress : the discovery bar accumulates services191 5. Admin Settings tab with theme and regional behaviour controls192 6. Geo Detection tab with CDN status and MaxMind upload193 7. Tag Managers tab for GTM and Microsoft UET111 1. Opt-in banner with category toggles 112 2. US notice with the "Do Not Sell or Share" link 113 3. Cookie Manager listing your site's cookies 114 4. Cookie Discovery in progress 115 5. Settings tab 116 6. Geo Detection tab 117 7. Tag Managers tab 194 118 195 119 == Changelog == 196 120 197 = 5.3.0 = 198 * Cookie monitoring now ignores cookies that come with a visitor's browser rather than from your site. Some browser extensions write the "already accepted" cookies of other consent tools (such as Complianz, CookieYes, Cookiebot, OneTrust or Didomi) onto every site they visit, and these were being reported as your site's cookies. A consent tool's cookie is now ignored when that tool doesn't run on your site: its plugin isn't active, its script isn't on the page, and no script on the page wrote the cookie. 199 * Cookies written by a browser extension's own code are ignored too. 200 * Cookies ignored this way go on the Permanently Ignored list with the reason, and the list now shows why each cookie was ignored. Restoring one keeps it monitored from then on. 201 * The Cookie Manager and the Cookie Discovery count leave these cookies out too, including ones your own browser extensions add. Cookies that came with your browser during Cookie Discovery go on the Permanently Ignored list with the reason. Cookies on the Permanently Ignored list no longer show in the Cookie Manager just because they are in your own browser. 202 * On update, other consent tools' cookies that were already reported are moved to the Permanently Ignored list. Cookies you classified yourself are left alone. 203 * Fixed: the "consent plugin detected" conflict warning could be set off by CookieFlame's own list of cookie names rather than by another consent plugin on the page. 204 * Cookie Manager: the last column is now "Status". Each cookie shows one label for where it stands (Known, Auto-Identified, Classified, Added by you, Changed by you, or Not identified yet), and cookies that aren't identified yet say where they came from: found by your scan, reported by visitors, or seen in your own browser. 205 * Cookie Manager: Remove and Ignore permanently are now one "Remove…" link that asks why. "My site doesn't use it any more" takes the cookie off your list, and it comes back if it's seen again. "It's not from my site" puts it on the Permanently Ignored list for good. 206 * Cookie Manager: with CookieFlame Pro, the padlock is now a "Lock as correct" button. A locked cookie keeps its vendor, category and purpose, even when you re-identify. 207 * Cookie Manager: the small help buttons beside each link are gone. Each link explains itself when you hover over it, and the Status column's help button explains every label and action. 208 * Improved: when another consent tool's code is on your pages, the warning now says so in plain words ("Cookiebot found on your pages"), names the page and the text to look for, and lists where such code usually lives (plugins, page builder custom code such as Elementor > Custom Code, the theme's header, or a header and footer scripts plugin). It used to say a plugin was installed even when the code came from somewhere else. 209 210 = 5.2.0 = 211 * Cookie monitoring now records which script first wrote each new cookie: a third-party script's domain, or the WordPress plugin or theme that set it. Previously only the full list of third-party scripts on the page was known. Cookie values are never read or sent. 212 * Cookie monitoring now checks again later in the visit, a few seconds after the visitor changes their consent choice, and when they leave the page, so cookies set after consent or after the first few seconds are found. 213 * Fixed: when a page reported several new cookies at the same moment, some could be lost, because the reports overwrote each other when saving. Reports are now saved one at a time, so every new cookie is kept. 214 * The cookieflame_new_cookie_discovered action now fires after the cookie's details are saved and passes them as a fourth argument (pre-existing flag, value type, writer). Listeners using three arguments are unaffected. 215 * Tested up to WordPress 7.1. 216 217 = 5.1.1 = 218 * Cookie declaration shortcode tag is now [cookieflame_declaration] (previously [cookieconsent_declaration]). If you embed the cookie declaration on your Privacy Policy page, update the shortcode tag. 219 * URL fields on the Settings page (Privacy URL, CCPA URL, Consent log endpoint) now use esc_url_raw, the WordPress URL sanitiser, in place of sanitize_text_field. 220 221 = 5.1.0 = 222 * Cookie Monitoring tab redesigned for clarity. The page now leads with the two controls that matter (Monitoring on/off and Update interval) and trims the long-form intro and guidance copy that buried them. 223 * New: the Update interval dropdown shows CookieFlame Pro intervals (15 minutes, 1 hour, 4 hours) inline as disabled options labelled "(Pro)", so you can see at a glance what an upgrade adds without leaving the page. They unlock when CookieFlame Pro is active. 224 * Removed the Monthly interval. It was producing digests so far apart that newly discovered cookies were already weeks stale by the time the email arrived. Existing sites set to Monthly will move to Daily on update. 225 * Removed the in-page "Clear monitoring history" button. The 30-day auto-purge handles staleness without intervention; for the rare support case where a full reset is needed, the new WP-CLI command "wp cookieflame purge-beacon-seen" replaces the button. 226 * Filter contract change for developers extending the interval list: cookieflame_interval_buckets now passes tier-aware entries (each entry shaped ['minutes' => N, 'tier' => 'free'|'pro']). Plain integers from third-party filter contributors are still accepted and default to the free tier for back-compatibility. 227 228 = 5.0.2 = 229 * Fixed: Geo Detection now correctly recognises the Cloudflare CF-IPCountry header again. Sites fronted by Cloudflare without a MaxMind database installed were falling through to the GDPR opt-in safety default for every visitor, which meant CCPA-eligible US visitors saw the GDPR banner instead of the CCPA notice, and visitors in regions where no banner is required also saw the GDPR banner. The Geo Detection admin panel was reporting "Not configured" on Cloudflare-fronted sites for the same reason. No setting changes are required; geo detection resumes automatically on update. 230 231 = 5.0.1 = 232 * Hardened consent boot: the five wp_head injection hooks (consent defaults, config, frontend script, Google Tag Manager, Microsoft UET) now register at deep-negative priorities so other plugins cannot hook ahead of CookieFlame and push tags before consent signals are set. 233 * Beacon endpoint refactored: the per-IP rate limit now runs as a REST permission_callback, rejecting bursts at the routing layer above the request handler. The same check still runs inside the handler as a belt-and-braces guard. 234 * Defensive shutdown handler added for the output buffer: walks the buffer stack at end of request and flushes only CookieFlame's own buffer if it remains open, leaving any caching or optimisation plugin buffers above it untouched. 235 * Database queries against the beacon-seen table reworked to inline the WordPress table prefix directly in each SQL literal, with rationale comments on the remaining advisory warnings. 236 * wp_unslash applied consistently to display-only query parameters on the Cookie Monitoring tab. 237 * Readme: added an Upgrade Notice section. 238 239 = 5.0.0 = 240 * Initial public release. 121 = 5.4.0 = 122 * New: an option to show "Decline All" on the banner's first screen, next to Accept All. Off by default, and only on the opt-in banner (UK, EU/EEA, Switzerland, Brazil, South Africa), never on the US notice. 123 * Global Privacy Control now follows California's rules (§7025): OK only closes the US notice, the notice says the signal was honoured, the opt-out is saved, and visitors can opt back in only through a separate choice. 124 * "Do Not Sell or Share My Personal Information" now opens a real opt-out choice on the page, instead of linking to your privacy page. 125 * Cookie Monitoring is now two settings: "Watch for new cookies" On/Off, and "Email me about changes": Weekly (the default for new sites), Daily, Instantly (Pro) or Off. Your old setting carries over; 15 minutes, 1 hour and 4 hours become Instantly with Pro and Daily without it. 126 * New: [cookieflame_settings_link] shortcode and a cc-open-settings class for a "Cookie settings" link. The cookie declaration now has a "Change cookie preferences" button. 127 * New: "Visitor logins" setting. When ticked, the cookie declaration lists the WordPress login cookies under "If you log in". Otherwise they are left out, so every visitor sees the same declaration. 128 * The upgrade message at the top of the CookieFlame pages can now be dismissed. 129 * For developers: --cc-* CSS properties, two filters, an action and the cc:banner-rendered event (see For developers). 130 * The IAB TCF placeholder, which never carried a consent string, is now off by default. 131 * Fixed: a Re-consent Period below 1 day made the banner appear on every page. It now means the default of 365 days. 132 * Fixed: the US notice could run off the screen on phones. 133 * Fixed: the X (Twitter) embed placeholder also caught other sites whose names end in "x.com", such as netflix.com and box.com. 134 * Fixed: the WordPress cookie list: login cookie expiry, comment and protected-page cookies, the wp-settings-time description, and PHP and WooCommerce cookies listed as WordPress core. 135 * Fixed: saving Cookie Monitoring settings, or switching monitoring on or off, turned "Exclude staff from tracking" off. 136 * Older changes: changelog.txt. 241 137 242 138 == Upgrade Notice == 243 139 244 = 5.3.0 = 245 Cookie monitoring now ignores other consent tools' cookies that visitors' browser extensions add, so they no longer appear as your site's cookies. The Cookie Manager's last column is tidier and easier to understand. 246 247 = 5.2.0 = 248 Better cookie detection: CookieFlame now records which script set each new cookie and keeps checking after consent is given. 249 250 = 5.1.0 = 251 Cookie Monitoring tab redesigned and the Monthly interval removed. If you were on Monthly, your setting will move to Daily on update. The "Clear monitoring history" button has been replaced with a WP-CLI command for the rare cases where it is needed. 252 253 = 5.0.2 = 254 Bug fix release. Restores Cloudflare CF-IPCountry geo detection on sites fronted by Cloudflare without MaxMind. Recommended for all users on Cloudflare. 255 256 = 5.0.1 = 257 Compatibility and hardening release. Refactors a public endpoint's permission check, hardens consent boot priority, and addresses static-analysis warnings. No setting or visitor-facing behaviour changes. 258 259 = 5.0.0 = 260 Initial public release. 140 = 5.4.0 = 141 Optional "Decline all" button, Global Privacy Control handled to California's rules, a real "Do Not Sell or Share" choice, and simpler Cookie Monitoring settings. Your settings carry over. -
cookieflame/tags/5.4.0/uninstall.php
r3769546 r3771537 37 37 delete_option( 'cookieflame_maxmind_last_error' ); 38 38 delete_option( 'cookieflame_beacon_seen_schema' ); 39 delete_metadata( 'user', 0, 'cookieflame_upgrade_banner_dismissed', '', true ); 39 40 40 41 // Drop the beacon-seen custom table (only on opt-in deletion). -
cookieflame/trunk/assets/css/admin.css
r3769546 r3771537 625 625 border: 1px solid #dcdcde; 626 626 border-radius: 4px; 627 } 628 .cf-admin-manual-row select { 629 padding-right: 28px; 627 630 } 628 631 .cf-admin-manual-row .button-link-delete { … … 1017 1020 color: #c2410c; 1018 1021 } 1022 .cf-admin-upgrade-banner-dismiss { 1023 margin: 0; 1024 flex-shrink: 0; 1025 align-self: flex-start; 1026 } 1027 .cf-admin-upgrade-banner-close { 1028 background: none; 1029 border: none; 1030 color: #8c8f94; 1031 font-size: 20px; 1032 line-height: 1; 1033 padding: 2px 4px; 1034 cursor: pointer; 1035 border-radius: 4px; 1036 } 1037 .cf-admin-upgrade-banner-close:hover, 1038 .cf-admin-upgrade-banner-close:focus { 1039 color: #1d2327; 1040 background: rgba(0, 0, 0, 0.05); 1041 } 1019 1042 1020 1043 /* Licence tab Pro features card */ … … 1475 1498 transition: border-color .15s; 1476 1499 } 1500 /* Room for WordPress's select arrow, drawn about 5-21px from the right edge. */ 1501 .cf-admin-wrap .form-table select { 1502 padding-right: 32px; 1503 } 1477 1504 .cf-admin-wrap .form-table input:focus, 1478 1505 .cf-admin-wrap .form-table select:focus, -
cookieflame/trunk/assets/css/cookieconsent.css
r3766806 r3771537 1 1 /** 2 * CookieFlame v 4.41.0 — Consent Banner Stylesheet2 * CookieFlame v5.4.0 — Consent Banner Stylesheet 3 3 * ───────────────────────────────────────────────────────────── 4 4 * Loaded on every front-end page via cookieflame_enqueue_assets(). 5 * Before v4.41.0 these rules were injected inline by6 * cookieconsent.js at runtime; they are now an external7 * stylesheet so the main JS bundle is ~5.6 KB smaller and8 * the CSS can be cached independently by the browser.9 5 * 10 6 * Dark mode: JS adds `.cc-dark` to <html> when theme=dark. 11 7 * Icon position: JS adds `.cc-icon-right` to <html> when 12 8 * iconPosition=right (default is left). 9 * 10 * Custom properties (v5.4.0): the banner's colours, corner radius and 11 * font come from the --cc-* properties below. Override them on :root 12 * (and :root.cc-dark for the dark theme) to restyle the banner. 13 13 */ 14 15 :root{--cc-font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;--cc-radius:20px;--cc-btn-radius:12px;--cc-bg:#fff;--cc-text:#1d1d1f;--cc-text-muted:#555;--cc-link:#0071e3;--cc-focus:#0071e3;--cc-banner-shadow:rgba(0,0,0,.15);--cc-accent:#0071e3;--cc-accent-hover:#005bb5;--cc-accent-text:#fff;--cc-accent-shadow:rgba(0,113,227,.3);--cc-accent-shadow-hover:rgba(0,113,227,.35);--cc-secondary-bg:#e8e8ed;--cc-secondary-hover:#d2d2d7;--cc-secondary-text:#1d1d1f;--cc-save-bg:#34c759;--cc-save-hover:#28a745;--cc-save-text:#fff;--cc-save-shadow:rgba(52,199,89,.3);--cc-save-shadow-hover:rgba(52,199,89,.35);--cc-details:#555;--cc-details-hover:#555;--cc-border:#e8e8ed;--cc-divider:#f0f0f2;--cc-cat-desc:#646970;--cc-always-on:#767676;--cc-toggle-off:#ccc;--cc-toggle-on:#0071e3;--cc-knob:#fff;--cc-powered:#999;--cc-powered-hover:#666;--cc-icon-bg:#1d1d1f;--cc-icon-text:#fff;--cc-icon-shadow:rgba(0,0,0,.25);--cc-icon-shadow-hover:rgba(0,0,0,.35)} 16 :root.cc-dark{--cc-bg:#1c1c1e;--cc-text:#f5f5f7;--cc-text-muted:#98989d;--cc-link:#2997ff;--cc-banner-shadow:rgba(0,0,0,.5);--cc-accent:#0a84ff;--cc-accent-hover:#0071e3;--cc-accent-shadow:rgba(10,132,255,.35);--cc-accent-shadow-hover:rgba(10,132,255,.4);--cc-secondary-bg:#2c2c2e;--cc-secondary-hover:#3a3a3c;--cc-secondary-text:#f5f5f7;--cc-save-bg:#30d158;--cc-save-hover:#28a745;--cc-save-shadow:rgba(48,209,88,.3);--cc-save-shadow-hover:rgba(48,209,88,.35);--cc-details:#98989d;--cc-details-hover:#c7c7cc;--cc-border:#2c2c2e;--cc-divider:#2c2c2e;--cc-cat-desc:#8e8e93;--cc-always-on:#8e8e93;--cc-toggle-off:#3a3a3c;--cc-toggle-on:#0a84ff;--cc-knob:#f5f5f7;--cc-powered:#aaa;--cc-powered-hover:#ddd;--cc-icon-bg:#f5f5f7;--cc-icon-text:#1d1d1f;--cc-icon-shadow:rgba(255,255,255,.15);--cc-icon-shadow-hover:rgba(255,255,255,.25)} 14 17 15 18 #cc-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:999998;opacity:0;pointer-events:none;transition:opacity .3s;backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)} 16 19 #cc-overlay.cc-show{opacity:1;pointer-events:auto} 17 #cc-banner{position:fixed;bottom:0;left:0;right:0;z-index:999999;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5;color:#1d1d1f;background:#fff;box-shadow:0 -8px 40px rgba(0,0,0,.15);transform:translateY(100%);transition:transform .4s cubic-bezier(.4,0,.2,1);border-radius:20px 20px0 0;max-height:90vh;overflow-y:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}20 #cc-banner{position:fixed;bottom:0;left:0;right:0;z-index:999999;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);background:var(--cc-bg);box-shadow:0 -8px 40px var(--cc-banner-shadow);transform:translateY(100%);transition:transform .4s cubic-bezier(.4,0,.2,1);border-radius:var(--cc-radius) var(--cc-radius) 0 0;max-height:90vh;overflow-y:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} 18 21 #cc-banner.cc-show{transform:translateY(0)} 19 22 #cc-banner,#cc-banner *{box-sizing:border-box;-webkit-tap-highlight-color:transparent} … … 21 24 .cc-inner{max-width:720px;margin:0 auto;padding:32px 32px 20px} 22 25 .cc-title{font-size:20px !important;font-weight:800 !important;margin:0 0 8px !important;letter-spacing:-.3px;line-height:1.3 !important;color:inherit !important;background:none !important;border:none !important;padding:0 !important;text-transform:none !important} 23 .cc-text{margin:0 0 20px !important;color: #555!important;font-size:14px !important;font-weight:400 !important;line-height:1.6 !important;padding:0 !important;border:none !important;background:none !important;text-transform:none !important}24 .cc-text a{color: #0071e3!important;text-decoration:none !important;font-weight:500 !important;background:none !important;border:none !important;box-shadow:none !important}26 .cc-text{margin:0 0 20px !important;color:var(--cc-text-muted) !important;font-size:14px !important;font-weight:400 !important;line-height:1.6 !important;padding:0 !important;border:none !important;background:none !important;text-transform:none !important} 27 .cc-text a{color:var(--cc-link) !important;text-decoration:none !important;font-weight:500 !important;background:none !important;border:none !important;box-shadow:none !important} 25 28 .cc-text a:hover{text-decoration:underline !important} 26 29 .cc-buttons{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:16px} 27 .cc-btn{display:inline-flex !important;align-items:center !important;justify-content:center !important;border-radius: 12px!important;border:none !important;font-weight:700 !important;cursor:pointer !important;letter-spacing:-.1px !important;text-transform:none !important;text-decoration:none !important;line-height:1 !important;white-space:nowrap !important;-webkit-appearance:none !important;appearance:none !important;outline:none !important;box-shadow:none !important;text-shadow:none !important;float:none !important;margin:0 !important}28 .cc-btn:focus{outline:2px solid #0071e3!important;outline-offset:2px !important}30 .cc-btn{display:inline-flex !important;align-items:center !important;justify-content:center !important;border-radius:var(--cc-btn-radius) !important;border:none !important;font-weight:700 !important;cursor:pointer !important;letter-spacing:-.1px !important;text-transform:none !important;text-decoration:none !important;line-height:1 !important;white-space:nowrap !important;-webkit-appearance:none !important;appearance:none !important;outline:none !important;box-shadow:none !important;text-shadow:none !important;float:none !important;margin:0 !important} 31 .cc-btn:focus{outline:2px solid var(--cc-focus) !important;outline-offset:2px !important} 29 32 .cc-btn:active{transform:scale(.97) !important} 30 .cc-btn-accept{background:#0071e3 !important;color:#fff !important;font-size:15px !important;padding:14px 32px !important;box-shadow:0 2px 8px rgba(0,113,227,.3) !important;transition:background .15s,box-shadow .15s,transform .1s !important} 31 .cc-btn-accept:hover{background:#005bb5 !important;box-shadow:0 4px 16px rgba(0,113,227,.35) !important;color:#fff !important} 32 .cc-btn-decline{background:#e8e8ed !important;color:#1d1d1f !important;font-size:13px !important;padding:11px 22px !important;box-shadow:none !important;transition:background .15s,transform .1s !important} 33 .cc-btn-decline:hover{background:#d2d2d7 !important;color:#1d1d1f !important} 34 .cc-btn-save{background:#34c759 !important;color:#fff !important;font-size:14px !important;padding:13px 28px !important;box-shadow:0 2px 8px rgba(52,199,89,.3) !important;transition:background .15s,box-shadow .15s,transform .1s !important} 35 .cc-btn-save:hover{background:#28a745 !important;box-shadow:0 4px 16px rgba(52,199,89,.35) !important;color:#fff !important} 36 .cc-btn-details{background:transparent !important;color:#555 !important;padding:11px 4px;font-weight:500;font-size:13px;text-decoration:none !important;border:none !important;box-shadow:none !important;cursor:pointer;transition:color .15s !important} 37 .cc-btn-details:hover{color:#555 !important;text-decoration:none !important;background:transparent !important;box-shadow:none !important} 38 .cc-categories{margin:0 0 16px !important;border:1px solid #e8e8ed !important;border-radius:14px !important;overflow:hidden !important;display:none} 33 .cc-btn-accept{background:var(--cc-accent) !important;color:var(--cc-accent-text) !important;font-size:15px !important;padding:14px 32px !important;box-shadow:0 2px 8px var(--cc-accent-shadow) !important;transition:background .15s,box-shadow .15s,transform .1s !important} 34 .cc-btn-accept:hover{background:var(--cc-accent-hover) !important;box-shadow:0 4px 16px var(--cc-accent-shadow-hover) !important;color:var(--cc-accent-text) !important} 35 .cc-btn-decline{background:var(--cc-secondary-bg) !important;color:var(--cc-secondary-text) !important;font-size:13px !important;padding:11px 22px !important;box-shadow:none !important;transition:background .15s,transform .1s !important} 36 .cc-btn-decline:hover{background:var(--cc-secondary-hover) !important;color:var(--cc-secondary-text) !important} 37 .cc-btn-reject{background:var(--cc-secondary-bg) !important;color:var(--cc-secondary-text) !important;font-size:15px !important;padding:14px 32px !important;transition:background .15s,transform .1s !important} 38 .cc-btn-reject:hover{background:var(--cc-secondary-hover) !important;color:var(--cc-secondary-text) !important} 39 .cc-btn-save{background:var(--cc-save-bg) !important;color:var(--cc-save-text) !important;font-size:14px !important;padding:13px 28px !important;box-shadow:0 2px 8px var(--cc-save-shadow) !important;transition:background .15s,box-shadow .15s,transform .1s !important} 40 .cc-btn-save:hover{background:var(--cc-save-hover) !important;box-shadow:0 4px 16px var(--cc-save-shadow-hover) !important;color:var(--cc-save-text) !important} 41 .cc-btn-details{background:transparent !important;color:var(--cc-details) !important;padding:11px 4px;font-weight:500;font-size:13px;text-decoration:none !important;border:none !important;box-shadow:none !important;cursor:pointer;transition:color .15s !important} 42 .cc-btn-details:hover{color:var(--cc-details-hover) !important;text-decoration:none !important;background:transparent !important;box-shadow:none !important} 43 .cc-categories{margin:0 0 16px !important;border:1px solid var(--cc-border) !important;border-radius:14px !important;overflow:hidden !important;display:none} 39 44 .cc-categories.cc-open{display:block !important} 40 .cc-cat{padding:16px 20px !important;border-bottom:1px solid #f0f0f2!important;display:flex !important;justify-content:space-between !important;align-items:center !important;gap:16px !important;background:none !important}45 .cc-cat{padding:16px 20px !important;border-bottom:1px solid var(--cc-divider) !important;display:flex !important;justify-content:space-between !important;align-items:center !important;gap:16px !important;background:none !important} 41 46 .cc-cat:last-child{border-bottom:none !important} 42 47 .cc-cat-label{font-weight:700 !important;font-size:14px !important;line-height:1.3 !important;color:inherit !important;text-transform:none !important} 43 .cc-cat-desc{font-size:12px !important;color:#646970 !important;margin-top:3px !important;font-weight:400 !important;line-height:1.4 !important;text-transform:none !important} 44 .cc-always-on{font-weight:400 !important;font-size:11px !important;color:#767676 !important} 48 .cc-cat-desc{font-size:12px !important;color:var(--cc-cat-desc) !important;margin-top:3px !important;font-weight:400 !important;line-height:1.4 !important;text-transform:none !important} 49 .cc-always-on{font-weight:400 !important;font-size:11px !important;color:var(--cc-always-on) !important} 50 .cc-gpc-note{font-size:12px !important;color:var(--cc-text) !important;margin-top:6px !important;font-weight:600 !important;line-height:1.4 !important;text-transform:none !important} 45 51 .cc-toggle{position:relative !important;width:46px !important;height:26px !important;flex-shrink:0 !important;display:block !important} 46 52 .cc-toggle input{opacity:0 !important;width:0 !important;height:0 !important;position:absolute !important} 47 .cc-toggle .cc-slider{position:absolute !important;inset:0 !important;background: #ccc!important;border-radius:26px !important;cursor:pointer !important;transition:background .2s !important;border:none !important;box-shadow:none !important}48 .cc-toggle .cc-slider:before{content:"" !important;position:absolute !important;width:20px !important;height:20px !important;left:3px !important;bottom:3px !important;background: #fff!important;border-radius:50% !important;transition:transform .2s !important;box-shadow:0 1px 3px rgba(0,0,0,.15) !important}49 .cc-toggle input:checked+.cc-slider{background: #0071e3!important}53 .cc-toggle .cc-slider{position:absolute !important;inset:0 !important;background:var(--cc-toggle-off) !important;border-radius:26px !important;cursor:pointer !important;transition:background .2s !important;border:none !important;box-shadow:none !important} 54 .cc-toggle .cc-slider:before{content:"" !important;position:absolute !important;width:20px !important;height:20px !important;left:3px !important;bottom:3px !important;background:var(--cc-knob) !important;border-radius:50% !important;transition:transform .2s !important;box-shadow:0 1px 3px rgba(0,0,0,.15) !important} 55 .cc-toggle input:checked+.cc-slider{background:var(--cc-toggle-on) !important} 50 56 .cc-toggle input:checked+.cc-slider:before{transform:translateX(20px) !important} 51 .cc-toggle input:disabled+.cc-slider{background: #0071e3!important;opacity:.5 !important;cursor:default !important}52 .cc-toggle input:focus-visible+.cc-slider{outline:2px solid #0071e3!important;outline-offset:2px !important}53 .cc-powered{text-align:center !important;padding:0 0 16px !important;font-size:11px !important;color: #999!important;font-weight:400 !important;background:none !important;border:none !important;margin:0 !important}57 .cc-toggle input:disabled+.cc-slider{background:var(--cc-toggle-on) !important;opacity:.5 !important;cursor:default !important} 58 .cc-toggle input:focus-visible+.cc-slider{outline:2px solid var(--cc-focus) !important;outline-offset:2px !important} 59 .cc-powered{text-align:center !important;padding:0 0 16px !important;font-size:11px !important;color:var(--cc-powered) !important;font-weight:400 !important;background:none !important;border:none !important;margin:0 !important} 54 60 .cc-pref-buttons{display:none !important;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:8px} 55 61 .cc-pref-buttons.cc-open{display:flex !important} 56 .cc-powered a{color: #999!important;text-decoration:none !important;font-weight:400 !important;background:none !important;border:none !important;box-shadow:none !important}57 .cc-powered a:hover{color: #666!important;text-decoration:none !important}58 #cc-icon{position:fixed !important;bottom:18px !important;left:18px !important;z-index:999997 !important;width:48px !important;height:48px !important;border-radius:50% !important;background: #1d1d1f !important;color:#fff !important;border:none !important;cursor:pointer !important;box-shadow:0 2px 12px rgba(0,0,0,.25) !important;display:none;align-items:center !important;justify-content:center !important;padding:0 !important;transition:transform .15s,box-shadow .15s !important;-webkit-appearance:none !important;appearance:none !important;margin:0 !important;text-decoration:none !important;outline:none !important}59 #cc-icon:hover{transform:scale(1.1) !important;box-shadow:0 4px 20px rgba(0,0,0,.35) !important}60 #cc-icon:focus-visible{outline:2px solid #0071e3!important;outline-offset:2px !important}62 .cc-powered a{color:var(--cc-powered) !important;text-decoration:none !important;font-weight:400 !important;background:none !important;border:none !important;box-shadow:none !important} 63 .cc-powered a:hover{color:var(--cc-powered-hover) !important;text-decoration:none !important} 64 #cc-icon{position:fixed !important;bottom:18px !important;left:18px !important;z-index:999997 !important;width:48px !important;height:48px !important;border-radius:50% !important;background:var(--cc-icon-bg) !important;color:var(--cc-icon-text) !important;border:none !important;cursor:pointer !important;box-shadow:0 2px 12px var(--cc-icon-shadow) !important;display:none;align-items:center !important;justify-content:center !important;padding:0 !important;transition:transform .15s,box-shadow .15s !important;-webkit-appearance:none !important;appearance:none !important;margin:0 !important;text-decoration:none !important;outline:none !important} 65 #cc-icon:hover{transform:scale(1.1) !important;box-shadow:0 4px 20px var(--cc-icon-shadow-hover) !important} 66 #cc-icon:focus-visible{outline:2px solid var(--cc-focus) !important;outline-offset:2px !important} 61 67 #cc-icon.cc-show{display:flex !important} 62 68 .cc-icon-right #cc-icon{left:auto !important;right:18px !important} 63 #cc-ccpa-banner{position:fixed;bottom:0;left:0;right:0;z-index:999999;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5;color:#1d1d1f;background:#fff;box-shadow:0 -8px 40px rgba(0,0,0,.15);transform:translateY(100%);transition:transform .4s cubic-bezier(.4,0,.2,1);border-radius:20px 20px 0 0}69 #cc-ccpa-banner{position:fixed;bottom:0;left:0;right:0;z-index:999999;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);background:var(--cc-bg);box-shadow:0 -8px 40px var(--cc-banner-shadow);transform:translateY(100%);transition:transform .4s cubic-bezier(.4,0,.2,1);border-radius:var(--cc-radius) var(--cc-radius) 0 0;max-height:90vh;overflow-y:auto} 64 70 #cc-ccpa-banner.cc-show{transform:translateY(0)} 65 71 #cc-ccpa-banner *{box-sizing:border-box} 66 72 .cc-ccpa-inner{max-width:720px;margin:0 auto;padding:20px 32px;display:flex;align-items:center;gap:20px;flex-wrap:wrap} 67 73 .cc-ccpa-text{flex:1;min-width:280px} 68 .cc-ccpa-text p{margin:0;color:#555} 74 .cc-ccpa-text p{margin:0;color:var(--cc-text-muted)} 75 .cc-ccpa-text p.cc-ccpa-gpc{margin:8px 0 0;color:var(--cc-text);font-weight:600} 76 .cc-ccpa-gpc:before{content:"\2713\00a0"} 69 77 .cc-ccpa-actions{display:flex;gap:10px;align-items:center;flex-shrink:0} 70 .cc-ccpa-link{color:#0071e3;text-decoration:underline;font-size:13px;font-weight:500;white-space:nowrap} 71 .cc-dark #cc-banner,.cc-dark #cc-ccpa-banner{background:#1c1c1e !important;color:#f5f5f7 !important;box-shadow:0 -8px 40px rgba(0,0,0,.5) !important} 72 .cc-dark .cc-text{color:#98989d !important} 73 .cc-dark .cc-text a,.cc-dark .cc-ccpa-link{color:#2997ff !important} 74 .cc-dark .cc-btn-details{color:#98989d !important} 75 .cc-dark .cc-btn-details:hover{color:#c7c7cc !important} 76 .cc-dark .cc-btn-decline{background:#2c2c2e !important;color:#f5f5f7 !important} 77 .cc-dark .cc-btn-decline:hover{background:#3a3a3c !important;color:#f5f5f7 !important} 78 .cc-dark .cc-categories{border-color:#2c2c2e !important} 79 .cc-dark .cc-cat{border-bottom-color:#2c2c2e !important} 80 .cc-dark .cc-cat-desc{color:#8e8e93 !important} 81 .cc-dark .cc-always-on{color:#8e8e93 !important} 82 .cc-dark .cc-toggle .cc-slider{background:#3a3a3c !important} 83 .cc-dark .cc-toggle .cc-slider:before{background:#f5f5f7 !important} 84 .cc-dark .cc-toggle input:checked+.cc-slider{background:#0a84ff !important} 85 .cc-dark .cc-toggle input:disabled+.cc-slider{background:#0a84ff !important} 86 .cc-dark .cc-btn-accept{background:#0a84ff !important;box-shadow:0 2px 8px rgba(10,132,255,.35) !important} 87 .cc-dark .cc-btn-accept:hover{background:#0071e3 !important;box-shadow:0 4px 16px rgba(10,132,255,.4) !important} 88 .cc-dark .cc-btn-save{background:#30d158 !important;box-shadow:0 2px 8px rgba(48,209,88,.3) !important} 89 .cc-dark .cc-btn-save:hover{background:#28a745 !important;box-shadow:0 4px 16px rgba(48,209,88,.35) !important} 90 .cc-dark .cc-ccpa-text p{color:#98989d !important} 91 .cc-dark .cc-powered{color:#aaa !important} 92 .cc-dark .cc-powered a{color:#aaa !important} 93 .cc-dark .cc-powered a:hover{color:#ddd !important} 94 .cc-dark .cc-cat-label{color:#f5f5f7 !important} 95 .cc-dark .cc-title{color:#f5f5f7 !important} 96 .cc-dark #cc-icon{background:#f5f5f7 !important;color:#1d1d1f !important;box-shadow:0 2px 12px rgba(255,255,255,.15) !important} 97 .cc-dark #cc-icon:hover{box-shadow:0 4px 20px rgba(255,255,255,.25) !important} 98 @media(max-width:480px){.cc-inner{padding:24px 20px 16px}.cc-title{font-size:18px}.cc-buttons{gap:8px}.cc-btn-accept{font-size:14px;padding:12px 24px}.cc-btn-decline{font-size:12px;padding:10px 18px}} 78 .cc-ccpa-link{color:var(--cc-link);text-decoration:underline;font-size:13px;font-weight:500;white-space:nowrap} 79 #cc-ccpa-banner .cc-btn-details{padding:11px 0} 80 .cc-ccpa-choice{display:none} 81 .cc-ccpa-choice.cc-open{display:block} 82 .cc-ccpa-choice-inner{max-width:720px;margin:0 auto;padding:20px 32px} 83 .cc-ccpa-choice-title{font-size:16px;font-weight:800;line-height:1.3;margin:0 0 6px;color:inherit} 84 .cc-ccpa-choice-text,.cc-ccpa-choice-note{margin:0 0 14px;color:var(--cc-text-muted)} 85 .cc-ccpa-choice-status{margin:0 0 14px;font-weight:600;color:var(--cc-text)} 86 .cc-ccpa-choice-note{font-size:12px;margin:10px 0 0} 87 .cc-ccpa-choice-buttons{display:flex;gap:10px;flex-wrap:wrap;align-items:center} 88 @media(max-width:480px){.cc-inner{padding:24px 20px 16px}.cc-title{font-size:18px}.cc-buttons{gap:8px}.cc-btn-accept{font-size:14px;padding:12px 24px}.cc-btn-decline{font-size:12px;padding:10px 18px}.cc-ccpa-inner{padding:16px 20px;gap:12px}.cc-ccpa-text{min-width:0;flex:1 1 100%}.cc-ccpa-actions{flex-wrap:wrap;flex-shrink:1;width:100%;justify-content:space-between}.cc-ccpa-link{white-space:normal}.cc-ccpa-choice-inner{padding:16px 20px}} -
cookieflame/trunk/assets/js/admin.js
r3769546 r3771537 10 10 cc_anon_id: { vendor: "CookieFlame", category: "necessary", purpose: "Anonymous ID for consent logging" }, 11 11 cookieflame_dnt: { vendor: "CookieFlame", category: "necessary", purpose: "Do-not-track opt-out flag" }, 12 "wp-settings-": { vendor: "WordPress", category: "necessary", purpose: "Admin interface preferences", prefix: true }, 13 "wp-settings-time-": { vendor: "WordPress", category: "necessary", purpose: "Admin settings timestamp", prefix: true }, 14 wordpress_logged_in_: { vendor: "WordPress", category: "necessary", purpose: "Login session", prefix: true }, 15 wordpress_sec_: { vendor: "WordPress", category: "necessary", purpose: "Secure login session", prefix: true }, 16 wordpress_test_cookie: { vendor: "WordPress", category: "necessary", purpose: "Checks cookie support" }, 17 wp_lang: { vendor: "WordPress", category: "necessary", purpose: "Language preference" }, 12 "wp-settings-": { vendor: "WordPress", category: "necessary", purpose: "Remembers dashboard layout settings", prefix: true }, 13 "wp-settings-time-": { vendor: "WordPress", category: "necessary", purpose: "Records when dashboard settings were last saved", prefix: true }, 14 wordpress_logged_in_: { vendor: "WordPress", category: "necessary", purpose: "Keeps you logged in", prefix: true }, 15 wordpress_sec_: { vendor: "WordPress", category: "necessary", purpose: "Confirms your login when you open the dashboard", prefix: true }, 16 wordpress_rec_: { vendor: "WordPress", category: "necessary", purpose: "Recovery mode after a site error (administrators only)", prefix: true }, 17 wordpress_test_cookie: { vendor: "WordPress", category: "necessary", purpose: "Checks your browser accepts cookies before you log in" }, 18 wp_lang: { vendor: "WordPress", category: "necessary", purpose: "Remembers the language picked on the login page" }, 19 comment_author_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your name for comments, if you ask it to", prefix: true }, 20 comment_author_email_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your email for comments, if you ask it to", prefix: true }, 21 comment_author_url_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your website for comments, if you ask it to", prefix: true }, 22 "wp-postpass_": { vendor: "WordPress", category: "necessary", purpose: "Remembers the password entered for a protected page", prefix: true }, 18 23 woocommerce_cart_hash: { vendor: "WooCommerce", category: "necessary", purpose: "Shopping cart contents" }, 19 24 woocommerce_items_in_cart: { vendor: "WooCommerce", category: "necessary", purpose: "Cart item count" }, … … 181 186 function identifyBuiltIn(name) { 182 187 if (KNOWN[name]) return KNOWN[name]; 183 var match = null; 188 // Longest prefix wins (wp-settings-time- over wp-settings-). 189 var match = null, matchLen = 0; 184 190 var keys = Object.keys(KNOWN); 185 191 for (var i = 0; i < keys.length; i++) { 186 if (KNOWN[keys[i]].prefix && name.indexOf(keys[i]) === 0) { match = KNOWN[keys[i]]; break; }192 if (KNOWN[keys[i]].prefix && keys[i].length > matchLen && name.indexOf(keys[i]) === 0) { match = KNOWN[keys[i]]; matchLen = keys[i].length; } 187 193 } 188 194 return match; … … 320 326 } 321 327 322 // WordPress core vendors that are always necessary 323 var WP_CORE_VENDORS = ["WordPress", "WooCommerce", "PHP"]; 328 // Vendors listed under WordPress Core Cookies (PHP sessions and 329 // WooCommerce are not core, so they appear in the main table). 330 var WP_CORE_VENDORS = ["WordPress"]; 324 331 var PLUGIN_VENDOR = "CookieFlame"; 325 332 … … 2354 2361 .then(function (data) { 2355 2362 if (!data.success) { 2356 if (startBtn) { startBtn.disabled = false; startBtn.textContent = "Start Cookie Discovery "; }2363 if (startBtn) { startBtn.disabled = false; startBtn.textContent = "Start Cookie Discovery (uses your own browser)"; } 2357 2364 return; 2358 2365 } … … 2405 2412 }) 2406 2413 .catch(function (err) { 2407 if (startBtn) { startBtn.disabled = false; startBtn.textContent = "Start Cookie Discovery "; }2414 if (startBtn) { startBtn.disabled = false; startBtn.textContent = "Start Cookie Discovery (uses your own browser)"; } 2408 2415 cookieflameNotice("Error: " + err.message, "error"); 2409 2416 }); -
cookieflame/trunk/assets/js/cookieconsent.js
r3769546 r3771537 1 1 /** 2 * CookieFlame v5. 3.02 * CookieFlame v5.4.0 3 3 * ───────────────────────────────────────────────────────────── 4 4 * Self-hosted cookie consent manager with Google Consent Mode v2, 5 * IAB TCF v2.2,automatic script blocking, iframe facades with5 * automatic script blocking, iframe facades with 6 6 * thumbnails, Elementor video widget support, multi-region 7 7 * compliance (GDPR/CCPA), and passive cookie monitoring. … … 40 40 } catch (e) {} 41 41 42 var PLUGIN_VERSION = "5. 3.0";42 var PLUGIN_VERSION = "5.4.0"; 43 43 44 44 /* ═══════════════════════════════════════════ … … 49 49 cookieExpiry: 365, 50 50 privacyUrl: "/privacy-policy", 51 ccpaUrl: "/privacy-policy#do-not-sell",52 51 geoApi: "", 53 52 logEndpoint: "", 54 53 reconsentDays: 365, 55 54 cspSafe: false, 56 tcfEnabled: true, 55 // IAB TCF v2.2 stub. Off since v5.4.0 (it never carried a TC string); 56 // the code is kept and can be switched on through the 57 // cookieflame_frontend_config filter (tcfEnabled: true). 58 tcfEnabled: false, 57 59 cmpId: 1, 58 60 cmpVersion: 1, … … 65 67 iconPosition: "left", 66 68 consentVersion: "", 69 // Show "Decline All" next to "Accept All" on the GDPR banner's first 70 // screen (Settings > Show "Decline all" on the first screen). 71 rejectFirstScreen: false, 72 // List WordPress login cookies in the declaration under "If you log 73 // in" (Settings > Visitor logins). Login cookies are never listed 74 // with the others, so the declaration is the same for every visitor. 75 visitorLogins: false, 67 76 68 77 autoBlockPatterns: [ … … 122 131 description: 'We use cookies to improve your experience, analyse traffic and personalise content. You can choose which categories to allow.', 123 132 acceptAllButton: "Accept All", 133 rejectAllButton: "Decline All", 124 134 declineAllButton: "Decline All", 125 135 saveButton: "Save Preferences", … … 128 138 alwaysOnLabel: "(always on)", 129 139 ccpaTitle: "Your Privacy Choices", 130 ccpaDescription: 'We use cookies and similar technologies. Under California law, you have the right to opt out of the sale or sharing of your personal information.',140 ccpaDescription: "We use cookies and similar technologies. Under US state privacy laws, such as California's, you have the right to opt out of the sale or sharing of your personal information.", 131 141 ccpaAcceptButton: "OK", 132 ccpaDnsmpiLink: "Do Not Sell My Personal Information" 142 ccpaDnsmpiLink: "Do Not Sell or Share My Personal Information", 143 ccpaGpcNotice: "Your browser asked us not to sell or share your information. We're honouring that.", 144 ccpaChoiceTitle: "Do Not Sell or Share My Personal Information", 145 ccpaChoiceText: "Information about your visit may be used by advertising partners whose cookies run on this site. Under US state privacy laws that can count as selling or sharing. You can opt out in this browser.", 146 ccpaOptOutButton: "Opt out", 147 ccpaCancelButton: "Cancel", 148 ccpaOptedOut: "You've opted out of the sale or sharing of your personal information in this browser.", 149 ccpaGpcOptedOut: "Your browser's Global Privacy Control signal asked us not to sell or share your information, so you're opted out in this browser.", 150 ccpaDoneButton: "Done", 151 ccpaOptInButton: "Allow selling or sharing", 152 ccpaOptInGpcNote: "Allowing it overrides your browser's Global Privacy Control signal on this site only.", 153 ccpaMoreSettings: "More cookie settings", 154 gpcPrefNote: "Your browser's Global Privacy Control signal is on, so Marketing stays off unless you switch it on here.", 155 changePreferences: "Change cookie preferences", 156 loginCookiesTitle: "If you log in", 157 loginCookiesIntro: "These cookies are only set if you log in to an account on this site. They keep you signed in and remember your account settings. They're needed for logging in to work, so they don't ask for your consent." 133 158 }, 134 159 … … 181 206 var ext = window.__ccConfig; 182 207 // shallow merge top-level primitives 183 var keys = ["cookieName","cookieExpiry","privacyUrl"," ccpaUrl","geoApi",208 var keys = ["cookieName","cookieExpiry","privacyUrl","geoApi", 184 209 "logEndpoint","reconsentDays","cspSafe","tcfEnabled","cmpId", 185 "cmpVersion","vendorListVersion","storageMethod","cookieDomain","shareSubdomains","theme","showPoweredBy","iconPosition","consentVersion"]; 210 "cmpVersion","vendorListVersion","storageMethod","cookieDomain","shareSubdomains","theme","showPoweredBy","iconPosition","consentVersion", 211 "rejectFirstScreen","visitorLogins"]; 186 212 keys.forEach(function (k) { 187 213 if (ext[k] !== undefined) CONFIG[k] = ext[k]; … … 207 233 // visitor's browser is sending the GPC signal. 208 234 var gpcActive = !!window.__ccGPC; 235 236 /* GPC rules (Cal. Code Regs. tit. 11 §7025, applied in every region): 237 - A GPC signal is a valid opt-out of selling and sharing: Marketing 238 stays off and Google's ad signals are denied, with no extra steps. 239 - The opt-out is saved in the consent cookie (state.gpc), so a later 240 visit without GPC keeps it. 241 - The visitor can opt back in only through a separate choice: the 242 US notice's "Allow selling or sharing" button, or switching 243 Marketing on and saving in the preferences. That sets 244 state.gpcOptIn. OK and Accept All never opt back in. */ 245 function applyGpc(state) { 246 if (gpcActive && state && !state.gpcOptIn) { 247 state.marketing = false; 248 state.gpc = 1; 249 } 250 return state; 251 } 209 252 210 253 // Merged blocking rules from PHP (built-in defaults + auto-generated … … 227 270 cc_anon_id: { vendor: "CookieFlame", category: "necessary", purpose: "Anonymous ID for consent logging", expiry: "1 year" }, 228 271 cookieflame_dnt: { vendor: "CookieFlame", category: "necessary", purpose: "Do-not-track opt-out flag", expiry: "1 year" }, 272 // PHP sessions (started by some plugins and hosts, not by WordPress) 273 PHPSESSID: { vendor: "PHP", category: "necessary", purpose: "Server-side session identifier", expiry: "Session" }, 229 274 // WordPress core 230 PHPSESSID: { vendor: "PHP", category: "necessary", purpose: "Server-side session identifier", expiry: "Session" }, 231 wp_settings_: { vendor: "WordPress", category: "necessary", purpose: "Admin interface preferences", prefix: true, expiry: "1 year" }, 232 "wp-settings-": { vendor: "WordPress", category: "necessary", purpose: "Admin interface preferences", prefix: true, expiry: "1 year" }, 233 "wp-settings-time-": { vendor: "WordPress", category: "necessary", purpose: "Admin settings timestamp", prefix: true, expiry: "1 year" }, 234 wordpress_logged_in_: { vendor: "WordPress", category: "necessary", purpose: "Login session", prefix: true, expiry: "Session" }, 235 wordpress_sec_: { vendor: "WordPress", category: "necessary", purpose: "Secure login session", prefix: true, expiry: "Session" }, 236 wordpress_test_cookie: { vendor: "WordPress", category: "necessary", purpose: "Checks cookie support", expiry: "Session" }, 237 wp_lang: { vendor: "WordPress", category: "necessary", purpose: "Language preference", expiry: "Session" }, 275 "wp-settings-": { vendor: "WordPress", category: "necessary", purpose: "Remembers dashboard layout settings", prefix: true, expiry: "1 year" }, 276 "wp-settings-time-": { vendor: "WordPress", category: "necessary", purpose: "Records when dashboard settings were last saved", prefix: true, expiry: "1 year" }, 277 wordpress_logged_in_: { vendor: "WordPress", category: "necessary", purpose: "Keeps you logged in", prefix: true, expiry: "Session, or 14 days with Remember Me" }, 278 wordpress_sec_: { vendor: "WordPress", category: "necessary", purpose: "Confirms your login when you open the dashboard", prefix: true, expiry: "Session, or 14 days with Remember Me" }, 279 wordpress_rec_: { vendor: "WordPress", category: "necessary", purpose: "Recovery mode after a site error (administrators only)", prefix: true, expiry: "1 week" }, 280 wordpress_test_cookie: { vendor: "WordPress", category: "necessary", purpose: "Checks your browser accepts cookies before you log in", expiry: "Session" }, 281 wp_lang: { vendor: "WordPress", category: "necessary", purpose: "Remembers the language picked on the login page", expiry: "Session" }, 282 comment_author_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your name for comments, if you ask it to", prefix: true, expiry: "1 year" }, 283 comment_author_email_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your email for comments, if you ask it to", prefix: true, expiry: "1 year" }, 284 comment_author_url_: { vendor: "WordPress", category: "necessary", purpose: "Remembers your website for comments, if you ask it to", prefix: true, expiry: "1 year" }, 285 "wp-postpass_": { vendor: "WordPress", category: "necessary", purpose: "Remembers the password entered for a protected page", prefix: true, expiry: "10 days" }, 238 286 // WooCommerce 239 287 woocommerce_cart_hash: { vendor: "WooCommerce", category: "necessary", purpose: "Shopping cart contents", expiry: "Session" }, … … 272 320 if (match) return match; 273 321 274 // Prefix match in built-in database 322 // Prefix match in built-in database: the longest prefix wins, so 323 // wp-settings-time-1 is not taken for a wp-settings- cookie. 324 var matchLen = 0; 275 325 Object.keys(KNOWN_COOKIES).forEach(function (key) { 276 if ( !match && KNOWN_COOKIES[key].prefix&& name.indexOf(key) === 0) {326 if (KNOWN_COOKIES[key].prefix && key.length > matchLen && name.indexOf(key) === 0) { 277 327 match = KNOWN_COOKIES[key]; 328 matchLen = key.length; 278 329 } 279 330 }); 280 331 281 332 return match || null; 333 } 334 335 // Cookies WordPress sets when someone logs in. The declaration never 336 // lists the ones in the viewer's own browser; with visitorLogins on it 337 // lists all of them under "If you log in" instead. wordpress_[hash] is 338 // the login cookie on sites without HTTPS, wordpress_rec_ is admin-only. 339 var LOGIN_COOKIE_RE = /^(?:wordpress_(?:logged_in_|sec_|rec_|test_cookie$|[0-9a-f]{32}$)|wp-settings-|wp_lang$)/; 340 341 function isLoginCookie(name) { 342 return LOGIN_COOKIE_RE.test(name); 343 } 344 345 function loginCookieRows() { 346 var keep = "Session, or 14 days with Remember Me"; 347 return [ 348 { name: "wordpress_logged_in_[hash]", purpose: "Keeps you logged in", expiry: keep }, 349 { name: location.protocol === "https:" ? "wordpress_sec_[hash]" : "wordpress_[hash]", purpose: "Confirms your login when you open your account dashboard", expiry: keep }, 350 { name: "wordpress_test_cookie", purpose: "Checks your browser accepts cookies before you log in", expiry: "Session" }, 351 { name: "wp_lang", purpose: "Remembers the language you picked on the login page", expiry: "Session" }, 352 { name: "wp-settings-[user ID]", purpose: "Remembers your dashboard layout settings", expiry: "1 year" }, 353 { name: "wp-settings-time-[user ID]", purpose: "Records when your dashboard settings were last saved", expiry: "1 year" } 354 ]; 282 355 } 283 356 … … 410 483 // Connecticut, Montana, Oregon, Texas, Delaware, Iowa, Nebraska, 411 484 // New Hampshire, New Jersey, Minnesota, plus others pending). 412 if (gpcActive ) {485 if (gpcActive && !state.gpcOptIn) { 413 486 update.ad_storage = "denied"; 414 487 update.ad_user_data = "denied"; … … 666 739 ]; 667 740 741 // Patterns match whole hostnames (the host itself or a subdomain of 742 // it), plus an optional path prefix: "x.com" matches x.com and 743 // www.x.com but not netflix.com or box.com. 744 function iframePatternMatches(src, pattern) { 745 var host, path; 746 try { 747 var u = new URL(src, location.href); 748 host = u.hostname.toLowerCase(); 749 path = u.pathname.toLowerCase(); 750 } catch (e) { return false; } 751 var slash = pattern.indexOf("/"); 752 var pHost = slash === -1 ? pattern : pattern.slice(0, slash); 753 var pPath = slash === -1 ? "" : pattern.slice(slash); 754 if (host !== pHost && host.slice(-pHost.length - 1) !== "." + pHost) return false; 755 return !pPath || path.indexOf(pPath) === 0; 756 } 757 668 758 function getIframeBlockCategory(src) { 669 759 if (!src) return null; 670 var lower = src.toLowerCase();671 760 for (var i = 0; i < IFRAME_BLOCK_PATTERNS.length; i++) { 672 if ( lower.indexOf(IFRAME_BLOCK_PATTERNS[i].pattern) !== -1) {761 if (iframePatternMatches(src, IFRAME_BLOCK_PATTERNS[i].pattern)) { 673 762 return IFRAME_BLOCK_PATTERNS[i].category; 674 763 } … … 838 927 var src = iframe.src || ""; 839 928 if (!src) continue; 840 for (var j = 0; j < IFRAME_BLOCK_PATTERNS.length; j++) { 841 if (src.indexOf(IFRAME_BLOCK_PATTERNS[j].pattern) !== -1) { 842 iframe.src = src; 843 break; 844 } 845 } 929 if (getIframeBlockCategory(src)) iframe.src = src; 846 930 } 847 931 } … … 896 980 function saveState(state, action) { 897 981 var prev = consentState; 982 // A click on an embed's placeholder loads that embed now; the GPC 983 // opt-out is saved again on the next page view (boot()). 984 if (!(action && action.indexOf("facade_accept_") === 0)) applyGpc(state); 898 985 consentState = state; 899 986 storageSet(CONFIG.cookieName, JSON.stringify(state), CONFIG.cookieExpiry); … … 958 1045 if (!s || typeof s !== "object" || Array.isArray(s)) return null; 959 1046 if (!s.ts || isNaN(new Date(s.ts).getTime())) return null; 960 // Check re-consent expiry 1047 // Check re-consent expiry. A period below 1 day means the default. 1048 var days = Number(CONFIG.reconsentDays); 1049 if (!(days >= 1)) days = 365; 961 1050 var age = (Date.now() - new Date(s.ts).getTime()) / 864e5; 962 if (age > CONFIG.reconsentDays) return null;1051 if (age > days) return null; 963 1052 // Consent version check: if the site owner has clicked 964 1053 // "Invalidate All Consent", the version string will not … … 1183 1272 var alwaysOn = t.required 1184 1273 ? ' <span class="cc-always-on">' + escHtml(txt.alwaysOnLabel) + '</span>' : ""; 1274 var gpcNote = gpcActive && t.id === "marketing" 1275 ? '<div class="cc-gpc-note">' + escHtml(txt.gpcPrefNote) + '</div>' : ""; 1185 1276 return '<div class="cc-cat">' + 1186 1277 '<div><div class="cc-cat-label">' + escHtml(t.name) + alwaysOn + '</div>' + 1187 '<div class="cc-cat-desc">' + escHtml(t.description || "") + '</div> </div>' +1278 '<div class="cc-cat-desc">' + escHtml(t.description || "") + '</div>' + gpcNote + '</div>' + 1188 1279 '<label class="cc-toggle"><input type="checkbox" id="cc-cb-' + escHtml(t.id) + '"' + 1189 1280 ' aria-label="' + escHtml(t.name) + '"' + … … 1200 1291 '<div class="cc-buttons">' + 1201 1292 '<button class="cc-btn cc-btn-accept" id="cc-accept">' + escHtml(txt.acceptAllButton) + '</button>' + 1293 (CONFIG.rejectFirstScreen ? '<button class="cc-btn cc-btn-reject" id="cc-reject">' + escHtml(txt.rejectAllButton) + '</button>' : '') + 1202 1294 '<button class="cc-btn cc-btn-details" id="cc-details" aria-expanded="false">' + escHtml(txt.showDetailsButton) + '</button>' + 1203 1295 '</div>' + … … 1221 1313 document.getElementById("cc-accept").addEventListener("click", acceptAll); 1222 1314 document.getElementById("cc-decline").addEventListener("click", declineAll); 1315 var rejectBtn = document.getElementById("cc-reject"); 1316 if (rejectBtn) rejectBtn.addEventListener("click", declineAll); 1223 1317 document.getElementById("cc-save").addEventListener("click", savePrefs); 1224 1318 document.getElementById("cc-details").addEventListener("click", toggleDetails); 1225 1319 icon.addEventListener("click", function () { showBanner("gdpr"); }); 1226 1320 // No overlay click handler — banner must be dismissed via buttons only 1321 emitRendered("gdpr", banner); 1322 } 1323 1324 // cc:banner-rendered fires once each banner's markup is in the page 1325 // (before it is shown), so other code can adjust it. 1326 // detail: { banner: "gdpr" | "ccpa", element, state } 1327 function emitRendered(type, el) { 1328 emit("cc:banner-rendered", { banner: type, element: el, state: getPublicState() }); 1227 1329 } 1228 1330 … … 1231 1333 ═══════════════════════════════════════════ */ 1232 1334 function buildCcpaBanner() { 1335 if (document.getElementById("cc-ccpa-banner")) return; 1233 1336 var txt = CONFIG.text; 1234 1337 var banner = document.createElement("div"); … … 1240 1343 1241 1344 banner.innerHTML = 1242 '<div class="cc-ccpa-inner">' + 1243 '<div class="cc-ccpa-text"><p>' + escHtml(txt.ccpaDescription) + '</p></div>' + 1345 '<div class="cc-ccpa-inner" id="cc-ccpa-notice">' + 1346 '<div class="cc-ccpa-text"><p>' + escHtml(txt.ccpaDescription) + '</p>' + 1347 (gpcActive ? '<p class="cc-ccpa-gpc">' + escHtml(txt.ccpaGpcNotice) + '</p>' : '') + 1348 '</div>' + 1244 1349 '<div class="cc-ccpa-actions">' + 1245 '<a href=" ' + escHtml(CONFIG.ccpaUrl) + '" class="cc-ccpa-link">' + escHtml(txt.ccpaDnsmpiLink) + '</a>' +1350 '<a href="#" class="cc-ccpa-link" id="cc-ccpa-dns" role="button">' + escHtml(txt.ccpaDnsmpiLink) + '</a>' + 1246 1351 '<button class="cc-btn cc-btn-accept" id="cc-ccpa-ok">' + escHtml(txt.ccpaAcceptButton) + '</button>' + 1247 1352 '</div>' + 1248 '</div>'; 1353 '</div>' + 1354 '<div class="cc-ccpa-choice" id="cc-ccpa-choice"></div>' + 1355 (CONFIG.showPoweredBy ? '<div class="cc-powered"><a href="https://www.cookieflame.com" target="_blank" rel="noopener">Powered by CookieFlame</a></div>' : ''); 1249 1356 1250 1357 document.body.appendChild(banner); 1251 1358 1359 // OK only closes the notice. It never changes the visitor's choices: 1360 // US visitors start with everything allowed, and a GPC visitor's 1361 // opt-out was saved before the notice appeared. 1252 1362 document.getElementById("cc-ccpa-ok").addEventListener("click", function () { 1253 var s = buildStateAllGranted(); 1254 saveState(s, "ccpa_acknowledge"); 1255 banner.classList.remove("cc-show"); 1363 if (consentState) logConsent("ccpa_acknowledge", consentState); 1364 closeCcpaBanner(); 1365 }); 1366 document.getElementById("cc-ccpa-dns").addEventListener("click", function (e) { 1367 e.preventDefault(); 1368 showCcpaChoice(true); 1369 }); 1370 emitRendered("ccpa", banner); 1371 } 1372 1373 // The "Do Not Sell or Share" choice, drawn from the current state. 1374 function renderCcpaChoice() { 1375 var el = document.getElementById("cc-ccpa-choice"); 1376 if (!el) return; 1377 var txt = CONFIG.text; 1378 var optedOut = !(consentState && consentState.marketing); 1379 var html = '<div class="cc-ccpa-choice-inner">' + 1380 '<p class="cc-ccpa-choice-title">' + escHtml(txt.ccpaChoiceTitle) + '</p>'; 1381 if (optedOut) { 1382 html += '<p class="cc-ccpa-choice-status">' + escHtml(gpcActive ? txt.ccpaGpcOptedOut : txt.ccpaOptedOut) + '</p>' + 1383 '<div class="cc-ccpa-choice-buttons">' + 1384 '<button class="cc-btn cc-btn-accept" id="cc-ccpa-done">' + escHtml(txt.ccpaDoneButton) + '</button>' + 1385 '<button class="cc-btn cc-btn-decline" id="cc-ccpa-optin">' + escHtml(txt.ccpaOptInButton) + '</button>' + 1386 '</div>' + 1387 (gpcActive ? '<p class="cc-ccpa-choice-note">' + escHtml(txt.ccpaOptInGpcNote) + '</p>' : ''); 1388 } else { 1389 html += '<p class="cc-ccpa-choice-text">' + escHtml(txt.ccpaChoiceText) + '</p>' + 1390 '<div class="cc-ccpa-choice-buttons">' + 1391 '<button class="cc-btn cc-btn-accept" id="cc-ccpa-optout">' + escHtml(txt.ccpaOptOutButton) + '</button>' + 1392 '<button class="cc-btn cc-btn-decline" id="cc-ccpa-cancel">' + escHtml(txt.ccpaCancelButton) + '</button>' + 1393 '</div>'; 1394 } 1395 html += '<button class="cc-btn cc-btn-details" id="cc-ccpa-more">' + escHtml(txt.ccpaMoreSettings) + '</button></div>'; 1396 el.innerHTML = html; 1397 1398 var bind = function (id, fn) { var b = document.getElementById(id); if (b) b.addEventListener("click", fn); }; 1399 bind("cc-ccpa-done", closeCcpaBanner); 1400 bind("cc-ccpa-cancel", closeCcpaBanner); 1401 bind("cc-ccpa-optout", function () { 1402 var s = copyState(consentState); 1403 s.marketing = false; 1404 saveState(s, "ccpa_opt_out"); 1405 renderCcpaChoice(); 1406 focusFirst("cc-ccpa-choice"); 1256 1407 emit("cc:update"); 1257 1408 }); 1409 bind("cc-ccpa-optin", function () { 1410 var s = copyState(consentState); 1411 s.marketing = true; 1412 if (gpcActive) s.gpcOptIn = 1; 1413 delete s.gpc; 1414 saveState(s, "ccpa_opt_in"); 1415 renderCcpaChoice(); 1416 focusFirst("cc-ccpa-choice"); 1417 emit("cc:update"); 1418 }); 1419 bind("cc-ccpa-more", function () { 1420 closeCcpaBanner(); 1421 openPreferences(); 1422 }); 1423 } 1424 1425 function showCcpaChoice(open) { 1426 var notice = document.getElementById("cc-ccpa-notice"); 1427 var choice = document.getElementById("cc-ccpa-choice"); 1428 if (!notice || !choice) return; 1429 if (open) renderCcpaChoice(); 1430 notice.style.display = open ? "none" : ""; 1431 choice.classList.toggle("cc-open", !!open); 1432 if (open) focusFirst("cc-ccpa-choice"); 1433 } 1434 1435 function closeCcpaBanner() { 1436 var b = document.getElementById("cc-ccpa-banner"); 1437 if (b) b.classList.remove("cc-show"); 1438 showCcpaChoice(false); 1439 document.removeEventListener("keydown", _trapFocus); 1440 if (_preFocusEl && typeof _preFocusEl.focus === "function") { 1441 try { _preFocusEl.focus(); } catch (e) {} 1442 } 1443 _preFocusEl = null; 1444 } 1445 1446 function focusFirst(id) { 1447 var el = document.getElementById(id); 1448 var b = el && el.querySelector("button"); 1449 if (b) { try { b.focus(); } catch (e) {} } 1450 } 1451 1452 // A fresh state with the same choices as src (everything allowed when 1453 // there is none yet), keeping an earlier GPC opt-in. 1454 function copyState(src) { 1455 var s = { ts: new Date().toISOString(), cv: CONFIG.consentVersion, cc: detectedCountry }; 1456 CONFIG.cookieTypes.forEach(function (t) { s[t.id] = src ? !!src[t.id] : true; }); 1457 if (src && src.gpcOptIn) s.gpcOptIn = 1; 1458 return s; 1258 1459 } 1259 1460 … … 1268 1469 function _trapFocus(e) { 1269 1470 if (e.key !== "Tab") return; 1270 var banner = document. getElementById("cc-banner") || document.getElementById("cc-ccpa-banner");1271 if (!banner || !banner.classList.contains("cc-show")) return;1471 var banner = document.querySelector("#cc-banner.cc-show, #cc-ccpa-banner.cc-show"); 1472 if (!banner) return; 1272 1473 var focusable = []; 1273 1474 var all = banner.querySelectorAll('button, a[href], input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'); … … 1278 1479 var p = el.parentElement; 1279 1480 while (p && p !== banner) { 1280 if ( p.style &&window.getComputedStyle(p).display === "none") { hidden = true; break; }1481 if (window.getComputedStyle(p).display === "none") { hidden = true; break; } 1281 1482 p = p.parentElement; 1282 1483 } … … 1295 1496 function showBanner(mode) { 1296 1497 if (mode === "ccpa") { 1498 _preFocusEl = document.activeElement; 1297 1499 requestAnimationFrame(function () { 1298 1500 var b = document.getElementById("cc-ccpa-banner"); 1299 1501 if (b) { 1300 1502 b.classList.add("cc-show"); 1301 _preFocusEl = document.activeElement; 1302 b.focus(); 1503 var choice = document.getElementById("cc-ccpa-choice"); 1504 if (choice && choice.classList.contains("cc-open")) focusFirst("cc-ccpa-choice"); 1505 else b.focus(); 1303 1506 document.addEventListener("keydown", _trapFocus); 1304 1507 } … … 1307 1510 } 1308 1511 _preFocusEl = document.activeElement; 1512 // "Decline All" on the first screen is for the GDPR banner only, never 1513 // when the preferences are opened by a US or other visitor. 1514 var rj = document.getElementById("cc-reject"); 1515 if (rj) { 1516 if (complianceMode === "gdpr") rj.style.removeProperty("display"); 1517 else rj.style.setProperty("display", "none", "important"); // beats .cc-btn's !important 1518 } 1309 1519 requestAnimationFrame(function () { 1310 1520 var ov = document.getElementById("cc-overlay"); … … 1354 1564 ═══════════════════════════════════════════ */ 1355 1565 function acceptAll() { 1356 saveState(buildStateAllGranted(), "accept_all"); 1566 var s = buildStateAllGranted(); 1567 if (consentState && consentState.gpcOptIn) s.gpcOptIn = 1; 1568 saveState(s, "accept_all"); 1357 1569 closeBanner(); 1358 1570 emit("cc:accept-all"); emit("cc:update"); … … 1366 1578 1367 1579 function savePrefs() { 1368 saveState(buildStateFromCheckboxes(), "save_preferences"); 1580 var s = buildStateFromCheckboxes(); 1581 // Switching Marketing on here is the separate choice that opts a GPC 1582 // visitor back in. 1583 if (s.marketing && (gpcActive || (consentState && consentState.gpcOptIn))) s.gpcOptIn = 1; 1584 saveState(s, "save_preferences"); 1369 1585 closeBanner(); 1370 1586 emit("cc:save"); emit("cc:update"); … … 1383 1599 } 1384 1600 } 1601 1602 /* ═══════════════════════════════════════════ 1603 COOKIE SETTINGS LINK 1604 [cookieflame_settings_link], the declaration's button and any 1605 element with the class cc-open-settings reopen the visitor's 1606 choices: the "Do Not Sell or Share" choice for US visitors, the 1607 preferences for everyone else. 1608 ═══════════════════════════════════════════ */ 1609 function openPreferences() { 1610 if (!document.getElementById("cc-banner")) { 1611 applyThemeClasses(); 1612 buildGdprBanner(); 1613 hideAllBanners(); 1614 if (consentState) syncCheckboxes(consentState); 1615 } 1616 showBanner("gdpr"); 1617 } 1618 1619 function openUsChoices() { 1620 applyThemeClasses(); 1621 buildCcpaBanner(); 1622 showBanner("ccpa"); 1623 showCcpaChoice(true); 1624 } 1625 1626 function openSettings() { 1627 if (complianceMode === "ccpa") openUsChoices(); 1628 else openPreferences(); 1629 } 1630 1631 document.addEventListener("click", function (e) { 1632 var t = e.target && e.target.closest ? e.target.closest(".cc-open-settings") : null; 1633 if (!t) return; 1634 e.preventDefault(); 1635 openSettings(); 1636 }); 1385 1637 1386 1638 /* ═══════════════════════════════════════════ … … 1435 1687 getComplianceMode: function () { return complianceMode; }, 1436 1688 getCountry: function () { return detectedCountry; }, 1437 show: function () { showBanner("gdpr"); }, // always open preference manager 1689 show: openPreferences, // always open preference manager 1690 showSettings: openSettings, // what the Cookie settings link opens 1438 1691 acceptAll: acceptAll, 1439 1692 declineAll: declineAll, … … 1461 1714 // Add browser cookies 1462 1715 var browserCookies = document.cookie.split(";").map(function (c) { return c.trim().split("=")[0]; }).filter(Boolean); 1716 browserCookies = browserCookies.filter(function (n) { return !isLoginCookie(n); }); 1463 1717 for (var b = 0; b < browserCookies.length; b++) { 1464 1718 var bInfo = identifyCookie(browserCookies[b]); … … 1473 1727 for (var c = 0; c < customCookies.length; c++) { 1474 1728 var cc = customCookies[c]; 1475 if (cc.name && cc.vendor && cc.category && cc.category !== "unclassified" && !allCookies[cc.name] ) {1729 if (cc.name && cc.vendor && cc.category && cc.category !== "unclassified" && !allCookies[cc.name] && !isLoginCookie(cc.name)) { 1476 1730 allCookies[cc.name] = { 1477 1731 name: cc.name, vendor: cc.vendor, category: cc.category, … … 1562 1816 } 1563 1817 1818 if (CONFIG.visitorLogins) { 1819 var loginRows = loginCookieRows(); 1820 html += '<div class="cc-declaration-login">'; 1821 html += '<p><b>' + escHtml(CONFIG.text.loginCookiesTitle) + '</b></p>'; 1822 html += '<p>' + escHtml(CONFIG.text.loginCookiesIntro) + '</p>'; 1823 html += '<table><thead><tr>'; 1824 html += '<th>Name</th><th>Provider</th><th>Purpose</th><th>Expiry</th>'; 1825 html += '</tr></thead><tbody>'; 1826 for (var li = 0; li < loginRows.length; li++) { 1827 html += '<tr>'; 1828 html += '<td><code>' + escHtml(loginRows[li].name) + '</code></td>'; 1829 html += '<td>WordPress</td>'; 1830 html += '<td>' + escHtml(loginRows[li].purpose) + '</td>'; 1831 html += '<td>' + escHtml(loginRows[li].expiry) + '</td>'; 1832 html += '</tr>'; 1833 } 1834 html += '</tbody></table></div>'; 1835 } 1836 1837 html += '<p><button type="button" class="cc-open-settings cc-declaration-change">' + escHtml(CONFIG.text.changePreferences) + '</button></p>'; 1564 1838 html += '</div>'; 1565 1839 el.innerHTML = html; … … 1581 1855 var preloadedConsent = loadState(); 1582 1856 if (preloadedConsent) { 1857 // Marketing scripts created before boot() runs stay blocked for GPC. 1858 if (gpcActive && !preloadedConsent.gpcOptIn) preloadedConsent.marketing = false; 1583 1859 consentState = preloadedConsent; 1584 1860 updateConsentMode(preloadedConsent); … … 1649 1925 var existing = loadState(); 1650 1926 1927 var gpcNewOptOut = false; 1651 1928 if (existing) { 1652 // GPC override: when Global Privacy Control is active, suppress 1653 // marketing consent in the effective state. The stored cookie 1654 // is not modified — if the visitor later disables GPC, their 1655 // original consent choice is restored. This ensures marketing 1656 // scripts and ad signals stay blocked while GPC is active. 1657 if (gpcActive) existing.marketing = false; 1929 // GPC: a signal that arrives after the visitor's choice is 1930 // processed as an opt-out and saved, so it holds on later visits 1931 // without GPC (§7025(c)(5)). An earlier opt-in made while GPC was 1932 // on (gpcOptIn) is kept. 1933 if (gpcActive && !existing.gpcOptIn && (existing.marketing || !existing.gpc)) { 1934 gpcNewOptOut = !!existing.marketing; 1935 applyGpc(existing); 1936 storageSet(CONFIG.cookieName, JSON.stringify(existing), CONFIG.cookieExpiry); 1937 } 1658 1938 consentState = existing; 1659 1939 updateConsentMode(existing); … … 1681 1961 if (ic) ic.classList.add("cc-show"); 1682 1962 } 1963 if (gpcNewOptOut) logConsent("gpc_optout", existing); 1683 1964 emit("cc:ready"); 1684 1965 renderDeclaration(); … … 1691 1972 if (ic) ic.classList.add("cc-show"); 1692 1973 } 1974 if (gpcNewOptOut) logConsent("gpc_optout", existing); 1693 1975 emit("cc:ready"); 1694 1976 renderDeclaration(); … … 1714 1996 startIframeObserver(); 1715 1997 if (gpcActive) { 1716 // GPC active: grant analytics and preferences but deny marketing. 1717 // This fulfills the CCPA/CPRA requirement to honor Global Privacy 1718 // Control as a valid opt-out of the sale or sharing of personal 1719 // information. Fines have been issued for ignoring GPC (Sephora 1720 // $1.2M, DoorDash $375K, Tractor Supply $1.3M). 1721 var gpcState = buildStateAllGranted(); 1722 gpcState.marketing = false; 1723 saveState(gpcState, "ccpa_gpc_optout"); 1998 // GPC active: allow analytics and preferences but opt out of 1999 // selling and sharing (Marketing off), saved before the notice 2000 // appears. The notice says the signal was honoured. 2001 saveState(applyGpc(buildStateAllGranted()), "ccpa_gpc_optout"); 1724 2002 } else { 1725 2003 saveState(buildStateAllGranted(), "ccpa_auto_grant"); -
cookieflame/trunk/assets/js/cookieconsent.min.js
r3769546 r3771537 1 1 /** 2 * CookieFlame v5. 3.02 * CookieFlame v5.4.0 3 3 * ───────────────────────────────────────────────────────────── 4 4 * Self-hosted cookie consent manager with Google Consent Mode v2, 5 * IAB TCF v2.2,automatic script blocking, iframe facades with5 * automatic script blocking, iframe facades with 6 6 * thumbnails, Elementor video widget support, multi-region 7 7 * compliance (GDPR/CCPA), and passive cookie monitoring. … … 17 17 * • Timezone-based geo heuristic with server-side fallback 18 18 */ 19 !function(){"use strict";var e={};try{for(var t=document.cookie.split(";"),n=0;n<t.length;n++){var o=t[n].trim().split("=")[0];o&&(e[o]=!0)}}catch(e){}var c="5.3.0",r={cookieName:"cc_consent",cookieExpiry:365,privacyUrl:"/privacy-policy",ccpaUrl:"/privacy-policy#do-not-sell",geoApi:"",logEndpoint:"",reconsentDays:365,cspSafe:!1,tcfEnabled:!0,cmpId:1,cmpVersion:1,vendorListVersion:2,storageMethod:"cookie",cookieDomain:"",shareSubdomains:!1,theme:"light",showPoweredBy:!1,iconPosition:"left",consentVersion:"",autoBlockPatterns:[{pattern:"googletagmanager.com/gtag",category:"analytics"},{pattern:"google-analytics.com",category:"analytics"},{pattern:"analytics.google.com",category:"analytics"},{pattern:"hotjar.com",category:"analytics"},{pattern:"mixpanel.com",category:"analytics"},{pattern:"segment.com/analytics",category:"analytics"},{pattern:"segment.io",category:"analytics"},{pattern:"clarity.ms",category:"analytics"},{pattern:"plausible.io",category:"analytics"},{pattern:"js.hs-analytics.net",category:"analytics"},{pattern:"connect.facebook.net",category:"marketing"},{pattern:"snap.licdn.com",category:"marketing"},{pattern:"ads.linkedin.com",category:"marketing"},{pattern:"googleads.g.doubleclick.net",category:"marketing"},{pattern:"googlesyndication.com",category:"marketing"},{pattern:"googleadservices.com",category:"marketing"},{pattern:"ads-twitter.com",category:"marketing"},{pattern:"analytics.tiktok.com",category:"marketing"},{pattern:"pintrk",category:"marketing"},{pattern:"s.pinimg.com/ct",category:"marketing"},{pattern:"widget.intercom.io",category:"preferences"},{pattern:"js.intercomcdn.com",category:"preferences"}],regionRules:{gdpr:["AT","BE","BG","CY","CZ","DE","DK","EE","ES","FI","FR","GR","HR","HU","IE","IT","LT","LU","LV","MT","NL","PL","PT","RO","SE","SI","SK","IS","LI","NO","GB","GG","JE","IM","CH","BR","ZA"],ccpa:["US"]},text:{title:"We value your privacy",description:"We use cookies to improve your experience, analyse traffic and personalise content. You can choose which categories to allow.",acceptAllButton:"Accept All",declineAllButton:"Decline All",saveButton:"Save Preferences",showDetailsButton:"Manage preferences",hideDetailsButton:"Hide preferences",alwaysOnLabel:"(always on)",ccpaTitle:"Your Privacy Choices",ccpaDescription:"We use cookies and similar technologies. Under California law, you have the right to opt out of the sale or sharing of your personal information.",ccpaAcceptButton:"OK",ccpaDnsmpiLink:"Do Not Sell My Personal Information"},cookieTypes:[{id:"necessary",name:"Necessary",description:"Required for the site to function. Cannot be disabled.",required:!0,consentMode:["security_storage","functionality_storage"],tcfPurposes:[1],onAccept:function(){}},{id:"preferences",name:"Preferences",description:"Remember your settings and personalisation choices.",required:!1,defaultValue:!1,consentMode:["functionality_storage","personalization_storage"],tcfPurposes:[2,3],onAccept:function(){},onReject:function(){}},{id:"analytics",name:"Analytics",description:"Help us understand how visitors use the site.",required:!1,defaultValue:!1,consentMode:["analytics_storage"],tcfPurposes:[4,5,6],onAccept:function(){},onReject:function(){}},{id:"marketing",name:"Marketing",description:"Used to deliver relevant ads and track campaigns.",required:!1,defaultValue:!1,consentMode:["ad_storage","ad_user_data","ad_personalization"],tcfPurposes:[7,8,9,10],onAccept:function(){},onReject:function(){}}]};if(window.__ccConfig&&"object"==typeof window.__ccConfig){var a=window.__ccConfig;["cookieName","cookieExpiry","privacyUrl","ccpaUrl","geoApi","logEndpoint","reconsentDays","cspSafe","tcfEnabled","cmpId","cmpVersion","vendorListVersion","storageMethod","cookieDomain","shareSubdomains","theme","showPoweredBy","iconPosition","consentVersion"].forEach(function(e){void 0!==a[e]&&(r[e]=a[e])}),a.text&&"object"==typeof a.text&&Object.keys(a.text).forEach(function(e){r.text[e]=a.text[e]}),Array.isArray(a.cookieTypes)&&(r.cookieTypes=a.cookieTypes),Array.isArray(a.autoBlockPatterns)&&(r.autoBlockPatterns=a.autoBlockPatterns),a.regionRules&&(r.regionRules=a.regionRules)}window.__ccConsentVersion&&(r.consentVersion=window.__ccConsentVersion);var i=!!window.__ccGPC;window.__ccBlockRules&&Array.isArray(window.__ccBlockRules)&&window.__ccBlockRules.length&&(r.autoBlockPatterns=window.__ccBlockRules);var s={cc_consent:{vendor:"CookieFlame",category:"necessary",purpose:"Stores your consent preferences",expiry:"1 year"},cc_anon_id:{vendor:"CookieFlame",category:"necessary",purpose:"Anonymous ID for consent logging",expiry:"1 year"},cookieflame_dnt:{vendor:"CookieFlame",category:"necessary",purpose:"Do-not-track opt-out flag",expiry:"1 year"},PHPSESSID:{vendor:"PHP",category:"necessary",purpose:"Server-side session identifier",expiry:"Session"},wp_settings_:{vendor:"WordPress",category:"necessary",purpose:"Admin interface preferences",prefix:!0,expiry:"1 year"},"wp-settings-":{vendor:"WordPress",category:"necessary",purpose:"Admin interface preferences",prefix:!0,expiry:"1 year"},"wp-settings-time-":{vendor:"WordPress",category:"necessary",purpose:"Admin settings timestamp",prefix:!0,expiry:"1 year"},wordpress_logged_in_:{vendor:"WordPress",category:"necessary",purpose:"Login session",prefix:!0,expiry:"Session"},wordpress_sec_:{vendor:"WordPress",category:"necessary",purpose:"Secure login session",prefix:!0,expiry:"Session"},wordpress_test_cookie:{vendor:"WordPress",category:"necessary",purpose:"Checks cookie support",expiry:"Session"},wp_lang:{vendor:"WordPress",category:"necessary",purpose:"Language preference",expiry:"Session"},woocommerce_cart_hash:{vendor:"WooCommerce",category:"necessary",purpose:"Shopping cart contents",expiry:"Session"},woocommerce_items_in_cart:{vendor:"WooCommerce",category:"necessary",purpose:"Cart item count",expiry:"Session"},wp_woocommerce_session_:{vendor:"WooCommerce",category:"necessary",purpose:"Shopping session",prefix:!0,expiry:"2 days"},_ga:{vendor:"Google Analytics",category:"analytics",purpose:"Distinguishes visitors",expiry:"2 years"},_ga_:{vendor:"Google Analytics",category:"analytics",purpose:"Stores session state",prefix:!0,expiry:"2 years"},_gid:{vendor:"Google Analytics",category:"analytics",purpose:"Distinguishes visitors (24h)",expiry:"24 hours"},_gat:{vendor:"Google Analytics",category:"analytics",purpose:"Throttles request rate",expiry:"1 minute"},wp_wk_ga_untrack_:{vendor:"Google Analytics",category:"analytics",purpose:"GA opt-out flag",prefix:!0,expiry:"Persistent"},_gcl_au:{vendor:"Google Ads",category:"marketing",purpose:"Conversion linker",expiry:"90 days"},_gcl_aw:{vendor:"Google Ads",category:"marketing",purpose:"Stores ad click info",expiry:"90 days"},_gac_:{vendor:"Google Ads",category:"marketing",purpose:"Campaign information",prefix:!0,expiry:"90 days"},IDE:{vendor:"Google DoubleClick",category:"marketing",purpose:"Ad serving and frequency",expiry:"1 year"},test_cookie:{vendor:"Google DoubleClick",category:"marketing",purpose:"Checks cookie support",expiry:"15 minutes"},NID:{vendor:"Google",category:"preferences",purpose:"Stores preferences",expiry:"6 months"}},d=window.__ccCustomCookies||[];function u(e){for(var t=0;t<d.length;t++){var n=d[t];if("exact"===n.match&&e===n.name)return n;if("prefix"===n.match&&0===e.indexOf(n.name))return n;if("contains"===n.match&&-1!==e.indexOf(n.name))return n}var o=s[e];return o||(Object.keys(s).forEach(function(t){!o&&s[t].prefix&&0===e.indexOf(t)&&(o=s[t])}),o||null)}var l=null,p="none",f=null,m=null,g=!1,y=0;function v(e){return"string"!=typeof e&&(e=String(e)),e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function h(e){if("localStorage"===r.storageMethod)try{return localStorage.getItem(e)}catch(e){return null}var t=document.cookie.match(new RegExp("(?:^|;\\s*)"+e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));if(!t)return null;try{return decodeURIComponent(t[1])}catch(e){return null}}function w(e,t,n){if("localStorage"!==r.storageMethod){var o=new Date;o.setTime(o.getTime()+864e5*n);var c=e+"="+encodeURIComponent(t)+";expires="+o.toUTCString()+";path=/;SameSite=Lax;Secure";r.cookieDomain&&(c+=";domain="+r.cookieDomain),document.cookie=c}else try{localStorage.setItem(e,t)}catch(e){}}function b(){if(m)return m;var e=h("cc_anon_id");return e?(m=e,e):(w("cc_anon_id",m="cc_"+function(){if("undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID)try{return crypto.randomUUID()}catch(e){}return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}(),730),m)}function k(){var e={timestamp:l?l.ts:null,complianceMode:p,country:f,gpc:i,consentVersion:r.consentVersion};return r.cookieTypes.forEach(function(t){e[t.id]=l?!!l[t.id]:!!t.required}),e}function _(e,t){window.dispatchEvent(new CustomEvent(e,{detail:t||k()}))}function x(){window.dataLayer=window.dataLayer||[],window.dataLayer.push(arguments)}function E(e){var t={};r.cookieTypes.forEach(function(n){n.consentMode&&n.consentMode.forEach(function(o){t[o]=e[n.id]?"granted":"denied"})}),i&&(t.ad_storage="denied",t.ad_user_data="denied",t.ad_personalization="denied"),x("consent","update",t)}var A=new Map,S=!1;function L(e){var t={};return r.cookieTypes.forEach(function(n){n.tcfPurposes&&n.tcfPurposes.forEach(function(o){t[o]=!!e[n.id]})}),{tcString:"",tcfPolicyVersion:4,cmpId:r.cmpId,cmpVersion:r.cmpVersion,gdprApplies:"gdpr"===p,eventStatus:"useractioncomplete",cmpStatus:"loaded",listenerId:void 0,isServiceSpecific:!0,useNonStandardTexts:!1,publisherCC:"GB",purposeOneTreatment:!1,purpose:{consents:t,legitimateInterests:{}},vendor:{consents:{},legitimateInterests:{}},specialFeatureOptins:{},publisher:{consents:{},legitimateInterests:{},customPurpose:{consents:{},legitimateInterests:{}},restrictions:{}}}}function C(e,t,n,o){if("function"==typeof n)if("addEventListener"===e){var c=++y;if(A.set(c,n),l){var a=L(l);a.listenerId=c,n(a,!0)}}else"removeEventListener"===e?n({listenerId:o},A.delete(o)):"getTCData"===e?n(l?L(l):null,!!l):"ping"===e?n({gdprApplies:"gdpr"===p,cmpLoaded:S,cmpStatus:S?"loaded":"stub",displayStatus:"hidden",apiVersion:"2.2",cmpVersion:r.cmpVersion,cmpId:r.cmpId,gvlVersion:r.vendorListVersion,tcfPolicyVersion:4},!0):n(null,!1)}function B(e){A.forEach(function(t,n){var o=L(e);o.listenerId=n;try{t(o,!0)}catch(e){}})}function I(e){if(!e)return null;for(var t=e.toLowerCase(),n=0;n<r.autoBlockPatterns.length;n++){var o=r.autoBlockPatterns[n];if(o.pattern instanceof RegExp){if(o.pattern.test(t))return o.category}else if(-1!==t.indexOf(o.pattern.toLowerCase()))return o.category}return null}function T(e){document.querySelectorAll('script[type="text/plain"][data-cc]').forEach(function(t){var n=(t.getAttribute("data-cc")||"").toLowerCase();if(e[n]&&!t.getAttribute("data-cc-activated")){t.setAttribute("data-cc-activated","1");var o=document.createElement("script");o.isWPRocket=!0,Array.from(t.attributes).forEach(function(e){"type"!==e.name&&"data-cc"!==e.name&&"data-cc-activated"!==e.name&&"data-cc-auto"!==e.name&&o.setAttribute(e.name,e.value)}),!t.src&&t.textContent&&(o.textContent=t.textContent),t.parentNode.insertBefore(o,t.nextSibling),t.parentNode.removeChild(t)}}),["img","iframe","link"].forEach(function(t){document.querySelectorAll(t+"[data-cc][data-cc-src]").forEach(function(n){var o=(n.getAttribute("data-cc")||"").toLowerCase();e[o]&&!n.getAttribute("data-cc-activated")&&(n.setAttribute("data-cc-activated","1"),"link"===t?n.href=n.getAttribute("data-cc-src"):n.src=n.getAttribute("data-cc-src"))})})}var O=[{pattern:"youtube.com",category:"marketing"},{pattern:"youtube-nocookie.com",category:"marketing"},{pattern:"youtu.be",category:"marketing"},{pattern:"player.vimeo.com",category:"marketing"},{pattern:"vimeo.com",category:"marketing"},{pattern:"dailymotion.com",category:"marketing"},{pattern:"open.spotify.com",category:"preferences"},{pattern:"soundcloud.com",category:"preferences"},{pattern:"google.com/maps",category:"preferences"},{pattern:"maps.google.com",category:"preferences"},{pattern:"facebook.com/plugins",category:"marketing"},{pattern:"instagram.com",category:"marketing"},{pattern:"tiktok.com/embed",category:"marketing"},{pattern:"twitter.com",category:"marketing"},{pattern:"x.com",category:"marketing"}];function D(){return!(!document.body||!(document.body.classList.contains("elementor-editor-active")||document.body.classList.contains("fl-builder-edit")||document.body.classList.contains("et-fb-root-ancestor")))}var P=[],M=0,R=null,q=!1;function V(){if(R&&P.length){var e=P;P=[];try{R(e)}catch(e){}}}function N(){0!==P.length&&(2!==M?1!==M&&3!==M&&(M=1,function(){var e=window.__ccConfig&&window.__ccConfig.facadesUrl||null;if(!e)return void(M=3);var t=document.createElement("script");t.src=e,t.async=!0,t.onload=function(){M=2,V()},t.onerror=function(){M=3},(document.head||document.documentElement).appendChild(t)}()):V())}function U(e){var t=e.src||e.getAttribute("data-src")||"";if(t&&!e.getAttribute("data-cc-facade")&&!D()){var n=function(e){if(!e)return null;for(var t=e.toLowerCase(),n=0;n<O.length;n++)if(-1!==t.indexOf(O[n].pattern))return O[n].category;return null}(t);if(n){var o=l||Z();o&&o[n]||(e.setAttribute("data-cc-facade","1"),e.setAttribute("data-cc-iframe-cat",n),e.setAttribute("data-cc-original-src",t),e.removeAttribute("src"),e.style.display="none",P.push({type:"iframe",node:e,category:n,src:t}),N())}}}function G(e){if(!e.getAttribute("data-cc-facade")&&!D()){var t=e.getAttribute("data-settings");if(t){var n;try{n=JSON.parse(t)}catch(e){return}var o=n.youtube_url||n.vimeo_url||"";if(o){var c=o.toLowerCase(),r=null;if((-1!==c.indexOf("youtube")||-1!==c.indexOf("youtu.be")||-1!==c.indexOf("vimeo")||-1!==c.indexOf("dailymotion"))&&(r="marketing"),r&&(!l||!l[r])){if(!l){var a=Z();if(a&&a[r])return}e.setAttribute("data-cc-facade","1"),q=!0,P.push({type:"elementor",node:e,category:r,url:o}),N()}}}}}function j(){for(var e=document.querySelectorAll("iframe[src]"),t=0;t<e.length;t++)U(e[t]);for(var n=document.querySelectorAll(".elementor-widget-video"),o=0;o<n.length;o++)G(n[o]);for(var c=document.querySelectorAll(".wp-block-embed__wrapper iframe[src]"),r=0;r<c.length;r++)U(c[r])}function J(){new MutationObserver(function(e){e.forEach(function(e){e.addedNodes.forEach(function(e){if(1===e.nodeType){if("IFRAME"===e.tagName&&U(e),e.querySelectorAll){for(var t=e.querySelectorAll("iframe[src]"),n=0;n<t.length;n++)U(t[n]);for(var o=e.querySelectorAll(".elementor-widget-video"),c=0;c<o.length;c++)G(o[c])}e.classList&&e.classList.contains("elementor-widget-video")&&G(e)}})})}).observe(document.documentElement,{childList:!0,subtree:!0})}function W(){for(var e=document.querySelectorAll("iframe[src]"),t=0;t<e.length;t++){var n=e[t];if(!n.getAttribute("data-cc-facade")){var o=n.src||"";if(o)for(var c=0;c<O.length;c++)if(-1!==o.indexOf(O[c].pattern)){n.src=o;break}}}}function H(e,t){r.cookieTypes.forEach(function(n){var o=!!e[n.id];if((t?!!t[n.id]:null)!==o||null===t)if(o&&"function"==typeof n.onAccept)try{n.onAccept()}catch(e){console.warn("[CookieConsent] onAccept error for "+n.id,e)}else if(!o&&"function"==typeof n.onReject)try{n.onReject()}catch(e){console.warn("[CookieConsent] onReject error for "+n.id,e)}})}function F(e,t){var n=l;if(l=e,w(r.cookieName,JSON.stringify(e),r.cookieExpiry),E(e),q&&t)for(var o=document.querySelectorAll(".cc-iframe-facade[data-cc-elementor-widget]"),a=0;a<o.length;a++){var s=o[a].getAttribute("data-cc-iframe-cat");if(s&&e[s])return void window.location.reload()}K(e,n),T(e),function(e){for(var t=document.querySelectorAll(".cc-iframe-facade"),n=0;n<t.length;n++)if(!t[n].getAttribute("data-cc-elementor-widget")&&e[t[n].getAttribute("data-cc-iframe-cat")]){var o=t[n].nextElementSibling;if(o&&"IFRAME"===o.tagName&&o.getAttribute("data-cc-facade")){var c=o.getAttribute("data-cc-original-src");c&&(o.src=c,o.style.display="",o.removeAttribute("data-cc-facade"))}t[n].parentNode.removeChild(t[n])}}(e),t&&"auto_grant_non_eu"!==t&&"ccpa_auto_grant"!==t&&setTimeout(W,300),H(e,n),$(e),r.tcfEnabled&&B(e),t&&function(e,t){if(r.logEndpoint){var n={};r.cookieTypes.forEach(function(e){n[e.id]=!!t[e.id]});var o={anonymousId:b(),timestamp:(new Date).toISOString(),action:e,consent:n,complianceMode:p,country:f,url:location.href,userAgent:navigator.userAgent,tcfEnabled:r.tcfEnabled,gpc:i,consentVersion:r.consentVersion,version:c};try{var a=JSON.stringify(o);navigator.sendBeacon?navigator.sendBeacon(r.logEndpoint,new Blob([a],{type:"application/json"})):fetch(r.logEndpoint,{method:"POST",body:a,headers:{"Content-Type":"application/json"},keepalive:!0})}catch(e){}}}(t,e)}function $(e){r.cookieTypes.forEach(function(t){if(!t.required){var n=document.getElementById("cc-cb-"+t.id);n&&(n.checked=!!e[t.id])}});var t=document.getElementById("cc-cats"),n=document.getElementById("cc-details"),o=document.getElementById("cc-pref-buttons");t&&t.classList.remove("cc-open"),n&&(n.textContent=r.text.showDetailsButton),o&&o.classList.remove("cc-open")}function Z(){var e=h(r.cookieName);if(!e)return null;try{var t=JSON.parse(e);return!t||"object"!=typeof t||Array.isArray(t)||(!t.ts||isNaN(new Date(t.ts).getTime())||(Date.now()-new Date(t.ts).getTime())/864e5>r.reconsentDays||r.consentVersion&&t.cv!==r.consentVersion)?null:t}catch(e){return null}}function Y(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:f};return r.cookieTypes.forEach(function(t){e[t.id]=!0}),e}function z(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:f};return r.cookieTypes.forEach(function(t){e[t.id]=!!t.required}),e}function K(e,t){if(t){var n=[];if(r.cookieTypes.forEach(function(o){o.required||t[o.id]&&!e[o.id]&&n.push(o.id)}),n.length)document.cookie.split(";").map(function(e){return e.trim().split("=")[0]}).filter(Boolean).forEach(function(e){var t=u(e);if(t&&-1!==n.indexOf(t.category)){var o=location.hostname,c="."+o;document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/",document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT",document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+c,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+o,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; Secure",r.cookieDomain&&(document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+r.cookieDomain,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+r.cookieDomain+"; Secure");var a=o.split(".");if(a.length>2){var i="."+a.slice(1).join(".");document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+i,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+i+"; Secure"}}})}}function Q(e){return(f=e?e.toUpperCase():null)?r.regionRules.gdpr&&-1!==r.regionRules.gdpr.indexOf(f)?"gdpr":r.regionRules.ccpa&&-1!==r.regionRules.ccpa.indexOf(f)?"ccpa":"none":"gdpr"}function X(e){if(window.__ccGeo)return e(Q(window.__ccGeo));r.geoApi?fetch(r.geoApi).then(function(e){return e.json()}).then(function(t){var n=t.country_code||t.country||"";n?e(Q(n)):ee(e)}).catch(function(){ee(e)}):ee(e)}function ee(e){var t="";try{t=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(e){}return 0===t.indexOf("Europe/")||"Atlantic/Reykjavik"===t?(f=null,e("gdpr")):0===t.indexOf("US/")||0===t.indexOf("America/")&&["New_York","Chicago","Denver","Los_Angeles","Phoenix","Anchorage","Boise","Detroit","Indianapolis","Louisville","Menominee","Nome","Adak","Juneau","Sitka","Yakutat","Metlakatla","Kentucky","Indiana","North_Dakota"].some(function(e){return-1!==t.indexOf(e)})||"Pacific/Honolulu"===t?(f="US",e("ccpa")):(f=null,void e("gdpr"))}function te(){"dark"===r.theme&&document.documentElement.classList.add("cc-dark"),"right"===r.iconPosition&&document.documentElement.classList.add("cc-icon-right")}function ne(){var e=r.text,t=document.createElement("div");t.id="cc-overlay",document.body.appendChild(t);var n=document.createElement("div");n.id="cc-banner",n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),n.setAttribute("aria-label","Cookie consent"),n.setAttribute("tabindex","-1");var o=r.cookieTypes.map(function(t){var n=t.required?' <span class="cc-always-on">'+v(e.alwaysOnLabel)+"</span>":"";return'<div class="cc-cat"><div><div class="cc-cat-label">'+v(t.name)+n+'</div><div class="cc-cat-desc">'+v(t.description||"")+'</div></div><label class="cc-toggle"><input type="checkbox" id="cc-cb-'+v(t.id)+'" aria-label="'+v(t.name)+'"'+(t.required||t.defaultValue?" checked":"")+(t.required?" disabled":"")+'><span class="cc-slider"></span></label></div>'}).join("");n.innerHTML='<div class="cc-inner"><p class="cc-title">'+v(e.title)+'</p><p class="cc-text">'+v(e.description)+' <a href="'+v(r.privacyUrl)+'">Privacy Policy</a></p><div class="cc-buttons"><button class="cc-btn cc-btn-accept" id="cc-accept">'+v(e.acceptAllButton)+'</button><button class="cc-btn cc-btn-details" id="cc-details" aria-expanded="false">'+v(e.showDetailsButton)+'</button></div><div class="cc-categories" id="cc-cats">'+o+'</div><div class="cc-pref-buttons" id="cc-pref-buttons"><button class="cc-btn cc-btn-save" id="cc-save">'+v(e.saveButton)+'</button><button class="cc-btn cc-btn-decline" id="cc-decline">'+v(e.declineAllButton)+"</button></div></div>"+(r.showPoweredBy?'<div class="cc-powered"><a href="https://www.cookieflame.com" target="_blank" rel="noopener">Powered by CookieFlame</a></div>':""),document.body.appendChild(n);var c=document.createElement("button");c.id="cc-icon",c.setAttribute("aria-label","Cookie settings"),c.innerHTML='<svg width="24" height="24" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19.1 1.16c-.06-.42-.38-.77-.8-.83A11.8 11.8 0 0 0 13.28 1.2L7.5 4.3A11.8 11.8 0 0 0 3.95 7.98L1.09 13.9A11.8 11.8 0 0 0 .38 18.96l1.13 6.47a11.8 11.8 0 0 0 3.53 4.99l4.71 4.56a11.8 11.8 0 0 0 4.59 2.24l6.48.91a11.8 11.8 0 0 0 5.52-.89l5.78-3.09a11.8 11.8 0 0 0 3.55-3.69l2.87-5.9a11.8 11.8 0 0 0 .71-5.08c-.07-.41-.42-.73-.83-.79a14.8 14.8 0 0 1-8.16-7.61c-.14-.59-.63-1.05-1.23-1.14A14.8 14.8 0 0 1 19.1 1.16ZM15.25 10.25a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Zm-5 15a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0Zm17.5-5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z" fill="currentColor"/></svg>',document.body.appendChild(c),document.getElementById("cc-accept").addEventListener("click",se),document.getElementById("cc-decline").addEventListener("click",de),document.getElementById("cc-save").addEventListener("click",ue),document.getElementById("cc-details").addEventListener("click",le),c.addEventListener("click",function(){re("gdpr")})}var oe=null;function ce(e){if("Tab"===e.key){var t=document.getElementById("cc-banner")||document.getElementById("cc-ccpa-banner");if(t&&t.classList.contains("cc-show")){for(var n=[],o=t.querySelectorAll('button, a[href], input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'),c=0;c<o.length;c++){for(var r=o[c],a=!1,i=r.parentElement;i&&i!==t;){if(i.style&&"none"===window.getComputedStyle(i).display){a=!0;break}i=i.parentElement}a||n.push(r)}if(0!==n.length){var s=n[0],d=n[n.length-1];e.shiftKey?document.activeElement===s&&(e.preventDefault(),d.focus()):document.activeElement===d&&(e.preventDefault(),s.focus())}}}}function re(e){"ccpa"!==e?(oe=document.activeElement,requestAnimationFrame(function(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");if(e&&e.classList.add("cc-show"),t){t.classList.add("cc-show");var o=t.querySelector(".cc-btn-accept");o&&o.focus(),document.addEventListener("keydown",ce)}n&&n.classList.remove("cc-show")})):requestAnimationFrame(function(){var e=document.getElementById("cc-ccpa-banner");e&&(e.classList.add("cc-show"),oe=document.activeElement,e.focus(),document.addEventListener("keydown",ce))})}function ae(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");if(e&&e.classList.remove("cc-show"),t&&t.classList.remove("cc-show"),n&&"gdpr"===p&&n.classList.add("cc-show"),document.removeEventListener("keydown",ce),oe&&"function"==typeof oe.focus)try{oe.focus()}catch(e){}oe=null}function ie(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");e&&e.classList.remove("cc-show"),t&&t.classList.remove("cc-show"),n&&n.classList.remove("cc-show"),document.removeEventListener("keydown",ce)}function se(){F(Y(),"accept_all"),ae(),_("cc:accept-all"),_("cc:update")}function de(){F(z(),"decline_all"),ae(),_("cc:decline-all"),_("cc:update")}function ue(){var e;F((e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:f},r.cookieTypes.forEach(function(t){if(t.required)e[t.id]=!0;else{var n=document.getElementById("cc-cb-"+t.id);e[t.id]=!!n&&n.checked}}),e),"save_preferences"),ae(),_("cc:save"),_("cc:update")}function le(){var e=document.getElementById("cc-cats"),t=document.getElementById("cc-details"),n=document.getElementById("cc-pref-buttons"),o=e.classList.toggle("cc-open");t.textContent=o?r.text.hideDetailsButton:r.text.showDetailsButton,t.setAttribute("aria-expanded",o?"true":"false"),n&&(o?n.classList.add("cc-open"):n.classList.remove("cc-open"))}function pe(){var e=document.getElementById("cc-declaration");if(e){for(var t=k(),n={},o=document.cookie.split(";").map(function(e){return e.trim().split("=")[0]}).filter(Boolean),c=0;c<o.length;c++){var a=u(o[c]);a&&(n[o[c]]={name:o[c],vendor:a.vendor,category:a.category,purpose:a.purpose,expiry:a.expiry||"",active:!0})}for(var i=0;i<d.length;i++){var s=d[i];s.name&&s.vendor&&s.category&&"unclassified"!==s.category&&!n[s.name]&&(n[s.name]={name:s.name,vendor:s.vendor,category:s.category,purpose:s.purpose||"",expiry:s.expiry||"",active:!1})}for(var l=0;l<o.length;l++)n[o[l]]||(n[o[l]]={name:o[l],vendor:"",category:"unclassified",purpose:"",expiry:"",active:!0});var p=["necessary","preferences","analytics","marketing"],f={};r.cookieTypes.forEach(function(e){f[e.id]=e.name});var m={};p.forEach(function(e){m[e]=[]}),Object.keys(n).forEach(function(e){var t=n[e],o=t.category;m[o]||(m[o]=[]),m[o].push(t)}),p.forEach(function(e){m[e]&&m[e].sort(function(e,t){return e.name.localeCompare(t.name)})});var g="<div>";if(t.timestamp){var y=new Date(t.timestamp);g+="<p>"+v("Your consent was last updated on "+y.toLocaleDateString(void 0,{year:"numeric",month:"long",day:"numeric"})+" at "+y.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"})+".")+"</p>"}else g+="<p>"+v("You have not yet made a cookie preference choice on this site.")+"</p>";for(var h=0;h<p.length;h++){var w=p[h],b=m[w];if(b&&b.length){var _,x=f[w]||w,E=r.cookieTypes.filter(function(e){return e.id===w})[0],A=E&&E.description||"";_=!!E&&E.required?"Always active":t[w]?"Active":"Inactive",g+="<div>",g+="<p><b>"+v(x)+"</b> ("+v(_)+")</p>",A&&(g+="<p>"+v(A)+"</p>"),g+="<table><thead><tr>",g+="<th>Name</th><th>Provider</th><th>Purpose</th><th>Expiry</th>",g+="</tr></thead><tbody>";for(var S=0;S<b.length;S++){var L=b[S];g+="<tr>",g+="<td><code>"+v(L.name)+"</code></td>",g+="<td>"+v(L.vendor||"Unknown")+"</td>",g+="<td>"+v(L.purpose||"Not specified")+"</td>",g+="<td>"+v(L.expiry||"—")+"</td>",g+="</tr>"}g+="</tbody></table></div>"}}g+="</div>",e.innerHTML=g}}window.__ccInternal={getResourceSnapshot:ye,scriptLoaded:function(e){for(var t=ge||ye(),n=0;n<t.allUrls.length;n++)if(-1!==t.allUrls[n].indexOf(e))return!0;return!1},scriptLoadedUrls:function(e){for(var t=ge||ye(),n=[],o=0;o<t.allUrls.length;o++)-1!==t.allUrls[o].indexOf(e)&&n.push(t.allUrls[o]);return n},hasCookie:function(e){return document.cookie.split(";").some(function(t){return t.trim().split("=")[0]===e})},hasCookiePrefix:function(e){return document.cookie.split(";").some(function(t){return 0===t.trim().split("=")[0].indexOf(e)})},setResourceSnapshot:function(e){ge=e},visitorCookies:function(e){for(var t=ye().thirdPartyDomains,n={},o=0;o<e.length;o++)we(e[o],t,he(e[o]))&&(n[e[o]]=!0);return n},facadeBridge:{getConfig:function(){return r},getConsentState:function(){return l},loadConsent:Z,saveConsent:function(e,t){F(e,t)},hideAllBanners:ie,registerRenderer:function(e){R=e,M=2,V()}}},window.CookieConsent={getState:k,getConfig:function(){return r},getComplianceMode:function(){return p},getCountry:function(){return f},show:function(){re("gdpr")},acceptAll:se,declineAll:de,getGPC:function(){return i},getConsentVersion:function(){return r.consentVersion},on:function(e,t){window.addEventListener(e,t)},off:function(e,t){window.removeEventListener(e,t)},VERSION:c},window.addEventListener("cc:ready",pe),window.addEventListener("cc:update",pe),function(){if(!window.__ccDefaultsSet){var e={wait_for_update:500};r.cookieTypes.forEach(function(t){t.consentMode&&t.consentMode.forEach(function(n){e[n]=t.required?"granted":"denied"})}),x("consent","default",e)}}(),function(){if(r.tcfEnabled){var e=window.__tcfapi&&window.__tcfapi.queue?window.__tcfapi.queue:[];window.__tcfapi=C,e.forEach(function(e){C.apply(null,e)}),S=!0,window===window.top&&window.addEventListener("message",function(e){var t;try{t="string"==typeof e.data?JSON.parse(e.data):e.data}catch(e){return}if(t&&t.__tcfapiCall){var n=t.__tcfapiCall;C(n.command,n.version,function(t,o){var c={__tcfapiReturn:{returnValue:t,success:o,callId:n.callId}};e.source&&e.source.postMessage("string"==typeof e.data?JSON.stringify(c):c,"*")},n.parameter)}})}}();var fe=Z();function me(){var e=window.__ccDiscovery&&window.__ccDiscovery.active;if(!e&&(window.__ccDNT||document.cookie.split(";").some(function(e){return 0===e.trim().indexOf("cookieflame_dnt=")})))return E(l=z()),K(l,Y()),J(),j(),p="dnt",te(),ne(),ie(),$(l),_("cc:ready"),void pe();if(e){E(l=Y()),T(l),J(),H(l,null),r.tcfEnabled&&B(l),window.dataLayer=window.dataLayer||[];for(var t=["consent_accepted_analytics","consent_accepted_marketing","consent_accepted_preferences","consent_accepted_necessary","consent_update","consent_given","cookie_consent_given","cookie_consent_update","consent_accepted"],n=0;n<t.length;n++)window.dataLayer.push({event:t[n]});return p="discovery",te(),ne(),ie(),_("cc:ready"),_("cc:update"),void pe()}var o=Z();if(o)if(i&&(o.marketing=!1),l=o,E(o),T(o),J(),j(),H(o,null),r.tcfEnabled&&B(o),te(),ne(),ie(),$(o),o.cc){if("gdpr"===(p=Q(o.cc))){var c=document.getElementById("cc-icon");c&&c.classList.add("cc-show")}_("cc:ready"),pe()}else X(function(e){if(p=e,"gdpr"===e){var t=document.getElementById("cc-icon");t&&t.classList.add("cc-show")}_("cc:ready"),pe()});else X(function(e){if(p=e,"none"===e)return J(),F(Y(),"auto_grant_non_eu"),_("cc:ready"),_("cc:update"),void pe();if("ccpa"===e){if(J(),i){var t=Y();t.marketing=!1,F(t,"ccpa_gpc_optout")}else F(Y(),"ccpa_auto_grant");return te(),n=r.text,(o=document.createElement("div")).id="cc-ccpa-banner",o.setAttribute("role","dialog"),o.setAttribute("aria-modal","true"),o.setAttribute("aria-label","Privacy notice"),o.setAttribute("tabindex","-1"),o.innerHTML='<div class="cc-ccpa-inner"><div class="cc-ccpa-text"><p>'+v(n.ccpaDescription)+'</p></div><div class="cc-ccpa-actions"><a href="'+v(r.ccpaUrl)+'" class="cc-ccpa-link">'+v(n.ccpaDnsmpiLink)+'</a><button class="cc-btn cc-btn-accept" id="cc-ccpa-ok">'+v(n.ccpaAcceptButton)+"</button></div></div>",document.body.appendChild(o),document.getElementById("cc-ccpa-ok").addEventListener("click",function(){F(Y(),"ccpa_acknowledge"),o.classList.remove("cc-show"),_("cc:update")}),re("ccpa"),_("cc:ready"),_("cc:update"),void pe()}var n,o;te(),J(),j(),ne(),re("gdpr"),_("cc:ready"),pe()})}fe&&(l=fe,E(fe)),function(){if(!g&&r.autoBlockPatterns&&r.autoBlockPatterns.length){g=!0;var e=document.createElement.bind(document),t=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype,"src");t&&t.set&&(document.createElement=function(){var n=e.apply(document,arguments);if("script"!==(arguments[0]||"").toLowerCase())return n;function o(e,t){if(t&&"string"==typeof t&&!e.getAttribute("data-cc")&&!e.getAttribute("data-cc-auto")&&-1===t.indexOf("cookieconsent")){var n=I(t);!n||l&&l[n]||(e.type="text/plain",e.setAttribute("data-cc",n),e.setAttribute("data-cc-auto","1"))}}Object.defineProperty(n,"src",{configurable:!0,enumerable:!0,get:function(){return t.get.call(this)},set:function(e){return o(this,e),t.set.call(this,e)}});var c=n.setAttribute.bind(n);return n.setAttribute=function(e,t){return"src"===e.toLowerCase()&&o(n,t),c(e,t)},n}),new MutationObserver(function(e){e.forEach(function(e){e.addedNodes.forEach(function(e){if(1===e.nodeType){var t=[];"SCRIPT"===e.tagName&&t.push(e),e.querySelectorAll&&e.querySelectorAll("script[src]").forEach(function(e){t.push(e)}),t.forEach(function(e){if(!(e.getAttribute("data-cc-auto")||e.getAttribute("data-cc")||e.src&&-1!==e.src.indexOf("cookieconsent"))){var t=I(e.src);t&&(l&&l[t]||(e.type="text/plain",e.setAttribute("data-cc",t),e.setAttribute("data-cc-auto","1")))}})}})})}).observe(document.documentElement,{childList:!0,subtree:!0})}}(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",me):me();var ge=null;function ye(){for(var e=[],t=[],n=[],o={},c={},r=document.querySelectorAll("script[src]"),a=0;a<r.length;a++)r[a].src&&e.push(r[a].src);for(var i=document.querySelectorAll("iframe[src]"),s=0;s<i.length;s++)i[s].src&&e.push(i[s].src);if(window.performance&&performance.getEntriesByType)for(var d=performance.getEntriesByType("resource"),u=0;u<d.length;u++){var l=d[u].name||"";if(l){-1===e.indexOf(l)&&e.push(l);var p=d[u].initiatorType||"";if("script"===p||"iframe"===p||"xmlhttprequest"===p||"fetch"===p||-1!==l.indexOf(".js"))try{var f=new URL(l),m=f.hostname;if(m===location.hostname||m==="www."+location.hostname||"www."+m===location.hostname){var g=f.pathname.match(/\/wp-content\/plugins\/([^\/]+)\//);g&&!c[g[1]]&&(c[g[1]]=!0,n.push({slug:g[1],url:l}))}else o[m]||(o[m]=!0,t.push(m))}catch(e){}}}return{allUrls:e,thirdPartyDomains:t,pluginScripts:n}}var ve={};function he(e){var t={set_by:"",set_by_plugin:""},n=window.__ccCookieSetters;if(!n||!Object.prototype.hasOwnProperty.call(n,e)||!n[e])return t;try{var o=new URL(String(n[e]).replace(/:\d+(:\d+)?$/,""));if(/^(chrome-extension|moz-extension|safari-web-extension|safari-extension):$/.test(o.protocol))return t.set_by="browser-extension",t;var c=o.hostname;if(c===location.hostname||c==="www."+location.hostname||"www."+c===location.hostname){t.set_by="first-party";var r=o.pathname.match(/\/wp-content\/(?:plugins|themes)\/([^\/]+)\//);r&&(t.set_by_plugin=r[1].toLowerCase())}else t.set_by=c.toLowerCase()}catch(e){}return t}function we(e,t,n){if("browser-extension"===n.set_by)return!0;var o=window.__ccVisitorCookieRules;if(!o||!o.tools)return!1;if(o.allow&&-1!==o.allow.indexOf(e))return!1;if(n.set_by)return!1;for(var c=0;c<o.tools.length;c++){for(var r=o.tools[c],a=-1!==(r.n||[]).indexOf(e),i=0;!a&&i<(r.p||[]).length;i++)0===e.indexOf(r.p[i])&&(a=!0);if(a){for(var s=0;s<t.length;s++)for(var d=0;d<(r.d||[]).length;d++){var u=String(t[s]).toLowerCase(),l=r.d[d];if(u===l||u.slice(-l.length-1)==="."+l)return!1}return!0}}return!1}function be(){var t=window.__ccClassified||[],n=window.__ccUnclassified||[],o=window.__ccBeaconUrl;if(o){var c,r={};for(c=0;c<t.length;c++)r[t[c]]=!0;for(c=0;c<n.length;c++)r[n[c]]=!0;var a=Object.keys(ve);for(c=0;c<a.length;c++)r[a[c]]=!0;var i=Object.keys(s);for(c=0;c<i.length;c++)r[i[c]]=!0;var d={},u=[],l=document.cookie.split(";");for(c=0;c<l.length;c++){var p=l[c].indexOf("="),f=(p>-1?l[c].substring(0,p):l[c]).trim(),m=p>-1?l[c].substring(p+1).trim():"";f&&(u.push(f),d[f]=m)}var g=[];for(c=0;c<u.length;c++)if(!r[u[c]]){for(var y=!1,v=Object.keys(s),h=0;h<v.length;h++)if(s[v[h]].prefix&&0===u[c].indexOf(v[h])){y=!0;break}y||g.push(u[c])}if(0!==g.length){for(var w=ye(),b=w.thirdPartyDomains,k=[],_=0;_<w.pluginScripts.length;_++)k.push(w.pluginScripts[_].slug);for(var x=location.pathname,E=0;E<g.length;E++){var A=g[E],S=!!e[A]&&!r[A],L=ke(d[A]||""),C=he(A);if(!we(A,b,C)||window.__ccDiscovery&&window.__ccDiscovery.active){var B=JSON.stringify({cookie_name:A,script_domains:b,plugin_scripts:k,page_path:x,pre_existing:S,value_shape:L,set_by:C.set_by,set_by_plugin:C.set_by_plugin});ve[A]=!0,navigator.sendBeacon&&navigator.sendBeacon(o,new Blob([B],{type:"application/json"}))}else ve[A]=!0}}}}function ke(e){if(!e||0===e.length)return"empty";if("true"===e||"false"===e||"1"===e||"0"===e)return"boolean";if(/^\d+$/.test(e))return e.length>12?"long-numeric":"numeric";if(/^\d+\.\d+$/.test(e))return"decimal";if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e))return"uuid";if(/^[A-Za-z0-9+\/]{20,}={0,2}$/.test(e))return"base64";if(/^(GA\d+\.\d+\.\d+\.\d+|G-[A-Z0-9]+)/.test(e))return"ga-id";try{if("object"==typeof JSON.parse(decodeURIComponent(e)))return"json"}catch(e){}return/^[a-f0-9]{24,}$/i.test(e)?"hex-id":/^\d{10,13}$/.test(e)?"timestamp":e.length<=20?"short-string":e.length<=100?"medium-string":"long-string"}setTimeout(be,3e3),setTimeout(be,15e3),setTimeout(be,45e3),window.addEventListener("cc:update",function(){setTimeout(be,3e3)}),window.addEventListener("pagehide",be)}();19 !function(){"use strict";var e={};try{for(var t=document.cookie.split(";"),n=0;n<t.length;n++){var c=t[n].trim().split("=")[0];c&&(e[c]=!0)}}catch(e){}var o="5.4.0",r={cookieName:"cc_consent",cookieExpiry:365,privacyUrl:"/privacy-policy",geoApi:"",logEndpoint:"",reconsentDays:365,cspSafe:!1,tcfEnabled:!1,cmpId:1,cmpVersion:1,vendorListVersion:2,storageMethod:"cookie",cookieDomain:"",shareSubdomains:!1,theme:"light",showPoweredBy:!1,iconPosition:"left",consentVersion:"",rejectFirstScreen:!1,visitorLogins:!1,autoBlockPatterns:[{pattern:"googletagmanager.com/gtag",category:"analytics"},{pattern:"google-analytics.com",category:"analytics"},{pattern:"analytics.google.com",category:"analytics"},{pattern:"hotjar.com",category:"analytics"},{pattern:"mixpanel.com",category:"analytics"},{pattern:"segment.com/analytics",category:"analytics"},{pattern:"segment.io",category:"analytics"},{pattern:"clarity.ms",category:"analytics"},{pattern:"plausible.io",category:"analytics"},{pattern:"js.hs-analytics.net",category:"analytics"},{pattern:"connect.facebook.net",category:"marketing"},{pattern:"snap.licdn.com",category:"marketing"},{pattern:"ads.linkedin.com",category:"marketing"},{pattern:"googleads.g.doubleclick.net",category:"marketing"},{pattern:"googlesyndication.com",category:"marketing"},{pattern:"googleadservices.com",category:"marketing"},{pattern:"ads-twitter.com",category:"marketing"},{pattern:"analytics.tiktok.com",category:"marketing"},{pattern:"pintrk",category:"marketing"},{pattern:"s.pinimg.com/ct",category:"marketing"},{pattern:"widget.intercom.io",category:"preferences"},{pattern:"js.intercomcdn.com",category:"preferences"}],regionRules:{gdpr:["AT","BE","BG","CY","CZ","DE","DK","EE","ES","FI","FR","GR","HR","HU","IE","IT","LT","LU","LV","MT","NL","PL","PT","RO","SE","SI","SK","IS","LI","NO","GB","GG","JE","IM","CH","BR","ZA"],ccpa:["US"]},text:{title:"We value your privacy",description:"We use cookies to improve your experience, analyse traffic and personalise content. You can choose which categories to allow.",acceptAllButton:"Accept All",rejectAllButton:"Decline All",declineAllButton:"Decline All",saveButton:"Save Preferences",showDetailsButton:"Manage preferences",hideDetailsButton:"Hide preferences",alwaysOnLabel:"(always on)",ccpaTitle:"Your Privacy Choices",ccpaDescription:"We use cookies and similar technologies. Under US state privacy laws, such as California's, you have the right to opt out of the sale or sharing of your personal information.",ccpaAcceptButton:"OK",ccpaDnsmpiLink:"Do Not Sell or Share My Personal Information",ccpaGpcNotice:"Your browser asked us not to sell or share your information. We're honouring that.",ccpaChoiceTitle:"Do Not Sell or Share My Personal Information",ccpaChoiceText:"Information about your visit may be used by advertising partners whose cookies run on this site. Under US state privacy laws that can count as selling or sharing. You can opt out in this browser.",ccpaOptOutButton:"Opt out",ccpaCancelButton:"Cancel",ccpaOptedOut:"You've opted out of the sale or sharing of your personal information in this browser.",ccpaGpcOptedOut:"Your browser's Global Privacy Control signal asked us not to sell or share your information, so you're opted out in this browser.",ccpaDoneButton:"Done",ccpaOptInButton:"Allow selling or sharing",ccpaOptInGpcNote:"Allowing it overrides your browser's Global Privacy Control signal on this site only.",ccpaMoreSettings:"More cookie settings",gpcPrefNote:"Your browser's Global Privacy Control signal is on, so Marketing stays off unless you switch it on here.",changePreferences:"Change cookie preferences",loginCookiesTitle:"If you log in",loginCookiesIntro:"These cookies are only set if you log in to an account on this site. They keep you signed in and remember your account settings. They're needed for logging in to work, so they don't ask for your consent."},cookieTypes:[{id:"necessary",name:"Necessary",description:"Required for the site to function. Cannot be disabled.",required:!0,consentMode:["security_storage","functionality_storage"],tcfPurposes:[1],onAccept:function(){}},{id:"preferences",name:"Preferences",description:"Remember your settings and personalisation choices.",required:!1,defaultValue:!1,consentMode:["functionality_storage","personalization_storage"],tcfPurposes:[2,3],onAccept:function(){},onReject:function(){}},{id:"analytics",name:"Analytics",description:"Help us understand how visitors use the site.",required:!1,defaultValue:!1,consentMode:["analytics_storage"],tcfPurposes:[4,5,6],onAccept:function(){},onReject:function(){}},{id:"marketing",name:"Marketing",description:"Used to deliver relevant ads and track campaigns.",required:!1,defaultValue:!1,consentMode:["ad_storage","ad_user_data","ad_personalization"],tcfPurposes:[7,8,9,10],onAccept:function(){},onReject:function(){}}]};if(window.__ccConfig&&"object"==typeof window.__ccConfig){var a=window.__ccConfig;["cookieName","cookieExpiry","privacyUrl","geoApi","logEndpoint","reconsentDays","cspSafe","tcfEnabled","cmpId","cmpVersion","vendorListVersion","storageMethod","cookieDomain","shareSubdomains","theme","showPoweredBy","iconPosition","consentVersion","rejectFirstScreen","visitorLogins"].forEach(function(e){void 0!==a[e]&&(r[e]=a[e])}),a.text&&"object"==typeof a.text&&Object.keys(a.text).forEach(function(e){r.text[e]=a.text[e]}),Array.isArray(a.cookieTypes)&&(r.cookieTypes=a.cookieTypes),Array.isArray(a.autoBlockPatterns)&&(r.autoBlockPatterns=a.autoBlockPatterns),a.regionRules&&(r.regionRules=a.regionRules)}window.__ccConsentVersion&&(r.consentVersion=window.__ccConsentVersion);var i=!!window.__ccGPC;function s(e){return i&&e&&!e.gpcOptIn&&(e.marketing=!1,e.gpc=1),e}window.__ccBlockRules&&Array.isArray(window.__ccBlockRules)&&window.__ccBlockRules.length&&(r.autoBlockPatterns=window.__ccBlockRules);var d={cc_consent:{vendor:"CookieFlame",category:"necessary",purpose:"Stores your consent preferences",expiry:"1 year"},cc_anon_id:{vendor:"CookieFlame",category:"necessary",purpose:"Anonymous ID for consent logging",expiry:"1 year"},cookieflame_dnt:{vendor:"CookieFlame",category:"necessary",purpose:"Do-not-track opt-out flag",expiry:"1 year"},PHPSESSID:{vendor:"PHP",category:"necessary",purpose:"Server-side session identifier",expiry:"Session"},"wp-settings-":{vendor:"WordPress",category:"necessary",purpose:"Remembers dashboard layout settings",prefix:!0,expiry:"1 year"},"wp-settings-time-":{vendor:"WordPress",category:"necessary",purpose:"Records when dashboard settings were last saved",prefix:!0,expiry:"1 year"},wordpress_logged_in_:{vendor:"WordPress",category:"necessary",purpose:"Keeps you logged in",prefix:!0,expiry:"Session, or 14 days with Remember Me"},wordpress_sec_:{vendor:"WordPress",category:"necessary",purpose:"Confirms your login when you open the dashboard",prefix:!0,expiry:"Session, or 14 days with Remember Me"},wordpress_rec_:{vendor:"WordPress",category:"necessary",purpose:"Recovery mode after a site error (administrators only)",prefix:!0,expiry:"1 week"},wordpress_test_cookie:{vendor:"WordPress",category:"necessary",purpose:"Checks your browser accepts cookies before you log in",expiry:"Session"},wp_lang:{vendor:"WordPress",category:"necessary",purpose:"Remembers the language picked on the login page",expiry:"Session"},comment_author_:{vendor:"WordPress",category:"necessary",purpose:"Remembers your name for comments, if you ask it to",prefix:!0,expiry:"1 year"},comment_author_email_:{vendor:"WordPress",category:"necessary",purpose:"Remembers your email for comments, if you ask it to",prefix:!0,expiry:"1 year"},comment_author_url_:{vendor:"WordPress",category:"necessary",purpose:"Remembers your website for comments, if you ask it to",prefix:!0,expiry:"1 year"},"wp-postpass_":{vendor:"WordPress",category:"necessary",purpose:"Remembers the password entered for a protected page",prefix:!0,expiry:"10 days"},woocommerce_cart_hash:{vendor:"WooCommerce",category:"necessary",purpose:"Shopping cart contents",expiry:"Session"},woocommerce_items_in_cart:{vendor:"WooCommerce",category:"necessary",purpose:"Cart item count",expiry:"Session"},wp_woocommerce_session_:{vendor:"WooCommerce",category:"necessary",purpose:"Shopping session",prefix:!0,expiry:"2 days"},_ga:{vendor:"Google Analytics",category:"analytics",purpose:"Distinguishes visitors",expiry:"2 years"},_ga_:{vendor:"Google Analytics",category:"analytics",purpose:"Stores session state",prefix:!0,expiry:"2 years"},_gid:{vendor:"Google Analytics",category:"analytics",purpose:"Distinguishes visitors (24h)",expiry:"24 hours"},_gat:{vendor:"Google Analytics",category:"analytics",purpose:"Throttles request rate",expiry:"1 minute"},wp_wk_ga_untrack_:{vendor:"Google Analytics",category:"analytics",purpose:"GA opt-out flag",prefix:!0,expiry:"Persistent"},_gcl_au:{vendor:"Google Ads",category:"marketing",purpose:"Conversion linker",expiry:"90 days"},_gcl_aw:{vendor:"Google Ads",category:"marketing",purpose:"Stores ad click info",expiry:"90 days"},_gac_:{vendor:"Google Ads",category:"marketing",purpose:"Campaign information",prefix:!0,expiry:"90 days"},IDE:{vendor:"Google DoubleClick",category:"marketing",purpose:"Ad serving and frequency",expiry:"1 year"},test_cookie:{vendor:"Google DoubleClick",category:"marketing",purpose:"Checks cookie support",expiry:"15 minutes"},NID:{vendor:"Google",category:"preferences",purpose:"Stores preferences",expiry:"6 months"}},u=window.__ccCustomCookies||[];function l(e){for(var t=0;t<u.length;t++){var n=u[t];if("exact"===n.match&&e===n.name)return n;if("prefix"===n.match&&0===e.indexOf(n.name))return n;if("contains"===n.match&&-1!==e.indexOf(n.name))return n}var c=d[e];if(c)return c;var o=0;return Object.keys(d).forEach(function(t){d[t].prefix&&t.length>o&&0===e.indexOf(t)&&(c=d[t],o=t.length)}),c||null}var p=/^(?:wordpress_(?:logged_in_|sec_|rec_|test_cookie$|[0-9a-f]{32}$)|wp-settings-|wp_lang$)/;function f(e){return p.test(e)}var g=null,m="none",y=null,v=null,h=!1,w=0;function b(e){return"string"!=typeof e&&(e=String(e)),e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function k(e){if("localStorage"===r.storageMethod)try{return localStorage.getItem(e)}catch(e){return null}var t=document.cookie.match(new RegExp("(?:^|;\\s*)"+e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));if(!t)return null;try{return decodeURIComponent(t[1])}catch(e){return null}}function _(e,t,n){if("localStorage"!==r.storageMethod){var c=new Date;c.setTime(c.getTime()+864e5*n);var o=e+"="+encodeURIComponent(t)+";expires="+c.toUTCString()+";path=/;SameSite=Lax;Secure";r.cookieDomain&&(o+=";domain="+r.cookieDomain),document.cookie=o}else try{localStorage.setItem(e,t)}catch(e){}}function x(){if(v)return v;var e=k("cc_anon_id");return e?(v=e,e):(_("cc_anon_id",v="cc_"+function(){if("undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID)try{return crypto.randomUUID()}catch(e){}return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}(),730),v)}function E(){var e={timestamp:g?g.ts:null,complianceMode:m,country:y,gpc:i,consentVersion:r.consentVersion};return r.cookieTypes.forEach(function(t){e[t.id]=g?!!g[t.id]:!!t.required}),e}function A(e,t){window.dispatchEvent(new CustomEvent(e,{detail:t||E()}))}function S(){window.dataLayer=window.dataLayer||[],window.dataLayer.push(arguments)}function C(e){var t={};r.cookieTypes.forEach(function(n){n.consentMode&&n.consentMode.forEach(function(c){t[c]=e[n.id]?"granted":"denied"})}),i&&!e.gpcOptIn&&(t.ad_storage="denied",t.ad_user_data="denied",t.ad_personalization="denied"),S("consent","update",t)}var I=new Map,L=!1;function B(e){var t={};return r.cookieTypes.forEach(function(n){n.tcfPurposes&&n.tcfPurposes.forEach(function(c){t[c]=!!e[n.id]})}),{tcString:"",tcfPolicyVersion:4,cmpId:r.cmpId,cmpVersion:r.cmpVersion,gdprApplies:"gdpr"===m,eventStatus:"useractioncomplete",cmpStatus:"loaded",listenerId:void 0,isServiceSpecific:!0,useNonStandardTexts:!1,publisherCC:"GB",purposeOneTreatment:!1,purpose:{consents:t,legitimateInterests:{}},vendor:{consents:{},legitimateInterests:{}},specialFeatureOptins:{},publisher:{consents:{},legitimateInterests:{},customPurpose:{consents:{},legitimateInterests:{}},restrictions:{}}}}function O(e,t,n,c){if("function"==typeof n)if("addEventListener"===e){var o=++w;if(I.set(o,n),g){var a=B(g);a.listenerId=o,n(a,!0)}}else"removeEventListener"===e?n({listenerId:c},I.delete(c)):"getTCData"===e?n(g?B(g):null,!!g):"ping"===e?n({gdprApplies:"gdpr"===m,cmpLoaded:L,cmpStatus:L?"loaded":"stub",displayStatus:"hidden",apiVersion:"2.2",cmpVersion:r.cmpVersion,cmpId:r.cmpId,gvlVersion:r.vendorListVersion,tcfPolicyVersion:4},!0):n(null,!1)}function T(e){I.forEach(function(t,n){var c=B(e);c.listenerId=n;try{t(c,!0)}catch(e){}})}function P(e){if(!e)return null;for(var t=e.toLowerCase(),n=0;n<r.autoBlockPatterns.length;n++){var c=r.autoBlockPatterns[n];if(c.pattern instanceof RegExp){if(c.pattern.test(t))return c.category}else if(-1!==t.indexOf(c.pattern.toLowerCase()))return c.category}return null}function D(e){document.querySelectorAll('script[type="text/plain"][data-cc]').forEach(function(t){var n=(t.getAttribute("data-cc")||"").toLowerCase();if(e[n]&&!t.getAttribute("data-cc-activated")){t.setAttribute("data-cc-activated","1");var c=document.createElement("script");c.isWPRocket=!0,Array.from(t.attributes).forEach(function(e){"type"!==e.name&&"data-cc"!==e.name&&"data-cc-activated"!==e.name&&"data-cc-auto"!==e.name&&c.setAttribute(e.name,e.value)}),!t.src&&t.textContent&&(c.textContent=t.textContent),t.parentNode.insertBefore(c,t.nextSibling),t.parentNode.removeChild(t)}}),["img","iframe","link"].forEach(function(t){document.querySelectorAll(t+"[data-cc][data-cc-src]").forEach(function(n){var c=(n.getAttribute("data-cc")||"").toLowerCase();e[c]&&!n.getAttribute("data-cc-activated")&&(n.setAttribute("data-cc-activated","1"),"link"===t?n.href=n.getAttribute("data-cc-src"):n.src=n.getAttribute("data-cc-src"))})})}var R=[{pattern:"youtube.com",category:"marketing"},{pattern:"youtube-nocookie.com",category:"marketing"},{pattern:"youtu.be",category:"marketing"},{pattern:"player.vimeo.com",category:"marketing"},{pattern:"vimeo.com",category:"marketing"},{pattern:"dailymotion.com",category:"marketing"},{pattern:"open.spotify.com",category:"preferences"},{pattern:"soundcloud.com",category:"preferences"},{pattern:"google.com/maps",category:"preferences"},{pattern:"maps.google.com",category:"preferences"},{pattern:"facebook.com/plugins",category:"marketing"},{pattern:"instagram.com",category:"marketing"},{pattern:"tiktok.com/embed",category:"marketing"},{pattern:"twitter.com",category:"marketing"},{pattern:"x.com",category:"marketing"}];function M(e,t){var n,c;try{var o=new URL(e,location.href);n=o.hostname.toLowerCase(),c=o.pathname.toLowerCase()}catch(e){return!1}var r=t.indexOf("/"),a=-1===r?t:t.slice(0,r),i=-1===r?"":t.slice(r);return(n===a||n.slice(-a.length-1)==="."+a)&&(!i||0===c.indexOf(i))}function N(e){if(!e)return null;for(var t=0;t<R.length;t++)if(M(e,R[t].pattern))return R[t].category;return null}function q(){return!(!document.body||!(document.body.classList.contains("elementor-editor-active")||document.body.classList.contains("fl-builder-edit")||document.body.classList.contains("et-fb-root-ancestor")))}var G=[],j=0,V=null,U=!1;function J(){if(V&&G.length){var e=G;G=[];try{V(e)}catch(e){}}}function W(){0!==G.length&&(2!==j?1!==j&&3!==j&&(j=1,function(){var e=window.__ccConfig&&window.__ccConfig.facadesUrl||null;if(!e)return void(j=3);var t=document.createElement("script");t.src=e,t.async=!0,t.onload=function(){j=2,J()},t.onerror=function(){j=3},(document.head||document.documentElement).appendChild(t)}()):J())}function F(e){var t=e.src||e.getAttribute("data-src")||"";if(t&&!e.getAttribute("data-cc-facade")&&!q()){var n=N(t);if(n){var c=g||ee();c&&c[n]||(e.setAttribute("data-cc-facade","1"),e.setAttribute("data-cc-iframe-cat",n),e.setAttribute("data-cc-original-src",t),e.removeAttribute("src"),e.style.display="none",G.push({type:"iframe",node:e,category:n,src:t}),W())}}}function H(e){if(!e.getAttribute("data-cc-facade")&&!q()){var t=e.getAttribute("data-settings");if(t){var n;try{n=JSON.parse(t)}catch(e){return}var c=n.youtube_url||n.vimeo_url||"";if(c){var o=c.toLowerCase(),r=null;if((-1!==o.indexOf("youtube")||-1!==o.indexOf("youtu.be")||-1!==o.indexOf("vimeo")||-1!==o.indexOf("dailymotion"))&&(r="marketing"),r&&(!g||!g[r])){if(!g){var a=ee();if(a&&a[r])return}e.setAttribute("data-cc-facade","1"),U=!0,G.push({type:"elementor",node:e,category:r,url:c}),W()}}}}}function $(){for(var e=document.querySelectorAll("iframe[src]"),t=0;t<e.length;t++)F(e[t]);for(var n=document.querySelectorAll(".elementor-widget-video"),c=0;c<n.length;c++)H(n[c]);for(var o=document.querySelectorAll(".wp-block-embed__wrapper iframe[src]"),r=0;r<o.length;r++)F(o[r])}function Y(){new MutationObserver(function(e){e.forEach(function(e){e.addedNodes.forEach(function(e){if(1===e.nodeType){if("IFRAME"===e.tagName&&F(e),e.querySelectorAll){for(var t=e.querySelectorAll("iframe[src]"),n=0;n<t.length;n++)F(t[n]);for(var c=e.querySelectorAll(".elementor-widget-video"),o=0;o<c.length;o++)H(c[o])}e.classList&&e.classList.contains("elementor-widget-video")&&H(e)}})})}).observe(document.documentElement,{childList:!0,subtree:!0})}function Z(){for(var e=document.querySelectorAll("iframe[src]"),t=0;t<e.length;t++){var n=e[t];if(!n.getAttribute("data-cc-facade")){var c=n.src||"";c&&N(c)&&(n.src=c)}}}function K(e,t){r.cookieTypes.forEach(function(n){var c=!!e[n.id];if((t?!!t[n.id]:null)!==c||null===t)if(c&&"function"==typeof n.onAccept)try{n.onAccept()}catch(e){console.warn("[CookieConsent] onAccept error for "+n.id,e)}else if(!c&&"function"==typeof n.onReject)try{n.onReject()}catch(e){console.warn("[CookieConsent] onReject error for "+n.id,e)}})}function z(e,t){if(r.logEndpoint){var n={};r.cookieTypes.forEach(function(e){n[e.id]=!!t[e.id]});var c={anonymousId:x(),timestamp:(new Date).toISOString(),action:e,consent:n,complianceMode:m,country:y,url:location.href,userAgent:navigator.userAgent,tcfEnabled:r.tcfEnabled,gpc:i,consentVersion:r.consentVersion,version:o};try{var a=JSON.stringify(c);navigator.sendBeacon?navigator.sendBeacon(r.logEndpoint,new Blob([a],{type:"application/json"})):fetch(r.logEndpoint,{method:"POST",body:a,headers:{"Content-Type":"application/json"},keepalive:!0})}catch(e){}}}function Q(e,t){var n=g;if(t&&0===t.indexOf("facade_accept_")||s(e),g=e,_(r.cookieName,JSON.stringify(e),r.cookieExpiry),C(e),U&&t)for(var c=document.querySelectorAll(".cc-iframe-facade[data-cc-elementor-widget]"),o=0;o<c.length;o++){var a=c[o].getAttribute("data-cc-iframe-cat");if(a&&e[a])return void window.location.reload()}ce(e,n),D(e),function(e){for(var t=document.querySelectorAll(".cc-iframe-facade"),n=0;n<t.length;n++)if(!t[n].getAttribute("data-cc-elementor-widget")&&e[t[n].getAttribute("data-cc-iframe-cat")]){var c=t[n].nextElementSibling;if(c&&"IFRAME"===c.tagName&&c.getAttribute("data-cc-facade")){var o=c.getAttribute("data-cc-original-src");o&&(c.src=o,c.style.display="",c.removeAttribute("data-cc-facade"))}t[n].parentNode.removeChild(t[n])}}(e),t&&"auto_grant_non_eu"!==t&&"ccpa_auto_grant"!==t&&setTimeout(Z,300),K(e,n),X(e),r.tcfEnabled&&T(e),t&&z(t,e)}function X(e){r.cookieTypes.forEach(function(t){if(!t.required){var n=document.getElementById("cc-cb-"+t.id);n&&(n.checked=!!e[t.id])}});var t=document.getElementById("cc-cats"),n=document.getElementById("cc-details"),c=document.getElementById("cc-pref-buttons");t&&t.classList.remove("cc-open"),n&&(n.textContent=r.text.showDetailsButton),c&&c.classList.remove("cc-open")}function ee(){var e=k(r.cookieName);if(!e)return null;try{var t=JSON.parse(e);if(!t||"object"!=typeof t||Array.isArray(t))return null;if(!t.ts||isNaN(new Date(t.ts).getTime()))return null;var n=Number(r.reconsentDays);return n>=1||(n=365),(Date.now()-new Date(t.ts).getTime())/864e5>n||r.consentVersion&&t.cv!==r.consentVersion?null:t}catch(e){return null}}function te(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:y};return r.cookieTypes.forEach(function(t){e[t.id]=!0}),e}function ne(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:y};return r.cookieTypes.forEach(function(t){e[t.id]=!!t.required}),e}function ce(e,t){if(t){var n=[];if(r.cookieTypes.forEach(function(c){c.required||t[c.id]&&!e[c.id]&&n.push(c.id)}),n.length)document.cookie.split(";").map(function(e){return e.trim().split("=")[0]}).filter(Boolean).forEach(function(e){var t=l(e);if(t&&-1!==n.indexOf(t.category)){var c=location.hostname,o="."+c;document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/",document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT",document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+o,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+c,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; Secure",r.cookieDomain&&(document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+r.cookieDomain,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+r.cookieDomain+"; Secure");var a=c.split(".");if(a.length>2){var i="."+a.slice(1).join(".");document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+i,document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain="+i+"; Secure"}}})}}function oe(e){return(y=e?e.toUpperCase():null)?r.regionRules.gdpr&&-1!==r.regionRules.gdpr.indexOf(y)?"gdpr":r.regionRules.ccpa&&-1!==r.regionRules.ccpa.indexOf(y)?"ccpa":"none":"gdpr"}function re(e){if(window.__ccGeo)return e(oe(window.__ccGeo));r.geoApi?fetch(r.geoApi).then(function(e){return e.json()}).then(function(t){var n=t.country_code||t.country||"";n?e(oe(n)):ae(e)}).catch(function(){ae(e)}):ae(e)}function ae(e){var t="";try{t=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(e){}return 0===t.indexOf("Europe/")||"Atlantic/Reykjavik"===t?(y=null,e("gdpr")):0===t.indexOf("US/")||0===t.indexOf("America/")&&["New_York","Chicago","Denver","Los_Angeles","Phoenix","Anchorage","Boise","Detroit","Indianapolis","Louisville","Menominee","Nome","Adak","Juneau","Sitka","Yakutat","Metlakatla","Kentucky","Indiana","North_Dakota"].some(function(e){return-1!==t.indexOf(e)})||"Pacific/Honolulu"===t?(y="US",e("ccpa")):(y=null,void e("gdpr"))}function ie(){"dark"===r.theme&&document.documentElement.classList.add("cc-dark"),"right"===r.iconPosition&&document.documentElement.classList.add("cc-icon-right")}function se(){var e=r.text,t=document.createElement("div");t.id="cc-overlay",document.body.appendChild(t);var n=document.createElement("div");n.id="cc-banner",n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),n.setAttribute("aria-label","Cookie consent"),n.setAttribute("tabindex","-1");var c=r.cookieTypes.map(function(t){var n=t.required?' <span class="cc-always-on">'+b(e.alwaysOnLabel)+"</span>":"",c=i&&"marketing"===t.id?'<div class="cc-gpc-note">'+b(e.gpcPrefNote)+"</div>":"";return'<div class="cc-cat"><div><div class="cc-cat-label">'+b(t.name)+n+'</div><div class="cc-cat-desc">'+b(t.description||"")+"</div>"+c+'</div><label class="cc-toggle"><input type="checkbox" id="cc-cb-'+b(t.id)+'" aria-label="'+b(t.name)+'"'+(t.required||t.defaultValue?" checked":"")+(t.required?" disabled":"")+'><span class="cc-slider"></span></label></div>'}).join("");n.innerHTML='<div class="cc-inner"><p class="cc-title">'+b(e.title)+'</p><p class="cc-text">'+b(e.description)+' <a href="'+b(r.privacyUrl)+'">Privacy Policy</a></p><div class="cc-buttons"><button class="cc-btn cc-btn-accept" id="cc-accept">'+b(e.acceptAllButton)+"</button>"+(r.rejectFirstScreen?'<button class="cc-btn cc-btn-reject" id="cc-reject">'+b(e.rejectAllButton)+"</button>":"")+'<button class="cc-btn cc-btn-details" id="cc-details" aria-expanded="false">'+b(e.showDetailsButton)+'</button></div><div class="cc-categories" id="cc-cats">'+c+'</div><div class="cc-pref-buttons" id="cc-pref-buttons"><button class="cc-btn cc-btn-save" id="cc-save">'+b(e.saveButton)+'</button><button class="cc-btn cc-btn-decline" id="cc-decline">'+b(e.declineAllButton)+"</button></div></div>"+(r.showPoweredBy?'<div class="cc-powered"><a href="https://www.cookieflame.com" target="_blank" rel="noopener">Powered by CookieFlame</a></div>':""),document.body.appendChild(n);var o=document.createElement("button");o.id="cc-icon",o.setAttribute("aria-label","Cookie settings"),o.innerHTML='<svg width="24" height="24" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19.1 1.16c-.06-.42-.38-.77-.8-.83A11.8 11.8 0 0 0 13.28 1.2L7.5 4.3A11.8 11.8 0 0 0 3.95 7.98L1.09 13.9A11.8 11.8 0 0 0 .38 18.96l1.13 6.47a11.8 11.8 0 0 0 3.53 4.99l4.71 4.56a11.8 11.8 0 0 0 4.59 2.24l6.48.91a11.8 11.8 0 0 0 5.52-.89l5.78-3.09a11.8 11.8 0 0 0 3.55-3.69l2.87-5.9a11.8 11.8 0 0 0 .71-5.08c-.07-.41-.42-.73-.83-.79a14.8 14.8 0 0 1-8.16-7.61c-.14-.59-.63-1.05-1.23-1.14A14.8 14.8 0 0 1 19.1 1.16ZM15.25 10.25a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Zm-5 15a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0Zm17.5-5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z" fill="currentColor"/></svg>',document.body.appendChild(o),document.getElementById("cc-accept").addEventListener("click",ke),document.getElementById("cc-decline").addEventListener("click",_e);var a=document.getElementById("cc-reject");a&&a.addEventListener("click",_e),document.getElementById("cc-save").addEventListener("click",xe),document.getElementById("cc-details").addEventListener("click",Ee),o.addEventListener("click",function(){he("gdpr")}),de("gdpr",n)}function de(e,t){A("cc:banner-rendered",{banner:e,element:t,state:E()})}function ue(){if(!document.getElementById("cc-ccpa-banner")){var e=r.text,t=document.createElement("div");t.id="cc-ccpa-banner",t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-label","Privacy notice"),t.setAttribute("tabindex","-1"),t.innerHTML='<div class="cc-ccpa-inner" id="cc-ccpa-notice"><div class="cc-ccpa-text"><p>'+b(e.ccpaDescription)+"</p>"+(i?'<p class="cc-ccpa-gpc">'+b(e.ccpaGpcNotice)+"</p>":"")+'</div><div class="cc-ccpa-actions"><a href="#" class="cc-ccpa-link" id="cc-ccpa-dns" role="button">'+b(e.ccpaDnsmpiLink)+'</a><button class="cc-btn cc-btn-accept" id="cc-ccpa-ok">'+b(e.ccpaAcceptButton)+'</button></div></div><div class="cc-ccpa-choice" id="cc-ccpa-choice"></div>'+(r.showPoweredBy?'<div class="cc-powered"><a href="https://www.cookieflame.com" target="_blank" rel="noopener">Powered by CookieFlame</a></div>':""),document.body.appendChild(t),document.getElementById("cc-ccpa-ok").addEventListener("click",function(){g&&z("ccpa_acknowledge",g),fe()}),document.getElementById("cc-ccpa-dns").addEventListener("click",function(e){e.preventDefault(),pe(!0)}),de("ccpa",t)}}function le(){var e=document.getElementById("cc-ccpa-choice");if(e){var t=r.text,n=!(g&&g.marketing),c='<div class="cc-ccpa-choice-inner"><p class="cc-ccpa-choice-title">'+b(t.ccpaChoiceTitle)+"</p>";c+=n?'<p class="cc-ccpa-choice-status">'+b(i?t.ccpaGpcOptedOut:t.ccpaOptedOut)+'</p><div class="cc-ccpa-choice-buttons"><button class="cc-btn cc-btn-accept" id="cc-ccpa-done">'+b(t.ccpaDoneButton)+'</button><button class="cc-btn cc-btn-decline" id="cc-ccpa-optin">'+b(t.ccpaOptInButton)+"</button></div>"+(i?'<p class="cc-ccpa-choice-note">'+b(t.ccpaOptInGpcNote)+"</p>":""):'<p class="cc-ccpa-choice-text">'+b(t.ccpaChoiceText)+'</p><div class="cc-ccpa-choice-buttons"><button class="cc-btn cc-btn-accept" id="cc-ccpa-optout">'+b(t.ccpaOptOutButton)+'</button><button class="cc-btn cc-btn-decline" id="cc-ccpa-cancel">'+b(t.ccpaCancelButton)+"</button></div>",c+='<button class="cc-btn cc-btn-details" id="cc-ccpa-more">'+b(t.ccpaMoreSettings)+"</button></div>",e.innerHTML=c;var o=function(e,t){var n=document.getElementById(e);n&&n.addEventListener("click",t)};o("cc-ccpa-done",fe),o("cc-ccpa-cancel",fe),o("cc-ccpa-optout",function(){var e=me(g);e.marketing=!1,Q(e,"ccpa_opt_out"),le(),ge("cc-ccpa-choice"),A("cc:update")}),o("cc-ccpa-optin",function(){var e=me(g);e.marketing=!0,i&&(e.gpcOptIn=1),delete e.gpc,Q(e,"ccpa_opt_in"),le(),ge("cc-ccpa-choice"),A("cc:update")}),o("cc-ccpa-more",function(){fe(),Ae()})}}function pe(e){var t=document.getElementById("cc-ccpa-notice"),n=document.getElementById("cc-ccpa-choice");t&&n&&(e&&le(),t.style.display=e?"none":"",n.classList.toggle("cc-open",!!e),e&&ge("cc-ccpa-choice"))}function fe(){var e=document.getElementById("cc-ccpa-banner");if(e&&e.classList.remove("cc-show"),pe(!1),document.removeEventListener("keydown",ve),ye&&"function"==typeof ye.focus)try{ye.focus()}catch(e){}ye=null}function ge(e){var t=document.getElementById(e),n=t&&t.querySelector("button");if(n)try{n.focus()}catch(e){}}function me(e){var t={ts:(new Date).toISOString(),cv:r.consentVersion,cc:y};return r.cookieTypes.forEach(function(n){t[n.id]=!e||!!e[n.id]}),e&&e.gpcOptIn&&(t.gpcOptIn=1),t}var ye=null;function ve(e){if("Tab"===e.key){var t=document.querySelector("#cc-banner.cc-show, #cc-ccpa-banner.cc-show");if(t){for(var n=[],c=t.querySelectorAll('button, a[href], input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'),o=0;o<c.length;o++){for(var r=c[o],a=!1,i=r.parentElement;i&&i!==t;){if("none"===window.getComputedStyle(i).display){a=!0;break}i=i.parentElement}a||n.push(r)}if(0!==n.length){var s=n[0],d=n[n.length-1];e.shiftKey?document.activeElement===s&&(e.preventDefault(),d.focus()):document.activeElement===d&&(e.preventDefault(),s.focus())}}}}function he(e){if("ccpa"===e)return ye=document.activeElement,void requestAnimationFrame(function(){var e=document.getElementById("cc-ccpa-banner");if(e){e.classList.add("cc-show");var t=document.getElementById("cc-ccpa-choice");t&&t.classList.contains("cc-open")?ge("cc-ccpa-choice"):e.focus(),document.addEventListener("keydown",ve)}});ye=document.activeElement;var t=document.getElementById("cc-reject");t&&("gdpr"===m?t.style.removeProperty("display"):t.style.setProperty("display","none","important")),requestAnimationFrame(function(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");if(e&&e.classList.add("cc-show"),t){t.classList.add("cc-show");var c=t.querySelector(".cc-btn-accept");c&&c.focus(),document.addEventListener("keydown",ve)}n&&n.classList.remove("cc-show")})}function we(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");if(e&&e.classList.remove("cc-show"),t&&t.classList.remove("cc-show"),n&&"gdpr"===m&&n.classList.add("cc-show"),document.removeEventListener("keydown",ve),ye&&"function"==typeof ye.focus)try{ye.focus()}catch(e){}ye=null}function be(){var e=document.getElementById("cc-overlay"),t=document.getElementById("cc-banner"),n=document.getElementById("cc-icon");e&&e.classList.remove("cc-show"),t&&t.classList.remove("cc-show"),n&&n.classList.remove("cc-show"),document.removeEventListener("keydown",ve)}function ke(){var e=te();g&&g.gpcOptIn&&(e.gpcOptIn=1),Q(e,"accept_all"),we(),A("cc:accept-all"),A("cc:update")}function _e(){Q(ne(),"decline_all"),we(),A("cc:decline-all"),A("cc:update")}function xe(){var e=function(){var e={ts:(new Date).toISOString(),cv:r.consentVersion,cc:y};return r.cookieTypes.forEach(function(t){if(t.required)e[t.id]=!0;else{var n=document.getElementById("cc-cb-"+t.id);e[t.id]=!!n&&n.checked}}),e}();e.marketing&&(i||g&&g.gpcOptIn)&&(e.gpcOptIn=1),Q(e,"save_preferences"),we(),A("cc:save"),A("cc:update")}function Ee(){var e=document.getElementById("cc-cats"),t=document.getElementById("cc-details"),n=document.getElementById("cc-pref-buttons"),c=e.classList.toggle("cc-open");t.textContent=c?r.text.hideDetailsButton:r.text.showDetailsButton,t.setAttribute("aria-expanded",c?"true":"false"),n&&(c?n.classList.add("cc-open"):n.classList.remove("cc-open"))}function Ae(){document.getElementById("cc-banner")||(ie(),se(),be(),g&&X(g)),he("gdpr")}function Se(){"ccpa"===m?(ie(),ue(),he("ccpa"),pe(!0)):Ae()}function Ce(){var e=document.getElementById("cc-declaration");if(e){var t=E(),n={},c=document.cookie.split(";").map(function(e){return e.trim().split("=")[0]}).filter(Boolean);c=c.filter(function(e){return!f(e)});for(var o=0;o<c.length;o++){var a=l(c[o]);a&&(n[c[o]]={name:c[o],vendor:a.vendor,category:a.category,purpose:a.purpose,expiry:a.expiry||"",active:!0})}for(var i=0;i<u.length;i++){var s=u[i];s.name&&s.vendor&&s.category&&"unclassified"!==s.category&&!n[s.name]&&!f(s.name)&&(n[s.name]={name:s.name,vendor:s.vendor,category:s.category,purpose:s.purpose||"",expiry:s.expiry||"",active:!1})}for(var d=0;d<c.length;d++)n[c[d]]||(n[c[d]]={name:c[d],vendor:"",category:"unclassified",purpose:"",expiry:"",active:!0});var p=["necessary","preferences","analytics","marketing"],g={};r.cookieTypes.forEach(function(e){g[e.id]=e.name});var m={};p.forEach(function(e){m[e]=[]}),Object.keys(n).forEach(function(e){var t=n[e],c=t.category;m[c]||(m[c]=[]),m[c].push(t)}),p.forEach(function(e){m[e]&&m[e].sort(function(e,t){return e.name.localeCompare(t.name)})});var y,v="<div>";if(t.timestamp){var h=new Date(t.timestamp);v+="<p>"+b("Your consent was last updated on "+h.toLocaleDateString(void 0,{year:"numeric",month:"long",day:"numeric"})+" at "+h.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"})+".")+"</p>"}else v+="<p>"+b("You have not yet made a cookie preference choice on this site.")+"</p>";for(var w=0;w<p.length;w++){var k=p[w],_=m[k];if(_&&_.length){var x,A=g[k]||k,S=r.cookieTypes.filter(function(e){return e.id===k})[0],C=S&&S.description||"";x=!!S&&S.required?"Always active":t[k]?"Active":"Inactive",v+="<div>",v+="<p><b>"+b(A)+"</b> ("+b(x)+")</p>",C&&(v+="<p>"+b(C)+"</p>"),v+="<table><thead><tr>",v+="<th>Name</th><th>Provider</th><th>Purpose</th><th>Expiry</th>",v+="</tr></thead><tbody>";for(var I=0;I<_.length;I++){var L=_[I];v+="<tr>",v+="<td><code>"+b(L.name)+"</code></td>",v+="<td>"+b(L.vendor||"Unknown")+"</td>",v+="<td>"+b(L.purpose||"Not specified")+"</td>",v+="<td>"+b(L.expiry||"—")+"</td>",v+="</tr>"}v+="</tbody></table></div>"}}if(r.visitorLogins){var B=[{name:"wordpress_logged_in_[hash]",purpose:"Keeps you logged in",expiry:y="Session, or 14 days with Remember Me"},{name:"https:"===location.protocol?"wordpress_sec_[hash]":"wordpress_[hash]",purpose:"Confirms your login when you open your account dashboard",expiry:y},{name:"wordpress_test_cookie",purpose:"Checks your browser accepts cookies before you log in",expiry:"Session"},{name:"wp_lang",purpose:"Remembers the language you picked on the login page",expiry:"Session"},{name:"wp-settings-[user ID]",purpose:"Remembers your dashboard layout settings",expiry:"1 year"},{name:"wp-settings-time-[user ID]",purpose:"Records when your dashboard settings were last saved",expiry:"1 year"}];v+='<div class="cc-declaration-login">',v+="<p><b>"+b(r.text.loginCookiesTitle)+"</b></p>",v+="<p>"+b(r.text.loginCookiesIntro)+"</p>",v+="<table><thead><tr>",v+="<th>Name</th><th>Provider</th><th>Purpose</th><th>Expiry</th>",v+="</tr></thead><tbody>";for(var O=0;O<B.length;O++)v+="<tr>",v+="<td><code>"+b(B[O].name)+"</code></td>",v+="<td>WordPress</td>",v+="<td>"+b(B[O].purpose)+"</td>",v+="<td>"+b(B[O].expiry)+"</td>",v+="</tr>";v+="</tbody></table></div>"}v+='<p><button type="button" class="cc-open-settings cc-declaration-change">'+b(r.text.changePreferences)+"</button></p>",v+="</div>",e.innerHTML=v}}document.addEventListener("click",function(e){(e.target&&e.target.closest?e.target.closest(".cc-open-settings"):null)&&(e.preventDefault(),Se())}),window.__ccInternal={getResourceSnapshot:Oe,scriptLoaded:function(e){for(var t=Be||Oe(),n=0;n<t.allUrls.length;n++)if(-1!==t.allUrls[n].indexOf(e))return!0;return!1},scriptLoadedUrls:function(e){for(var t=Be||Oe(),n=[],c=0;c<t.allUrls.length;c++)-1!==t.allUrls[c].indexOf(e)&&n.push(t.allUrls[c]);return n},hasCookie:function(e){return document.cookie.split(";").some(function(t){return t.trim().split("=")[0]===e})},hasCookiePrefix:function(e){return document.cookie.split(";").some(function(t){return 0===t.trim().split("=")[0].indexOf(e)})},setResourceSnapshot:function(e){Be=e},visitorCookies:function(e){for(var t=Oe().thirdPartyDomains,n={},c=0;c<e.length;c++)De(e[c],t,Pe(e[c]))&&(n[e[c]]=!0);return n},facadeBridge:{getConfig:function(){return r},getConsentState:function(){return g},loadConsent:ee,saveConsent:function(e,t){Q(e,t)},hideAllBanners:be,registerRenderer:function(e){V=e,j=2,J()}}},window.CookieConsent={getState:E,getConfig:function(){return r},getComplianceMode:function(){return m},getCountry:function(){return y},show:Ae,showSettings:Se,acceptAll:ke,declineAll:_e,getGPC:function(){return i},getConsentVersion:function(){return r.consentVersion},on:function(e,t){window.addEventListener(e,t)},off:function(e,t){window.removeEventListener(e,t)},VERSION:o},window.addEventListener("cc:ready",Ce),window.addEventListener("cc:update",Ce),function(){if(!window.__ccDefaultsSet){var e={wait_for_update:500};r.cookieTypes.forEach(function(t){t.consentMode&&t.consentMode.forEach(function(n){e[n]=t.required?"granted":"denied"})}),S("consent","default",e)}}(),function(){if(r.tcfEnabled){var e=window.__tcfapi&&window.__tcfapi.queue?window.__tcfapi.queue:[];window.__tcfapi=O,e.forEach(function(e){O.apply(null,e)}),L=!0,window===window.top&&window.addEventListener("message",function(e){var t;try{t="string"==typeof e.data?JSON.parse(e.data):e.data}catch(e){return}if(t&&t.__tcfapiCall){var n=t.__tcfapiCall;O(n.command,n.version,function(t,c){var o={__tcfapiReturn:{returnValue:t,success:c,callId:n.callId}};e.source&&e.source.postMessage("string"==typeof e.data?JSON.stringify(o):o,"*")},n.parameter)}})}}();var Ie=ee();function Le(){var e=window.__ccDiscovery&&window.__ccDiscovery.active;if(!e&&(window.__ccDNT||document.cookie.split(";").some(function(e){return 0===e.trim().indexOf("cookieflame_dnt=")})))return C(g=ne()),ce(g,te()),Y(),$(),m="dnt",ie(),se(),be(),X(g),A("cc:ready"),void Ce();if(e){C(g=te()),D(g),Y(),K(g,null),r.tcfEnabled&&T(g),window.dataLayer=window.dataLayer||[];for(var t=["consent_accepted_analytics","consent_accepted_marketing","consent_accepted_preferences","consent_accepted_necessary","consent_update","consent_given","cookie_consent_given","cookie_consent_update","consent_accepted"],n=0;n<t.length;n++)window.dataLayer.push({event:t[n]});return m="discovery",ie(),se(),be(),A("cc:ready"),A("cc:update"),void Ce()}var c=ee(),o=!1;if(c)if(!i||c.gpcOptIn||!c.marketing&&c.gpc||(o=!!c.marketing,s(c),_(r.cookieName,JSON.stringify(c),r.cookieExpiry)),g=c,C(c),D(c),Y(),$(),K(c,null),r.tcfEnabled&&T(c),ie(),se(),be(),X(c),c.cc){if("gdpr"===(m=oe(c.cc))){var a=document.getElementById("cc-icon");a&&a.classList.add("cc-show")}o&&z("gpc_optout",c),A("cc:ready"),Ce()}else re(function(e){if(m=e,"gdpr"===e){var t=document.getElementById("cc-icon");t&&t.classList.add("cc-show")}o&&z("gpc_optout",c),A("cc:ready"),Ce()});else re(function(e){return m=e,"none"===e?(Y(),Q(te(),"auto_grant_non_eu"),A("cc:ready"),A("cc:update"),void Ce()):"ccpa"===e?(Y(),i?Q(s(te()),"ccpa_gpc_optout"):Q(te(),"ccpa_auto_grant"),ie(),ue(),he("ccpa"),A("cc:ready"),A("cc:update"),void Ce()):(ie(),Y(),$(),se(),he("gdpr"),A("cc:ready"),void Ce())})}Ie&&(i&&!Ie.gpcOptIn&&(Ie.marketing=!1),g=Ie,C(Ie)),function(){if(!h&&r.autoBlockPatterns&&r.autoBlockPatterns.length){h=!0;var e=document.createElement.bind(document),t=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype,"src");t&&t.set&&(document.createElement=function(){var n=e.apply(document,arguments);if("script"!==(arguments[0]||"").toLowerCase())return n;function c(e,t){if(t&&"string"==typeof t&&!e.getAttribute("data-cc")&&!e.getAttribute("data-cc-auto")&&-1===t.indexOf("cookieconsent")){var n=P(t);!n||g&&g[n]||(e.type="text/plain",e.setAttribute("data-cc",n),e.setAttribute("data-cc-auto","1"))}}Object.defineProperty(n,"src",{configurable:!0,enumerable:!0,get:function(){return t.get.call(this)},set:function(e){return c(this,e),t.set.call(this,e)}});var o=n.setAttribute.bind(n);return n.setAttribute=function(e,t){return"src"===e.toLowerCase()&&c(n,t),o(e,t)},n}),new MutationObserver(function(e){e.forEach(function(e){e.addedNodes.forEach(function(e){if(1===e.nodeType){var t=[];"SCRIPT"===e.tagName&&t.push(e),e.querySelectorAll&&e.querySelectorAll("script[src]").forEach(function(e){t.push(e)}),t.forEach(function(e){if(!(e.getAttribute("data-cc-auto")||e.getAttribute("data-cc")||e.src&&-1!==e.src.indexOf("cookieconsent"))){var t=P(e.src);t&&(g&&g[t]||(e.type="text/plain",e.setAttribute("data-cc",t),e.setAttribute("data-cc-auto","1")))}})}})})}).observe(document.documentElement,{childList:!0,subtree:!0})}}(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",Le):Le();var Be=null;function Oe(){for(var e=[],t=[],n=[],c={},o={},r=document.querySelectorAll("script[src]"),a=0;a<r.length;a++)r[a].src&&e.push(r[a].src);for(var i=document.querySelectorAll("iframe[src]"),s=0;s<i.length;s++)i[s].src&&e.push(i[s].src);if(window.performance&&performance.getEntriesByType)for(var d=performance.getEntriesByType("resource"),u=0;u<d.length;u++){var l=d[u].name||"";if(l){-1===e.indexOf(l)&&e.push(l);var p=d[u].initiatorType||"";if("script"===p||"iframe"===p||"xmlhttprequest"===p||"fetch"===p||-1!==l.indexOf(".js"))try{var f=new URL(l),g=f.hostname;if(g===location.hostname||g==="www."+location.hostname||"www."+g===location.hostname){var m=f.pathname.match(/\/wp-content\/plugins\/([^\/]+)\//);m&&!o[m[1]]&&(o[m[1]]=!0,n.push({slug:m[1],url:l}))}else c[g]||(c[g]=!0,t.push(g))}catch(e){}}}return{allUrls:e,thirdPartyDomains:t,pluginScripts:n}}var Te={};function Pe(e){var t={set_by:"",set_by_plugin:""},n=window.__ccCookieSetters;if(!n||!Object.prototype.hasOwnProperty.call(n,e)||!n[e])return t;try{var c=new URL(String(n[e]).replace(/:\d+(:\d+)?$/,""));if(/^(chrome-extension|moz-extension|safari-web-extension|safari-extension):$/.test(c.protocol))return t.set_by="browser-extension",t;var o=c.hostname;if(o===location.hostname||o==="www."+location.hostname||"www."+o===location.hostname){t.set_by="first-party";var r=c.pathname.match(/\/wp-content\/(?:plugins|themes)\/([^\/]+)\//);r&&(t.set_by_plugin=r[1].toLowerCase())}else t.set_by=o.toLowerCase()}catch(e){}return t}function De(e,t,n){if("browser-extension"===n.set_by)return!0;var c=window.__ccVisitorCookieRules;if(!c||!c.tools)return!1;if(c.allow&&-1!==c.allow.indexOf(e))return!1;if(n.set_by)return!1;for(var o=0;o<c.tools.length;o++){for(var r=c.tools[o],a=-1!==(r.n||[]).indexOf(e),i=0;!a&&i<(r.p||[]).length;i++)0===e.indexOf(r.p[i])&&(a=!0);if(a){for(var s=0;s<t.length;s++)for(var d=0;d<(r.d||[]).length;d++){var u=String(t[s]).toLowerCase(),l=r.d[d];if(u===l||u.slice(-l.length-1)==="."+l)return!1}return!0}}return!1}function Re(){var t=window.__ccClassified||[],n=window.__ccUnclassified||[],c=window.__ccBeaconUrl;if(c){var o,r={};for(o=0;o<t.length;o++)r[t[o]]=!0;for(o=0;o<n.length;o++)r[n[o]]=!0;var a=Object.keys(Te);for(o=0;o<a.length;o++)r[a[o]]=!0;var i=Object.keys(d);for(o=0;o<i.length;o++)r[i[o]]=!0;var s={},u=[],l=document.cookie.split(";");for(o=0;o<l.length;o++){var p=l[o].indexOf("="),f=(p>-1?l[o].substring(0,p):l[o]).trim(),g=p>-1?l[o].substring(p+1).trim():"";f&&(u.push(f),s[f]=g)}var m=[];for(o=0;o<u.length;o++)if(!r[u[o]]){for(var y=!1,v=Object.keys(d),h=0;h<v.length;h++)if(d[v[h]].prefix&&0===u[o].indexOf(v[h])){y=!0;break}y||m.push(u[o])}if(0!==m.length){for(var w=Oe(),b=w.thirdPartyDomains,k=[],_=0;_<w.pluginScripts.length;_++)k.push(w.pluginScripts[_].slug);for(var x=location.pathname,E=0;E<m.length;E++){var A=m[E],S=!!e[A]&&!r[A],C=Me(s[A]||""),I=Pe(A);if(!De(A,b,I)||window.__ccDiscovery&&window.__ccDiscovery.active){var L=JSON.stringify({cookie_name:A,script_domains:b,plugin_scripts:k,page_path:x,pre_existing:S,value_shape:C,set_by:I.set_by,set_by_plugin:I.set_by_plugin});Te[A]=!0,navigator.sendBeacon&&navigator.sendBeacon(c,new Blob([L],{type:"application/json"}))}else Te[A]=!0}}}}function Me(e){if(!e||0===e.length)return"empty";if("true"===e||"false"===e||"1"===e||"0"===e)return"boolean";if(/^\d+$/.test(e))return e.length>12?"long-numeric":"numeric";if(/^\d+\.\d+$/.test(e))return"decimal";if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e))return"uuid";if(/^[A-Za-z0-9+\/]{20,}={0,2}$/.test(e))return"base64";if(/^(GA\d+\.\d+\.\d+\.\d+|G-[A-Z0-9]+)/.test(e))return"ga-id";try{if("object"==typeof JSON.parse(decodeURIComponent(e)))return"json"}catch(e){}return/^[a-f0-9]{24,}$/i.test(e)?"hex-id":/^\d{10,13}$/.test(e)?"timestamp":e.length<=20?"short-string":e.length<=100?"medium-string":"long-string"}setTimeout(Re,3e3),setTimeout(Re,15e3),setTimeout(Re,45e3),window.addEventListener("cc:update",function(){setTimeout(Re,3e3)}),window.addEventListener("pagehide",Re)}(); -
cookieflame/trunk/cookieflame.php
r3769546 r3771537 3 3 * Plugin Name: CookieFlame 4 4 * Plugin URI: https://cookieflame.com 5 * Description: A lightweight, self-hosted cookie consent manager with Google Consent Mode v2, IAB TCF v2.2,automatic script blocking, multi-region compliance (GDPR/CCPA/LGPD/POPIA), passive cookie monitoring, and server-side geo detection via Cloudflare.6 * Version: 5. 3.05 * Description: A lightweight, self-hosted cookie consent manager with Google Consent Mode v2, automatic script blocking, multi-region compliance (GDPR/CCPA/LGPD/POPIA), passive cookie monitoring, and server-side geo detection via Cloudflare. 6 * Version: 5.4.0 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 8.0 … … 19 19 } 20 20 21 define( 'COOKIEFLAME_VERSION', '5. 3.0' );21 define( 'COOKIEFLAME_VERSION', '5.4.0' ); 22 22 define( 'COOKIEFLAME_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 23 23 define( 'COOKIEFLAME_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); … … 27 27 28 28 /* ═══════════════════════════════════════════════════════════ 29 MONITORING INTERVAL BUCKETS (in minutes) 30 A single 'digest_interval' option drives both beacon 31 processing cadence and email digest cadence. Value 0 = off. 32 Free tier: off, daily, weekly. 33 Pro tier: free buckets + 15m, 1h, 4h, monthly (added via the 34 cookieflame_interval_buckets filter by the Pro plugin). 29 COOKIE MONITORING SETTINGS (v5.4.0) 30 Two separate settings on the Cookie Monitoring tab: 31 - Watch for new cookies: On/Off. Visitors' browsers report 32 new cookies as they browse, processed straight away. 33 Stored as the inverse of 'monitoring_paused'. 34 - Email me about changes: 'weekly' (default), 'daily', 35 'instant' or 'off'. Stored as 'email_frequency'. 36 Before v5.4.0 a single 'digest_interval' (minutes, 0 = off) 37 drove both. cookieflame_migrate_monitoring_settings() converts 38 it once; until then the getters derive the same answer from it. 39 40 The cookieflame_interval_buckets filter is kept: a plugin that 41 adds a bucket shorter than a day (Pro 1.5.x adds 15 minutes) 42 makes "Instantly" emails possible, sent after that delay. 35 43 ═══════════════════════════════════════════════════════════ */ 36 44 … … 53 61 */ 54 62 function cookieflame_interval_buckets_free() { 63 // v5.4.0: the 15-minute, 1-hour and 4-hour entries are no longer 64 // declared here. Pro 1.5.x still contributes them through the filter. 55 65 return array( 56 66 array( 'minutes' => 0, 'tier' => 'free' ), 57 array( 'minutes' => 15, 'tier' => 'pro' ),58 array( 'minutes' => 60, 'tier' => 'pro' ),59 array( 'minutes' => 240, 'tier' => 'pro' ),60 67 array( 'minutes' => 1440, 'tier' => 'free' ), 61 68 array( 'minutes' => 10080, 'tier' => 'free' ), … … 159 166 160 167 /** 161 * Return the currently configured monitoring interval in minutes, 162 * normalised to a valid bucket for the current tier. Defaults to 163 * daily (1440) on new installs with no value set. 168 * Delay before the next email summary, in minutes (0 = no email). 169 * Kept under its pre-5.4.0 name for any code that still calls it. 164 170 */ 165 171 function cookieflame_current_interval_minutes() { 166 $settings = get_option( 'cookieflame_settings', array() ); 167 $raw = isset( $settings['digest_interval'] ) ? intval( $settings['digest_interval'] ) : 1440; 168 return cookieflame_normalise_interval( $raw ); 169 } 170 172 return cookieflame_email_delay_minutes(); 173 } 174 175 /** 176 * Whether "Watch for new cookies" is off. Every beacon, queue and 177 * digest path gates on this, so turning it off silences them all. 178 */ 171 179 function cookieflame_monitoring_is_off() { 172 if ( cookieflame_monitoring_is_paused() ) { 173 return true; 174 } 175 return 0 === cookieflame_current_interval_minutes(); 176 } 177 178 /** 179 * Whether monitoring is currently paused. Paused is a temporary 180 * suppression state — distinct from "Off" — that keeps the chosen 181 * update interval configured but silences beacons and digests while 182 * the admin does site surgery. Admin toggles this from the Cookie 183 * Monitoring tab. cookieflame_monitoring_is_off() folds this into the same 184 * gate so every downstream path inherits pause automatically. 180 return cookieflame_monitoring_is_paused(); 181 } 182 183 /** 184 * Whether "Watch for new cookies" is off (stored as 'monitoring_paused', 185 * the flag behind the On/Off switch on the Cookie Monitoring tab). A 186 * pre-5.4.0 'digest_interval' of 0 also means off until migrated. 185 187 */ 186 188 function cookieflame_monitoring_is_paused() { 187 189 $settings = get_option( 'cookieflame_settings', array() ); 188 return ! empty( $settings['monitoring_paused'] ); 189 } 190 if ( ! empty( $settings['monitoring_paused'] ) ) { 191 return true; 192 } 193 if ( ! isset( $settings['email_frequency'] ) && isset( $settings['digest_interval'] ) && intval( $settings['digest_interval'] ) <= 0 ) { 194 return true; 195 } 196 return false; 197 } 198 199 /** 200 * The saved "Email me about changes" choice: 'weekly', 'daily', 201 * 'instant' or 'off'. Sites not yet migrated get the answer their old 202 * 'digest_interval' maps to (15/60/240 minutes map to 'instant'). 203 */ 204 function cookieflame_email_frequency() { 205 $settings = get_option( 'cookieflame_settings', array() ); 206 if ( isset( $settings['email_frequency'] ) ) { 207 return cookieflame_sanitize_email_frequency( $settings['email_frequency'] ); 208 } 209 if ( ! isset( $settings['digest_interval'] ) ) { 210 return 'weekly'; 211 } 212 return cookieflame_email_frequency_from_interval( $settings['digest_interval'], true ); 213 } 214 215 /** 216 * Map a pre-5.4.0 'digest_interval' to an email frequency. Intervals 217 * under a day become 'instant' when $instant_ok, otherwise 'daily'. 218 */ 219 function cookieflame_email_frequency_from_interval( $minutes, $instant_ok ) { 220 $minutes = intval( $minutes ); 221 if ( $minutes <= 0 ) { 222 return 'off'; 223 } 224 if ( 10080 === $minutes ) { 225 return 'weekly'; 226 } 227 if ( $minutes < 1440 ) { 228 return $instant_ok ? 'instant' : 'daily'; 229 } 230 return 'daily'; 231 } 232 233 function cookieflame_sanitize_email_frequency( $value ) { 234 $value = is_string( $value ) ? $value : ''; 235 return in_array( $value, array( 'weekly', 'daily', 'instant', 'off' ), true ) ? $value : 'weekly'; 236 } 237 238 /** 239 * Shortest sub-daily bucket another plugin added through the 240 * cookieflame_interval_buckets filter, in minutes, or 0 when none. 241 */ 242 function cookieflame_instant_bucket_minutes() { 243 $shortest = 0; 244 foreach ( cookieflame_interval_buckets() as $bucket ) { 245 $minutes = intval( $bucket['minutes'] ); 246 if ( $minutes > 0 && $minutes < 1440 && ( 0 === $shortest || $minutes < $shortest ) ) { 247 $shortest = $minutes; 248 } 249 } 250 return $shortest; 251 } 252 253 /** 254 * Whether "Instantly" can be chosen on this site: CookieFlame Pro is 255 * active and a plugin supplies a delay shorter than a day. 256 */ 257 function cookieflame_instant_email_available() { 258 return cookieflame_is_premium() && cookieflame_instant_bucket_minutes() > 0; 259 } 260 261 /** 262 * Minutes from the first new cookie to the email summary, or 0 when 263 * emails are off. 'instant' uses the shortest contributed bucket and 264 * falls back to daily when there is none. 265 * 266 * Filter: cookieflame_email_delay_minutes ( int $minutes, string $frequency ). 267 */ 268 function cookieflame_email_delay_minutes() { 269 $frequency = cookieflame_email_frequency(); 270 $map = array( 'off' => 0, 'daily' => 1440, 'weekly' => 10080 ); 271 if ( 'instant' === $frequency ) { 272 $minutes = cookieflame_instant_bucket_minutes(); 273 $minutes = $minutes > 0 ? $minutes : 1440; 274 } else { 275 $minutes = $map[ $frequency ]; 276 } 277 return max( 0, intval( apply_filters( 'cookieflame_email_delay_minutes', $minutes, $frequency ) ) ); 278 } 279 280 /** 281 * One-time conversion of the pre-5.4.0 'digest_interval' setting: 282 * 0 -> watch Off, email Off 283 * daily, weekly -> watch On, email unchanged 284 * 15m, 1h, 4h -> 'instant' when Pro is active, 'daily' otherwise 285 * 'digest_interval' itself is left in place. 286 */ 287 function cookieflame_migrate_monitoring_settings() { 288 $settings = get_option( 'cookieflame_settings', false ); 289 if ( ! is_array( $settings ) || isset( $settings['email_frequency'] ) ) { 290 return; 291 } 292 if ( isset( $settings['digest_interval'] ) ) { 293 $minutes = intval( $settings['digest_interval'] ); 294 $settings['email_frequency'] = cookieflame_email_frequency_from_interval( $minutes, $minutes > 0 && $minutes < 1440 && cookieflame_is_premium() ); 295 if ( $minutes <= 0 ) { 296 $settings['monitoring_paused'] = 1; 297 } 298 } else { 299 $settings['email_frequency'] = 'weekly'; 300 } 301 update_option( 'cookieflame_settings', $settings ); 302 } 303 add_action( 'admin_init', 'cookieflame_migrate_monitoring_settings', 5 ); 190 304 191 305 /** … … 209 323 $host = is_string( $host ) ? strtolower( $host ) : ''; 210 324 return substr( sha1( $host . '|' . $page_path ), 0, 16 ); 325 } 326 327 /** 328 * Version string for one of the plugin's own scripts or stylesheets: the 329 * plugin version plus the file's modification time, so a changed file 330 * always gets a new URL and no browser or CDN cache serves an old copy. 331 */ 332 function cookieflame_asset_version( $relative_path ) { 333 $file = COOKIEFLAME_PLUGIN_DIR . $relative_path; 334 $mtime = file_exists( $file ) ? filemtime( $file ) : false; 335 return $mtime ? COOKIEFLAME_VERSION . '.' . $mtime : COOKIEFLAME_VERSION; 211 336 } 212 337 … … 337 462 add_action( 'admin_post_cookieflame_save_notifications', 'cookieflame_save_notifications' ); 338 463 add_action( 'admin_post_cookieflame_toggle_pause', 'cookieflame_toggle_monitoring_pause' ); 464 add_action( 'admin_post_cookieflame_dismiss_upgrade_banner', 'cookieflame_dismiss_upgrade_banner' ); 465 466 /** 467 * Hide the "Upgrade to Pro" banner at the top of the CookieFlame tabs 468 * for the current user. 469 */ 470 function cookieflame_dismiss_upgrade_banner() { 471 if ( ! current_user_can( 'manage_options' ) ) { 472 wp_die( esc_html__( 'You do not have permission to do this.', 'cookieflame' ) ); 473 } 474 check_admin_referer( 'cookieflame_dismiss_upgrade_banner' ); 475 update_user_meta( get_current_user_id(), 'cookieflame_upgrade_banner_dismissed', time() ); 476 $tab = sanitize_key( wp_unslash( $_POST['tab'] ?? 'cookies' ) ); 477 wp_safe_redirect( add_query_arg( array( 'page' => 'cookieflame', 'tab' => $tab ? $tab : 'cookies' ), admin_url( 'admin.php' ) ) ); 478 exit; 479 } 339 480 340 481 /** … … 1193 1334 // - The beacon carries a script_domain or plugin_script not yet 1194 1335 // recorded for this (URL, cookie) pair 1195 // - The configured update interval has elapsed since last_processed 1336 // - A day has passed since last_processed (heartbeat; this only 1337 // refreshes "last seen", which Tidy up relies on) 1196 1338 // 1197 1339 // Otherwise we silently drop the beacon. 1198 1340 // 1199 // Monitoring off (interval = 0)is handled at enqueue time — the1341 // Watch for new cookies Off is handled at enqueue time — the 1200 1342 // beacon URL is not injected so nothing fires. We defensively drop 1201 1343 // here too in case a stale page serves the URL from cache. … … 1208 1350 $url_hash = cookieflame_url_hash_for_path( $page_path ); 1209 1351 $now = time(); 1210 $interval_s = cookieflame_current_interval_minutes() * 60;1352 $interval_s = DAY_IN_SECONDS; // Heartbeat: fixed daily since v5.4.0, independent of the email setting. 1211 1353 1212 1354 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table; high-write-rate beacon storage where the object cache adds no value (writes invalidate cache on every visitor request, reads are admin-only and infrequent). … … 1404 1546 */ 1405 1547 function cookieflame_queue_notification( $cookie_name, $script_domains, $page_path, $pre_existing = false, $value_shape = '' ) { 1406 // Monitoringoff -> do not queue and do not schedule a digest.1407 if ( cookieflame_monitoring_is_off() ) {1548 // Watching off, or emails off -> do not queue and do not schedule a digest. 1549 if ( cookieflame_monitoring_is_off() || 0 === cookieflame_email_delay_minutes() ) { 1408 1550 return; 1409 1551 } … … 1422 1564 update_option( 'cookieflame_notification_queue', $queue ); 1423 1565 1424 // Start digest timer if not already running . The same update_interval1425 // setting drives both beacon filtering and digest cadence.1566 // Start digest timer if not already running, from the 1567 // "Email me about changes" setting. 1426 1568 $scheduled = get_option( 'cookieflame_digest_scheduled', 0 ); 1427 1569 if ( empty( $scheduled ) ) { 1428 $interval = cookieflame_ current_interval_minutes() * 60;1570 $interval = cookieflame_email_delay_minutes() * 60; 1429 1571 if ( $interval <= 0 ) { 1430 1572 return; … … 1601 1743 // invoking the hook directly. Silently clean up and return — the 1602 1744 // queue is preserved for resume. 1603 if ( cookieflame_monitoring_is_off() ) {1745 if ( cookieflame_monitoring_is_off() || 0 === cookieflame_email_delay_minutes() ) { 1604 1746 delete_option( 'cookieflame_digest_scheduled' ); 1605 1747 return; … … 1685 1827 1686 1828 // Header 1687 $body = "CookieFlame Cookie Discovery Report\n";1829 $body = "CookieFlame: new cookies found by Watch for new cookies\n"; 1688 1830 $body .= $site_name . " (" . $site_url . ")\n"; 1689 1831 $body .= wp_date( 'j M Y, H:i' ) . "\n"; … … 1813 1955 $body .= "-- \n"; 1814 1956 $body .= "CookieFlame v" . COOKIEFLAME_VERSION . " on " . $site_name . "\n"; 1815 $body .= "You are receiving this because Cookie Monitoring is enabled.\n";1957 $body .= "You are receiving this because \"Email me about changes\" is on in Cookie Monitoring.\n"; 1816 1958 $body .= "Change settings: " . admin_url( 'admin.php?page=cookieflame&tab=notifications' ) . "\n"; 1817 1959 1818 wp_mail( $to, $subject, $body ); 1960 /** 1961 * Filter the email summary before it is sent. 1962 * 1963 * Return an array with 'to', 'subject', 'body' and 'headers' to change 1964 * the email, or false to skip sending it (the queue is still cleared). 1965 * 1966 * @param array $email { to, subject, body, headers }. 1967 * @param array $actionable Queue entries that need a decision. 1968 * @param array $residual Queue entries from plugins no longer active. 1969 */ 1970 $email = apply_filters( 'cookieflame_digest_email', array( 1971 'to' => $to, 1972 'subject' => $subject, 1973 'body' => $body, 1974 'headers' => array(), 1975 ), $actionable, $residual ); 1976 1977 if ( is_array( $email ) && ! empty( $email['to'] ) ) { 1978 wp_mail( $email['to'], (string) ( $email['subject'] ?? $subject ), (string) ( $email['body'] ?? $body ), $email['headers'] ?? array() ); 1979 } 1819 1980 1820 1981 // Clear the queue and timer … … 2218 2379 COOKIEFLAME_PLUGIN_URL . 'assets/css/admin.css', 2219 2380 array(), 2220 COOKIEFLAME_VERSION2381 cookieflame_asset_version( 'assets/css/admin.css' ) 2221 2382 ); 2222 2383 wp_enqueue_script( … … 2224 2385 COOKIEFLAME_PLUGIN_URL . 'assets/js/admin.js', 2225 2386 array(), 2226 COOKIEFLAME_VERSION,2387 cookieflame_asset_version( 'assets/js/admin.js' ), 2227 2388 true 2228 2389 ); … … 2253 2414 $cookieflame_js_data = apply_filters( 'cookieflame_admin_js_data', $cookieflame_js_data ); 2254 2415 wp_localize_script( 'cookieflame-admin', 'cookieflameAdmin', $cookieflame_js_data ); 2416 2417 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab navigation, no data modification. 2418 if ( isset( $_GET['tab'] ) && 'preview' === sanitize_key( wp_unslash( $_GET['tab'] ) ) ) { 2419 wp_enqueue_script( 2420 'cookieflame-admin-preview', 2421 COOKIEFLAME_PLUGIN_URL . 'assets/js/admin-preview.js', 2422 array(), 2423 cookieflame_asset_version( 'assets/js/admin-preview.js' ), 2424 true 2425 ); 2426 wp_localize_script( 'cookieflame-admin-preview', 'cookieflamePreview', array( 2427 'privacyUrl' => $opts['privacy_url'] ?? '/privacy-policy', 2428 'theme' => $opts['theme'] ?? 'light', 2429 'rejectFirstScreen' => ! empty( $opts['show_reject_first'] ), 2430 'showPoweredBy' => ! empty( $opts['show_powered_by'] ), 2431 ) ); 2432 } 2255 2433 } 2256 2434 … … 2305 2483 } 2306 2484 2485 /** 2486 * [cookieflame_settings_link text="Cookie settings"]: a link that reopens 2487 * the visitor's cookie choices (the US "Do Not Sell or Share" choice for 2488 * US visitors). Any element with the class cc-open-settings does the same, 2489 * for example a menu item. 2490 */ 2491 add_shortcode( 'cookieflame_settings_link', 'cookieflame_settings_link_shortcode' ); 2492 2493 function cookieflame_settings_link_shortcode( $atts ) { 2494 $atts = shortcode_atts( array( 2495 'text' => __( 'Cookie settings', 'cookieflame' ), 2496 'class' => '', 2497 ), $atts, 'cookieflame_settings_link' ); 2498 $class = trim( 'cc-open-settings ' . implode( ' ', array_map( 'sanitize_html_class', preg_split( '/\s+/', (string) $atts['class'], -1, PREG_SPLIT_NO_EMPTY ) ) ) ); 2499 return '<a href="#" class="' . esc_attr( $class ) . '" role="button">' . esc_html( $atts['text'] ) . '</a>'; 2500 } 2501 2307 2502 /* ═══════════════════════════════════════════════════════════ 2308 2503 REST API — GEO ENDPOINT … … 2339 2534 update_option( 'cookieflame_settings', array( 2340 2535 'privacy_url' => '/privacy-policy', 2341 'ccpa_url' => '/privacy-policy#do-not-sell',2342 2536 'log_endpoint' => '', 2343 2537 'gtm_id' => '', … … 2350 2544 'track_logged_in' => 1, 2351 2545 'extra_patterns' => '', 2352 ' digest_interval' => 1440, // daily2546 'email_frequency' => 'weekly', 2353 2547 'monitoring_paused' => 0, 2354 2548 ) ); … … 2540 2734 . "if(raw.cv!=={$cv_js})return;" 2541 2735 . "var gpc=window.__ccGPC;" 2542 . "var mkt=raw.marketing&& !gpc;"2736 . "var mkt=raw.marketing&&(!gpc||raw.gpcOptIn);" 2543 2737 . "gtag('consent','update',{" 2544 2738 . "analytics_storage:raw.analytics?'granted':'denied'," … … 2629 2823 // blockable iframe or Elementor video widget is present on the 2630 2824 // page). Versioned so cache busts cleanly on plugin update. 2631 $overrides['facadesUrl'] = COOKIEFLAME_PLUGIN_URL . 'assets/js/cookieconsent-facades.min.js?ver=' . COOKIEFLAME_VERSION;2825 $overrides['facadesUrl'] = COOKIEFLAME_PLUGIN_URL . 'assets/js/cookieconsent-facades.min.js?ver=' . cookieflame_asset_version( 'assets/js/cookieconsent-facades.min.js' ); 2632 2826 2633 2827 if ( ! empty( $options['privacy_url'] ) ) $overrides['privacyUrl'] = $options['privacy_url']; 2634 // CCPA "Do Not Sell" link points to the same privacy policy page with an anchor.2635 $cookieflame_privacy = ! empty( $options['privacy_url'] ) ? $options['privacy_url'] : '/privacy-policy';2636 $overrides['ccpaUrl'] = $cookieflame_privacy . '#do-not-sell';2637 2828 if ( ! empty( $options['log_endpoint'] ) ) $overrides['logEndpoint'] = $options['log_endpoint']; 2638 2829 if ( ! empty( $options['storage_method'] ) ) $overrides['storageMethod'] = $options['storage_method']; … … 2648 2839 $overrides['shareSubdomains'] = ! empty( $options['share_subdomains'] ) || ! empty( $options['cookie_domain'] ); 2649 2840 if ( isset( $options['reconsent_days'] ) && $options['reconsent_days'] !== '' ) { 2650 $overrides['reconsentDays'] = intval( $options['reconsent_days'] ); 2841 $overrides['reconsentDays'] = cookieflame_reconsent_days( $options['reconsent_days'] ); 2842 } 2843 if ( ! empty( $options['show_reject_first'] ) ) { 2844 $overrides['rejectFirstScreen'] = true; 2845 } 2846 if ( ! empty( $options['visitor_logins'] ) ) { 2847 $overrides['visitorLogins'] = true; 2651 2848 } 2652 2849 if ( ! empty( $options['show_powered_by'] ) ) { … … 2655 2852 if ( ! empty( $options['icon_position'] ) && 'right' === $options['icon_position'] ) { 2656 2853 $overrides['iconPosition'] = 'right'; 2854 } 2855 2856 /** 2857 * Filter the banner configuration sent to the front end as 2858 * window.__ccConfig. Keys the banner script knows are applied over 2859 * its defaults; any other keys are left on window.__ccConfig for the 2860 * code that added them. 2861 * 2862 * @param array $overrides Front-end config. 2863 * @param array $options The cookieflame_settings option. 2864 */ 2865 $filtered = apply_filters( 'cookieflame_frontend_config', $overrides, $options ); 2866 if ( is_array( $filtered ) ) { 2867 $overrides = $filtered; 2657 2868 } 2658 2869 … … 2797 3008 COOKIEFLAME_PLUGIN_URL . 'assets/js/cookieconsent' . $suffix . '.js', 2798 3009 array(), 2799 COOKIEFLAME_VERSION,3010 cookieflame_asset_version( 'assets/js/cookieconsent' . $suffix . '.js' ), 2800 3011 false // Load in head, not footer. 2801 3012 ); … … 2811 3022 COOKIEFLAME_PLUGIN_URL . 'assets/js/cookieconsent-discovery' . $suffix . '.js', 2812 3023 array( 'cookieflame-consent' ), 2813 COOKIEFLAME_VERSION,3024 cookieflame_asset_version( 'assets/js/cookieconsent-discovery' . $suffix . '.js' ), 2814 3025 true // Footer is fine -- discovery runs after DOM is ready. 2815 3026 ); … … 2847 3058 COOKIEFLAME_PLUGIN_URL . 'assets/css/cookieconsent.css', 2848 3059 array(), 2849 COOKIEFLAME_VERSION3060 cookieflame_asset_version( 'assets/css/cookieconsent.css' ) 2850 3061 ); 2851 3062 } … … 3378 3589 array( 'key' => 'privacy_url', 3379 3590 'wp_default_page' => 'wp_page_for_privacy_policy', 3380 'desc' => __( 'The page linked from the consent banner and the CCPA "Do Not Sell My Personal Information" notice. Defaults to the page set under Settings > Privacy. Add the [cookieflame_declaration] shortcode to this page to show visitors their consent choices.', 'cookieflame' ) ) ); 3591 'desc' => __( 'The page linked from the consent banner. Defaults to the page set under Settings > Privacy. Add the [cookieflame_declaration] shortcode to this page to show visitors their consent choices.', 'cookieflame' ) ) ); 3592 3593 add_settings_field( 'cookieflame_visitor_logins', __( 'Visitor logins', 'cookieflame' ), 'cookieflame_field_checkbox', 'cookieflame', 'cookieflame_consent', 3594 array( 'key' => 'visitor_logins', 3595 'desc' => __( 'Visitors can log in to this site (for example, customer accounts or a members area)', 'cookieflame' ), 3596 'help' => __( 'Tick this if people other than you and your team can log in, for example to see their orders, download purchases or read members-only pages. Your cookie declaration will then also list the cookies WordPress sets when someone logs in. Leave it unticked if only you and your team log in.', 'cookieflame' ) ) ); 3381 3597 3382 3598 add_settings_field( 'cookieflame_reconsent', __( 'Re-consent Period', 'cookieflame' ), 'cookieflame_field_number', 'cookieflame', 'cookieflame_consent', … … 3386 3602 add_settings_field( 'cookieflame_theme', __( 'Banner Theme', 'cookieflame' ), 'cookieflame_field_theme', 'cookieflame', 'cookieflame_consent' ); 3387 3603 3604 add_settings_field( 'cookieflame_show_reject_first', __( 'Show "Decline all" on the first screen', 'cookieflame' ), 'cookieflame_field_checkbox', 'cookieflame', 'cookieflame_consent', 3605 array( 'key' => 'show_reject_first', 3606 'desc' => __( 'Adds a Decline All button next to Accept All on the banner for visitors in the UK, EU/EEA, Switzerland, Brazil and South Africa. Off by default: visitors can always decline everything under Manage preferences. Never shown on the US notice.', 'cookieflame' ) ) ); 3607 3388 3608 add_settings_field( 'cookieflame_share_subdomains', __( 'Share consent across subdomains', 'cookieflame' ), 'cookieflame_field_checkbox', 'cookieflame', 'cookieflame_consent', 3389 3609 array( 'key' => 'share_subdomains', … … 3428 3648 } 3429 3649 3650 /** 3651 * Re-consent period in days. Anything below 1 (blank, 0, negative or 3652 * junk) means the default of 365, so consent never expires on load. 3653 */ 3654 function cookieflame_reconsent_days( $value ) { 3655 $days = intval( $value ); 3656 return $days >= 1 ? $days : 365; 3657 } 3658 3430 3659 function cookieflame_sanitize_settings( $input ) { 3431 // Handle the DNT device cookie (not stored in DB, stored as browser cookie) 3432 cookieflame_handle_dnt_cookie( ! empty( $input['dnt_device'] ) ); 3660 $input = is_array( $input ) ? $input : array(); 3433 3661 3434 3662 // Preserve consent_bump from existing settings (managed by the 3435 3663 // Force Re-consent button, not by this form) 3436 3664 $existing = get_option( 'cookieflame_settings', array() ); 3437 3438 return array( 3439 'privacy_url' => esc_url_raw( $input['privacy_url'] ?? '' ), 3440 'ccpa_url' => esc_url_raw( $input['ccpa_url'] ?? '' ), 3441 'log_endpoint' => esc_url_raw( $input['log_endpoint'] ?? '' ), 3665 $existing = is_array( $existing ) ? $existing : array(); 3666 3667 // The Settings tab form posts a '_form' marker. Other saves (the 3668 // Cookie Monitoring and Tag Managers handlers, the pause switch, the 3669 // migration) pass the whole stored array back through this callback, 3670 // so an unticked checkbox there means "not in this form", not "off". 3671 $main_form = ! empty( $input['_form'] ); 3672 $flag = function ( $key, $default = 0 ) use ( $input, $existing, $main_form ) { 3673 if ( $main_form ) { 3674 return ! empty( $input[ $key ] ) ? 1 : 0; 3675 } 3676 $source = array_key_exists( $key, $input ) ? $input : $existing; 3677 return isset( $source[ $key ] ) ? ( ! empty( $source[ $key ] ) ? 1 : 0 ) : $default; 3678 }; 3679 3680 if ( $main_form ) { 3681 // Handle the DNT device cookie (not stored in DB, stored as browser cookie) 3682 cookieflame_handle_dnt_cookie( ! empty( $input['dnt_device'] ) ); 3683 $track_logged_in = empty( $input['exclude_staff'] ) ? 1 : 0; 3684 } else { 3685 $track_logged_in = $flag( 'track_logged_in', 1 ); 3686 } 3687 3688 $email_frequency = cookieflame_sanitize_email_frequency( $input['email_frequency'] ?? ( $existing['email_frequency'] ?? 'weekly' ) ); 3689 3690 $clean = array( 3691 'privacy_url' => esc_url_raw( $input['privacy_url'] ?? ( $main_form ? '' : ( $existing['privacy_url'] ?? '' ) ) ), 3692 'log_endpoint' => esc_url_raw( $input['log_endpoint'] ?? ( $main_form ? '' : ( $existing['log_endpoint'] ?? '' ) ) ), 3442 3693 'storage_method' => 'cookie', // hardcoded — localStorage removed 3443 'reconsent_days' => intval( $input['reconsent_days'] ?? 365 ), 3444 'cookie_domain' => cookieflame_sanitize_cookie_domain( $input['cookie_domain'] ?? '' ), 3445 'share_subdomains' => ! empty( $input['share_subdomains'] ) ? 1 : 0, 3446 'csp_safe' => ! empty( $input['csp_safe'] ) ? 1 : 0, 3447 'theme' => in_array( $input['theme'] ?? '', array( 'light', 'dark' ), true ) ? $input['theme'] : 'light', 3448 'show_powered_by' => ! empty( $input['show_powered_by'] ) ? 1 : 0, 3449 'icon_position' => in_array( $input['icon_position'] ?? '', array( 'left', 'right' ), true ) ? $input['icon_position'] : 'left', 3450 'track_logged_in' => empty( $input['exclude_staff'] ) ? 1 : 0, 3451 'extra_patterns' => sanitize_textarea_field( $input['extra_patterns'] ?? '' ), 3694 'reconsent_days' => cookieflame_reconsent_days( $input['reconsent_days'] ?? ( $existing['reconsent_days'] ?? 365 ) ), 3695 'cookie_domain' => cookieflame_sanitize_cookie_domain( $input['cookie_domain'] ?? ( $existing['cookie_domain'] ?? '' ) ), 3696 'share_subdomains' => $flag( 'share_subdomains' ), 3697 'csp_safe' => $flag( 'csp_safe' ), 3698 'theme' => in_array( $input['theme'] ?? ( $existing['theme'] ?? '' ), array( 'light', 'dark' ), true ) ? ( $input['theme'] ?? $existing['theme'] ) : 'light', 3699 'show_reject_first' => $flag( 'show_reject_first' ), 3700 'visitor_logins' => $flag( 'visitor_logins' ), 3701 'show_powered_by' => $flag( 'show_powered_by' ), 3702 'icon_position' => in_array( $input['icon_position'] ?? ( $existing['icon_position'] ?? '' ), array( 'left', 'right' ), true ) ? ( $input['icon_position'] ?? $existing['icon_position'] ) : 'left', 3703 'track_logged_in' => $track_logged_in, 3704 'extra_patterns' => sanitize_textarea_field( $input['extra_patterns'] ?? ( $main_form ? '' : ( $existing['extra_patterns'] ?? '' ) ) ), 3452 3705 'consent_bump' => intval( $existing['consent_bump'] ?? 0 ), 3453 'delete_on_uninstall' => ! empty( $input['delete_on_uninstall'] ) ? 1 : 0,3706 'delete_on_uninstall' => $flag( 'delete_on_uninstall' ), 3454 3707 // Separate-tab fields: use $input if present (handler included it), else preserve $existing 3455 3708 'gtm_id' => isset( $input['gtm_id'] ) ? cookieflame_sanitize_gtm_id( $input['gtm_id'] ) : ( $existing['gtm_id'] ?? '' ), 3456 3709 'uet_id' => isset( $input['uet_id'] ) ? cookieflame_sanitize_uet_id( $input['uet_id'] ) : ( $existing['uet_id'] ?? '' ), 3457 3710 'notification_email' => isset( $input['notification_email'] ) ? sanitize_email( $input['notification_email'] ) : ( $existing['notification_email'] ?? '' ), 3458 'digest_interval' => isset( $input['digest_interval'] ) ? cookieflame_normalise_interval( $input['digest_interval'] ) : cookieflame_normalise_interval( $existing['digest_interval'] ?? 1440 ), 3459 // monitoring_paused is toggled only via the dedicated 3460 // admin-post handler (cookieflame_toggle_monitoring_pause). When the 3461 // handler's update_option() fires, the sanitize_option filter 3462 // runs with $input containing the new value the handler wants 3463 // to commit. When the main Settings tab saves, $input does not 3464 // include this field, so we fall back to $existing to preserve 3465 // whatever pause state was previously set. 3711 'email_frequency' => $email_frequency, 3712 // "Watch for new cookies" Off. Toggled only via the dedicated 3713 // admin-post handler (cookieflame_toggle_monitoring_pause) or 3714 // the migration; the Settings tab preserves it. 3466 3715 'monitoring_paused' => isset( $input['monitoring_paused'] ) ? ( ! empty( $input['monitoring_paused'] ) ? 1 : 0 ) : ( ! empty( $existing['monitoring_paused'] ) ? 1 : 0 ), 3467 3716 ); 3717 // Pre-5.4.0 setting, kept as-is so a downgrade still finds it. 3718 if ( isset( $input['digest_interval'] ) || isset( $existing['digest_interval'] ) ) { 3719 $clean['digest_interval'] = intval( $input['digest_interval'] ?? $existing['digest_interval'] ); 3720 } 3721 return $clean; 3468 3722 } 3469 3723 … … 3532 3786 check_admin_referer( 'cookieflame_save_notifications' ); 3533 3787 3534 $opts = get_option( 'cookieflame_settings', array() ); 3535 $previous = cookieflame_normalise_interval( $opts['digest_interval'] ?? 1440 ); 3788 $opts = get_option( 'cookieflame_settings', array() ); 3789 $previous = cookieflame_email_frequency(); 3790 $frequency = cookieflame_sanitize_email_frequency( sanitize_key( wp_unslash( $_POST['email_frequency'] ?? 'weekly' ) ) ); 3791 if ( 'instant' === $frequency && ! cookieflame_instant_email_available() ) { 3792 $frequency = 'daily'; 3793 } 3536 3794 $opts['notification_email'] = sanitize_email( wp_unslash( $_POST['notification_email'] ?? '' ) ); 3537 $opts[' digest_interval'] = cookieflame_normalise_interval( intval( $_POST['digest_interval'] ?? 1440 ) );3795 $opts['email_frequency'] = $frequency; 3538 3796 update_option( 'cookieflame_settings', $opts ); 3539 3797 3540 // If the interval changed, clear any pending single-event digest so 3541 // the next queued cookie reschedules against the new cadence. If the 3542 // admin has just turned monitoring off, drop any pending digest too. 3543 if ( $previous !== $opts['digest_interval'] || 0 === $opts['digest_interval'] ) { 3798 // If the frequency changed, clear any pending single-event digest so 3799 // the next queued cookie reschedules against the new setting. Emails 3800 // turned off also drops the waiting list, so nothing stale is sent 3801 // when they are turned back on. 3802 if ( $previous !== $frequency ) { 3544 3803 wp_clear_scheduled_hook( 'cookieflame_send_digest' ); 3545 3804 delete_option( 'cookieflame_digest_scheduled' ); 3805 if ( 'off' === $frequency ) { 3806 update_option( 'cookieflame_notification_queue', array() ); 3807 } 3546 3808 } 3547 3809 … … 3554 3816 3555 3817 /** 3556 * Toggle the monitoring_paused flag from the Cookie Monitoring tab.3557 * Pausesuppresses beacons, beacon handling, digest queueing, and3818 * Toggle "Watch for new cookies" (the monitoring_paused flag) from the 3819 * Cookie Monitoring tab. Off suppresses beacons, beacon handling, digest queueing, and 3558 3820 * digest sending — all three paths gate on cookieflame_monitoring_is_off(), 3559 3821 * which now returns true when paused. On pause we also clear any … … 3571 3833 3572 3834 $opts = get_option( 'cookieflame_settings', array() ); 3573 $was_paused = ! empty( $opts['monitoring_paused']);3835 $was_paused = cookieflame_monitoring_is_paused(); 3574 3836 $now_paused = ! $was_paused; 3575 3837 … … 3588 3850 // next cadence window rather than immediately. 3589 3851 $queue = get_option( 'cookieflame_notification_queue', array() ); 3590 $interval = cookieflame_ current_interval_minutes();3852 $interval = cookieflame_email_delay_minutes(); 3591 3853 if ( ! empty( $queue ) && $interval > 0 && ! wp_next_scheduled( 'cookieflame_send_digest' ) ) { 3592 3854 wp_schedule_single_event( time() + ( $interval * MINUTE_IN_SECONDS ), 'cookieflame_send_digest' ); … … 3930 4192 esc_html( $a['desc'] ?? '' ) 3931 4193 ); 4194 if ( ! empty( $a['help'] ) ) { 4195 printf( '<p class="description">%s</p>', esc_html( $a['help'] ) ); 4196 } 3932 4197 } 3933 4198 … … 4192 4457 </div> 4193 4458 4194 <?php if ( ! cookieflame_is_premium() ) : ?>4459 <?php if ( ! cookieflame_is_premium() && ! get_user_meta( get_current_user_id(), 'cookieflame_upgrade_banner_dismissed', true ) ) : ?> 4195 4460 <div class="cf-admin-upgrade-banner"> 4196 4461 <div class="cf-admin-upgrade-banner-icon">✨</div> … … 4200 4465 </div> 4201 4466 <a href="https://cookieflame.com/pricing" target="_blank" class="cf-admin-btn-upgrade"><?php esc_html_e( 'Upgrade to Pro', 'cookieflame' ); ?> →</a> 4467 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="cf-admin-upgrade-banner-dismiss"> 4468 <?php wp_nonce_field( 'cookieflame_dismiss_upgrade_banner' ); ?> 4469 <input type="hidden" name="action" value="cookieflame_dismiss_upgrade_banner" /> 4470 <input type="hidden" name="tab" value="<?php echo esc_attr( $active_tab ); ?>" /> 4471 <button type="submit" class="cf-admin-upgrade-banner-close" aria-label="<?php esc_attr_e( 'Dismiss this message', 'cookieflame' ); ?>" title="<?php esc_attr_e( 'Dismiss this message', 'cookieflame' ); ?>">×</button> 4472 </form> 4202 4473 </div> 4203 4474 <?php endif; ?> … … 4209 4480 <p><?php esc_html_e( 'Add this shortcode to the page you have set as your Privacy Policy page (under Settings > Privacy). It shows visitors which cookie categories are active, what each cookie does, and a button to change their consent choices.', 'cookieflame' ); ?></p> 4210 4481 <pre class="cf-admin-code-block">[cookieflame_declaration]</pre> 4482 <h3><?php esc_html_e( 'Cookie Settings Link', 'cookieflame' ); ?></h3> 4483 <p><?php esc_html_e( 'Put this shortcode in your footer or anywhere else to give visitors a "Cookie settings" link that reopens their choices. US visitors get the "Do Not Sell or Share My Personal Information" choice. You can change the wording with text="...". To use a menu item or your own button instead, give it the CSS class cc-open-settings.', 'cookieflame' ); ?></p> 4484 <pre class="cf-admin-code-block">[cookieflame_settings_link]</pre> 4211 4485 </div> 4212 4486 … … 4255 4529 <?php 4256 4530 settings_fields( 'cookieflame_settings_group' ); 4531 ?> 4532 <input type="hidden" name="cookieflame_settings[_form]" value="main" /> 4533 <?php 4257 4534 do_settings_sections( 'cookieflame' ); 4258 4535 submit_button(); 4259 4536 ?> 4260 4537 </form> 4538 4539 <?php 4540 /** 4541 * Fires on the Settings tab after the main settings form, so other 4542 * plugins can add their own section (with its own form and option). 4543 */ 4544 do_action( 'cookieflame_settings_sections' ); 4545 ?> 4261 4546 4262 4547 <div class="cf-admin-card cf-admin-card-warning"> … … 4454 4739 4455 4740 <h4 style="margin-top:16px;margin-bottom:8px;"><?php esc_html_e( 'How the scan works', 'cookieflame' ); ?></h4> 4456 <p style="margin-bottom:12px;"><?php esc_html_e( ' The scan reads cookies from this browser, right now. It does not crawl your website or check other visitors. If a cookie does not exist in your browser at this moment, it will not appear in the scan. That means you need to visit your own website first and trigger all the scripts that set cookies.', 'cookieflame' ); ?></p>4741 <p style="margin-bottom:12px;"><?php esc_html_e( 'Cookie Discovery uses your own browser. It records the cookies your browser picks up while you browse your site with every cookie allowed. It does not crawl your website or check other visitors, so a cookie only appears if a page you visit sets it. Cookies your visitors pick up are found separately by Watch for new cookies on the Cookie Monitoring tab.', 'cookieflame' ); ?></p> 4457 4742 4458 4743 <h4 style="margin-bottom:8px;"><?php esc_html_e( 'Before you start', 'cookieflame' ); ?></h4> 4459 <p style="margin-bottom:4px; color:#d63638;"><strong><?php esc_html_e( 'Check your tracking settings first.', 'cookieflame' ); ?></strong></p>4460 <p style="margin-bottom:12px;"><?php esc_html_e( ' Go to the Settings tab. Make sure "Track logged-in users" is checked and "Do not track this device" is unchecked. If either of these is blocking tracking, scripts will be suppressed for you even after you accept cookies, and no third-party cookies will appear in the scan. Save the settings and clear your site cache if you make changes. You can turn these back on after you have finished scanning.', 'cookieflame' ); ?></p>4744 <p style="margin-bottom:4px;"><strong><?php esc_html_e( 'Clear your page cache if you run one.', 'cookieflame' ); ?></strong></p> 4745 <p style="margin-bottom:12px;"><?php esc_html_e( 'You do not need to change any CookieFlame settings. While Cookie Discovery runs, "Exclude staff from tracking" and "Do not track this device" are paused for your browser, and they are put back when you click Finish Discovery.', 'cookieflame' ); ?></p> 4461 4746 4462 4747 <h4 style="margin-bottom:8px;"><?php esc_html_e( 'Step by step', 'cookieflame' ); ?></h4> … … 4464 4749 <li> 4465 4750 <strong><?php esc_html_e( 'Start Cookie Discovery.', 'cookieflame' ); ?></strong> 4466 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( ' When you click the Start Cookie Discovery button above, your website opens in this tab with all cookies temporarily accepted, so every tracking script can run and set its cookies. You need to be in the same browser you are using right now for wp-admin, because cookies are stored per browser.', 'cookieflame' ); ?></span>4751 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( 'Click Start Cookie Discovery above, then Open my site. Your site opens in this tab with every cookie allowed for your browser only, and a green bar at the top counts what is found. Use the browser you are using for wp-admin right now, because cookies are stored per browser.', 'cookieflame' ); ?></span> 4467 4752 </li> 4468 4753 <li> 4469 <strong><?php esc_html_e( ' Click "Accept All" onthe consent banner.', 'cookieflame' ); ?></strong>4470 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( ' This is essential. The plugin blocks all tracking scripts until consent is given. If you only accept some categories, only those scripts will run. You need to accept everything so every script fires and every cookie gets created.', 'cookieflame' ); ?></span>4754 <strong><?php esc_html_e( 'You will not see the consent banner.', 'cookieflame' ); ?></strong> 4755 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( 'Cookie Discovery allows every cookie for your browser while it runs, so every script on your site can run and set its cookies. Your visitors still see the banner as normal.', 'cookieflame' ); ?></span> 4471 4756 </li> 4472 4757 <li> … … 4484 4769 </li> 4485 4770 <li> 4486 <strong><?php esc_html_e( 'C ome back to this tab to see your results.', 'cookieflame' ); ?></strong>4487 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( ' The table will update with every cookie now sitting in your browser. Known cookies will have their details filled in automatically. Unknown cookies will be highlighted in yellow.', 'cookieflame' ); ?></span>4771 <strong><?php esc_html_e( 'Click Finish Discovery on the green bar.', 'cookieflame' ); ?></strong> 4772 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( 'You come back here, and the table lists every cookie found. Known cookies have their details filled in automatically. Unknown cookies are highlighted in yellow.', 'cookieflame' ); ?></span> 4488 4773 </li> 4489 4774 <li> … … 4500 4785 </li> 4501 4786 <li> 4502 <strong><?php esc_html_e( ' Re-enable your tracking protection.', 'cookieflame' ); ?></strong>4503 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( ' If you turned off "Do not track this device" in step 1, go back to the Settings tab and turn it back on. You can also click "Delete Non-Essential Cookies" on the Settings tab to clean up the tracking cookies that were just created during your scan.', 'cookieflame' ); ?></span>4787 <strong><?php esc_html_e( 'Tidy up your browser if you like.', 'cookieflame' ); ?></strong> 4788 <br /><span style="color:#666;font-size:13px;"><?php esc_html_e( 'Your tracking settings are put back automatically. To remove the tracking cookies your browser picked up during the scan, click "Delete Non-Essential Cookies from This Browser" on the Settings tab.', 'cookieflame' ); ?></span> 4504 4789 </li> 4505 4790 </ol> … … 4510 4795 <h4 style="margin-bottom:4px;"><?php esc_html_e( 'Why are some cookies not showing up?', 'cookieflame' ); ?></h4> 4511 4796 <ul style="margin:0 0 12px 20px;line-height:1.8;"> 4512 <li><strong><?php esc_html_e( 'You did not click Accept All:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'If you only accepted some categories, scripts in the other categories never ran. Their cookies do not exist. Go back and accept all categories.', 'cookieflame' ); ?></li> 4513 <li><strong><?php esc_html_e( 'Your tracking protection is still on:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'If "Do not track this device" is enabled or "Track logged-in users" is unchecked on the Settings tab, tracking is suppressed for your browser even after you accept cookies. Temporarily disable these before scanning.', 'cookieflame' ); ?></li> 4797 <li><strong><?php esc_html_e( 'Your page cache served an old page:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'Clear your page cache and run Cookie Discovery again.', 'cookieflame' ); ?></li> 4514 4798 <li><strong><?php esc_html_e( 'The cookie is page-specific:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'Some cookies are only set on specific pages (e.g. Stripe only on checkout, a chat widget only on contact pages). Visit those pages before re-scanning.', 'cookieflame' ); ?></li> 4515 4799 <li><strong><?php esc_html_e( 'The cookie is on a subdomain:', 'cookieflame' ); ?></strong> <?php esc_html_e( 'If you have subdomains (like app.yoursite.com), cookies locked to that subdomain are invisible from your main site. Enable "Share consent across subdomains" in Settings to share cookies, or use the "Add Cookie Manually" section below to register them by name.', 'cookieflame' ); ?></li> … … 4587 4871 <div id="cf-admin-discovery-idle"> 4588 4872 <div style="display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;"> 4589 <button type="button" id="cf-admin-discovery-start" class="cf-admin-btn-primary"><?php esc_html_e( 'Start Cookie Discovery ', 'cookieflame' ); ?></button>4590 <span style="font-size:13px;color:#50575e;flex:1;min-width:200px;"><?php esc_html_e( ' Temporarily disables tracking protection, opens your site in a new tab with all cookies accepted, and lets you browse a few pages. When you finish, it scans for cookies and restores your settings.', 'cookieflame' ); ?></span>4873 <button type="button" id="cf-admin-discovery-start" class="cf-admin-btn-primary"><?php esc_html_e( 'Start Cookie Discovery (uses your own browser)', 'cookieflame' ); ?></button> 4874 <span style="font-size:13px;color:#50575e;flex:1;min-width:200px;"><?php esc_html_e( 'Cookie Discovery uses your own browser: it allows every cookie for you only while you browse a few pages of your site in this tab. When you finish, it lists the cookies found and puts your settings back.', 'cookieflame' ); ?></span> 4591 4875 </div> 4592 4876 <p style="font-size:12px;color:#646970;margin:10px 0 0;"><?php esc_html_e( 'If you notice cookies you cannot explain after scanning, try running discovery in an incognito or private browser window. Browser extensions can occasionally inject tracking cookies that do not belong to your site, and incognito mode disables them by default. This is uncommon.', 'cookieflame' ); ?></p> … … 5002 5286 <?php if ( $cookieflame_paused ) : ?> 5003 5287 <div class="notice notice-warning is-dismissible" style="margin:0 0 16px;"> 5004 <p><strong><?php esc_html_e( ' Monitoring is now Off.', 'cookieflame' ); ?></strong> <?php esc_html_e( 'Cookie beacons and alert emails are suppressed. Your update interval is preserved for when you switch monitoringback on.', 'cookieflame' ); ?></p>5288 <p><strong><?php esc_html_e( 'Watch for new cookies is now Off.', 'cookieflame' ); ?></strong> <?php esc_html_e( 'Visitors\' browsers no longer report new cookies, and no emails are sent. Your email setting is kept for when you turn it back on.', 'cookieflame' ); ?></p> 5005 5289 </div> 5006 5290 <?php endif; ?> … … 5008 5292 <?php if ( $cookieflame_resumed ) : ?> 5009 5293 <div class="notice notice-success is-dismissible" style="margin:0 0 16px;"> 5010 <p><strong><?php esc_html_e( ' Monitoring is now On.', 'cookieflame' ); ?></strong> <?php esc_html_e( 'New cookies will be detected on the cadence you have configured.', 'cookieflame' ); ?></p>5294 <p><strong><?php esc_html_e( 'Watch for new cookies is now On.', 'cookieflame' ); ?></strong> <?php esc_html_e( 'New cookies appear in the Cookie Manager as soon as a visitor\'s browser reports them.', 'cookieflame' ); ?></p> 5011 5295 </div> 5012 5296 <?php endif; ?> 5013 5297 5014 <p class="cf-admin-tab-intro"><?php esc_html_e( ' Watch real visitor sessions in the background and surface new cookies as they appear.', 'cookieflame' ); ?></p>5298 <p class="cf-admin-tab-intro"><?php esc_html_e( 'Your visitors\' browsers report cookies as they browse your site, so new ones show up in the Cookie Manager without you scanning.', 'cookieflame' ); ?></p> 5015 5299 5016 5300 <?php $cookieflame_is_paused = cookieflame_monitoring_is_paused(); ?> 5017 5301 <div class="cf-admin-pause-card <?php echo $cookieflame_is_paused ? 'cf-admin-pause-card-paused' : 'cf-admin-pause-card-running'; ?>"> 5018 5302 <div class="cf-admin-pause-card-body"> 5019 <h3 class="cf-admin-pause-card-title"><?php esc_html_e( ' Visitor Monitoring', 'cookieflame' ); ?></h3>5020 <p class="cf-admin-pause-card-desc"><?php esc_html_e( ' Pause during site surgery (plugin swaps, theme changes, migrations). Your update interval is preserved.', 'cookieflame' ); ?></p>5303 <h3 class="cf-admin-pause-card-title"><?php esc_html_e( 'Watch for new cookies', 'cookieflame' ); ?></h3> 5304 <p class="cf-admin-pause-card-desc"><?php esc_html_e( 'Uses your visitors\' browsers. New cookies are recorded the moment a visitor\'s browser reports them. Turn it off for a while during big changes (plugin swaps, theme changes, migrations) if you prefer. Clear your page cache after switching, so cached pages pick up the change.', 'cookieflame' ); ?></p> 5021 5305 </div> 5022 5306 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="cf-admin-pause-card-action"> … … 5027 5311 <span class="cf-admin-toggle-label"><?php 5028 5312 echo $cookieflame_is_paused 5029 ? esc_html__( ' Monitoring: Off', 'cookieflame' )5030 : esc_html__( ' Monitoring: On', 'cookieflame' );5313 ? esc_html__( 'Watch for new cookies: Off', 'cookieflame' ) 5314 : esc_html__( 'Watch for new cookies: On', 'cookieflame' ); 5031 5315 ?></span> 5032 5316 </button> … … 5035 5319 5036 5320 <?php 5037 $notif_opts = get_option( 'cookieflame_settings', array() ); 5038 $notif_email = $notif_opts['notification_email'] ?? ''; 5039 $current_mins = cookieflame_normalise_interval( $notif_opts['digest_interval'] ?? 1440 ); 5040 $buckets = cookieflame_interval_buckets(); 5041 $bucket_label = array( 5042 0 => __( 'Off', 'cookieflame' ), 5043 15 => __( '15 minutes', 'cookieflame' ), 5044 60 => __( '1 hour', 'cookieflame' ), 5045 240 => __( '4 hours', 'cookieflame' ), 5046 1440 => __( 'Daily', 'cookieflame' ), 5047 10080 => __( 'Weekly', 'cookieflame' ), 5321 $notif_opts = get_option( 'cookieflame_settings', array() ); 5322 $notif_email = $notif_opts['notification_email'] ?? ''; 5323 $cf_frequency = cookieflame_email_frequency(); 5324 $cf_instant_ok = cookieflame_instant_email_available(); 5325 $cf_is_premium = cookieflame_is_premium(); 5326 if ( 'instant' === $cf_frequency && ! $cf_instant_ok ) { 5327 $cf_frequency = 'daily'; // What the site actually gets without Pro. 5328 } 5329 $cf_frequencies = array( 5330 'weekly' => __( 'Weekly', 'cookieflame' ), 5331 'daily' => __( 'Daily', 'cookieflame' ), 5332 'instant' => $cf_instant_ok ? __( 'Instantly', 'cookieflame' ) : __( 'Instantly (Pro)', 'cookieflame' ), 5333 'off' => __( 'Off', 'cookieflame' ), 5048 5334 ); 5049 // Dropdown ordering: fastest at top, then slower cadences,5050 // with Off at the very bottom as the "I don't want this"5051 // answer rather than a cadence choice. Off is excluded from5052 // the ordered list and re-appended at the end.5053 $cf_dropdown_order = array( 15, 60, 240, 1440, 10080 );5054 $cf_ordered_buckets = array();5055 $cf_buckets_by_minutes = array();5056 foreach ( $buckets as $cf_b ) {5057 $cf_buckets_by_minutes[ $cf_b['minutes'] ] = $cf_b;5058 }5059 foreach ( $cf_dropdown_order as $cf_m ) {5060 if ( isset( $cf_buckets_by_minutes[ $cf_m ] ) ) {5061 $cf_ordered_buckets[] = $cf_buckets_by_minutes[ $cf_m ];5062 }5063 }5064 if ( isset( $cf_buckets_by_minutes[ 0 ] ) ) {5065 $cf_ordered_buckets[] = $cf_buckets_by_minutes[ 0 ];5066 }5067 $cf_is_premium = cookieflame_is_premium();5068 5335 ?> 5069 5336 … … 5075 5342 <tr> 5076 5343 <th scope="row"> 5077 <label for="cf-admin- digest-interval" title="<?php esc_attr_e( 'How often CookieFlame refreshes the cookie list and sends digest alerts. Shorter cadences surface new cookies sooner; longer cadences group changes into a single summary.', 'cookieflame' ); ?>"><?php esc_html_e( 'Update interval', 'cookieflame' ); ?></label>5344 <label for="cf-admin-email-frequency"><?php esc_html_e( 'Email me about changes', 'cookieflame' ); ?></label> 5078 5345 </th> 5079 5346 <td> 5080 <select id="cf-admin-digest-interval" name="digest_interval"> 5081 <?php foreach ( $cf_ordered_buckets as $cf_b ) : 5082 $cf_min = intval( $cf_b['minutes'] ); 5083 $cf_tier = (string) ( $cf_b['tier'] ?? 'free' ); 5084 $cf_label = $bucket_label[ $cf_min ] ?? ( $cf_min . ' min' ); 5085 $cf_is_pro_tier = ( 'pro' === $cf_tier ); 5086 $cf_disabled = ( $cf_is_pro_tier && ! $cf_is_premium ); 5087 if ( $cf_is_pro_tier ) { 5088 /* translators: %s: human-readable interval label, e.g. "15 minutes" */ 5089 $cf_label = sprintf( __( '%s (Pro)', 'cookieflame' ), $cf_label ); 5090 } 5091 ?> 5092 <option value="<?php echo esc_attr( $cf_min ); ?>"<?php 5093 selected( $current_mins, $cf_min ); 5094 disabled( $cf_disabled, true ); 5347 <select id="cf-admin-email-frequency" name="email_frequency"> 5348 <?php foreach ( $cf_frequencies as $cf_value => $cf_label ) : ?> 5349 <option value="<?php echo esc_attr( $cf_value ); ?>"<?php 5350 selected( $cf_frequency, $cf_value ); 5351 disabled( 'instant' === $cf_value && ! $cf_instant_ok, true ); 5095 5352 ?>><?php echo esc_html( $cf_label ); ?></option> 5096 5353 <?php endforeach; ?> 5097 5354 </select> 5098 <p class="description"><?php esc_html_e( ' Pro intervals are visible above so you can see what an upgrade adds. They unlock when CookieFlame Pro is active.', 'cookieflame' ); ?></p>5355 <p class="description"><?php esc_html_e( 'One email lists every new cookie found since the last one. Nothing is sent when there is nothing new.', 'cookieflame' ); ?></p> 5099 5356 <?php if ( ! $cf_is_premium ) : ?> 5100 <p class="description">< a href="https://cookieflame.com/pricing" target="_blank"><?php esc_html_e( 'Upgrade to CookieFlame Pro', 'cookieflame' ); ?></a></p>5357 <p class="description"><?php esc_html_e( 'Instant emails come with CookieFlame Pro.', 'cookieflame' ); ?> <a href="https://cookieflame.com/pricing" target="_blank"><?php esc_html_e( 'Upgrade to CookieFlame Pro', 'cookieflame' ); ?></a></p> 5101 5358 <?php endif; ?> 5102 5359 </td> … … 5113 5370 <p style="display:flex;align-items:center;gap:14px;flex-wrap:wrap;"> 5114 5371 <?php submit_button( __( 'Save Cookie Monitoring Settings', 'cookieflame' ), 'primary', 'submit', false ); ?> 5115 <span class="description" style="margin:0;"><?php esc_html_e( 'Clear your page cache after saving so visitors pick up the new beacon configuration.', 'cookieflame' ); ?></span>5116 5372 </p> 5117 5373 </form> … … 5123 5379 <div class="cf-admin-card"> 5124 5380 <h2><?php esc_html_e( 'Banner Preview', 'cookieflame' ); ?></h2> 5125 <p><?php esc_html_e( ' Preview how the consent banner appears to visitors in different regions. Select a region and theme to see the exact banner they would see.', 'cookieflame' ); ?></p>5381 <p><?php esc_html_e( 'Choose a region and theme to see the banner those visitors get.', 'cookieflame' ); ?></p> 5126 5382 5127 5383 <div style="display:flex;gap:16px;flex-wrap:wrap;margin:20px 0;"> 5128 5384 <div> 5129 5385 <label style="font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:#50575e;display:block;margin-bottom:6px;"><?php esc_html_e( 'Region', 'cookieflame' ); ?></label> 5130 <select id="cf-admin-preview-region" style="min-width:200px;padding:8px 12px;border-radius:6px;border:1px solid #dcdcde;">5386 <select id="cf-admin-preview-region" style="min-width:200px;padding:8px 36px 8px 12px;border-radius:6px;border:1px solid #dcdcde;"> 5131 5387 <option value="gdpr"><?php esc_html_e( 'GDPR / Opt-in (EU, EEA, UK, Brazil, South Africa, Switzerland)', 'cookieflame' ); ?></option> 5132 5388 <option value="ccpa"><?php esc_html_e( 'CCPA / Opt-out (United States)', 'cookieflame' ); ?></option> … … 5136 5392 <div> 5137 5393 <label style="font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:#50575e;display:block;margin-bottom:6px;"><?php esc_html_e( 'Theme', 'cookieflame' ); ?></label> 5138 <select id="cf-admin-preview-theme" style="min-width:200px;padding:8px 12px;border-radius:6px;border:1px solid #dcdcde;">5394 <select id="cf-admin-preview-theme" style="min-width:200px;padding:8px 36px 8px 12px;border-radius:6px;border:1px solid #dcdcde;"> 5139 5395 <option value="light"><?php esc_html_e( 'Light', 'cookieflame' ); ?></option> 5140 5396 <option value="dark"><?php esc_html_e( 'Dark', 'cookieflame' ); ?></option> 5141 5397 </select> 5142 5398 </div> 5399 <div id="cf-admin-preview-reject-wrap" style="align-self:flex-end;padding-bottom:8px;"> 5400 <label><input type="checkbox" id="cf-admin-preview-reject" /> <?php esc_html_e( 'Show "Decline all" on the first screen', 'cookieflame' ); ?></label> 5401 </div> 5402 <div id="cf-admin-preview-gpc-wrap" style="align-self:flex-end;padding-bottom:8px;display:none;"> 5403 <label><input type="checkbox" id="cf-admin-preview-gpc" /> <?php esc_html_e( 'Visitor\'s browser sends Global Privacy Control', 'cookieflame' ); ?></label> 5404 </div> 5143 5405 </div> 5144 5145 <div style="margin-top:8px;font-size:12px;color:#888;line-height:1.8;"> 5146 <strong><?php esc_html_e( 'GDPR / Opt-in:', 'cookieflame' ); ?></strong> 5147 <?php esc_html_e( 'Full consent banner with Accept All, Decline All, and Manage Preferences. Visitors must opt in before non-essential cookies are set.', 'cookieflame' ); ?><br /> 5148 <span style="color:#aaa;"><?php esc_html_e( 'Countries: Austria, Belgium, Bulgaria, Croatia, Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Ireland, Italy, Latvia, Lithuania, Luxembourg, Malta, Netherlands, Poland, Portugal, Romania, Slovakia, Slovenia, Spain, Sweden (EU); Iceland, Liechtenstein, Norway (EEA); United Kingdom, Guernsey, Jersey, Isle of Man (UK); Switzerland (revFADP); Brazil (LGPD); South Africa (POPIA).', 'cookieflame' ); ?></span><br /><br /> 5149 <strong><?php esc_html_e( 'CCPA / Opt-out:', 'cookieflame' ); ?></strong> 5150 <?php esc_html_e( 'Compact notice bar with a "Do Not Sell My Personal Information" link. All cookies are granted by default. The visitor can opt out of the sale or sharing of personal data. If Global Privacy Control (GPC) is detected in the browser, marketing cookies are automatically suppressed.', 'cookieflame' ); ?><br /> 5151 <span style="color:#aaa;"><?php esc_html_e( 'Countries: United States (CCPA/CPRA applies to California residents; GPC is legally binding in 12+ states including California, Colorado, Connecticut, Texas, Montana, Oregon, and others).', 'cookieflame' ); ?></span><br /><br /> 5152 <strong><?php esc_html_e( 'No banner:', 'cookieflame' ); ?></strong> 5153 <?php esc_html_e( 'No consent banner is shown. All cookies are auto-granted. The cookie icon appears in the bottom-left corner so visitors can manage preferences if they choose. This applies to visitors in countries not covered by the regions above.', 'cookieflame' ); ?> 5406 <p class="description" id="cf-admin-preview-reject-note" style="margin-top:-8px;"><?php esc_html_e( 'The "Decline all" box here only changes the preview. To change your banner, use the setting on the Settings tab.', 'cookieflame' ); ?></p> 5407 5408 <div id="cf-admin-preview-container" style="position:relative;border-radius:12px;overflow:hidden;margin-top:16px;"></div> 5409 5410 <div id="cf-admin-preview-notes" style="margin-top:16px;font-size:12px;color:#888;line-height:1.8;"> 5411 <p data-region="gdpr" style="margin:0;"><strong><?php esc_html_e( 'GDPR / Opt-in:', 'cookieflame' ); ?></strong> 5412 <?php esc_html_e( 'Full consent banner with Accept All and Manage preferences. Decline All is under Manage preferences, and you can also show it next to Accept All with the setting on the Settings tab. Visitors must opt in before non-essential cookies are set.', 'cookieflame' ); ?><br /> 5413 <span style="color:#aaa;"><?php esc_html_e( 'Countries: Austria, Belgium, Bulgaria, Croatia, Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Ireland, Italy, Latvia, Lithuania, Luxembourg, Malta, Netherlands, Poland, Portugal, Romania, Slovakia, Slovenia, Spain, Sweden (EU); Iceland, Liechtenstein, Norway (EEA); United Kingdom, Guernsey, Jersey, Isle of Man (UK); Switzerland (revFADP); Brazil (LGPD); South Africa (POPIA).', 'cookieflame' ); ?></span></p> 5414 <p data-region="ccpa" style="margin:0;display:none;"><strong><?php esc_html_e( 'CCPA / Opt-out:', 'cookieflame' ); ?></strong> 5415 <?php esc_html_e( 'Compact notice with a "Do Not Sell or Share My Personal Information" link, which opens a real opt-out choice. Cookies are allowed by default. If the visitor\'s browser sends Global Privacy Control (GPC), they are opted out of selling and sharing straight away: Marketing stays off, the notice says the signal was honoured, and OK only closes the notice. They can opt back in only through a separate choice, and a later visit without GPC keeps the opt-out.', 'cookieflame' ); ?><br /> 5416 <span style="color:#aaa;"><?php esc_html_e( 'Countries: United States. The same notice covers California (CCPA/CPRA) and the other states with similar laws.', 'cookieflame' ); ?></span></p> 5417 <p data-region="none" style="margin:0;display:none;"><strong><?php esc_html_e( 'No banner:', 'cookieflame' ); ?></strong> 5418 <?php esc_html_e( 'No consent banner is shown and all cookies are allowed. Visitors can still change their choices from a [cookieflame_settings_link] link or your [cookieflame_declaration] page. This applies to visitors in countries not covered by the regions above.', 'cookieflame' ); ?></p> 5154 5419 </div> 5155 5420 </div> 5156 5157 <div id="cf-admin-preview-container" style="position:relative;border-radius:12px;overflow:hidden;min-height:400px;margin-top:4px;">5158 </div>5159 5421 </div> 5160 5422 5161 <?php5162 $opts = get_option( 'cookieflame_settings', array() );5163 $preview_config = array(5164 'privacyUrl' => $opts['privacy_url'] ?? '/privacy-policy',5165 'ccpaUrl' => ( $opts['privacy_url'] ?? '/privacy-policy' ) . '#do-not-sell',5166 );5167 $preview_cfg_json = wp_json_encode( $preview_config );5168 $preview_theme_json = wp_json_encode( $opts['theme'] ?? 'light' );5169 $preview_js = <<<'JS'5170 (function () {5171 var regionEl = document.getElementById("cf-admin-preview-region");5172 var themeEl = document.getElementById("cf-admin-preview-theme");5173 var container = document.getElementById("cf-admin-preview-container");5174 if (!regionEl || !themeEl || !container) return;5175 5176 var cfg = __COOKIEFLAME_PREVIEW_CFG__;5177 5178 var txt = {5179 title: "We value your privacy",5180 description: "We use cookies to improve your experience, analyse traffic and personalise content. You can choose which categories to allow.",5181 acceptAll: "Accept All",5182 declineAll: "Decline All",5183 savePrefs: "Save Preferences",5184 managePrefs: "Manage preferences",5185 hidePrefs: "Hide preferences",5186 ccpaText: "We use cookies and similar technologies. Under California law, you have the right to opt out of the sale of your personal information.",5187 ccpaDnsmpi: "Do Not Sell My Personal Information",5188 ccpaOk: "OK"5189 };5190 5191 var categories = [5192 { id: "necessary", name: "Necessary", desc: "Required for the site to function. Cannot be disabled.", required: true },5193 { id: "preferences", name: "Preferences", desc: "Remember your settings and personalisation choices.", required: false },5194 { id: "analytics", name: "Analytics", desc: "Help us understand how visitors use the site.", required: false },5195 { id: "marketing", name: "Marketing", desc: "Used to deliver relevant ads and track campaigns.", required: false }5196 ];5197 5198 function esc(s) { var d = document.createElement("div"); d.textContent = s; return d.innerHTML; }5199 5200 function render() {5201 var region = regionEl.value;5202 var theme = themeEl.value;5203 var isDark = theme === "dark";5204 var bg = isDark ? "#121212" : "#f5f5f5";5205 var bannerBg = isDark ? "#1c1c1e" : "#fff";5206 var textColor = isDark ? "#f5f5f7" : "#1d1d1f";5207 var mutedColor = isDark ? "#98989d" : "#555";5208 var borderColor = isDark ? "#2c2c2e" : "#e8e8ed";5209 var catDescColor = isDark ? "#636366" : "#888";5210 var acceptBg = isDark ? "#0a84ff" : "#0071e3";5211 var declineBg = isDark ? "#2c2c2e" : "#e8e8ed";5212 var declineColor = isDark ? "#f5f5f7" : "#1d1d1f";5213 var saveBg = isDark ? "#30d158" : "#34c759";5214 var detailsColor = isDark ? "#636366" : "#888";5215 var linkColor = isDark ? "#2997ff" : "#0071e3";5216 var toggleBg = isDark ? "#3a3a3c" : "#ccc";5217 var toggleActiveBg = isDark ? "#0a84ff" : "#0071e3";5218 var sliderBg = isDark ? "#f5f5f7" : "#fff";5219 var poweredColor = isDark ? "#888" : "#ccc";5220 5221 var html = '<div style="background:' + bg + ';padding:60px 20px 20px;min-height:400px;display:flex;align-items:flex-end;justify-content:center;border-radius:12px;">';5222 5223 if (region === "none") {5224 html += '<div style="text-align:center;padding:40px;color:' + mutedColor + ';font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;font-size:14px;align-self:center;">';5225 html += '<div style="font-size:48px;margin-bottom:16px;">🌍</div>';5226 html += '<div style="font-weight:700;font-size:16px;color:' + textColor + ';margin-bottom:8px;">No banner shown</div>';5227 html += '<div>Visitors outside the regulated regions (EU/EEA, UK, US,<br>Switzerland, Brazil, South Africa) see no consent banner.<br>All cookies are auto-granted. The cookie icon appears in the<br>bottom-left corner so they can manage preferences if they choose.</div>';5228 html += '<div style="margin-top:24px;width:48px;height:48px;border-radius:50%;background:' + (isDark ? "#f5f5f7" : "#1d1d1f") + ';display:inline-flex;align-items:center;justify-content:center;">';5229 html += '<svg width="24" height="24" viewBox="0 0 38 38" fill="none"><path d="M19.1 1.16c-.06-.42-.38-.77-.8-.83A11.8 11.8 0 0 0 13.28 1.2L7.5 4.3A11.8 11.8 0 0 0 3.95 7.98L1.09 13.9A11.8 11.8 0 0 0 .38 18.96l1.13 6.47a11.8 11.8 0 0 0 3.53 4.99l4.71 4.56a11.8 11.8 0 0 0 4.59 2.24l6.48.91a11.8 11.8 0 0 0 5.52-.89l5.78-3.09a11.8 11.8 0 0 0 3.55-3.69l2.87-5.9a11.8 11.8 0 0 0 .71-5.08c-.07-.41-.42-.73-.83-.79a14.8 14.8 0 0 1-8.16-7.61c-.14-.59-.63-1.05-1.23-1.14A14.8 14.8 0 0 1 19.1 1.16ZM15.25 10.25a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Zm-5 15a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0Zm17.5-5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z" fill="' + (isDark ? "#1d1d1f" : "#fff") + '"/></svg>';5230 html += '</div>';5231 html += '</div>';5232 html += '</div>';5233 container.innerHTML = html;5234 return;5235 }5236 5237 if (region === "ccpa") {5238 html += '<div style="position:relative;bottom:0;left:0;right:0;width:100%;background:' + bannerBg + ';box-shadow:0 -8px 40px rgba(0,0,0,.15);border-radius:20px 20px 0 0;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;font-size:14px;color:' + textColor + ';">';5239 html += '<div style="max-width:720px;margin:0 auto;padding:20px 32px;display:flex;align-items:center;gap:20px;flex-wrap:wrap;">';5240 html += '<div style="flex:1;min-width:280px;"><p style="margin:0;color:' + mutedColor + ';">' + esc(txt.ccpaText) + '</p></div>';5241 html += '<div style="display:flex;gap:10px;align-items:center;flex-shrink:0;">';5242 html += '<a href="' + esc(cfg.ccpaUrl) + '" style="color:' + linkColor + ';text-decoration:underline;font-size:13px;font-weight:500;white-space:nowrap;">' + esc(txt.ccpaDnsmpi) + '</a>';5243 html += '<button style="display:inline-flex;align-items:center;justify-content:center;border-radius:12px;border:none;font-weight:700;cursor:pointer;background:' + acceptBg + ';color:#fff;font-size:15px;padding:14px 32px;box-shadow:0 2px 8px rgba(0,113,227,.3);">' + esc(txt.ccpaOk) + '</button>';5244 html += '</div></div>';5245 html += '<div style="text-align:center;padding:0 0 12px;font-size:11px;color:' + poweredColor + ';"><a href="https://www.cookieflame.com" target="_blank" rel="noopener" style="color:' + poweredColor + ';text-decoration:none;">Powered by CookieFlame</a></div>';5246 html += '</div>';5247 html += '</div>';5248 container.innerHTML = html;5249 return;5250 }5251 5252 html += '<div style="position:relative;bottom:0;left:0;right:0;width:100%;background:' + bannerBg + ';box-shadow:0 -8px 40px rgba(0,0,0,.15);border-radius:20px 20px 0 0;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;font-size:14px;color:' + textColor + ';">';5253 html += '<div style="max-width:720px;margin:0 auto;padding:32px 32px 20px;">';5254 html += '<p style="font-size:20px;font-weight:800;margin:0 0 8px;letter-spacing:-.3px;">' + esc(txt.title) + '</p>';5255 html += '<p style="margin:0 0 20px;color:' + mutedColor + ';font-size:14px;line-height:1.6;">' + esc(txt.description) + ' <a href="' + esc(cfg.privacyUrl) + '" style="color:' + linkColor + ';text-decoration:none;font-weight:500;">Privacy Policy</a></p>';5256 5257 html += '<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:16px;">';5258 html += '<button style="display:inline-flex;align-items:center;justify-content:center;border-radius:12px;border:none;font-weight:700;cursor:pointer;background:' + acceptBg + ';color:#fff;font-size:15px;padding:14px 32px;box-shadow:0 2px 8px rgba(0,113,227,.3);">' + esc(txt.acceptAll) + '</button>';5259 html += '<button id="cf-admin-preview-manage" style="background:transparent;color:' + detailsColor + ';padding:11px 4px;font-weight:500;font-size:13px;border:none;cursor:pointer;">' + esc(txt.managePrefs) + '</button>';5260 html += '</div>';5261 5262 html += '<div id="cf-admin-preview-cats" style="display:none;margin:0 0 16px;border:1px solid ' + borderColor + ';border-radius:14px;overflow:hidden;">';5263 for (var i = 0; i < categories.length; i++) {5264 var cat = categories[i];5265 var isLast = i === categories.length - 1;5266 var checkedAttr = cat.required ? " checked disabled" : "";5267 var alwaysOn = cat.required ? ' <span style="font-weight:400;font-size:11px;color:' + (isDark ? "#48484a" : "#aaa") + ';">(always on)</span>' : "";5268 5269 html += '<div style="padding:16px 20px;' + (isLast ? "" : "border-bottom:1px solid " + borderColor + ";") + 'display:flex;justify-content:space-between;align-items:center;gap:16px;">';5270 html += '<div><div style="font-weight:700;font-size:14px;">' + esc(cat.name) + alwaysOn + '</div>';5271 html += '<div style="font-size:12px;color:' + catDescColor + ';margin-top:3px;">' + esc(cat.desc) + '</div></div>';5272 html += '<label style="position:relative;width:46px;height:26px;flex-shrink:0;display:block;">';5273 html += '<input type="checkbox"' + checkedAttr + ' style="opacity:0;width:0;height:0;position:absolute;" />';5274 html += '<span style="position:absolute;inset:0;background:' + (cat.required ? toggleActiveBg + ";opacity:.5" : toggleBg) + ';border-radius:26px;cursor:' + (cat.required ? "default" : "pointer") + ';transition:background .2s;"></span>';5275 html += '<span style="position:absolute;width:20px;height:20px;left:' + (cat.required ? "23px" : "3px") + ';bottom:3px;background:' + sliderBg + ';border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.15);transition:transform .2s;"></span>';5276 html += '</label></div>';5277 }5278 html += '</div>';5279 5280 html += '<div id="cf-admin-preview-pref-btns" style="display:none;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:8px;">';5281 html += '<button style="display:inline-flex;align-items:center;justify-content:center;border-radius:12px;border:none;font-weight:700;cursor:pointer;background:' + saveBg + ';color:#fff;font-size:14px;padding:13px 28px;box-shadow:0 2px 8px rgba(52,199,89,.3);">' + esc(txt.savePrefs) + '</button>';5282 html += '<button style="display:inline-flex;align-items:center;justify-content:center;border-radius:12px;border:none;font-weight:700;cursor:pointer;background:' + declineBg + ';color:' + declineColor + ';font-size:13px;padding:11px 22px;">' + esc(txt.declineAll) + '</button>';5283 html += '</div>';5284 5285 html += '</div>';5286 html += '<div style="text-align:center;padding:0 0 16px;font-size:11px;color:' + poweredColor + ';"><a href="https://www.cookieflame.com" target="_blank" rel="noopener" style="color:' + poweredColor + ';text-decoration:none;">Powered by CookieFlame</a></div>';5287 html += '</div>';5288 html += '</div>';5289 5290 container.innerHTML = html;5291 5292 var manageBtn = document.getElementById("cf-admin-preview-manage");5293 var catsEl = document.getElementById("cf-admin-preview-cats");5294 var prefBtnsEl = document.getElementById("cf-admin-preview-pref-btns");5295 if (manageBtn && catsEl) {5296 manageBtn.addEventListener("click", function () {5297 var open = catsEl.style.display === "none";5298 catsEl.style.display = open ? "block" : "none";5299 if (prefBtnsEl) prefBtnsEl.style.display = open ? "flex" : "none";5300 manageBtn.textContent = open ? txt.hidePrefs : txt.managePrefs;5301 });5302 }5303 }5304 5305 render();5306 regionEl.addEventListener("change", render);5307 themeEl.addEventListener("change", render);5308 5309 themeEl.value = __COOKIEFLAME_PREVIEW_THEME__;5310 render();5311 })();5312 JS;5313 $preview_js = str_replace(5314 array( '__COOKIEFLAME_PREVIEW_CFG__', '__COOKIEFLAME_PREVIEW_THEME__' ),5315 array( $preview_cfg_json, $preview_theme_json ),5316 $preview_js5317 );5318 wp_print_inline_script_tag( $preview_js );5319 ?>5320 5423 5321 5424 <?php endif; ?> -
cookieflame/trunk/readme.txt
r3769546 r3771537 5 5 Tested up to: 7.1 6 6 Requires PHP: 8.0 7 Stable tag: 5. 3.07 Stable tag: 5.4.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 Cookie consent for WordPress that finds cookies automatically. Runs continuously, no external service, correct banner for GDPR, CCPA, LGPD, POPIA.11 Cookie consent for WordPress that finds cookies automatically. Self-hosted, with the right banner for GDPR, CCPA, LGPD and POPIA visitors. 12 12 13 13 == Description == 14 14 15 CookieFlame is a cookie consent plugin for WordPress that does three things most other free plugins do not. It finds the cookies on your site automatically by watching real visitor sessions. It runs entirely on your own server with no outside connections. And it shows the right banner for each visitor based on where they are, rather than showing a single GDPR banner to the whole world.15 CookieFlame is a cookie consent plugin that finds your site's cookies for you, runs on your own server, and shows each visitor the banner their region requires. Full details are at [cookieflame.com](https://cookieflame.com). 16 16 17 It is built by [Zoobu] and the full product site is at [cookieflame.com](https://cookieflame.com). 17 = Finds cookies for you = 18 18 19 = 🔍 Cookie discovery, continuously = 19 * **Watch for new cookies:** your visitors' browsers report cookies as they browse, so a new plugin's or tag's cookies appear in the Cookie Manager without you scanning. Get an email about changes weekly, daily, or not at all. 20 * **Cookie Discovery:** for a full list now, browse your own site with every cookie allowed for your browser only. It uses your own browser. 20 21 21 Most consent plugins expect you to know which cookies your site sets. CookieFlame does not. A lightweight beacon watches real visitor sessions in the background and reports any cookies it has not seen before. New plugin installed? Its cookies surface automatically. Tracking tag changed? Picked up next time a visitor triggers it. No scheduled scans, no headless browsers, no external scanning service. 22 = The right banner for each region = 22 23 23 For a one-off inventory you can also run an on-demand Discovery scan from the admin. Browse your own site as logged in, watch the discovery bar accumulate cookies and detected services, click Finish when done. 24 * UK, EU/EEA, Switzerland, Brazil and South Africa: an opt-in banner with Accept All and Manage preferences. You can also show Decline All on the first screen (off by default). 25 * United States: a short notice with a "Do Not Sell or Share My Personal Information" link that opens a real opt-out choice. 26 * Everywhere else: no banner. 24 27 25 = 🌍 Correct behaviour per region = 28 Country comes from CDN headers such as Cloudflare's, or a free MaxMind database you upload, with the time zone as a fallback. If none works, the opt-in banner is shown. 26 29 27 CookieFlame detects visitor country and shows the banner that region actually requires: 30 = Global Privacy Control = 28 31 29 * EU, UK, EEA, Switzerland: full two-step GDPR opt-in banner with Accept, Manage Preferences, and Decline 30 * California and other US states with similar laws: compact CCPA opt-out notice with a Do Not Sell link 31 * Brazil (LGPD) and South Africa (POPIA): GDPR-style opt-in banner 32 * Everywhere else: no banner, all cookies auto-granted 32 A browser that sends Global Privacy Control (GPC) is opted out of selling and sharing straight away: marketing cookies stay off and Google's ad signals are denied. US visitors see that the signal was honoured, and OK only closes the notice. The opt-out is saved, so a later visit without GPC keeps it. A visitor can opt back in only through a separate, clear choice. 33 33 34 Detection uses three layers in order: Cloudflare and other CDN country headers first (zero latency, works with every caching setup), then a local MaxMind GeoLite2 database if uploaded, then a browser timezone heuristic as a last-resort fallback. If all three fail, the plugin defaults to GDPR opt-in for safety rather than silently granting consent. 34 = Blocks trackers before consent = 35 35 36 = 🔒 No outside connections = 36 Tracking scripts are rewritten on your server before the page is sent, so blocking works with WP Rocket, LiteSpeed, Cloudflare APO and other caches. Built-in patterns cover Google Analytics, Google Ads, Google Tag Manager, Microsoft Ads, Meta, X (Twitter), LinkedIn, Pinterest, Reddit and TikTok. Cookies you classify get matching rules automatically. 37 37 38 CookieFlame does not load JavaScript from any content delivery network, does not call any geo API, does not phone home. Every part of the consent flow runs on your server and in the visitor's browser. The only optional outbound request is Google Tag Manager itself, if you enter a GTM container ID in the settings (see the External services section below).38 YouTube, Vimeo, Google Maps, Spotify, SoundCloud, Dailymotion and Elementor videos show a click-to-load placeholder until the visitor consents. 39 39 40 This matters because a consent tool that itself sets third-party cookies is an awkward contradiction, and because self-hosted plugins keep working during outages of services you did not choose to depend on. 40 = Google Consent Mode v2 = 41 41 42 = 🚫 Server-side script blocking = 42 All Google consent signals start as denied before any tag runs and update the moment a visitor chooses. Returning visitors' choices are restored before Google Tag Manager loads. 43 43 44 When a visitor has not granted consent, CookieFlame rewrites tracking scripts in the HTML output so the browser does not execute them. This happens before the page leaves your server, which means it works with any caching stack: WP Rocket, LiteSpeed, Cloudflare APO, W3 Total Cache, and others all hold the correct pre-modified HTML. A lighter JavaScript layer handles scripts injected later by GTM or other plugins. 44 = For developers = 45 45 46 Known trackers are blocked from the moment you install the plugin. Ten services ship with built-in patterns: Google Analytics, Google Ads, Google Tag Manager, Microsoft Ads, Meta and Facebook, Twitter and X, LinkedIn, Pinterest, Reddit, TikTok. Anything else you classify in the Cookie Manager gets a matching block rule generated automatically. 46 * Events: cc:ready, cc:update, cc:accept-all, cc:decline-all, cc:save and cc:banner-rendered. 47 * JavaScript API: CookieConsent.getState(), show(), showSettings() and more. 48 * [cookieflame_settings_link] shortcode, or the class cc-open-settings on any link, to reopen a visitor's choices. 49 * CSS custom properties (--cc-accent, --cc-bg, --cc-radius, --cc-font and others) to restyle the banner. 50 * Filters cookieflame_frontend_config and cookieflame_digest_email, and the action cookieflame_settings_sections. 51 * An optional consent log endpoint for your own audit trail. 47 52 48 = 🪟 Iframe facades=53 = CookieFlame Pro = 49 54 50 YouTube, Vimeo, Google Maps, Spotify, SoundCloud, Dailymotion, and Elementor video widgets are replaced with click-to-accept placeholders until the visitor grants consent. The iframe source is stripped synchronously before the embed has a chance to contact its origin.55 Pro identifies and classifies cookies automatically, creates the blocking rules, and adds instant emails. See [cookieflame.com](https://cookieflame.com). 51 56 52 = 📊 Google Consent Mode v2=57 = Compliance note = 53 58 54 All seven Google consent signals are managed automatically and set to denied by default before any tag runs. When a visitor grants consent, the correct signals update immediately. Returning visitors keep their stored choices, with the signals restored before GTM loads (so the first dataLayer push is already correct). 55 56 Works with a GTM container ID you paste into the settings. No separate GTM consent template, no additional configuration. The plugin also injects the body noscript tag in the correct position. 57 58 = 🛡️ Global Privacy Control = 59 60 GPC is detected automatically and suppresses marketing consent for CCPA visitors. Required by California, Colorado, Connecticut, Texas, and nine other US states. Turning GPC off later restores the visitor's original stored choice rather than overwriting it. 61 62 = 📜 IAB TCF v2.2 = 63 64 A TCF v2.2 stub is registered on every page so ad-tech vendors that need TCF signals can read them. 65 66 = 🚀 Lightweight and framework-free = 67 68 The frontend JavaScript is a single 37 KB file (12 KB gzipped) with no jQuery and no framework dependencies. A separate facades bundle is only loaded on pages that actually contain a blockable embed. The Discovery bundle only loads for administrators during an active scan. 69 70 = 👨💻 Developer-friendly = 71 72 * Event API with CustomEvents: cc:ready, cc:update, cc:accept-all, cc:decline-all, cc:save 73 * Public JavaScript API for reading consent state and showing the banner on demand 74 * Per-category onAccept and onReject callbacks 75 * Extension points via WordPress filters and actions 76 * Optional consent log endpoint for audit trails 77 78 = ✨ Want automatic classification? = 79 80 CookieFlame Pro adds automatic cookie classification using a central database and AI. You click Auto-Identify All, and the plugin classifies every unclassified cookie with vendor, category, and purpose, then generates the blocking rules automatically. Flat annual pricing by number of sites, no pageview limits. Learn more at [cookieflame.com](https://cookieflame.com). 81 82 = 📋 Compliance note = 83 84 CookieFlame provides tools that help support compliance with GDPR, CCPA, LGPD, POPIA and similar regulations. Compliance itself is the site owner's responsibility and depends on how the plugin is configured. For legal advice, consult a qualified professional. 59 CookieFlame helps with GDPR, CCPA, LGPD, POPIA and similar laws; how you set it up is your responsibility. For legal advice, ask a qualified professional. 85 60 86 61 == Installation == 87 62 88 1. Upload the `cookieflame` folder to `/wp-content/plugins/`, or install via Plugins > Add New in the WordPress admin89 2. Activate the plugin through the Plugins menu90 3. Go to **CookieFlame** in the admin menu to review settings63 1. Install from Plugins > Add New, or upload the `cookieflame` folder to `/wp-content/plugins/`. 64 2. Activate the plugin. 65 3. Open **CookieFlame** in the admin menu to check the settings. 91 66 92 That is it. The plugin works immediately with sensible defaults. The ten built-in tracking services are blocked before any visitor can trigger them, and the banner shows correctly for each region. 93 94 For best geo detection, either run Cloudflare (the plugin reads the CF-IPCountry header automatically) or upload a free MaxMind GeoLite2 database on the Geo Detection tab. Neither is required. 67 It works straight away. For the best country detection, use Cloudflare or upload a free MaxMind database on the Geo Detection tab. 95 68 96 69 == Frequently Asked Questions == … … 98 71 = Do I need to know which cookies my site sets? = 99 72 100 No. CookieFlame watches real visitor sessions and reports new cookies to the Cookie Manager automatically. You classify them when you are ready, and the plugin generates the blocking rules for you.73 No. Watch for new cookies lists them as your visitors' browsers report them, and Cookie Discovery finds them on demand. You classify them when you are ready. 101 74 102 = Does this work with Google Tag Manager? =75 = Does it work with Google Tag Manager and caching plugins? = 103 76 104 Yes. Enter your GTM container ID on the Tag Managers tab and the plugin injects both the head script and body noscript tag in the correct positions. All GTM tags that respect Google Consent Mode will see the denied state on load and wait for consent.77 Yes. Blocking happens in the HTML before it is cached. Turn off Cloudflare Rocket Loader; the plugin warns you if it is on. 105 78 106 = Does this work with Cloudflare, WP Rocket, and other caching setups? =79 = What do US visitors see? = 107 80 108 Yes. Script blocking runs server-side in the PHP output buffer, so the cached HTML already has the correct tags rewritten. Cloudflare is specifically supported for geo detection via the CF-IPCountry header on all plans including free.81 A short notice. Cookies are allowed by default because US laws use an opt-out model. The "Do Not Sell or Share My Personal Information" link opens a choice to opt out. Put [cookieflame_settings_link] in your footer so visitors can reach it again later. 109 82 110 One exception: Cloudflare Rocket Loader rewrites scripts at the edge after PHP has run, which defeats the blocking layer. Rocket Loader must be disabled when using CookieFlame. The plugin detects this and shows a warning if it is active. 83 = Can I show "Decline all" on the first screen? = 111 84 112 = What happens for US visitors? = 85 Yes. Turn on "Show 'Decline all' on the first screen" on the Settings tab. It appears only on the opt-in banner, never on the US notice. 113 86 114 US visitors see a compact CCPA opt-out notice at the bottom of the page with a Do Not Sell My Personal Information link. Cookies are auto-granted by default because CCPA uses an opt-out model, and the visitor can revoke consent through the notice. 87 = Can I change the banner text? = 115 88 116 = What happens for visitors outside the regulated regions? = 117 118 No banner is shown and all cookies are auto-granted. CookieFlame does not show GDPR banners to visitors whose region does not require them. 119 120 = Does the plugin contact any external service? = 121 122 By default, no. Geo detection reads CDN headers first, then a local MaxMind database if you have uploaded one, then the browser timezone as a last resort. No external geo API is called. The plugin does not load scripts from any CDN and does not phone home. 123 124 The one optional external request is Google Tag Manager, but only if you have entered a GTM container ID in the settings. See the External services section for full details. 125 126 = Does it handle Global Privacy Control (GPC)? = 127 128 Yes. GPC is required by twelve US states and the plugin detects the browser signal automatically. For CCPA visitors with GPC active, marketing consent is suppressed before any tag runs. Turning GPC off later restores the visitor's original choice. 129 130 = Can I customise the banner text and colours? = 131 132 Yes. Every string shown to visitors is editable through the Settings tab, and the banner supports both light and dark themes. For per-language versions, use WPML or Polylang with the standard WordPress translation pipeline. 133 134 = What is CookieFlame Pro? = 135 136 Pro adds automatic cookie classification. Instead of classifying each cookie manually, you click Auto-Identify All and the plugin uses a central database of known cookies plus AI to classify them with vendor, category, and purpose. Pro also adds tighter monitoring intervals, a more detailed digest email, and automatic MaxMind database updates. Pricing is flat annual by site count with no pageview limits. See [cookieflame.com](https://cookieflame.com) for details. 89 Not yet from the settings. The banner is in English, with light and dark themes. Developers can change the text and colours with the cookieflame_frontend_config filter and the --cc-* CSS properties. 137 90 138 91 = Is my visitors' data safe? = 139 92 140 CookieFlame does not collect visitor data. IP addresses are hashed for rate-limiting and never stored raw. Cookie values are never transmitted, only their name and a shape classification (uuid, timestamp, and similar) used for identification. The plugin stores consent decisions in a first-party cookie on the visitor's ownbrowser.93 CookieFlame collects no visitor data. Cookie values are never sent, only names. IP addresses are hashed for rate limiting and never stored. Choices are kept in a first-party cookie in the visitor's browser. 141 94 142 95 == External services == 143 96 144 CookieFlame is a self-hosted plugin. By default it does not contact any external service from the visitor's browser, and it does not phone home.97 CookieFlame does not phone home. These requests happen only in the cases described. 145 98 146 There is one optional external interaction, only if the site owner configures it.99 **Google Tag Manager (optional).** If you enter a container ID on the Tag Managers tab, Google's script (https://www.googletagmanager.com/gtm.js) loads on every page, except for visitors you exclude from tracking. Tags inside it follow the visitor's Consent Mode choices. Sent: the container ID and the browser's standard request data (IP address, user agent, page URL). [Terms](https://marketingplatform.google.com/about/analytics/tag-manager/use-policy/), [privacy policy](https://policies.google.com/privacy). 147 100 148 ** Google Tag Manager (optional)**101 **Microsoft UET (optional).** If you enter a UET tag ID, Microsoft's loader (https://bat.bing.com/bat.js) loads on every page, except for visitors you exclude from tracking. It records nothing until the visitor allows Marketing. Sent: the tag ID and the browser's standard request data. [Microsoft Advertising agreement](https://about.ads.microsoft.com/en/resources/policies/microsoft-advertising-agreement), [privacy statement](https://privacy.microsoft.com/en-gb/privacystatement). 149 102 150 If the site owner enters a Google Tag Manager container ID on the CookieFlame Tag Managers settings tab, CookieFlame injects the standard Google Tag Manager script (https://www.googletagmanager.com/gtm.js) into the site front-end under the visitor's consent. This script is provided by Google and is loaded from Google's servers when the page is viewed.103 **YouTube thumbnails.** When a page has a YouTube embed that is waiting for consent, its placeholder shows the video's thumbnail from img.youtube.com. Sent: the video ID and the browser's standard request data. [YouTube terms](https://www.youtube.com/t/terms), [Google privacy policy](https://policies.google.com/privacy). 151 104 152 * What is sent: standard Google Tag Manager request, including the container ID and whatever browser request data Google receives for any embed. 153 * When: only if the admin has set a GTM container ID, and only when a visitor loads a page and has consented. 154 * Google Tag Manager terms: https://marketingplatform.google.com/about/analytics/tag-manager/use-policy/ 155 * Google privacy policy: https://policies.google.com/privacy 105 **Consent log endpoint (optional).** If you enter a URL in the Consent Log Endpoint setting, each consent choice is sent there from the visitor's browser as JSON: an anonymous ID, time, action, the choice per category, region, country code, page URL, user agent, the GPC flag, consent version and plugin version. No cookie values or IP address are in it. You choose the destination and are responsible for it. 156 106 157 If no GTM container ID is set, no request is made. 158 159 **Microsoft UET (Bing Ads tag, optional)** 160 161 If the site owner enters a Microsoft UET tag ID on the CookieFlame Tag Managers settings tab, CookieFlame injects the standard Microsoft UET loader script (https://bat.bing.com/bat.js) into the site front-end. The loader is provided by Microsoft and is loaded from Microsoft's servers when the page is viewed. 162 163 The UET runtime reads Microsoft Consent Mode v2 signals from the dataLayer that CookieFlame already manages, so the loader honours visitor consent internally: tracking events do not record until the visitor has granted Marketing consent. The loader script itself is fetched on every page load while a UET tag ID is configured. 164 165 * What is sent: the UET tag ID, plus the standard browser request data Microsoft receives for any third-party script load (visitor IP, user agent, page URL, referer). 166 * When: every page load while a UET tag ID is configured, except for visitors whose browser sends the Do Not Track header in which case CookieFlame suppresses the injection entirely. 167 * Microsoft Advertising agreement: https://about.ads.microsoft.com/en/resources/policies/microsoft-advertising-agreement 168 * Microsoft privacy statement: https://privacy.microsoft.com/en-gb/privacystatement 169 170 If no UET tag ID is set, no request is made. 171 172 **Consent log endpoint (optional, site-owner-nominated webhook)** 173 174 If the site owner enters a URL in the Consent Log endpoint setting, CookieFlame sends a JSON record of each consent decision to that URL from the visitor's browser via navigator.sendBeacon (with a fetch fallback for browsers without sendBeacon). The default for this setting is empty, in which case nothing is sent. The destination URL is nominated by the site owner; terms and privacy obligations for that URL are the site owner's responsibility, not CookieFlame's. 175 176 * What is sent: a JSON payload containing an anonymous ID, an ISO-8601 timestamp, the action that produced the record (accept-all, decline-all, save, or one of the region-based auto-grants), the per-category consent decision (necessary, preferences, analytics, marketing), the compliance mode (gdpr, ccpa, none), the visitor country code, the page URL, the user agent string, the TCF v2.2 flag, the Global Privacy Control flag, the consent policy version, and the plugin version. No cookie values are included. No IP address is included in the payload, although the receiving endpoint will of course see the visitor IP via the underlying HTTP request, the same as any other request. 177 * When: every time the visitor's consent state is set. This includes banner actions (accept-all, decline-all, save) and region-based auto-grants for visitors outside GDPR regions and for CCPA opt-out visitors. 178 179 If no Consent Log endpoint URL is set, no request is made. 180 181 **Domain strings in the plugin code** 182 183 The plugin code contains many third-party domain names (for example, connect.facebook.net, googletagmanager.com, and similar). These are pattern strings used for local matching only. They identify scripts that may already be present on the admin's site so CookieFlame can detect them, show them to the admin in the Cookie Manager, and block them until the visitor has given consent. CookieFlame does not fetch from these domains. 107 Third-party domain names in the code (such as connect.facebook.net) are only used to recognise and block scripts; they are never contacted. 184 108 185 109 == Screenshots == 186 110 187 1. GDPR consent banner with category toggles (two-step flow)188 2. CCPA opt-out notice for US visitors189 3. Cookie Manager showing discovered cookies with their origin190 4. Cookie Discovery in progress : the discovery bar accumulates services191 5. Admin Settings tab with theme and regional behaviour controls192 6. Geo Detection tab with CDN status and MaxMind upload193 7. Tag Managers tab for GTM and Microsoft UET111 1. Opt-in banner with category toggles 112 2. US notice with the "Do Not Sell or Share" link 113 3. Cookie Manager listing your site's cookies 114 4. Cookie Discovery in progress 115 5. Settings tab 116 6. Geo Detection tab 117 7. Tag Managers tab 194 118 195 119 == Changelog == 196 120 197 = 5.3.0 = 198 * Cookie monitoring now ignores cookies that come with a visitor's browser rather than from your site. Some browser extensions write the "already accepted" cookies of other consent tools (such as Complianz, CookieYes, Cookiebot, OneTrust or Didomi) onto every site they visit, and these were being reported as your site's cookies. A consent tool's cookie is now ignored when that tool doesn't run on your site: its plugin isn't active, its script isn't on the page, and no script on the page wrote the cookie. 199 * Cookies written by a browser extension's own code are ignored too. 200 * Cookies ignored this way go on the Permanently Ignored list with the reason, and the list now shows why each cookie was ignored. Restoring one keeps it monitored from then on. 201 * The Cookie Manager and the Cookie Discovery count leave these cookies out too, including ones your own browser extensions add. Cookies that came with your browser during Cookie Discovery go on the Permanently Ignored list with the reason. Cookies on the Permanently Ignored list no longer show in the Cookie Manager just because they are in your own browser. 202 * On update, other consent tools' cookies that were already reported are moved to the Permanently Ignored list. Cookies you classified yourself are left alone. 203 * Fixed: the "consent plugin detected" conflict warning could be set off by CookieFlame's own list of cookie names rather than by another consent plugin on the page. 204 * Cookie Manager: the last column is now "Status". Each cookie shows one label for where it stands (Known, Auto-Identified, Classified, Added by you, Changed by you, or Not identified yet), and cookies that aren't identified yet say where they came from: found by your scan, reported by visitors, or seen in your own browser. 205 * Cookie Manager: Remove and Ignore permanently are now one "Remove…" link that asks why. "My site doesn't use it any more" takes the cookie off your list, and it comes back if it's seen again. "It's not from my site" puts it on the Permanently Ignored list for good. 206 * Cookie Manager: with CookieFlame Pro, the padlock is now a "Lock as correct" button. A locked cookie keeps its vendor, category and purpose, even when you re-identify. 207 * Cookie Manager: the small help buttons beside each link are gone. Each link explains itself when you hover over it, and the Status column's help button explains every label and action. 208 * Improved: when another consent tool's code is on your pages, the warning now says so in plain words ("Cookiebot found on your pages"), names the page and the text to look for, and lists where such code usually lives (plugins, page builder custom code such as Elementor > Custom Code, the theme's header, or a header and footer scripts plugin). It used to say a plugin was installed even when the code came from somewhere else. 209 210 = 5.2.0 = 211 * Cookie monitoring now records which script first wrote each new cookie: a third-party script's domain, or the WordPress plugin or theme that set it. Previously only the full list of third-party scripts on the page was known. Cookie values are never read or sent. 212 * Cookie monitoring now checks again later in the visit, a few seconds after the visitor changes their consent choice, and when they leave the page, so cookies set after consent or after the first few seconds are found. 213 * Fixed: when a page reported several new cookies at the same moment, some could be lost, because the reports overwrote each other when saving. Reports are now saved one at a time, so every new cookie is kept. 214 * The cookieflame_new_cookie_discovered action now fires after the cookie's details are saved and passes them as a fourth argument (pre-existing flag, value type, writer). Listeners using three arguments are unaffected. 215 * Tested up to WordPress 7.1. 216 217 = 5.1.1 = 218 * Cookie declaration shortcode tag is now [cookieflame_declaration] (previously [cookieconsent_declaration]). If you embed the cookie declaration on your Privacy Policy page, update the shortcode tag. 219 * URL fields on the Settings page (Privacy URL, CCPA URL, Consent log endpoint) now use esc_url_raw, the WordPress URL sanitiser, in place of sanitize_text_field. 220 221 = 5.1.0 = 222 * Cookie Monitoring tab redesigned for clarity. The page now leads with the two controls that matter (Monitoring on/off and Update interval) and trims the long-form intro and guidance copy that buried them. 223 * New: the Update interval dropdown shows CookieFlame Pro intervals (15 minutes, 1 hour, 4 hours) inline as disabled options labelled "(Pro)", so you can see at a glance what an upgrade adds without leaving the page. They unlock when CookieFlame Pro is active. 224 * Removed the Monthly interval. It was producing digests so far apart that newly discovered cookies were already weeks stale by the time the email arrived. Existing sites set to Monthly will move to Daily on update. 225 * Removed the in-page "Clear monitoring history" button. The 30-day auto-purge handles staleness without intervention; for the rare support case where a full reset is needed, the new WP-CLI command "wp cookieflame purge-beacon-seen" replaces the button. 226 * Filter contract change for developers extending the interval list: cookieflame_interval_buckets now passes tier-aware entries (each entry shaped ['minutes' => N, 'tier' => 'free'|'pro']). Plain integers from third-party filter contributors are still accepted and default to the free tier for back-compatibility. 227 228 = 5.0.2 = 229 * Fixed: Geo Detection now correctly recognises the Cloudflare CF-IPCountry header again. Sites fronted by Cloudflare without a MaxMind database installed were falling through to the GDPR opt-in safety default for every visitor, which meant CCPA-eligible US visitors saw the GDPR banner instead of the CCPA notice, and visitors in regions where no banner is required also saw the GDPR banner. The Geo Detection admin panel was reporting "Not configured" on Cloudflare-fronted sites for the same reason. No setting changes are required; geo detection resumes automatically on update. 230 231 = 5.0.1 = 232 * Hardened consent boot: the five wp_head injection hooks (consent defaults, config, frontend script, Google Tag Manager, Microsoft UET) now register at deep-negative priorities so other plugins cannot hook ahead of CookieFlame and push tags before consent signals are set. 233 * Beacon endpoint refactored: the per-IP rate limit now runs as a REST permission_callback, rejecting bursts at the routing layer above the request handler. The same check still runs inside the handler as a belt-and-braces guard. 234 * Defensive shutdown handler added for the output buffer: walks the buffer stack at end of request and flushes only CookieFlame's own buffer if it remains open, leaving any caching or optimisation plugin buffers above it untouched. 235 * Database queries against the beacon-seen table reworked to inline the WordPress table prefix directly in each SQL literal, with rationale comments on the remaining advisory warnings. 236 * wp_unslash applied consistently to display-only query parameters on the Cookie Monitoring tab. 237 * Readme: added an Upgrade Notice section. 238 239 = 5.0.0 = 240 * Initial public release. 121 = 5.4.0 = 122 * New: an option to show "Decline All" on the banner's first screen, next to Accept All. Off by default, and only on the opt-in banner (UK, EU/EEA, Switzerland, Brazil, South Africa), never on the US notice. 123 * Global Privacy Control now follows California's rules (§7025): OK only closes the US notice, the notice says the signal was honoured, the opt-out is saved, and visitors can opt back in only through a separate choice. 124 * "Do Not Sell or Share My Personal Information" now opens a real opt-out choice on the page, instead of linking to your privacy page. 125 * Cookie Monitoring is now two settings: "Watch for new cookies" On/Off, and "Email me about changes": Weekly (the default for new sites), Daily, Instantly (Pro) or Off. Your old setting carries over; 15 minutes, 1 hour and 4 hours become Instantly with Pro and Daily without it. 126 * New: [cookieflame_settings_link] shortcode and a cc-open-settings class for a "Cookie settings" link. The cookie declaration now has a "Change cookie preferences" button. 127 * New: "Visitor logins" setting. When ticked, the cookie declaration lists the WordPress login cookies under "If you log in". Otherwise they are left out, so every visitor sees the same declaration. 128 * The upgrade message at the top of the CookieFlame pages can now be dismissed. 129 * For developers: --cc-* CSS properties, two filters, an action and the cc:banner-rendered event (see For developers). 130 * The IAB TCF placeholder, which never carried a consent string, is now off by default. 131 * Fixed: a Re-consent Period below 1 day made the banner appear on every page. It now means the default of 365 days. 132 * Fixed: the US notice could run off the screen on phones. 133 * Fixed: the X (Twitter) embed placeholder also caught other sites whose names end in "x.com", such as netflix.com and box.com. 134 * Fixed: the WordPress cookie list: login cookie expiry, comment and protected-page cookies, the wp-settings-time description, and PHP and WooCommerce cookies listed as WordPress core. 135 * Fixed: saving Cookie Monitoring settings, or switching monitoring on or off, turned "Exclude staff from tracking" off. 136 * Older changes: changelog.txt. 241 137 242 138 == Upgrade Notice == 243 139 244 = 5.3.0 = 245 Cookie monitoring now ignores other consent tools' cookies that visitors' browser extensions add, so they no longer appear as your site's cookies. The Cookie Manager's last column is tidier and easier to understand. 246 247 = 5.2.0 = 248 Better cookie detection: CookieFlame now records which script set each new cookie and keeps checking after consent is given. 249 250 = 5.1.0 = 251 Cookie Monitoring tab redesigned and the Monthly interval removed. If you were on Monthly, your setting will move to Daily on update. The "Clear monitoring history" button has been replaced with a WP-CLI command for the rare cases where it is needed. 252 253 = 5.0.2 = 254 Bug fix release. Restores Cloudflare CF-IPCountry geo detection on sites fronted by Cloudflare without MaxMind. Recommended for all users on Cloudflare. 255 256 = 5.0.1 = 257 Compatibility and hardening release. Refactors a public endpoint's permission check, hardens consent boot priority, and addresses static-analysis warnings. No setting or visitor-facing behaviour changes. 258 259 = 5.0.0 = 260 Initial public release. 140 = 5.4.0 = 141 Optional "Decline all" button, Global Privacy Control handled to California's rules, a real "Do Not Sell or Share" choice, and simpler Cookie Monitoring settings. Your settings carry over. -
cookieflame/trunk/uninstall.php
r3769546 r3771537 37 37 delete_option( 'cookieflame_maxmind_last_error' ); 38 38 delete_option( 'cookieflame_beacon_seen_schema' ); 39 delete_metadata( 'user', 0, 'cookieflame_upgrade_banner_dismissed', '', true ); 39 40 40 41 // Drop the beacon-seen custom table (only on opt-in deletion).
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)