1// functions.js â Front-end utilities for the Agile Privacy plugin. 2// 3// Sections: 4// 1. Countdown timers â agile_initCountdown 5// 2. Mautic form integration â internalMauticFormSend 6// 3. Fluent Forms tracking â trackFluentFormEvents 7// 4. GA / Meta / Matomo events â trackGAEvents 8// 5. CC365 modal â MapCC365Modal (IIFE) 9// 6. CC365 public API â window.MapCC365 (IIFE) 10// 7. Pricing checkbox â cc365PricingCheckbox 11// 8. Product view tracking â mapTrackProducts 12// 9. Pills blocks â initPillsBlock 13// 10. DOMContentLoaded bootstrap 14 15// Scans all .countdown-container elements and starts a 1-second interval for each, 16// reading expiry from data-scadenza and language from data-lang (IT default, EN supported). 17// Clears the interval and zeroes all values when the expiry date is reached. 18function agile_initCountdown() { 19 console.info("- INIT COUNTDOWN -"); 20 var countdown_container_array = document.querySelectorAll('.countdown-container'); 21 22 countdown_container_array.forEach(this_countdown => { 23 24 let expire_date = this_countdown.getAttribute('data-scadenza'); 25 let current_lang = this_countdown.getAttribute('data-lang'); 26 27 let expire_date_totime = new Date(expire_date).getTime(); 28 29 let days_container = this_countdown.querySelector('.col-days'); 30 let hours_container = this_countdown.querySelector('.col-hours'); 31 let mins_container = this_countdown.querySelector('.col-mins'); 32 let secs_container = this_countdown.querySelector('.col-secs'); 33 34 let x = setInterval(function () { 35 let now = new Date().getTime(); 36 let distance = expire_date_totime - now; 37 38 if (distance > 0) { 39 var days = Math.floor(distance / (1000 * 60 * 60 * 24)); 40 var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); 41 var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); 42 var seconds = Math.floor((distance % (1000 * 60)) / 1000); 43 } else { 44 var days = 0; 45 var hours = 0; 46 var minutes = 0; 47 var seconds = 0; 48 49 clearInterval(x); 50 51 } 52 53 var days_text = 'giorni'; 54 var hours_text = 'ore'; 55 var mins_text = 'minuti'; 56 var secs_text = 'secondi'; 57 58 if (current_lang == 'en') { 59 var days_text = 'days'; 60 var hours_text = 'hours'; 61 var mins_text = 'minutes'; 62 var secs_text = 'seconds'; 63 } 64 65 days_container.innerHTML = '<span>' + days + "</span>" + days_text; 66 hours_container.innerHTML = '<span>' + hours + "</span>" + hours_text; 67 mins_container.innerHTML = '<span>' + minutes + "</span>" + mins_text; 68 secs_container.innerHTML = '<span>' + seconds + "</span>" + secs_text; 69 70 71 }, 1000); 72 73 74 }); 75} 76 77// Wrapper around parseInt that always uses base 10. 78function _parseInt(number) { 79 return parseInt(number, 10); 80} 81 82// Handles submission of Mautic lead-magnet forms (4 form names supported: 83// agile_lead_magnet_7_2_errori, _en, agile_lead_playground, _en). 84// Builds the Mautic payload, POSTs it via AJAX, fires Meta/GA4/Matomo lead events on success, 85// and redirects the user to the thank-you page after 5 seconds. 86function internalMauticFormSend(caller) { 87 88 console.log('internalMauticFormSend'); 89 90 var customer_lang = 'en'; 91 92 var $html = jQuery('html'); 93 var html_lang = $html.attr('lang'); 94 95 if (html_lang == 'it-IT') { 96 customer_lang = 'it'; 97 } 98 99 var afterfinish_func = jQuery(caller).attr('data-after'); 100 var afterfinish_func2 = jQuery(caller).attr('data-after2'); 101 var afterfinish_func3 = jQuery(caller).attr('data-after3'); 102 103 var form = caller.parents('form'); 104 var form_name = form.attr('name'); 105 var next_action = ''; 106 var url_to_send_to = ''; 107 var formId = ''; 108 var returnString = ''; 109 var formName = ''; 110 var redirect_to = null; 111 112 var event_to_track = null; 113 114 if (form_name == 'agile_lead_magnet_7_2_errori') { 115 next_action = 'redirect'; 116 redirect_to = "https://www.myagileprivacy.com/download-guida-gratuita/"; 117 118 event_to_track = 'lead'; 119 120 // var $frm_first_name = jQuery( ':input[name="frm_first_name"]', form ); 121 // var frm_first_name = $frm_first_name.val(); 122 123 var $frm_email = jQuery(':input[name="frm_email"]', form); 124 var frm_email = $frm_email.val(); 125 126 var $frm_telefono = jQuery(':input[name="frm_telefono"]', form); 127 var frm_telefono = $frm_telefono.val(); 128 129 var $frm_ruolo = jQuery(':input[name="frm_ruolo_aziendale"]', form); 130 var frm_ruolo = $frm_ruolo.val(); 131 132 var $frm_consenso_marketing = jQuery(':input[name="frm_consenso_marketing"]', form); 133 var frm_consenso_marketing = ($frm_consenso_marketing.prop('checked') ? 1 : 0); 134
135 url_to_send_to = 'https://www.robomarketing.it/automation/myagileprivacy.com/form/submit?formId=103'; 136 formId = '103'; 137 returnString = ''; 138 formName = 'leadmagnet72errori'; 139 140 var object_to_send = new Object(); 141 142 //object_to_send['mauticform[first_name]'] = frm_first_name; 143 object_to_send['mauticform[customer_lang]'] = customer_lang; 144 object_to_send['mauticform[email]'] = frm_email; 145 object_to_send['mauticform[telefono1]'] = frm_telefono; 146 object_to_send['mauticform[ruolo]'] = frm_ruolo; 147 148 149 object_to_send['mauticform[privacy][]'] = 1; 150 object_to_send['mauticform[formId]'] = formId; 151 object_to_send['mauticform[return]'] = returnString; 152 object_to_send['mauticform[formName]'] = formName; 153 object_to_send['mauticform[consenso_marketing]'] = frm_consenso_marketing; 154 object_to_send['mauticform[messenger]'] = 1; 155 156 var data_to_send = jQuery.param(object_to_send); 157 } 158 159 if (form_name == 'agile_lead_magnet_7_2_errori_en') { 160 next_action = 'redirect'; 161 redirect_to = "https://www.myagileprivacy.com/en/download-free-guide/"; 162 163 event_to_track = 'lead'; 164 165 // var $frm_first_name = jQuery( ':input[name="frm_first_name"]', form ); 166 // var frm_first_name = $frm_first_name.val(); 167 168 var $frm_email = jQuery(':input[name="frm_email"]', form); 169 var frm_email = $frm_email.val(); 170 171 var $frm_telefono = jQuery(':input[name="frm_telefono"]', form); 172 var frm_telefono = $frm_telefono.val(); 173 174 var $frm_ruolo = jQuery(':input[name="frm_ruolo_aziendale"]', form); 175 var frm_ruolo = $frm_ruolo.val(); 176 177 var $frm_consenso_marketing = jQuery(':input[name="frm_consenso_marketing"]', form); 178 var frm_consenso_marketing = ($frm_consenso_marketing.prop('checked') ? 1 : 0); 179 180 url_to_send_to = 'https://www.robomarketing.it/automation/myagileprivacy.com/form/submit?formId=107'; 181 formId = '107'; 182 returnString = ''; 183 formName = 'leadmagnet72errorien'; 184 185 var object_to_send = new Object(); 186 187 //object_to_send['mauticform[first_name]'] = frm_first_name; 188 object_to_send['mauticform[customer_lang]'] = customer_lang; 189 object_to_send['mauticform[email]'] = frm_email; 190 object_to_send['mauticform[telefono1]'] = frm_telefono; 191 object_to_send['mauticform[ruolo]'] = frm_ruolo; 192 193 194 object_to_send['mauticform[privacy][]'] = 1; 195 object_to_send['mauticform[formId]'] = formId; 196 object_to_send['mauticform[return]'] = returnString; 197 object_to_send['mauticform[formName]'] = formName; 198 object_to_send['mauticform[consenso_marketing]'] = frm_consenso_marketing; 199 object_to_send['mauticform[messenger]'] = 1; 200 201 var data_to_send = jQuery.param(object_to_send); 202 } 203 204 if (form_name == 'agile_lead_playground') { 205 next_action = 'redirect'; 206 redirect_to = "https://playground.myagileprivacy.com/_______start.html"; 207 208 event_to_track = 'lead'; 209 210 // var $frm_first_name = jQuery( ':input[name="frm_first_name"]', form ); 211 // var frm_first_name = $frm_first_name.val(); 212 213 var $frm_email = jQuery(':input[name="frm_email"]', form); 214 var frm_email = $frm_email.val(); 215 216 var $frm_tipologia_utente = jQuery(':input[name="frm_tipologia_utente"]', form); 217 var frm_tipologia_utente = $frm_tipologia_utente.val(); 218 219 var $frm_consenso_marketing = jQuery(':input[name="frm_consenso_marketing"]', form); 220 var frm_consenso_marketing = ($frm_consenso_marketing.prop('checked') ? 1 : 0); 221 222 url_to_send_to = 'https://www.robomarketing.it/automation/myagileprivacy.com/form/submit?formId=104'; 223 formId = '104'; 224 returnString = ''; 225 formName = 'leadplayground'; 226 227 var object_to_send = new Object(); 228 229 //object_to_send['mauticform[first_name]'] = frm_first_name; 230 object_to_send['mauticform[customer_lang]'] = customer_lang; 231 object_to_send['mauticform[email]'] = frm_email; 232 object_to_send['mauticform[tipologia_utente]'] = frm_tipologia_utente; 233 234 235 object_to_send['mauticform[privacy][]'] = 1; 236 object_to_send['mauticform[formId]'] = formId; 237 object_to_send['mauticform[return]'] = returnString; 238 object_to_send['mauticform[formName]'] = formName; 239 object_to_send['mauticform[consenso_marketing]'] = frm_consenso_marketing; 240 object_to_send['mauticform[messenger]'] = 1; 241 242 var data_to_send = jQuery.param(object_to_send); 243 } 244 245 246 if (form_name == 'agile_lead_playground_en') { 247 next_action = 'redirect'; 248 redirect_to = "https://playground.myagileprivacy.com/_______start_eng_new.html"; 249 250 event_to_track = 'lead'; 251 252 // var $frm_first_name = jQuery( ':input[name="frm_first_name"]', form ); 253 // var frm_first_name = $frm_first_name.val(); 254 255 var $frm_email = jQuery(':input[name="frm_email"]', form); 256 var frm_email = $frm_email.val(); 257 258 var $frm_tipologia_utente = jQuery(':input[name="frm_tipologia_utente"]', form); 259 var frm_tipologia_utente = $frm_tipologia_utente.val(); 260 261 var $frm_consenso_marketing = jQuery(':input[name="frm_consenso_marketing"]', form); 262 var frm_consenso_marketing = ($frm_consenso_marketing.prop('checked') ? 1 : 0); 263
264 url_to_send_to = 'https://www.robomarketing.it/automation/myagileprivacy.com/form/submit?formId=105'; 265 formId = '105'; 266 returnString = ''; 267 formName = 'leadplaygrounden'; 268 269 var object_to_send = new Object(); 270 271 //object_to_send['mauticform[first_name]'] = frm_first_name; 272 object_to_send['mauticform[customer_lang]'] = customer_lang; 273 object_to_send['mauticform[email]'] = frm_email; 274 object_to_send['mauticform[tipologia_utente]'] = frm_tipologia_utente; 275 276 277 object_to_send['mauticform[privacy][]'] = 1; 278 object_to_send['mauticform[formId]'] = formId; 279 object_to_send['mauticform[return]'] = returnString; 280 object_to_send['mauticform[formName]'] = formName; 281 object_to_send['mauticform[consenso_marketing]'] = frm_consenso_marketing; 282 object_to_send['mauticform[messenger]'] = 1; 283 284 var data_to_send = jQuery.param(object_to_send); 285 } 286 287 288 // console.log( data_to_send ); 289 290 /**/ 291 jQuery.ajax({ 292 url: url_to_send_to, 293 beforeSend: function () { 294 jQuery('.default_loading', form).css('display', 'block').slideDown('slow'); 295 296 if (!form.hasClass('nohide')) jQuery('.all_fields', form).slideUp('slow'); 297 }, 298 dataType: 'html', 299 data: data_to_send, 300 type: 'POST', 301 error: function () { 302 jQuery('.default_loading', form).slideUp('slow'); 303 jQuery('.thanks', form).slideDown('slow'); 304 305 if (afterfinish_func) { 306 window[afterfinish_func](form, response); 307 }
308 if (afterfinish_func2) { 309 window[afterfinish_func2](form, response); 310 } 311 if (afterfinish_func3) { 312 window[afterfinish_func3](form, response); 313 } 314 315 console.log('formSend --> error during sending :'); 316 return false; 317 }, 318 success: function (response) { 319 jQuery('.default_loading', form).slideUp('slow'); 320 jQuery('.thanks', form).slideDown('slow'); 321 322 323 console.log(response); 324 325 // GA and Meta event tracking 326 if (event_to_track) { 327 switch (event_to_track) { 328 case 'lead': 329 330 try { 331 fbq('track', 'Lead'); 332 } catch (e) { 333 console.error(e); 334 } 335 336 try { 337 gtag('event', 'sign_up'); 338 } catch (e) { 339 console.error(e); 340 } 341 try { 342 _paq.push(['trackEvent', 'Lead', 'sign_up']); 343 } catch (e) { 344 console.error(e); 345 } 346 347 break; 348 default: 349 350 try { 351 fbq('track', 'Lead'); 352 } catch (e) { 353 console.error(e); 354 } 355 356 try { 357 gtag('event', 'sign_up'); 358 } catch (e) { 359 console.error(e); 360 } 361 try { 362 _paq.push(['trackEvent', 'Lead', 'sign_up']); 363 } catch (e) { 364 console.error(e); 365 } 366 break; 367 } 368 } 369 370 371 if (afterfinish_func) { 372 window[afterfinish_func](form, response); 373 }
374 if (afterfinish_func2) { 375 window[afterfinish_func2](form, response); 376 } 377 if (afterfinish_func3) { 378 window[afterfinish_func3](form, response); 379 } 380 381 return false; 382 } 383 }); 384 385 /**/ 386 387 if (next_action == 'redirect') { 388 setTimeout(function () { 389 window.location.href = redirect_to; 390 }, 5000); 391 } else if (next_action != '') { 392 var data = { 393 'action': next_action, 394 'email': frm_email, 395 'form_name': form_name 396 }; 397 398 jQuery.post('/wp-admin/admin-ajax.php', data, function (response) { }); 399 } 400 401 return false; 402} 403 404 405// Hooks the fluentform_submission_success event on every .frm-fluent-form. 406// On success, sends a Lead event to Meta (with SHA-256 email if available), a generate_lead 407// event to GA4 (with user_data), a Matomo trackEvent, and a push to window.gdmEvents. 408function trackFluentFormEvents() { 409 410 var fluentForms = jQuery('form.frm-fluent-form'); 411 fluentForms.each(function () { 412 var $form = jQuery(this); 413 var formId = $form.attr('data-form_id'); 414 415 416 $form.on('fluentform_submission_success', function () { 417 console.log('form sent with success'); 418 419 // Enhanced conversion 420 var userdata = {}; 421 userdata.email = jQuery(':input[name="email"]', $form).val(); 422 423 try { 424 425 if (typeof window.sha256_calc !== 'undefined') { 426 var fb_data = { 427 email: userdata.email, 428 }; 429 430 fbq('track', 'Lead', fb_data); 431 } 432 else { 433 434 fbq('track', 'Lead'); 435 } 436 437 438 } catch (e) { 439 console.error(e); 440 } 441 442 try { 443 444 if (typeof window.sha256_calc !== 'undefined') { 445 var user_data_details = { 446 "sha256_email_address": sha256_calc(userdata?.email), 447 }; 448 449 console.table(user_data_details); 450 gtag("set", "user_data", user_data_details); 451 } 452 453 gtag('event', 'generate_lead'); 454 455 } catch (e) { 456 console.error(e); 457 } 458 459 try { 460 _paq.push(['trackEvent', 'Lead', 'form_contatti']); 461 } catch (e) { 462 console.error(e); 463 } 464 465 try { 466 window.gdmEvents.push("contact-sales"); 467 } catch (e) { 468 console.error(e); 469 } 470 471 }); 472 473 }); 474} 475 476 477 478// Fires a custom 30_sec_pageview event to GA4 and Matomo 30 seconds after the page loads. 479function trackGAEvents() { 480 console.log('trackGAEvents'); 481 482 setTimeout(function () { 483 484 console.log('sending 30_sec_pageview event'); 485 486 try { 487 gtag('event', '30_sec_pageview'); 488 489 _paq.push(['trackEvent', '30_sec_pageview', '30_sec_pageview']); 490 491 } catch (e) { 492 console.error(e); 493 } 494 495 }, 30000); 496} 497 498/** 499 * ComplianceCheck365 â removal confirmation modal + public API. 500 * 501 * WHEN IT TRIGGERS 502 * The modal appears automatically when the user DESELECTS a 503 * `.map-cc365-checkbox` checkbox inside a `.map-plan-card`, 504 * OR when a non-CC365 radio is selected in a group managed 505 * by bindProductSelector(). 506 * Selecting (checking/switching to CC365) does NOT open any modal. 507 * 508 * FLOW 509 * 1. user unchecks / switches radio â event `cc365:remove-attempt` 510 * 2a. clicks "Keep" â element restored, event `cc365:remove-cancelled` 511 * 2b. clicks "Proceed" â element left deselected, event `cc365:remove-confirmed` 512 * 513 * CUSTOM EVENTS (bubbling, dispatched on the CC365 element) 514 * - 'cc365:remove-attempt' 515 * - 'cc365:remove-cancelled' 516 * - 'cc365:remove-confirmed' 517 * detail = { el, planId, planName } 518 * 519 * PUBLIC API (window.MapCC365) 520 * window.MapCC365.confirmRemoval(el) 521 * window.MapCC365.bindProductSelector(wrapper [, opts]) 522 * window.MapCC365.modal.open({ onConfirm, onCancel }) 523 * window.MapCC365.modal.close([action]) 524 * window.MapCC365.debugGroups() 525 * 526 * DEBUG 527 * Set CC365_DEBUG = true to enable console logs. 528 * 529 * I18N 530 * IT/EN strings chosen from document.documentElement.lang. Defaults to IT. 531 */ 532 533/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 534 PART 1 â MapCC365Modal (original, unchanged) 535âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 536// Manages the modal lifecycle: lazy DOM build, open/close, ESC key, focus restore, IT/EN i18n. 537var MapCC365Modal = (function () {
538 var overlay = null; 539 var current = null; 540 var lastFocus = null; 541 542 var i18n = { 543 it: { 544 title: 'Stai rimuovendo la verifica umana', 545 body: 'Senza ComplianceCheck365, il tuo sito potrebbe risultare non conforme al GDPR senza che tu lo sappia â e una sanzione può arrivare fino al 4% del fatturato annuo. I nostri esperti controllano il tuo sito ogni 6 mesi e ti avvisano prima che sia troppo tardi.', 546 question: 'Vuoi davvero procedere senza protezione?', 547 cancel: 'Mantieni ComplianceCheck365', 548 confirm: 'Sì, procedi senza protezione' 549 }, 550 en: { 551 title: 'You are removing human verification', 552 body: 'Without ComplianceCheck365, your site could be non-compliant with the GDPR without you knowing â and a fine can reach up to 4% of annual turnover. Our experts check your site every 6 months and warn you before it is too late.', 553 question: 'Do you really want to proceed without protection?', 554 cancel: 'Keep ComplianceCheck365', 555 confirm: 'Yes, proceed without protection' 556 } 557 }; 558 559 function getLang() { 560 var l = (document.documentElement.lang || 'it').toLowerCase(); 561 return (l.indexOf('en') === 0) ? 'en' : 'it'; 562 } 563 564 function build() { 565 var t = i18n[getLang()]; 566 overlay = document.createElement('div'); 567 overlay.className = 'map-cc365-modal-overlay'; 568 overlay.setAttribute('hidden', ''); 569 overlay.innerHTML = 570 '<div class="map-cc365-modal" role="dialog" aria-modal="true" aria-labelledby="map-cc365-modal-title">' + 571 '<button type="button" class="map-cc365-modal-close" data-action="cancel" aria-label="Close">' + 572 '<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M18.3 5.71L12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.29 19.7 2.88 18.3 9.17 12 2.88 5.71 4.29 4.29l6.3 6.3 6.29-6.3z"/></svg>' + 573 '</button>' + 574 '<div class="map-cc365-modal-icon" aria-hidden="true">' + 575 '<svg viewBox="0 0 24 24" width="34" height="34" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + 576 '<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/>' + 577 '<line x1="12" y1="9" x2="12" y2="13"/>' + 578 '<line x1="12" y1="17" x2="12.01" y2="17"/>' + 579 '</svg>' + 580 '</div>' + 581 '<h3 id="map-cc365-modal-title" class="map-cc365-modal-title"></h3>' + 582 '<p class="map-cc365-modal-body"></p>' + 583 '<div class="map-cc365-modal-question-box">' + 584 '<p class="map-cc365-modal-question"></p>' + 585 '</div>' + 586 '<div class="map-cc365-modal-actions">' + 587 '<button type="button" class="map-cc365-modal-btn map-cc365-modal-btn--primary" data-action="cancel">' + 588 '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>' + 589 '<span class="map-cc365-modal-btn-label"></span>' + 590 '</button>' + 591 '<button type="button" class="map-cc365-modal-btn map-cc365-modal-btn--secondary" data-action="confirm"></button>' + 592 '</div>' + 593 '</div>'; 594 595 overlay.querySelector('.map-cc365-modal-title').textContent = t.title; 596 overlay.querySelector('.map-cc365-modal-body').textContent = t.body; 597 overlay.querySelector('.map-cc365-modal-question').textContent = t.question; 598 overlay.querySelector('.map-cc365-modal-btn-label').textContent = t.cancel; 599 overlay.querySelector('.map-cc365-modal-btn--secondary').textContent = t.confirm; 600 601 document.body.appendChild(overlay); 602 603 overlay.addEventListener('click', function (e) { 604 if (e.target === overlay) { close('cancel'); return; } 605 var btn = e.target.closest('[data-action]'); 606 if (btn) close(btn.dataset.action); 607 }); 608 609 document.addEventListener('keydown', function (e) { 610 if (e.key === 'Escape' && overlay && !overlay.hasAttribute('hidden')) { 611 close('cancel'); 612 } 613 }); 614 } 615 616 function open(opts) { 617 // Build DOM lazily on first open. 618 if (!overlay) build(); 619 current = opts || {}; 620 lastFocus = document.activeElement; 621 overlay.removeAttribute('hidden'); 622 overlay.classList.add('is-open'); 623 document.body.classList.add('map-cc365-modal-open'); 624 var primaryBtn = overlay.querySelector('.map-cc365-modal-btn--primary'); 625 if (primaryBtn) setTimeout(function () { primaryBtn.focus(); }, 30); 626 } 627 628 function close(action) { 629 if (!overlay) return; 630 overlay.classList.remove('is-open'); 631 overlay.setAttribute('hidden', ''); 632 document.body.classList.remove('map-cc365-modal-open'); 633 var cb = current; 634 current = null; 635 // Restore focus to the previously active element. 636 if (lastFocus && typeof lastFocus.focus === 'function') { 637 try { lastFocus.focus(); } catch (e) { } 638 } 639 lastFocus = null; 640 if (!cb) return; 641 if (action === 'confirm' && typeof cb.onConfirm === 'function') cb.onConfirm(); 642 else if (typeof cb.onCancel === 'function') cb.onCancel(); 643 } 644 645 return { open: open, close: close }; 646})(); 647 648 649/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 650 PART 2 â window.MapCC365 (public API for radios/checkboxes) 651 Activates only if the page contains .AbF-container. 652âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 653(function (Modal) { 654 655 /* ââ Debug ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 656 657 var CC365_DEBUG = false; // true = print logs to console, false = silent 658 659 function log() { if (CC365_DEBUG) { console.log.apply(console, arguments); } } 660 function warn() { if (CC365_DEBUG) { console.warn.apply(console, arguments); } } 661 662 /* ââ Guard + DOMContentLoaded âââââââââââââââââââââââââââââââââââââââââââââ */ 663
664 function init() { 665 if (!document.querySelector('.AbF-container')) { 666 log('[CC365] .AbF-container non trovato â API radio disattivata'); 667 return; 668 } 669 log('[CC365] .AbF-container trovato â API radio attiva'); 670 run(); 671 } 672 673 if (document.readyState === 'loading') { 674 document.addEventListener('DOMContentLoaded', init); 675 } else { 676 init(); 677 } 678 679 /* ââ Main logic ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 680 681 function run() { 682 683 var WRAPPER_SELECTOR = '.abf-product-selector-wrapper'; 684 var CC365_SELECTOR = '.abf-product-option-upgrade input[type="radio"]'; 685 686 var groups = {}; 687 var pendingWrappers = []; 688 689 /* ââ helpers ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 690 691 function dispatch(el, name, detail) { 692 log('[CC365] dispatch "' + name + '"', detail); 693 var ev; 694 try { 695 ev = new CustomEvent(name, { bubbles: true, cancelable: false, detail: detail }); 696 } catch (e) { 697 ev = document.createEvent('CustomEvent'); 698 ev.initCustomEvent(name, true, false, detail); 699 } 700 el.dispatchEvent(ev); 701 } 702 703 function fireChange(el) { 704 log('[CC365] fireChange su', el); 705 var ev; 706 try { ev = new Event('change', { bubbles: true }); } 707 catch (e) { ev = document.createEvent('Event'); ev.initEvent('change', true, false); } 708 el.dispatchEvent(ev); 709 } 710 711 function buildDetail(el) { 712 var container = el.closest('label') || 713 el.closest('.abf-product-option') || 714 el.closest('.map-plan-card') || 715 el.parentElement; 716 var heading = container ? container.querySelector('h5, h4, h3, h2') : null; 717 return { 718 el: el, 719 planId: el.value || null, 720 planName: (heading ? heading.textContent.trim() : null) || 721 el.getAttribute('aria-label') || el.name || '' 722 }; 723 } 724 725 /* ââ Delegated listener â capture phase ââââââââââââââââââââââââââââââ */ 726 727 document.addEventListener('change', function (e) { 728 var radio = e.target; 729 if (!radio || radio.type !== 'radio' || !radio.checked) { return; } 730 731 var group = groups[radio.name]; 732 if (!group) { return; } 733 734 log('[CC365] capture change name="' + radio.name + '" value="' + radio.value + '" | cc365Active:', group.cc365Active); 735 736 if (radio.value === group.cc365Value) { 737 log('[CC365] selezionato CC365 â cc365Active = true'); 738 group.cc365Active = true; 739 return; 740 } 741 742 if (group.cc365Active) { 743 log('[CC365] CC365 era attivo â apro modale'); 744 group.cc365Active = false; 745 var cc365Radio = document.querySelector( 746 'input[type="radio"][name="' + radio.name + '"][value="' + group.cc365Value + '"]' 747 ); 748 if (cc365Radio) { 749 confirmRemoval(cc365Radio, group); 750 } else { 751 warn('[CC365] cc365Radio non trovato nel DOM'); 752 } 753 } 754 755 }, true); // capture:true â immune to stopPropagation during bubbling 756 757 /* ââ confirmRemoval âââââââââââââââââââââââââââââââââââââââââââââââââ */ 758 759 // Opens the modal for a CC365 element; restores the radio on cancel or dispatches confirmed. 760 function confirmRemoval(cc365El, group) { 761 log('[CC365] confirmRemoval', cc365El); 762 var detail = buildDetail(cc365El); 763 dispatch(cc365El, 'cc365:remove-attempt', detail); 764 765 Modal.open({ 766 onConfirm: function () { 767 log('[CC365] confermato â CC365 rimosso'); 768 dispatch(cc365El, 'cc365:remove-confirmed', detail); 769 }, 770 onCancel: function () { 771 log('[CC365] annullato â ripristino CC365'); 772 var fresh = document.querySelector( 773 'input[type="radio"][name="' + cc365El.name + '"][value="' + cc365El.value + '"]' 774 ); 775 if (fresh) { 776 fresh.checked = true; 777 group.cc365Active = true; 778 fireChange(fresh); 779 } 780 dispatch(cc365El, 'cc365:remove-cancelled', detail); 781 } 782 }); 783 } 784 785 /* ââ bindProductSelector ââââââââââââââââââââââââââââââââââââââââââââ */ 786 787 // Registers a wrapper element as a CC365 radio group; queues it if radios are not yet in DOM. 788 function bindProductSelector(wrapper, opts) { 789 if (typeof wrapper === 'string') { 790 wrapper = document.querySelector(wrapper); 791 } 792 if (!wrapper) { 793 warn('[CC365] bindProductSelector â wrapper non trovato'); 794 return; 795 } 796 797 var cc365Sel = (opts && opts.cc365Selector) || CC365_SELECTOR; 798 var cc365Radio = wrapper.querySelector(cc365Sel); 799 800 if (!cc365Radio) { 801 if (pendingWrappers.indexOf(wrapper) === -1) { 802 pendingWrappers.push(wrapper); 803 log('[CC365] bindProductSelector â radio non ancora presenti, in coda'); 804 } 805 return; 806 } 807 808 var groupName = cc365Radio.name; 809 var cc365Value = cc365Radio.value; 810 811 if (groups[groupName]) { 812 groups[groupName].cc365Active = cc365Radio.checked; 813 log('[CC365] bindProductSelector â gruppo "' + groupName + '" aggiornato'); 814 return; 815 } 816 817 groups[groupName] = { cc365Value: cc365Value, cc365Active: cc365Radio.checked }; 818 819 var idx = pendingWrappers.indexOf(wrapper); 820 if (idx !== -1) { pendingWrappers.splice(idx, 1); } 821 822 log('[CC365] bindProductSelector â â gruppo "' + groupName + '" registrato | value:', cc365Value, '| active:', cc365Radio.checked); 823 } 824 825 /* ââ retryPending âââââââââââââââââââââââââââââââââââââââââââââââââââ */ 826 827 // Re-attempts bindProductSelector on all queued wrappers (called from MutationObserver). 828 function retryPending() { 829 if (!pendingWrappers.length) { return; } 830 var queue = pendingWrappers.slice(); 831 for (var i = 0; i < queue.length; i++) { bindProductSelector(queue[i]); } 832 } 833 834 /* ââ auto-init with MutationObserver ââââââââââââââââââââââââââââââââ */ 835 836 // Binds any wrapper selector found in a newly added DOM node or its desce
836ndants. 837 function tryBind(node) { 838 if (!node || node.nodeType !== 1) { return; } 839 if (node.matches && node.matches(WRAPPER_SELECTOR)) { bindProductSelector(node); } 840 var nested = node.querySelectorAll ? node.querySelectorAll(WRAPPER_SELECTOR) : []; 841 for (var k = 0; k < nested.length; k++) { bindProductSelector(nested[k]); } 842 } 843 844 var existing = document.querySelectorAll(WRAPPER_SELECTOR); 845 log('[CC365] autoInit â wrapper già nel DOM:', existing.length); 846 for (var i = 0; i < existing.length; i++) { bindProductSelector(existing[i]); } 847 848 if (window.MutationObserver) { 849 var observer = new MutationObserver(function (mutations) { 850 for (var m = 0; m < mutations.length; m++) { 851 var added = mutations[m].addedNodes; 852 for (var n = 0; n < added.length; n++) { tryBind(added[n]); } 853 } 854 retryPending(); 855 }); 856 observer.observe(document.body || document.documentElement, { 857 childList: true, subtree: true 858 }); 859 log('[CC365] MutationObserver avviato'); 860 } 861 862 /* ââ Exported API âââââââââââââââââââââââââââââââââââââââââââââââââââ */ 863 864 window.MapCC365 = { 865 modal: Modal, 866 confirmRemoval: function (el) { confirmRemoval(el, groups[el.name] || {}); }, 867 bindProductSelector: bindProductSelector, 868 debugGroups: function () { return JSON.parse(JSON.stringify(groups)); } 869 }; 870 871 } // end run() 872 873})(MapCC365Modal); 874 875 876// Binds .map-cc365-checkbox checkboxes in the pricing section. 877// Toggles CC365 state on each plan card (price, CTA link, CSS class), fires GA4/Matomo/Meta 878// view_item/ViewContent events on change, and opens the removal confirmation modal on uncheck. 879// Exposes window.MapCC365.confirmRemoval and window.MapCC365.modal as public shortcuts. 880function cc365PricingCheckbox() { 881 882 function updateCardState(checkbox) { 883 var card = checkbox.closest('.map-plan-card'); 884 var priceEl = card.querySelector('.map-plan-price'); 885 var priceValue = card.querySelector('.price-value'); 886 var ctaLink = card.querySelector('.map-plan-cta'); 887 888 if (checkbox.checked) { 889 card.classList.add('cc365-active'); 890 priceValue.textContent = priceEl.dataset.cc; 891 892 var url = new URL(ctaLink.href); 893 url.searchParams.set('alt_product', 'true'); 894 ctaLink.href = url.toString(); 895 } else { 896 card.classList.remove('cc365-active'); 897 priceValue.textContent = priceEl.dataset.base; 898 899 var url = new URL(ctaLink.href); 900 url.searchParams.delete('alt_product'); 901 ctaLink.href = url.toString(); 902 } 903 } 904 905 function trackCheckboxChange(checkbox) { 906 var card = checkbox.closest('.map-plan-card'); 907 var priceEl = card.querySelector('.map-plan-price'); 908 909 var idprodotto = checkbox.checked ? card.dataset.idprodottoCc : card.dataset.idprodotto; 910 var nome = card.dataset.planName + (checkbox.checked ? ' + CC365' : ''); 911 var prezzo = checkbox.checked ? parseFloat(priceEl.dataset.cc) || 0 : parseFloat(priceEl.dataset.base) || 0; 912 913 try { 914 gtag('event', 'view_item', { 915 currency: 'EUR', 916 value: prezzo, 917 items: [{ 918 item_id: String(idprodotto), 919 item_name: nome, 920 price: prezzo, 921 quantity: 1 922 }] 923 }); 924 console.log('[GA4] view_item inviato | id:', idprodotto, '| nome:', nome, '| prezzo:', prezzo); 925 } catch (e) { console.error('[GA4] view_item error:', e); } 926 927 try { 928 _paq.push(['trackEvent', 'Ecommerce', 'view_item', nome, prezzo]); 929 console.log('[Matomo] trackEvent inviato | id:', idprodotto, '| nome:', nome, '| prezzo:', prezzo); 930 } catch (e) { console.error('[Matomo] trackEvent error:', e); } 931 932 try { 933 fbq('track', 'ViewContent', { 934 content_name: nome, 935 content_ids: [String(idprodotto)], 936 content_type: 'product', 937 value: prezzo, 938 currency: 'EUR' 939 }); 940 console.log('[Meta] ViewContent inviato | id:', idprodotto, '| nome:', nome, '| prezzo:', prezzo); 941 } catch (e) { console.error('[Meta] ViewContent error:', e); } 942 } 943 944 function fireEvent(checkbox, name) { 945 var card = checkbox.closest('.map-plan-card'); 946 checkbox.dispatchEvent(new CustomEvent(name, { 947 bubbles: true, 948 detail: { 949 checkbox: checkbox, 950 planId: card ? card.dataset.planId : null, 951 planName: card ? card.dataset.planName : null, 952 idprodotto: card ? card.dataset.idprodotto : null, 953 idprodottoCc: card ? card.dataset.idprodottoCc : null 954 } 955 })); 956 } 957 958 function applyRemoval(checkbox) { 959 updateCardState(checkbox);
960 trackCheckboxChange(checkbox); 961 fireEvent(checkbox, 'cc365:remove-confirmed'); 962 } 963 964 function cancelRemoval(checkbox) { 965 checkbox.checked = true; 966 fireEvent(checkbox, 'cc365:remove-cancelled'); 967 } 968 969 function openRemovalConfirm(checkbox) { 970 fireEvent(checkbox, 'cc365:remove-attempt'); 971 MapCC365Modal.open({ 972 onConfirm: function () { applyRemoval(checkbox); }, 973 onCancel: function () { cancelRemoval(checkbox); } 974 }); 975 } 976 977 document.querySelectorAll('.map-cc365-checkbox').forEach(function (checkbox) { 978 979 if (checkbox.dataset.bound) return; 980 checkbox.dataset.bound = 'true'; 981 982 checkbox.addEventListener('change', function () { 983 console.log('[CC365] change triggered'); 984 if (this.checked) { 985 updateCardState(this); 986 trackCheckboxChange(this); 987 } else { 988 openRemovalConfirm(this); 989 } 990 }); 991 992 // DOM update only, no tracking 993 if (checkbox.checked) { 994 updateCardState(checkbox); 995 } 996 }); 997 998 window.MapCC365 = window.MapCC365 || {}; 999 window.MapCC365.confirmRemoval = openRemovalConfirm; 1000 window.MapCC365.modal = MapCC365Modal; 1001 1002} 1003 1004// Sends a Meta ViewContent event for each product in the array, 1005// starting with a 10-second initial delay and staggering each subsequent item by 500 ms. 1006function mapTrackProducts(products) { 1007 if (!products || !products.length) return; 1008 var delay = 10000; 1009 products.forEach(function (product) { 1010 setTimeout(function () { 1011 try { 1012 fbq('track', 'ViewContent', { 1013 content_name: product.name, 1014 content_ids: [String(product.idprodotto)], 1015 content_type: 'product', 1016 value: product.price, 1017 currency: 'EUR' 1018 }); 1019 console.log('[Meta] ViewContent inviato | id:', product.idprodotto, '| nome:', product.name, '| prezzo:', product.price); 1020 } catch (e) { console.error('[Meta] ViewContent error:', e); } 1021 }, delay); 1022 delay += 500; 1023 }); 1024} 1025 1026// Initialises a pills block (languages or regulations). 1027// Detects block type from data-langs-pills / data-regs-pills, picks IT/EN labels from 1028// <html lang>, and wires the toggle button to expand/collapse hidden .is-extra items. 1029function initPillsBlock(block) { 1030 const btn = block.querySelector('.toggle-more'); 1031 if (!btn) return; 1032 1033 // Detect block type: langs or regs 1034 const isLangs = block.hasAttribute('data-langs-pills'); 1035 const type = isLangs ? 'langs' : 'regs'; 1036 1037 // UI language 1038 function detectLang() { 1039 const htmlLang = (document.documentElement.lang || '').toLowerCase(); 1040 return htmlLang.startsWith('it') ? 'it' : 'en'; 1041 } 1042 const lang = detectLang(); 1043 1044 // Labels by type and count 1045 const labels = { 1046 regs: { 1047 it: (n) => `+${n} ${n === 1 ? 'regolamento' : 'regolamenti'} â`, 1048 en: (n) => `+${n} ${n === 1 ? 'other regulation' : 'other regulations'} â` 1049 }, 1050 langs: { 1051 it: (n) => `+${n} ${n === 1 ? 'altra lingua' : 'altre lingue'} â`, 1052 en: (n) => `+${n} ${n === 1 ? 'other language' : 'other languages'} â` 1053 } 1054 }; 1055 1056 function labelForCount(count) { 1057 return labels[type][lang](count); 1058 } 1059 1060 const hiddenItems = block.querySelectorAll('.is-extra'); 1061 const hiddenCount = hiddenItems.length; 1062 1063 block.dataset.expanded = 'false'; 1064 btn.setAttribute('aria-expanded', 'false'); 1065 btn.textContent = labelForCount(hiddenCount); 1066 1067 btn.addEventListener('click', function (e) { 1068 const root = e.currentTarget.closest('[data-regs-pills], [data-langs-pills]'); 1069 if (!root) return; 1070 1071 const expanded = root.dataset.expanded === 'true'; 1072 const next = !expanded; 1073 1074 root.classList.toggle('is-expanded', next); 1075 root.dataset.expanded = String(next); 1076 btn.setAttribute('aria-expanded', String(next)); 1077 btn.textContent = next ? 'â' : labelForCount(hiddenCount); 1078 }); 1079} 1080// Global click delegate: walks up from the click target and, if any ancestor carries 1081// a "track-ga-event" attribute, sends a GA4 event using the attribute value as event name. 1082// NOTE: this listener is registered twice (here and inside DOMContentLoaded below) â kept as-is. 1083 // Intercepts all elements with the track-ga-event attribute 1084 document.addEventListener('click', function(e) { 1085 // Walk up the DOM from the clicked element looking for track-ga-event 1086 let targetElement = e.target; 1087 let eventName = null; 1088 1089 // Check the clicked element and its ancestors up to the document 1090 while (targetElement && targetElement !== document) { 1091 if (targetElement.hasAttribute && targetElement.hasAttribute('track-ga-event')) { 1092 eventName = targetElement.getAttribute('track-ga-event'); 1093 break; 1094 } 1095 targetElement = targetElement.parentElement; 1096 } 1097 1098 // If an element with the attribute was found, track the event 1099 if (eventName) { 1100 console.log('Elemento trovato:', targetElement); 1101 console.log('Nome evento:', eventName); 1102 1103 // Send the event to GA4 via gtag 1104 try { 1105 gtag('event', eventName, { 1106 'page_url': window.location.href, 1107 'page_title': document.title 1108 }); 1109 console.log('Track GA Event: ', eventName, window.location.href, document.title); 1110 } catch (error) { 1111 console.error('Errore tracciamento GA4:', error); 1112 } 1113 } 1114 }); 1115 1116 1117// Main bootstrap: initialises pills blocks, registers the click GA delegate, wires MAP consent/ 1118// cookie listeners, pricing checkbox, plugin forms, enemies accordion fix, tippy tooltips, 1119// and starts the countdown, Fluent Forms, and GA event trackers. 1120document.addEventListener("DOMContentLoaded", function () { 1121 console.log('functions.js document ready'); 1122 1123 document.querySelectorAll('[data-regs-pills], [data-langs-pills]').forEach(initPillsBlock); 1124 1125 // Intercepts all elements with the track-ga-event attribute 1126 document.addEventListener('click', function(e) { 1127 // Walk up the DOM from the clicked element looking for track-ga-event 1128 let targetElement = e.target; 1129 let eventName = null; 1130 1131 // Check the clicked element and its ancestors up to the document 1132 while (targetElement && targetElement !== document) { 1133 if (targetElement.hasAttribute && targetElement.hasAttribute('track-ga-event')) { 1134 eventName = targetElement.getAttribute('track-ga-event'); 1135 break; 1136 } 1137 targetElement = targetElement.parentElement; 1138 } 1139 1140 // If an element with the attribute was found, track the event 1141 if (eventName) { 1142 console.log('Elemento trovato:', targetElement); 1143 console.log('Nome evento:', eventName); 1144 1145 // Send the event to GA4 via gtag 1146 try { 1147 gtag('event', eventName, { 1148 'page_url': window.location.href, 1149 'page_title': document.title 1150 }); 1151 console.log('Track GA Event: ', eventName, window.location.href, document.title); 1152 } catch (error) { 1153 console.error('Errore tracciamento GA4:', error); 1154 } 1155 } 1156 }); 1157 1158 1159 // begin MAP listeners 1160 window.addEventListener('myagileprivacy_gcmode_consent_granted', function (e) { 1161 _paq.push(['trackEvent', 'MAP', 'myagileprivacy_gcmode_consent_granted']); 1162 }); 1163 1164 // Consent denied listener 1165 window.addEventListener('myagileprivacy_gcmode_consent_denied', function (e) { 1166 _paq.push(['trackEvent', 'MAP', 'myagileprivacy_gcmode_consent_denied']); 1167 }); 1168 1169 1170 window.addEventListener('myagileprivacy_cookie_reject', function (e) { 1171 _paq.push(['trackEvent', 'MAP', 'myagileprivacy_cookie_reject']); 1172 }); 1173 1174 1175 window.addEventListener('myagileprivacy_cookie_customize', function (e) { 1176 _paq.push(['trackEvent', 'MAP', 'myagileprivacy_cookie_customize']); 1177 }); 1178 1179 window.addEventListener('myagileprivacy_cookie_accept', function (e) { 1180 _paq.push(['trackEvent', 'MAP', 'myagileprivacy_cookie_accept']); 1181 }); 1182 1183 window.addEventListener('myagileprivacy_resurface', function (e) { 1184 _paq.push(['trackEvent', 'MAP', 'myagileprivacy_resurface']); 1185 }); 1186 1187 window.addEventListener('myagileprivacy_single_cookie_accept', function (e) { 1188 _paq.push(['trackEvent', 'MAP', 'myagileprivacy_single_cookie_accept']); 1189 }); 1190 1191 window.addEventListener('myagileprivacy_single_cookie_reject', function (e) { 1192 _paq.push(['trackEvent', 'MAP', 'myagileprivacy_single_cookie_reject']); 1193 }); 1194 // end MAP listeners 1195 1196 cc365PricingCheckbox(); 1197 1198 1199 1200 jQuery('.pluginForm').each(function () { 1201 jQuery(this).formSend(); 1202 }); 1203 1204 // Enemies accordion fix â pages 14086 / 14091 only
1205 /**/ 1206 setTimeout(function () { 1207 if (document.body.classList.contains('page-id-14086') || 1208 document.body.classList.contains('page-id-14091')) { 1209 1210 var readMoreSpans = document.querySelectorAll('a.oxy-read-more-link'); 1211 readMoreSpans.forEach(function (readMoreSpan) { 1212 readMoreSpan.addEventListener('click', function () { 1213 1214 setTimeout(function () { 1215 console.log('dispatch'); 1216 window.dispatchEvent(new Event('resize')); 1217 }, 500); 1218 }); 1219 }); 1220 } 1221 }, 800); 1222 /**/ 1223 1224 1225 1226 // Only on elements with a non-empty data-tippy-content attribute 1227 var elementsWithContent = Array.from(document.querySelectorAll('[data-tippy-content]')) 1228 .filter(element => element.getAttribute('data-tippy-content').trim().length > 0); 1229 jQuery(elementsWithContent).addClass('dotted-underline'); 1230 tippy(elementsWithContent, { 1231 animation: 'shift-away' 1232 }); 1233 1234 agile_initCountdown(); 1235 1236 trackFluentFormEvents(); 1237 1238 trackGAEvents(); 1239 1240});
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.