1/** 2 * HOB-RGPD â logique de consentement côté client. 3 * 4 * Transpose mod_hob_rgpd/tmpl/default.js (CLAUDE.md §3.3) avec deux améliorations : 5 * - le consentement est indexé sur la clé de service (keyname), pas un id numérique ; 6 * - les scripts neutralisés par le blocker sont réactivés dynamiquement, sans 7 * rechargement de page (CLAUDE.md §7). 8 * 9 * @package HobRgpd 10 */ 11( function () { 12 'use strict'; 13 14 var config = window.hobRgpdConfig || { cookieName: 'hobConsentPrefs', cookieMaxAge: 31536000 }; 15 16 var banner = document.getElementById( 'hob-rgpd-banner' ); 17 var popup = document.getElementById( 'hob-rgpd-popup' ); 18 var settingsIcon = document.getElementById( 'hob-rgpd-settings-icon' ); 19 var toggles = Array.prototype.slice.call( document.querySelectorAll( '.hob-rgpd-toggle' ) ); 20 21 /** 22 * Lit la valeur d'un cookie. 23 * 24 * @param {string} name Nom du cookie. 25 * @return {string|null} 26 */ 27 function readCookie( name ) { 28 var match = document.cookie.match( '(?:^|; )' + name.replace( /([.$?*|{}()\[\]\\\/+^])/g, '\\$1' ) + '=([^;]*)' ); 29 return match ? match[ 1 ] : null; 30 } 31 32 // Services réellement actifs sur la page (le serveur n'a pas bloqué ceux à true au 33 // chargement, + ceux qu'on réactive dynamiquement ensuite). Sert à savoir si un choix 34 // RETIRE un service déjà chargé (â rechargement nécessaire). 35 var activeServices = ( function () { 36 var out = {}; 37 try { 38 var raw = readCookie( config.cookieName ); 39 var prefs = raw ? JSON.parse( atob( raw ) ) : {}; 40 Object.keys( prefs ).forEach( function ( key ) { 41 if ( prefs[ key ] ) { 42 out[ key ] = true; 43 } 44 } ); 45 } catch ( e ) {} // eslint-disable-line no-empty 46 return out; 47 } )(); 48 49 /** 50 * Ãcrit le cookie de préférences lu par le serveur : base64(JSON {keyname:bool}). 51 * 52 * @param {Object} prefs Préférences par clé de service. 53 */ 54 function writeConsentCookie( prefs ) { 55 var encoded = btoa( JSON.stringify( prefs ) ); 56 document.cookie = config.cookieName + '=' + encoded + ';path=/;max-age=' + config.cookieMaxAge + ';SameSite=Lax'; 57 } 58 59 /** 60 * Construit les préférences à partir de l'état courant des toggles. 61 * 62 * @return {Object} 63 */ 64 function prefsFromToggles() { 65 var prefs = {}; 66 toggles.forEach( function ( input ) { 67 prefs[ input.dataset.serviceKey ] = input.checked; 68 } ); 69 return prefs; 70 } 71 72 /** 73 * Réactive les scripts/iframes neutralisés des services consentis. 74 * 75 * @param {Object} prefs Préférences par clé de service. 76 */ 77 function enableConsentedServices( prefs ) { 78 Object.keys( prefs ).forEach( function ( key ) { 79 if ( ! prefs[ key ] ) { 80 return; 81 } 82 var nodes = document.querySelectorAll( '[data-hob-service="' + key + '"]' ); 83 Array.prototype.slice.call( nodes ).forEach( reactivateNode ); 84 // Ce service est désormais actif sur la page. 85 activeServices[ key ] = true; 86 } ); 87 } 88 89 /** 90 * Reconstruit un nÅud neutralisé en élément actif. 91 * 92 * @param {Element} el Ãlément neutralisé. 93 */ 94 function reactivateNode( el ) { 95 var tag = el.tagName.toLowerCase(); 96 97 if ( 'script' === tag ) { 98 var script = document.createElement( 'script' ); 99 Array.prototype.slice.call( el.attributes ).forEach( function ( attr ) { 100 if ( 'type' === attr.name || 'data-hob-service' === attr.name ) { 101 return; 102 } 103 if ( 'data-hob-src' === attr.name ) { 104 script.src = attr.value; 105 } else { 106 script.setAttribute( attr.name, attr.value ); 107 } 108 } ); 109 if ( ! el.getAttribute( 'data-hob-src' ) ) { 110 script.text = el.textContent; 111 } 112 el.parentNode.replaceChild( script, el ); 113 return; 114 } 115 116 // iframe ou autre tag porteur de data-hob-src. 117 var src = el.getAttribute( 'data-hob-src' ); 118 if ( src ) { 119 el.setAttribute( 'src', src ); 120 el.removeAttribute( 'data-hob-src' ); 121 } 122 el.removeAttribute( 'data-hob-service' ); 123 } 124 125 /** 126 * Applique un consentement complet (toggles + cookie + réactivation). 127 * 128 * @param {Object} prefs Préférences par clé de service. 129 * @param {string} mode 'accepted' | 'rejected' | 'custom'. 130 */ 131 function applyConsent( prefs, mode ) { 132 try { 133 localStorage.setItem( 'hobConsent', mode ); 134 localStorage.setItem( 'hobServicePrefs', JSON.stringify( prefs ) ); 135 } catch ( e ) { 136 // localStorage indisponible (mode privé) : on continue avec le cookie. 137 } 138 139 writeConsentCookie( prefs ); 140 141 // Si on RETIRE le consentement d'un service déjà actif (script/iframe chargé),
142 // on ne peut pas le décharger proprement â rechargement de la page. 143 var withdrawing = Object.keys( activeServices ).some( function ( key ) { 144 return true === activeServices[ key ] && false === prefs[ key ]; 145 } ); 146 147 if ( withdrawing ) { 148 window.location.reload(); 149 return; 150 } 151 152 // Sinon (ajout de consentement) : réactivation dynamique, sans reload. 153 enableConsentedServices( prefs ); 154 hideBanner(); 155 closePopup(); 156 } 157 158 /** Accepte tous les services. */ 159 function acceptAll() { 160 var prefs = {}; 161 toggles.forEach( function ( input ) { 162 input.checked = true; 163 prefs[ input.dataset.serviceKey ] = true; 164 } ); 165 applyConsent( prefs, 'accepted' ); 166 } 167 168 /** Refuse tous les services. */ 169 function rejectAll() { 170 var prefs = {}; 171 toggles.forEach( function ( input ) { 172 input.checked = false; 173 prefs[ input.dataset.serviceKey ] = false; 174 } ); 175 applyConsent( prefs, 'rejected' ); 176 } 177 178 /** Enregistre les choix individuels. */ 179 function savePreferences() { 180 applyConsent( prefsFromToggles(), 'custom' ); 181 } 182 183 /** Cache le bandeau et montre l'icône de réglages. */ 184 function hideBanner() { 185 if ( banner ) { 186 banner.hidden = true; 187 } 188 if ( settingsIcon ) { 189 settingsIcon.hidden = false; 190 } 191 } 192 193 /** Ouvre la popup de préférences. */ 194 function openPopup() { 195 if ( popup ) { 196 popup.hidden = false; 197 } 198 } 199 200 /** Ferme la popup de préférences. */ 201 function closePopup() { 202 if ( popup ) { 203 popup.hidden = true; 204 } 205 } 206 207 /** Restaure l'état des toggles depuis les préférences enregistrées. */ 208 function restoreSavedState() { 209 var consent = null; 210 var saved = {}; 211 try { 212 consent = localStorage.getItem( 'hobConsent' ); 213 saved = JSON.parse( localStorage.getItem( 'hobServicePrefs' ) || '{}' ); 214 } catch ( e ) { 215 return false; 216 } 217 218 if ( ! consent ) { 219 return false; 220 } 221 222 toggles.forEach( function ( input ) { 223 input.checked = !! saved[ input.dataset.serviceKey ]; 224 } ); 225 226 // Réactive ce qui a déjà été consenti (utile si le serveur n'a pas encore vu le cookie). 227 enableConsentedServices( saved ); 228 hideBanner(); 229 return true; 230 } 231 232 /** Gestion des onglets de la popup. */ 233 function initTabs() { 234 var tabs = Array.prototype.slice.call( document.querySelectorAll( '.hob-rgpd-tab' ) );
235 tabs.forEach( function ( tab ) { 236 tab.addEventListener( 'click', function () { 237 var target = tab.getAttribute( 'data-tab' ); 238 239 tabs.forEach( function ( t ) { 240 t.classList.remove( 'is-active' ); 241 t.setAttribute( 'aria-selected', 'false' ); 242 } ); 243 tab.classList.add( 'is-active' ); 244 tab.setAttribute( 'aria-selected', 'true' ); 245 246 Array.prototype.slice.call( document.querySelectorAll( '.hob-rgpd-panel' ) ).forEach( function ( panel ) { 247 var match = panel.id === target; 248 panel.classList.toggle( 'is-active', match ); 249 panel.hidden = ! match; 250 } ); 251 } ); 252 } ); 253 } 254 255 /** Branche tous les événements. */ 256 function bindEvents() { 257 Array.prototype.slice.call( document.querySelectorAll( '.hob-accept-all' ) ).forEach( function ( btn ) { 258 btn.addEventListener( 'click', acceptAll ); 259 } ); 260 Array.prototype.slice.call( document.querySelectorAll( '.hob-reject-all' ) ).forEach( function ( btn ) { 261 btn.addEventListener( 'click', rejectAll ); 262 } ); 263 Array.prototype.slice.call( document.querySelectorAll( '.hob-manage' ) ).forEach( function ( btn ) { 264 btn.addEventListener( 'click', openPopup ); 265 } ); 266 267 var closeBtn = document.querySelector( '.hob-rgpd-popup-close' ); 268 if ( closeBtn ) { 269 closeBtn.addEventListener( 'click', closePopup ); 270 } 271 var saveBtn = document.querySelector( '.hob-save' ); 272 if ( saveBtn ) { 273 saveBtn.addEventListener( 'click', savePreferences ); 274 } 275 if ( settingsIcon ) { 276 settingsIcon.addEventListener( 'click', openPopup ); 277 } 278 279 document.addEventListener( 'keydown', function ( event ) { 280 if ( 'Escape' === event.key ) { 281 closePopup(); 282 } 283 } ); 284 285 if ( popup ) { 286 popup.addEventListener( 'click', function ( event ) { 287 if ( event.target === popup ) { 288 closePopup(); 289 } 290 } ); 291 } 292 } 293 294 document.addEventListener( 'DOMContentLoaded', function () { 295 initTabs(); 296 bindEvents(); 297 restoreSavedState(); 298 } ); 299} )();
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.