1/** 2 * RS 2026-06-19 CMP Bridge 3 * 4 * Universal, provider-agnostic consent listener. Sits between the active CMP 5 * (CookieYes, OneTrust, Cookiebot, â¦) and the site's tracking layer. 6 * 7 * - Resolves the visitor's consent using the SAME category_map the server uses 8 * to tag scripts (emitted as window.cmpBridgeConfig by getCMPScripts()), so 9 * there are NO hard-coded provider category IDs. The map is internal -> the 10 * provider's own category id/key: 11 * { analytics:'2', advertisement:'4', personalisation:'3' } // OneTrust 12 * { analytics:'analytics', advertisement:'advertisement', personalisation:'functional' } // CookieYes 13 * - Syncs Google Consent Mode v2 (gtag) and Facebook Pixel (fbq) on every change. 14 * - Drives the Rolex / Adobe (Content Square) consent signals: rlx-consent and 15 * rlx-marketing cookies + the Rolex iframe / _satellite events. 16 * 17 * Script LAUNCHING per category is the provider SDK's job via the blocking 18 * attributes rendered in conversion_codes.php (e.g. OneTrust's 19 * `type="text/plain" class="optanon-category-2"`); the bridge propagates the 20 * consent signals the SDK does not, and launches the tags it left blocked 21 * (RS 2026-09-02, see activateBlockedScripts). 22 * 23 * Rolex / Adobe consent (rlx-consent) defaults to the ANALYTICS category, and 24 * marketing (rlx-marketing) defaults to the ADVERTISEMENT category. If the admin 25 * configures a dedicated provider category under category_map.rolex / 26 * category_map.marketing, that takes precedence â so a separate OneTrust group 27 * for the Rolex/Adobe section works without code changes. 28 * 29 * Load order: MUST be included BEFORE the provider SDK (see getCMPScripts()) so 30 * OptanonWrapper and the CookieYes/Cookiebot listeners register in time. 31 */ 32(function (window, document) { 33 'use strict'; 34 35 if (window.__cmpBridgeInitialized) return; 36 window.__cmpBridgeInitialized = true; 37 38 var cfg = window.cmpBridgeConfig || {}; 39 var provider = cfg.provider || 'internal'; 40 var rolexEnabled = !!cfg.rolexEnabled; 41 var ROLEX_ORIGIN = 'https://cornersv7.Rolex.com'; 42 var RLX_CONSENT = 'rlx-consent'; 43 var RLX_MARKETING = 'rlx-marketing'; 44 var COOKIE_DAYS = 365; 45 46 // --- gtag / dataLayer --- 47 window.dataLayer = window.dataLayer || []; 48 function gtag() { window.dataLayer.push(arguments); } 49 50 // GPC (Global Privacy Control) â "do not sell/share"; under CPRA governs 51 // advertising. When present we force advertising/marketing off. 52 function gpcActive() { 53 return navigator.globalPrivacyControl === true || window.__gpcSignal === true; 54 } 55 56 // ------------------------------------------------------------ 57 // category_map: internal category -> provider's own category id/key. 58 // Falls back to sensible per-provider defaults when not configured. 59 // ------------------------------------------------------------ 60 var PROVIDER_DEFAULT_MAP = { 61 cookieyes: { analytics: 'analytics', advertisement: 'advertisement', personalisation: 'functional' }, 62 cookiebot: { analytics: 'statistics', advertisement: 'marketing', personalisation: 'preferences' } 63 // onetrust: intentionally none â group ids are account-specific (see info.txt), 64 // so they MUST come from the admin-configured category_map. 65 }; 66 67 function buildCategoryMap() { 68 var map = {}; 69 var configured = cfg.categoryMap || {}; 70 var defaults = PROVIDER_DEFAULT_MAP[provider] || {}; 71 // start from defaults, then let configured values win 72 var k; 73 for (k in defaults) { if (defaults.hasOwnProperty(k)) map[k] = String(defaults[k]); } 74 for (k in configured) { 75 if (configured.hasOwnProperty(k) && configured[k] !== '' && configured[k] != null) { 76 map[k] = String(configured[k]); 77 } 78 } 79 // 'functional' is the legacy alias of 'personalisation' 80 if (!map.personalisation && configured.functional) map.personalisation = String(configured.functional); 81 // Rolex/Adobe defaults to analytics; marketing defaults to advertisement. 82 if (!map.rolex && map.analytics) map.rolex = map.analytics; 83 if (!map.marketing && map.advertisement) map.marketing = map.advertisement; 84 return map; 85 } 86 var categoryMap = buildCategoryMap(); 87 // True when marketing has no dedicated category and simply follows advertising; 88 // used to decide whether GPC ("do-not-sell") should also disable rlx-marketing. 89 var marketingFollowsAd = !!categoryMap.advertisement && categoryMap.marketing === categoryMap.advertisement; 90 91 // Given the set of provider categories the visitor has consented to, resolve 92 // the internal granted flags via category_map. 93 function resolveGranted(activeSet) {
94 var g = { advertisement: false, analytics: false, personalisation: false, marketing: false, rolex: false }; 95 for (var internalKey in categoryMap) { 96 if (categoryMap.hasOwnProperty(internalKey) && activeSet[categoryMap[internalKey]]) { 97 g[internalKey] = true; 98 } 99 } 100 return g; 101 } 102 103 // ------------------------------------------------------------ 104 // Cookie helpers 105 // ------------------------------------------------------------ 106 function getCookie(name) { 107 var parts = ('; ' + document.cookie).split('; ' + name + '='); 108 return parts.length === 2 ? parts.pop().split(';').shift() : null; 109 } 110 function setCookie(name, value, days) { 111 var str = name + '=' + value + '; path=/; SameSite=Lax'; 112 if (days) { 113 var d = new Date(); 114 d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000); 115 str += '; expires=' + d.toUTCString(); 116 } 117 document.cookie = str; 118 } 119 120 // ------------------------------------------------------------ 121 // Rolex / Adobe (Content Square) events â mirrored from cmp.js 122 // ------------------------------------------------------------ 123 function rolexCmpEvents(enable) { 124 var iframe = document.getElementById('rlx-corner'); 125 if (enable) { 126 if (iframe && iframe.contentWindow) { 127 iframe.contentWindow.postMessage('consentTrue', ROLEX_ORIGIN); 128 iframe.contentWindow.postMessage('consentValidation', ROLEX_ORIGIN); 129 } else if (document.querySelector('.rolex_page')) { 130 if (typeof _satellite !== 'undefined' && _satellite.track) { 131 _satellite.track('PageView'); 132 } 133 } 134 } else if (iframe && iframe.contentWindow) { 135 iframe.contentWindow.postMessage('consentFalse', ROLEX_ORIGIN); 136 } 137 } 138 139 // ------------------------------------------------------------ 140 // RS 2026-09-02 141 // SDKs re-enable only tags they blocked by URL, so inline tags stay dead after 142 // consent. The delay lets an SDK that activates its own tags go first. 143 // ------------------------------------------------------------ 144 var ACTIVATION_DELAY = 1000; 145 var ACTIVATED_FLAG = 'data-cmp-activated'; 146 var blockAttr = cfg.blockingAttribute || ''; 147 var blockFormat = cfg.blockingFormat || '{CATEGORY}'; 148 var srcAttr = cfg.srcAttribute || 'data-src'; 149 150 function isExecutableType(type) { 151 if (!type) return true; 152 type = type.toLowerCase(); 153 return type.indexOf('javascript') !== -1 || type === 'module';
154 } 155 156 // `class` (OneTrust: class="optanon-category-C0008") can hold several tokens. 157 function carriesToken(node, token) { 158 var value = node.getAttribute(blockAttr); 159 if (!value) return false; 160 return value === token || (' ' + value + ' ').indexOf(' ' + token + ' ') !== -1; 161 } 162 163 function activateNode(node, token) { 164 var replacement = document.createElement('script'); 165 var attrs = node.attributes; 166 for (var i = 0; i < attrs.length; i++) { 167 if (attrs[i].name === 'type' || attrs[i].name === srcAttr) continue; 168 try { replacement.setAttribute(attrs[i].name, attrs[i].value); } catch (e) {} 169 } 170 var suppressedSrc = node.getAttribute(srcAttr); 171 if (suppressedSrc) replacement.src = suppressedSrc; 172 else if (node.src) replacement.src = node.src; 173 else replacement.text = node.text; 174 node.setAttribute(ACTIVATED_FLAG, '1'); 175 // Drop the token so an in-place SDK pass cannot run this node again. 176 var left = (' ' + node.getAttribute(blockAttr) + ' ').replace(' ' + token + ' ', ' ').trim(); 177 if (left) node.setAttribute(blockAttr, left); 178 else node.removeAttribute(blockAttr); 179 if (node.parentNode) node.parentNode.insertBefore(replacement, node.nextSibling); 180 } 181 182 function activateBlockedScripts(granted) { 183 if (!blockAttr) return; 184 var tokens = []; 185 for (var internalKey in categoryMap) { 186 if (categoryMap.hasOwnProperty(internalKey) && granted[internalKey]) { 187 tokens.push(blockFormat.replace('{CATEGORY}', categoryMap[internalKey])); 188 } 189 } 190 if (!tokens.length) return; 191 var nodes = document.getElementsByTagName('script'); 192 var pending = []; 193 for (var i = 0; i < nodes.length; i++) { 194 if (nodes[i].hasAttribute(ACTIVATED_FLAG)) continue; 195 if (isExecutableType(nodes[i].getAttribute('type'))) continue; 196 for (var t = 0; t < tokens.length; t++) { 197 if (carriesToken(nodes[i], tokens[t])) { pending.push([nodes[i], tokens[t]]); break; } 198 } 199 } 200 for (var j = 0; j < pending.length; j++) { 201 try { activateNode(pending[j][0], pending[j][1]); } catch (e) {} 202 } 203 } 204 205 // ------------------------------------------------------------ 206 // RS 2026-09-01 207 // GA4 sends one page_view, at gtag("config") time, and never repeats it on a consent 208 // change. The CMP reactivates blocked tags synchronously but dispatches its consent 209 // event a task later, so config always runs before the grant reaches gtag. 210 // ------------------------------------------------------------ 211 function consentDefaults() { 212 var dl = window.dataLayer || []; 213 for (var i = 0; i < dl.length; i++) { 214 var a = dl[i]; 215 if (a && a[0] === 'consent' && a[1] === 'default' && a[2]) return a[2]; 216 } 217 return null; 218 } 219 220 function matchesDefaults(update) { 221 var defaults = consentDefaults(); 222 if (!defaults) return false; 223 for (var k in update) { 224 if (update.hasOwnProperty(k) && defaults[k] !== update[k]) return false; 225 } 226 return true; 227 } 228 229 // gtag counts wait_for_update from when the tag loads, not from navigation start. 230 // 0 means the tag has not loaded and no page_view can have been sent. 231 function consentWaitEndsAt() { 232 var defaults = consentDefaults(); 233 var wait = (defaults && typeof defaults.wait_for_update === 'number') ? defaults.wait_for_update : 0; 234 var loadedAt = 0; 235 try { 236 var res = performance.getEntriesByType('resource'); 237 for (var i = 0; i < res.length; i++) { 238 if (/googletagmanager\.com\/(gtag\/js|gtm\.js)/.test(res[i].name)) { 239 loadedAt = Math.max(loadedAt, res[i].responseEnd); 240 } 241 } 242 } catch (e) {} 243 return loadedAt ? loadedAt + wait : 0; 244 } 245 246 function ga4Configured() { 247 var dl = window.dataLayer || []; 248 for (var i = 0; i < dl.length; i++) { 249 if (dl[i] && dl[i][0] === 'config') return true; 250 } 251 return false;
252 } 253 254 // Inside the window gtag still holds the original page_view and will send it granted, 255 // so re-sending there would double count. 256 function resendPageViewIfLost() { 257 if (!ga4Configured()) return; 258 var defaults = consentDefaults(); 259 if (!defaults || defaults.analytics_storage !== 'denied') return; 260 var endsAt = consentWaitEndsAt(); 261 if (!endsAt) return; 262 try { 263 if (performance.now() <= endsAt) return; 264 } catch (e) { return; } 265 gtag('event', 'page_view'); 266 } 267 268 // ------------------------------------------------------------ 269 // Core: apply resolved consent across every layer 270 // ------------------------------------------------------------ 271 var lastSig = null; 272 var initialApplyDone = false; 273 var analyticsGranted = false; 274 275 function applyConsent(granted) { 276 granted = granted || {}; 277 var ad = !!granted.advertisement; 278 var an = !!granted.analytics; 279 var fn = !!granted.personalisation; 280 var mkt = (granted.marketing !== undefined) ? !!granted.marketing : ad; 281 var rlx = (granted.rolex !== undefined) ? !!granted.rolex : an; 282 283 // GPC ("do-not-sell/share", CPRA) governs advertising/targeting only. It 284 // forces the advertisement signal off, but NOT analytics-natured consent 285 // such as the Adobe/Content Square (Rolex) category that drives rlx-*. 286 if (gpcActive()) { 287 ad = false; 288 // If marketing follows the advertisement category (the default), honour 289 // GPC for it too; if it is mapped to its own (e.g. Adobe/Content Square, 290 // analytics-natured) category, GPC does not apply. 291 if (marketingFollowsAd) mkt = false; 292 } 293 294 var sig = [ad, an, fn, mkt, rlx].join('|'); 295 if (sig === lastSig) return; // CMPs often fire several events per change 296 lastSig = sig; 297 var firstApply = !initialApplyDone; 298 initialApplyDone = true; 299 300 // 1. Google Consent Mode v2 301 var update = { 302 ad_storage: ad ? 'granted' : 'denied', 303 ad_user_data: ad ? 'granted' : 'denied', 304 ad_personalization: ad ? 'granted' : 'denied', 305 analytics_storage: an ? 'granted' : 'denied', 306 functionality_storage: fn ? 'granted' : 'denied', 307 personalization_storage: fn ? 'granted' : 'denied', 308 security_storage: 'granted' 309 }; 310 // An update that only restates the defaults spends the wait window for nothing. 311 if (!(firstApply && matchesDefaults(update))) { 312 gtag('consent', 'update', update); 313 gtag('set', 'ads_data_redaction', ad ? false : true); 314 } 315 if (an && !analyticsGranted) resendPageViewIfLost(); 316 analyticsGranted = an; 317 318 // 2. Facebook Pixel 319 if (typeof window.fbq === 'function') { 320 try { window.fbq('consent', ad ? 'grant' : 'revoke'); } catch (e) {} 321 } 322 323 // 3. Rolex / Adobe (only on Rolex-enabled sites) 324 if (rolexEnabled) { 325 setCookie(RLX_MARKETING, mkt ? 'true' : 'false', COOKIE_DAYS); 326 setCookie(RLX_CONSENT, rlx ? 'true' : 'false', COOKIE_DAYS); 327 rolexCmpEvents(rlx); 328 } 329 330 // 4. Let the rest of the site react (lazy widgets, Rolex iframe, etc.) 331 var resolved = { advertisement: ad, analytics: an, personalisation: fn, marketing: mkt, rolex: rlx }; 332 try { 333 document.dispatchEvent(new CustomEvent('cmp_bridge_consent', { 334 detail: { granted: resolved } 335 })); 336 } catch (e) {} 337 338 // 5. Launch the tags the SDK left blocked - RS 2026-09-02 339 window.setTimeout(function () { activateBlockedScripts(resolved); }, ACTIVATION_DELAY); 340 } 341 342 function applyFromActiveSet(activeSet) { 343 applyConsent(resolveGranted(activeSet)); 344 } 345 346 // Turn a list of provider categories into a lookup object. 347 function toSet(list) { 348 var s = {}; 349 if (list && list.length !== undefined) { 350 for (var i = 0; i < list.length; i++) { 351 var v = ('' + list[i]).replace(/:.*/, '').trim(); // strip OneTrust ":1" sub-state 352 if (v) s[v] = true; 353 } 354 } 355 return s; 356 } 357 358 // ============================================================
359 // Adapter: CookieYes 360 // ============================================================ 361 function cookieYesActiveSet(detail) { 362 // Live API is most reliable on returning visits. 363 if (typeof window.getCkyConsent === 'function') { 364 var c = window.getCkyConsent(); 365 var cats = (c && c.categories) || {}; 366 var s = {}; 367 for (var key in cats) { if (cats.hasOwnProperty(key) && cats[key]) s[key] = true; } 368 return s; 369 } 370 // Fall back to event payload: detail.accepted is an array of CY keys. 371 if (detail && detail.accepted) return toSet(detail.accepted); 372 return null; 373 } 374 function wireCookieYes() { 375 function handler(e) { 376 var s = cookieYesActiveSet(e && e.detail); 377 if (s) applyFromActiveSet(s); 378 } 379 document.addEventListener('cookieyes_consent_update', handler); 380 document.addEventListener('cookieyes_banner_load', handler); 381 } 382 383 // ============================================================ 384 // Adapter: OneTrust 385 // ============================================================ 386 function oneTrustActiveSet(source) { 387 if (Object.prototype.toString.call(source) === '[object Array]') return toSet(source); 388 if (typeof source === 'string') return toSet(source.split(',')); 389 if (typeof window.OnetrustActiveGroups === 'string') return toSet(window.OnetrustActiveGroups.split(',')); 390 return {}; 391 } 392 function wireOneTrust() { 393 // Fires on load and on every consent change; detail = active-group array. 394 window.addEventListener('OneTrustGroupsUpdated', function (e) { 395 applyFromActiveSet(oneTrustActiveSet(e && e.detail)); 396 });
397 // OptanonWrapper is OneTrust's load + consent-change callback. Chain it so 398 // we don't clobber whatever the manual SDK snippet defined (info.txt shows 399 // the snippet defines an empty OptanonWrapper). 400 var previous = window.OptanonWrapper; 401 window.OptanonWrapper = function () { 402 if (typeof previous === 'function') { 403 try { previous.apply(this, arguments); } catch (e) {} 404 } 405 applyFromActiveSet(oneTrustActiveSet(window.OnetrustActiveGroups)); 406 }; 407 } 408 409 // ============================================================ 410 // Adapter: Cookiebot (best-effort) 411 // ============================================================ 412 function cookiebotActiveSet() { 413 var c = window.Cookiebot && window.Cookiebot.consent; 414 if (!c) return null; 415 var s = {}; 416 ['preferences', 'statistics', 'marketing'].forEach(function (k) { if (c[k]) s[k] = true; }); 417 return s; 418 } 419 function wireCookiebot() { 420 function handler() { 421 var s = cookiebotActiveSet(); 422 if (s) applyFromActiveSet(s); 423 } 424 window.addEventListener('CookiebotOnAccept', handler); 425 window.addEventListener('CookiebotOnDecline', handler); 426 window.addEventListener('CookiebotOnConsentReady', handler); 427 } 428 429 // ============================================================ 430 // Public manual API (internal/manual provider or custom code) 431 // ============================================================ 432 window.cmpBridge = { 433 // accepts internal granted flags directly 434 update: function (granted) { lastSig = null; applyConsent(granted); }, 435 grantAll: function () { lastSig = null; applyConsent({ advertisement: true, analytics: true, personalisation: true, marketing: true, rolex: true }); }, 436 denyAll: function () { lastSig = null; applyConsent({ advertisement: false, analytics: false, personalisation: false, marketing: false, rolex: false }); }, 437 // accepts a list of the active PROVIDER categories (e.g. ['2','3']) 438 fromActiveCategories: function (list) { applyFromActiveSet(toSet(list)); } 439 }; 440 441 // ============================================================ 442 // Init 443 // ============================================================ 444 // Register listeners synchronously â the bridge loads before the SDK, so 445 // every adapter is armed before its CMP can fire its first event. 446 wireCookieYes(); 447 wireOneTrust(); 448 wireCookiebot(); 449 450 // Fallback initial read, in case stored consent was restored and the load 451 // event fired before this script attached (defensive). 452 function initialSync() { 453 var s = null; 454 if (typeof window.getCkyConsent === 'function') s = cookieYesActiveSet(null); 455 else if (typeof window.OnetrustActiveGroups === 'string' && window.OnetrustActiveGroups.replace(/,/g, '').length) s = oneTrustActiveSet(window.OnetrustActiveGroups); 456 else if (window.Cookiebot && window.Cookiebot.consent) s = cookiebotActiveSet(); 457 if (s) applyFromActiveSet(s); 458 } 459 460 if (document.readyState === 'complete') initialSync(); 461 else window.addEventListener('load', initialSync); 462})(window, document);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.