1/** 2 * HMNS Footer Front-End Logic 3 * 4 * Extracted verbatim from the former inline <script> in footer.php so it can be 5 * enqueued the WordPress way (versioned via filemtime, cacheable) instead of 6 * being re-parsed inline on every page load. 7 * 8 * All values that were previously injected via PHP (SVG icons, the Tessitura 9 * server URL, the current permalink, a URL promo code, notification-bar session 10 * flags, and the search query) are now passed in through window.hmnsFooterData, 11 * localized just before this file by hmns_enqueue_footer_script(). 12 * 13 * Depends on jQuery and js/hmns-tnew-helpers.js (hmnsSetAjaxHeaders, 14 * hmnsCreateTNEWSession) and on the hmnsCookieDomain global (set inline in 15 * header.php) and window.hmns_ajax_router (set in the footer at priority 1). 16 */ 17var hmnsFooterData = window.hmnsFooterData || {}; 18hmnsFooterData.icons = hmnsFooterData.icons || {}; 19jQuery(document).ready(function ($) { 20 21 /** 22 * Keeps #container padding in sync with the real height of .top-nav + header#header (including 0â2 notification bars). 23 * When no notification bars are visible, restores header top padding to 12px. 24 */ 25 var hmnsLastHeaderStackBottom = 0; 26 window.hmnsApplyHeaderStackOffset = function () { 27 var header = document.querySelector('header#header'); 28 if (!header) { 29 return; 30 } 31 var $vis = $('.hmns-notifications').filter(function () { 32 return !$(this).hasClass('hide'); 33 }); 34 $vis.css('margin-top', ''); 35 if ($vis.length >= 2) { 36 $vis.eq(1).css('margin-top', '-21px'); 37 } 38 if ($vis.length === 0) { 39 header.style.paddingTop = '12px'; 40 } else { 41 header.style.paddingTop = '0px'; 42 } 43 var topNav = document.querySelector('.top-nav'); 44 var tnRect = topNav ? topNav.getBoundingClientRect() : null; 45 var tnBottom = tnRect ? Math.ceil(tnRect.bottom) : 0; 46 var hRect = header.getBoundingClientRect(); 47 var hBottom = Math.ceil(hRect.bottom); 48 var hH = header.offsetHeight; 49 // If header#header shares the same top origin as the viewport (or sits above .top-nav's bottom), 50 // max(tnBottom, hBottom) under-counts (e.g. 122 instead of 53 + 122). Stack: top-nav + full header height. 51 var stackBottom; 52 if (topNav && hRect.top < tnBottom - 2) {
53 stackBottom = tnBottom + hH; 54 } else { 55 stackBottom = Math.max(tnBottom, hBottom); 56 } 57 stackBottom = Math.ceil(stackBottom); 58 // Logged-in WP admin bar: extra clearance (toolbar not fully reflected in fixed header measurements). 59 if (document.body.classList.contains('admin-bar')) { 60 stackBottom += window.innerWidth < 787 ? 46 : 32; 61 } 62 if (stackBottom > 48) { 63 hmnsLastHeaderStackBottom = stackBottom; 64 } 65 var px = hmnsLastHeaderStackBottom + 'px'; 66 var rootStyle = document.documentElement.style; 67 if (rootStyle.getPropertyValue('--hmns-header-stack-height').trim() === px) { 68 return; 69 } 70 rootStyle.setProperty('--hmns-header-stack-height', px); 71 }; 72 73 const setAjaxHeaders = window.hmnsSetAjaxHeaders; 74 75 // Override jQuery.ajax to automatically queue router calls until nonce is ready 76 var _origAjax = $.ajax; 77 $.ajax = function(options) { 78 var url = options && options.url; 79 var isRouter = url && (url === window.hmns_ajax_router?.url || 80 (typeof url === 'string' && url.indexOf('/wp-json/hmns-api/v1/router') !== -1)); 81 if (isRouter && !(window.hmns_ajax_router && window.hmns_ajax_router.ready)) { 82 var deferred = $.Deferred(); 83 $(document).one('hmns_nonce_ready', function() { 84 _origAjax.call($, options) 85 .done(function() { deferred.resolve.apply(deferred, arguments); }) 86 .fail(function() { deferred.reject.apply(deferred, arguments); }); 87 }); 88 return deferred.promise(); 89 } 90 return _origAjax.call(this, options); 91 }; 92 93 $('.cookies-menu').on('click tap',function () { 94 $('.cookies-modal').removeClass('hide'); 95 $(this).addClass('open'); 96 }); 97 var clearcookiesreset = 0; 98 $('.cookie-button-flex .clear-cookies').on('click tap',function () { 99 if (clearcookiesreset == 0) { 100 $(this).text('Please Click Again To Confirm'); 101 clearcookiesreset++; 102 } else { 103 eat_cookies(true); 104 if (window.sessionTimerInterval) { clearInterval(window.sessionTimerInterval); } 105 window.sessionTimerReady = false; 106 $('.cookies-modal .top-bar h4').text('Cookies Cleared!'); 107 $('.cookies-modal .cookie-button-flex').addClass('hide'); 108 $('.cookies-modal .cookie-clear-success').removeClass('hide'); 109 $('.tessitura-cell.cart .cart-counter span').text('0'); 110 $('.tessitura-cell.cart').removeClass('populated'); 111 $('.sessionTimer').addClass('hide').html(''); 112 window.setTimeout(function () { 113 $('.cookies-modal').addClass('hide'); 114 $('.cookies-menu').removeClass('open'); 115 $('.cookies-modal .top-bar h4').text('Clear Cookies?'); 116 $('.cookies-modal .cookie-button-flex').removeClass('hide'); 117 $('.cookies-modal .cookie-clear-success').addClass('hide'); 118 },3000); 119 clearcookiesreset = 0; 120 $(this).text("Yes, Clear All Tessitura Cookies."); 121 } 122 }); 123 $('.cookie-button-flex .close-cookie-modal').on('click tap',function () { 124 $('.cookies-modal').addClass('hide'); 125 $('.cookies-menu').removeClass('open'); 126 }); 127 var sessionKey = getCookie('sessionKey'); 128 // console.log(sessionKey); 129 var mmtoggle = hmnsFooterData.icons.mobileToggle; 130 var cartclosed = 0; 131 132 var createTNEWSession = window.hmnsCreateTNEWSession; 133 134 function clearSessionTimerCookie() { 135 document.cookie = "sessionTimer=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + hmnsCookieDomain + "; path=/;"; 136 } 137 138 function isLoginFlowActive() { 139 return window.hmnsLoginInProgress === true; 140 } 141 142 $('.cart-exp-close button').on('click tap',function (e) { 143 e.preventDefault(); 144 e.stopImmediatePropagation(); 145 $('.cart-expired').addClass('hide'); 146 cartclosed = 1; 147 }); 148 $('#mega-menu-wrap-main-menu .mega-menu-toggle .mega-toggle-animated-box').html(mmtoggle); 149 var tickets = hmnsFooterData.icons.tickets; 150 var trex = hmnsFooterData.icons.trex; 151 var chevdownwhite = hmnsFooterData.icons.chevronDownWhite; 152 $('#mega-menu-main-menu').prepend('<li><div class="menu-search-box"><form role="search" method="get" id="searchform-m" class="searchform" action="/"><div class="mobile-search-flex"><input type="text" name="s" placeholder="Search" value="' + hmnsFooterData.searchQuery + '"/><div class="search-icon"><svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.1579 2.5C6.52472 2.5 4.95844 3.14878 3.80361 4.30361C2.64878 5.45844 2 7.02472 2 8.6579C2 10.2911 2.64878 11.8574 3.80361 13.0122C4.95844 14.167 6.52472 14.8158 8.1579 14.8158C9.78273 14.8158 11.3414 14.1736 12.4945 13.0298C12.5015 13.0224 12.5086 13.0151 12.5158 13.0078C12.5224 13.0013 12.5289 12.995 12.5356 12.9887C13.6758 11.8361 14.3158 10.28 14.3158 8.6579C14.3158 7.02472 13.667 5.45844 12.5122 4.30361C11.3574 3.14878 9.79107 2.5 8.1579 2.5ZM14.5925 13.6727C15.7034 12.2472 16.3158
15210.4841 16.3158 8.6579C16.3158 6.49429 15.4563 4.41929 13.9264 2.88939C12.3965 1.35949 10.3215 0.5 8.1579 0.5C5.99429 0.5 3.91929 1.35949 2.38939 2.88939C0.85949 4.41929 0 6.49429 0 8.6579C0 10.8215 0.85949 12.8965 2.38939 14.4264C3.91929 15.9563 5.99429 16.8158 8.1579 16.8158C9.98679 16.8158 11.7524 16.2017 13.1789 15.0876L16.2923 18.2065C16.6824 18.5973 17.3156 18.5979 17.7065 18.2077C18.0973 17.8176 18.0979 17.1844 17.7077 16.7935L14.5925 13.6727Z" fill="#2B2E2E"></path></svg></div></div><input type="submit" id="searchsubmit" value="Search" class="hide"></form></div></li>'); 153 $('.top-nav #mega-menu-wrap-main-menu #mega-menu-main-menu > li.mega-get-tickets > a.mega-menu-link').prepend(tickets); 154 $('.top-nav #mega-menu-wrap-main-menu #mega-menu-main-menu > li.mega-membership > a.mega-menu-link').prepend(trex); 155 var ticketsmenu = $('.top-nav #mega-menu-wrap-main-menu #mega-menu-main-menu > li.mega-get-tickets'); 156 var membershipmenu = $('.top-nav #mega-menu-wrap-main-menu #mega-menu-main-menu > li.mega-membership'); 157 158// Check if the items are already in the .mobile-menu-bot-section 159if ($('.mobile-menu-bot-section .mega-get-tickets').length === 0) { 160 if (ticketsmenu.length > 0) { 161 ticketsmenu = ticketsmenu.detach(); 162 } 163} 164if ($('.mobile-menu-bot-section .mega-membership').length === 0) { 165 if (membershipmenu.length > 0) { 166 membershipmenu = membershipmenu.detach(); 167 } 168} 169 170$('#mega-menu-wrap-main-menu .mega-menu-toggle .mega-toggle-block-0 button.mega-toggle-animated').on('click tap',function () { 171 $(this).children('span').children('svg').map(function () { 172 $(this).toggleClass('hide'); 173 }).get(); 174}); 175 176if ($('.mobile-menu-bot-section').length === 0) { 177 $('#mega-menu-main-menu').append('<div class="mobile-menu-bot-section"></div>'); 178} 179 $('.mobile-menu-bot-section').append(membershipmenu); 180 $('.mobile-menu-bot-section').append(ticketsmenu); 181 if (typeof window.hmnsApplyHeaderStackOffset === 'function') { 182 window.hmnsApplyHeaderStackOffset(); 183 } 184 $('.search-icon').click(function (e) { 185 $('form#searchform-m').submit(); 186 }); 187 $('.search-icon.search').click(function (e) { 188 $('form#searchform').submit(); 189 }); 190 //open search modal for desktop 191 $('.search-menu').on('click tap',function (e) { 192 e.preventDefault(); 193 e.stopImmediatePropagation; 194 $('#search').toggleClass('hide'); 195 }); 196 //change gttranslate svg 197 $('.gt_float_switcher .gt-selected .gt-current-lang span.gt_float_switcher-arrow').html(chevdownwhite); 198 // Notification-bar dismissals: a session cookie plus an <html> class replace the 199 // old PHP-session AJAX round-trip (page-cache safe; state survives navigation for 200 // the browser session, mirroring the previous PHP-session lifetime). 201 $('.close-nb2').on('click tap',function () { 202 document.cookie = 'hmnsNb2Closed=1; path=/'; 203 document.documentElement.className += ' hmns-nb2-closed'; 204 $(this).parent('.hmns-notifications').addClass('hide'); 205 if (typeof window.hmnsApplyHeaderStackOffset === 'function') { 206 window.hmnsApplyHeaderStackOffset(); 207 } 208 return false; 209 }); 210 211 $('.close-nb').on('click tap',function () { 212 document.cookie = 'hmnsNbClosed=1; path=/'; 213 document.documentElement.className += ' hmns-nb-closed'; 214 $(this).parent('.hmns-notifications').addClass('hide'); 215 if (typeof window.hmnsApplyHeaderStackOffset === 'function') { 216 window.hmnsApplyHeaderStackOffset(); 217 } 218 return false; 219 }); 220 // Sync #container offset after layout settles (debounced: load + fonts + ResizeObserver fire in a tight burst). 221 (function () { 222 var debounceMs = 80; 223 var timer = null; 224 function schedule() { 225 if (timer) { 226 clearTimeout(timer); 227 } 228 timer = setTimeout(function () { 229 timer = null; 230 if (typeof window.hmnsApplyHeaderStackOffset === 'function') { 231 window.hmnsApplyHeaderStackOffset(); 232 } 233 }, debounceMs); 234 } 235 $(window).on('resize', schedule); 236 $(window).on('load', schedule); 237 if (document.fonts && document.fonts.ready) { 238 document.fonts.ready.then(schedule); 239 } 240 if (typeof ResizeObserver !== 'undefined') { 241 var hdr = document.querySelector('header#header'); 242 if (hdr) { 243 new ResizeObserver(schedule).observe(hdr); 244 } 245 var tn = document.querySelector('.top-nav'); 246 if (tn) { 247 new ResizeObserver(schedule).observe(tn); 248 } 249 $('.hmns-notifications').each(function () { 250 new ResizeObserver(schedule).observe(this); 251 }); 252 } 253 // One pass after this tick (catches reflow from code above); merges with observers via debounce. 254 schedule(); 255 })();
256 //Tessitura Cookies & TessituraBar Interactions 257 var tessituraStatus = getCookie('tessituraLogin') || false; 258 var promoCode = getCookie('promoCode') || ''; 259 //console.log(promoCode); 260 var returnURL = hmnsFooterData.returnURL; 261 // CACHE-COMPATIBLE: Initialize tessitura bar from static skeleton 262 function initializeTessituraBar() { 263 // Initialize sessionTimer ready flag - will be set to true if timer initializes successfully 264 window.sessionTimerReady = false; 265 266 // Get current cookies 267 sessionKey = getCookie('sessionKey'); 268 var currentPromoCode = getCookie('promoCode') || ''; 269 var modeOfSale = getCookie('modeOfSale') || '2'; 270 var sessionTimer = getCookie('sessionTimer'); 271 272 // Update promo fields (both desktop and mobile) if cookie exists 273 var promoHasValue = false; 274 if (currentPromoCode) { 275 if (currentPromoCode !== '') { 276 promoHasValue = true; 277 } 278 } 279 if (promoHasValue) { 280 // Desktop promo - show promo applied state, but keep X hidden until cart check completes 281 $('#promoCode').val(currentPromoCode).addClass('hide'); 282 $('.hmns-tessitura-bar .promo-clear-code').text(currentPromoCode).removeClass('hide'); 283 $('.hmns-tessitura-bar .promoSubmit').addClass('hide'); 284 // .promo-clear visibility handled by updatePromoClearVisibility after session check 285 286 // Mobile promo 287 $('#promoCodeMobile').val(currentPromoCode).addClass('hide'); 288 $('.mobile-tessitura-bar .promo-clear-code').text(currentPromoCode).removeClass('hide'); 289 $('.mobile-tessitura-bar .promoSubmit').addClass('hide'); 290 // .promo-clear visibility handled by updatePromoClearVisibility after session check 291 setCartPromoLink(currentPromoCode); 292 } else { 293 setCartPromoLink(null); 294 } 295 296 // Update mode of sale for both desktop and mobile 297 $('.tessitura-cell.user-menu').attr('data-mosid', modeOfSale); 298 299 // Initialize session timer if exists and valid 300 var timerHasValue = false; 301 if (sessionTimer) { 302 if (sessionTimer !== '') { 303 if (sessionTimer !== '0') { 304 timerHasValue = true; 305 } 306 } 307 } 308 if (timerHasValue) { 309 // console.log('initializeTessituraBar: converting sessionTimer to Central Time:', sessionTimer); 310 311 // Recreate the PHP timezone conversion logic 312 var tte = new Date(sessionTimer); 313 var now = new Date(); 314 315 // Convert both times to Central Time (like PHP code did) 316 var options = { timeZone: 'America/Chicago', hour12: false, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }; 317 var formatter = new Intl.DateTimeFormat('en-US', options); 318 319 // Get current time in Central 320 var nowParts = formatter.formatToParts(now); 321 var nowCentral = new Date( 322 nowParts.find(p => p.type === 'year').value + '-' + 323 nowParts.find(p => p.type === 'month').value + '-' + 324 nowParts.find(p => p.type === 'day').value + 'T' + 325 nowParts.find(p => p.type === 'hour').value + ':' + 326 nowParts.find(p => p.type === 'minute').value + ':' + 327 nowParts.find(p => p.type === 'second').value 328 ); 329 330 // Get expiration time in Central 331 var tteParts = formatter.formatToParts(tte); 332 var tteCentral = new Date( 333 tteParts.find(p => p.type === 'year').value + '-' + 334 tteParts.find(p => p.type === 'month').value + '-' + 335 tteParts.find(p => p.type === 'day').value + 'T' + 336 tteParts.find(p => p.type === 'hour').value + ':' + 337 tteParts.find(p => p.type === 'minute').value + ':' + 338 tteParts.find(p => p.type === 'second').value 339 ); 340 341 // Calculate difference in Central time (like PHP strtotime comparison) 342 var timetoexpirydiff = tteCentral.getTime() - nowCentral.getTime(); 343 344 // console.log('Central time calculation - now:', nowCentral, 'expires:', tteCentral, 'diff:', timetoexpirydiff); 345 346 if (timetoexpirydiff > 0) { 347 // Use the original sessionTimer as timetoexpiry (initializeSessionTimer handles the rest) 348 var timetoexpiry = sessionTimer; 349 initializeSessionTimer(timetoexpiry); 350 } else { 351 // console.log('Timer already expired in Central time, not starting'); 352 $('.sessionTimer').addClass('hide'); 353 } 354 } else { 355 // console.log('initializeTessituraBar: no valid session timer cookie found:', sessionTimer); 356 } 357 358 // console.log('Tessitura bar initialized with sessionKey:', sessionKey, 'promoCode:', currentPromoCode, 'modeOfSale:', modeOfSale, 'sessionTimer:', sessionTimer); 359 } 360 361 // CACHE-COMPATIBLE: Session timer initialization 362 function initializeSessionTimer(timetoexpiry) { 363 // console.log('initializeSessionTimer called with:', timetoexpiry); 364 365 // Update sessionKey from cookie if not set 366 if (!sessionKey || sessionKey == '') { 367 sessionKey = getCookie('sessionKey'); 368 if (!sessionKey || sessionKey == '') { 369 // console.log('Timer initialization aborted: no sessionKey'); 370 $('.sessionTimer').addClass('hide'); 371 return; 372 } 373 } 374 375 // Validate timetoexpiry more thoroughly 376 if (!timetoexpiry || timetoexpiry == 0 || timetoexpiry == '' || timetoexpiry == '0') {
377 // console.log('Timer initialization aborted: invalid timetoexpiry value:', timetoexpiry); 378 $('.sessionTimer').addClass('hide'); 379 return; 380 } 381 382 // Try to parse the expiration time 383 var expiration = new Date(timetoexpiry).getTime(); 384 if (isNaN(expiration)) { 385 // console.log('Timer initialization aborted: invalid date format:', timetoexpiry); 386 $('.sessionTimer').addClass('hide'); 387 return; 388 } 389 390 // Check if timer has already expired (with 5 second buffer) 391 var now = new Date().getTime(); 392 if (expiration <= (now + 5000)) { 393 // console.log('Timer initialization aborted: timer already expired or expires too soon:', new Date(expiration), 'vs now:', new Date(now)); 394 $('.sessionTimer').addClass('hide'); 395 if (isLoginFlowActive()) { 396 clearSessionTimerCookie(); 397 return; 398 } 399 eat_cookies(); // Clean up expired session 400 return; 401 } 402 403 // console.log('Timer initializing with sessionKey:', sessionKey, 'expires:', new Date(expiration)); 404 // Don't show timer yet - wait for cart count to come back 405 // Set flag that timer is ready 406 window.sessionTimerReady = true; 407 // Keep timer hidden until cart count is checked 408 $('.sessionTimer').addClass('hide'); 409 410 var dst = getCookie('dst') || (isDaylightSavingTimeCentral() ? '1' : '0'); 411 function updateTimer() { 412 // Get the current local time 413 let now = new Date(); 414 415 // Convert the current local time to CST 416 let options = { timeZone: 'America/Chicago', hour12: false, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }; 417 let formatter = new Intl.DateTimeFormat('en-US', options); 418 let parts = formatter.formatToParts(now); 419 let [month, day, year, hour, minute, second] = parts.filter(part => part.type !== 'literal').map(part => part.value); 420 let cstTime; 421 if (dst == '1') { 422 cstTime = new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}-05:00`).getTime(); 423 } else { 424 cstTime = new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}-06:00`).getTime(); 425 } 426 427 428 // Calculate the distance between the expiration time and the current CST time 429 let distance = expiration - cstTime; 430 431 // Calculate hours, minutes, and seconds 432 let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); 433 let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); 434 let seconds = Math.floor((distance % (1000 * 60)) / 1000); 435 436 // Update the countdown timer on the webpage 437 if (distance <= 0) { 438 $('.sessionTimer').html(''); 439 window.setTimeout(function () { 440 $('.sessionTimer').addClass('hide'); 441 $('.tessitura-cell.cart .cart-counter span').text('0'); 442 $('.tessitura-cell.cart').removeClass('populated'); 443 $('.tessitura-cell.user-menu .logged-in').addClass('hide'); 444 $('.tessitura-cell.user-menu .logged-out').removeClass('hide'); 445 //$('.tessitura-cell.cart').addClass('hide'); 446 //$('.tessitura-cell.tickets').removeClass('hide'); 447 if (cartclosed == 0) { $('.cart-expired').removeClass('hide'); } 448 }, 1000); 449 clearInterval(window.sessionTimerInterval); 450 window.sessionTimerReady = false; // Reset flag when timer expires 451 // console.log('this is definitely happening, right'); 452 if (isLoginFlowActive()) { 453 clearSessionTimerCookie(); 454 return; 455 } 456 eat_cookies(); 457 } else { 458 let timeLeft = `Time Left: ${minutes}:${seconds < 10 ? '0' + seconds : seconds}`; 459 $('.sessionTimer').html(timeLeft); 460 } 461 } 462 463 // Clear any existing timer and start new one 464 if (window.sessionTimerInterval) { 465 // console.log('Clearing existing timer interval'); 466 clearInterval(window.sessionTimerInterval); 467 } 468 // console.log('Starting new timer interval'); 469 window.sessionTimerInterval = setInterval(updateTimer, 1000); 470 updateTimer(); // Initial call 471 } 472 473 // Initialize the bar immediately 474 initializeTessituraBar(); 475 476 if (tessituraStatus == true) { 477 $('.tessitura-cell.user-menu .logged-in').removeClass('hide'); 478 $('.tessitura-cell.user-menu .logged-out').addClass('hide'); 479 } 480 var tserverURL = hmnsFooterData.tserverURL; 481 var ajaxArray = []; 482 483 // TNEW storefront base from the hmnsTnewBase global (header.php, per-environment 484 // HMNS_TNEW_URL constant); fallback keeps dev working standalone. 485 var tnewBase = (typeof hmnsTnewBase === 'string' && hmnsTnewBase !== '') ? hmnsTnewBase : 'https://hmns-tnew-test.tnhs.cloud'; 486 var tnewHost = ''; 487 try { 488 tnewHost = new URL(tnewBase).hostname; 489 } catch (e) { 490 tnewHost = tnewBase.replace(/^https?:\/\//, '').split('/')[0]; 491 } 492 493 // Manage Tessitura cart URL so we can append promo parameters when needed. 494 // Use premove=y ONLY when: promo was just cleared on an empty cart and user is 495 // now going to cart or applying a different promo (so TNEW can clear old promo state). 496 var initialCartLink = $('.tessitura-cell.cart').attr('data-link') || (tnewBase + '/cart/detail
496s'); 497 var cartBaseUrl = initialCartLink.split('?')[0]; 498 var cartBaseQuery = initialCartLink.indexOf('?') !== -1 ? initialCartLink.substring(initialCartLink.indexOf('?')) : ''; 499 var cartPromoUrl = ''; 500 var promoJustClearedEmptyCart = false; // true only after user clears promo while cart is empty 501 502 function refreshCartBaseFromAttr() { 503 var currentLink = $('.tessitura-cell.cart').attr('data-link'); 504 if (!currentLink) { 505 return; 506 } 507 var isPromoLinkMatch = false; 508 if (cartPromoUrl) { 509 if (currentLink === cartPromoUrl) { 510 isPromoLinkMatch = true; 511 } 512 } 513 if (isPromoLinkMatch) { 514 return; 515 } 516 var questionIndex = currentLink.indexOf('?'); 517 if (questionIndex !== -1) { 518 cartBaseUrl = currentLink.substring(0, questionIndex); 519 cartBaseQuery = currentLink.substring(questionIndex); 520 } else { 521 cartBaseUrl = currentLink; 522 cartBaseQuery = ''; // No premove by default - avoids child-ticket conversion issue in TNEW 523 } 524 } 525 526 function setCartPromoLink(promoCode) { 527 refreshCartBaseFromAttr(); 528 var hasPromoValue = false; 529 if (promoCode) { 530 if (promoCode !== '') { 531 hasPromoValue = true; 532 } 533 } 534 if (hasPromoValue) { 535 // Only add premove=y when replacing a just-cleared promo (empty cart, user cleared then applied new) 536 if (promoJustClearedEmptyCart) { 537 cartPromoUrl = cartBaseUrl + '?premove=y&promo=' + encodeURIComponent(promoCode); 538 promoJustClearedEmptyCart = false; 539 } else { 540 cartPromoUrl = cartBaseUrl + '?promo=' + encodeURIComponent(promoCode); 541 } 542 $('.tessitura-cell.cart').attr('data-link', cartPromoUrl); 543 } else { 544 cartPromoUrl = ''; 545 $('.tessitura-cell.cart').attr('data-link', cartBaseUrl + cartBaseQuery); 546 } 547 } 548 549 function getCartDestinationUrl() { 550 // If user just cleared promo (empty cart) and has no new promo, use premove once so TNEW clears it 551 if (promoJustClearedEmptyCart && !cartPromoUrl) { 552 promoJustClearedEmptyCart = false; 553 return cartBaseUrl + '?premove=y'; 554 } 555 return cartPromoUrl || (cartBaseUrl + cartBaseQuery); 556 } 557$('.tessitura-cell.user-menu .logged-in').on('click tap',function (e) { 558 e.preventDefault(); 559 e.stopPropagation(); 560 var $currentMenu = $(this).closest('.tessitura-cell.user-menu'); 561 $('.tessitura-cell.user-menu .user-dropdown').not($currentMenu.find('.user-dropdown')).addClass('hide'); 562 $currentMenu.find('.user-dropdown').toggleClass('hide'); 563}); 564 565// CONVERTED: .tessitura-cell.cart - Simplified with New TNEW Method 566$('.tessitura-cell.cart').on('click tap',function () { 567 // Block navigation while an add-to-cart is still in flight so the user can't 568 // land in an empty TNEW cart before the item is committed server-side. 569 if (window.hmnsCartBusy) { 570 return false; 571 } 572 sessionKey = getCookie('sessionKey'); 573 var destinationUrl = getCartDestinationUrl(); 574 var cartHasItems = !$(this).hasClass('populated'); 575 if (cartHasItems) { 576 var sessionMissing = (sessionKey == '' || sessionKey == null); 577 if (sessionMissing) { 578 // Create new TNEW session and redirect to cart 579 createTNEWSession(function(newSessionKey) { 580 window.location.assign(destinationUrl); 581 }); 582 return; 583 } 584 } 585 window.location.assign(destinationUrl); 586}); 587 588 // MODIFIED: Main sessionKey check AJAX call - wait for nonce to be ready 589 function checkInitialSession() { 590 sessionKey = getCookie('sessionKey'); 591 if (sessionKey != '') { 592 var checkedSessionKey = sessionKey; 593 ajaxArray = []; 594 ajaxArray.push('/api/Web/Session/{sessionkey}'); 595 ajaxArray.push(checkedSessionKey); 596 query = JSON.stringify(ajaxArray); 597 $.ajax({ 598 type : 'post', 599 url : hmns_ajax_router.url, 600 beforeSend: setAjaxHeaders, 601 data : { 602 action : 'tessitura_api_interactions', 603 query : query 604 }, 605 xhrFields: { withCredentials: true }, 606 success : function (response) { 607 // Handle new structured JSON object instead of string 608 $('.tessitura-cell.spinner').addClass('hide'); 609 $('.tessitura-cell.user-menu .logged-spinner').addClass('hide'); 610 // Share result so login block (and others) can use it without a duplicate API call 611 window.hmns_last_session_check = response; 612 if (isLoginFlowActive()) { 613 return; 614 } 615 if (response.isSessionValid === false) { 616 if (getCookie('sessionKey') === checkedSessionKey) { 617 document.cookie = "sessionKey=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + hmnsCookieDomain + "; path=/;"; 618 sessionKey = ''; 619 } else { 620 return; 621 } 622 } 623 if (response.isLoggedIn) { 624 $('.tessitura-cell.user-menu .logged-in').removeClass('hide'); 625 $('.tessitura-cell.user-menu').attr('data-mosid',response.modeOfSaleId);
626 setCookie('modeOfSale', response.modeOfSaleId, 30); // Persist mode of sale for page reloads 627 setCookie('tessituraLogin', true, 30); 628 $('.tessitura-cell.user-menu .logged-out').addClass('hide'); 629 } else { 630 $('.tessitura-cell.user-menu .logged-in').addClass('hide'); 631 $('.tessitura-cell.user-menu .logged-out').removeClass('hide'); 632 $('.tessitura-cell.user-menu').attr('data-mosid',2); 633 setCookie('modeOfSale', 2, 30); // Persist mode of sale for page reloads 634 setCookie('tessituraLogin', false, 30); 635 } 636 // queryDB / hmns_session_checked must run after cookies because listeners read getCookie(). 637 $(document).trigger('queryDB'); 638 $(document).trigger('hmns_session_checked', [response]); 639 if (response.cartCount >= 1) { 640 if (response.checkoutStatus == 'No Checkout') { 641 $('.tessitura-cell.cart .cart-counter span').text(response.cartCount); 642 $('.tessitura-cell.cart').addClass('populated'); 643 $('.tessitura-cell.cart').removeClass('hide'); 644 // Only show sessionTimer if timer is ready 645 if (window.sessionTimerReady) { 646 $('.sessionTimer').removeClass('hide'); 647 } else { 648 $('.sessionTimer').addClass('hide'); 649 } 650 } else { 651 $('.sessionTimer').addClass('hide'); 652 $('.tessitura-cell.cart .cart-counter span').text(0); 653 $('.tessitura-cell.cart').removeClass('hide'); 654 $('.tessitura-cell.cart').removeClass('populated'); 655 // Start new session only if checkout was just completed 656 if (response.checkoutStatus == 'Completed') { 657 startNewSession(); 658 } 659 } 660 } else { 661 $('.sessionTimer').addClass('hide'); 662 $('.tessitura-cell.cart .cart-counter span').text(0); 663 $('.tessitura-cell.cart').removeClass('hide'); 664 $('.tessitura-cell.cart').removeClass('populated'); 665 // Start new session only if checkout was just completed 666 if (response.checkoutStatus == 'Completed') { 667 startNewSession(); 668 } 669 } 670 }, 671 error : function(jqXHR, textStatus, errorThrown) { 672 // Hide spinners and show logged-out state so UI is never stuck 673 $('.tessitura-cell.spinner').addClass('hide'); 674 $('.tessitura-cell.user-menu .logged-spinner').addClass('hide'); 675 $('.tessitura-cell.user-menu .logged-in').addClass('hide'); 676 $('.tessitura-cell.user-menu .logged-out').removeClass('hide'); 677 $('.tessitura-cell.cart').removeClass('hide'); 678 refreshCartBaseFromAttr(); 679 setCartPromoLink(null); 680 // Keep the shared global in sync â gate logic reads it, not the event payload. 681 window.hmns_last_session_check = { isLoggedIn: false }; 682 $(document).trigger('queryDB'); 683 $(document).trigger('hmns_session_checked', [window.hmns_last_session_check]); 684 } 685 }); 686 } else { 687 $('.tessitura-cell.spinner').addClass('hide'); 688 $('.tessitura-cell.user-menu .logged-spinner').addClass('hide'); 689 $('.tessitura-cell.user-menu .logged-out').removeClass('hide'); 690 $('.tessitura-cell.cart').removeClass('hide'); 691 refreshCartBaseFromAttr(); 692 setCartPromoLink(null); 693 $(document).trigger('queryDB'); 694 window.hmns_last_session_check = { isLoggedIn: false }; 695 $(document).trigger('hmns_session_checked', [window.hmns_last_session_check]); 696 } 697 } 698 699 // Production can safely ask my.hmns.org for a TNEW session on every load because it is same-site. 700 // Dev's TNEW endpoint is cross-site and can mutate the TNEWQA cookie on ordinary WP navigation, 701 // so only sync from TNEW when the user is returning from TNEW. 702 var tnewSyncResult = null; // null = pending | { existingKey: true } | { encrypted: string } | { noKey: true } 703 var syncAndCheckRan = false; 704 function isTnewReturnNavigation() { 705 var referrer = document.referrer || ''; 706 if (!referrer) { 707 return false;
708 } 709 try { 710 var referrerHost = new URL(referrer).hostname; 711 return (tnewHost !== '' && referrerHost === tnewHost) || referrerHost.endsWith('.tnhs.cloud'); 712 } catch (e) { 713 return (tnewHost !== '' && referrer.indexOf(tnewHost) !== -1) || referrer.indexOf('.tnhs.cloud') !== -1; 714 } 715 } 716 function validateExistingSessionBeforeTnewSwap(currentSessionKey, nextSessionKey) { 717 ajaxArray = []; 718 ajaxArray.push('/api/Web/Session/{sessionkey}'); 719 ajaxArray.push(currentSessionKey); 720 query = JSON.stringify(ajaxArray); 721 $.ajax({ 722 type: 'post', 723 url: hmns_ajax_router.url, 724 beforeSend: setAjaxHeaders, 725 data: { 726 action: 'tessitura_api_interactions', 727 query: query 728 }, 729 xhrFields: { withCredentials: true }, 730 success: function(response) { 731 if (response && response.isSessionValid === true && (response.isLoggedIn || response.cartCount > 0 || response.membershipCount > 0)) { 732 sessionKey = currentSessionKey; 733 checkInitialSession(); 734 return; 735 } 736 setCookie('sessionKey', nextSessionKey, 30); 737 sessionKey = nextSessionKey; 738 checkInitialSession(); 739 }, 740 error: function() { 741 setCookie('sessionKey', nextSessionKey, 30); 742 sessionKey = nextSessionKey; 743 checkInitialSession(); 744 } 745 }); 746 } 747 function runWhenNonceAndTnewReady() { 748 if (!window.hmns_ajax_router || !window.hmns_ajax_router.ready) return; 749 if (syncAndCheckRan) return; 750 if (tnewSyncResult === null) return; 751 syncAndCheckRan = true; 752 var oldSessionKey = getCookie('sessionKey') || ''; 753 if (tnewSyncResult.existingKey) { 754 sessionKey = oldSessionKey; 755 checkInitialSession(); 756 return; 757 } 758 if (tnewSyncResult.noKey) { 759 if (oldSessionKey) { 760 sessionKey = oldSessionKey; 761 checkInitialSession(); 762 return; 763 } 764 if (isLoginFlowActive()) { 765 checkInitialSession(); 766 return; 767 } 768 document.cookie = "sessionKey=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + hmnsCookieDomain + "; path=/;"; 769 setCookie('tessituraLogin', false, 30); 770 sessionKey = ''; 771 checkInitialSession(); 772 return; 773 } 774 $.ajax({ 775 url: hmns_ajax_router.url, 776 type: 'POST', 777 beforeSend: setAjaxHeaders, 778 data: { action: 'decrypt_tnew_string', encrypted_string: tnewSyncResult.encrypted }, 779 success: function(decryptResponse) { 780 if (decryptResponse && decryptResponse.decrypted_value) { 781 var sessionKeyParts = decryptResponse.decrypted_value.split('|'); 782 var retrievedSessionKey = sessionKeyParts[0]; 783 if (retrievedSessionKey !== oldSessionKey) { 784 if (oldSessionKey) { 785 validateExistingSessionBeforeTnewSwap(oldSessionKey, retrievedSessionKey); 786 return; 787 } 788 setCookie('sessionKey', retrievedSessionKey, 30); 789 sessionKey = retrievedSessionKey; 790 // console.log('Session key updated from TNEW (transfer):', oldSessionKey, '->', retrievedSessionKey); 791 } 792 } 793 checkInitialSession(); 794 }, 795 error: function() { checkInitialSession(); } 796 }); 797 } 798 if (getCookie('sessionKey') && !isTnewReturnNavigation()) { 799 tnewSyncResult = { existingKey: true }; 800 } else if (isTnewReturnNavigation()) { 801 // Start TNEW GET only when we may be receiving a TNEW transfer. 802 $.ajax({ 803 url: tnewBase + '/api/session/sessionkey', 804 type: 'GET', 805 xhrFields: { withCredentials: true }, 806 success: function(response) { 807 var encrypted = ''; 808 if (typeof response === 'string') encrypted = response; 809 else if (response && response.encryptedSessionKey) encrypted = response.encryptedSessionKey; 810 tnewSyncResult = encrypted ? { encrypted: encrypted } : { noKey: true }; 811 runWhenNonceAndTnewReady(); 812 }, 813 error: function() { 814 tnewSyncResult = { noKey: true }; 815 runWhenNonceAndTnewReady(); 816 } 817 }); 818 } else { 819 tnewSyncResult = { noKey: true }; 820 } 821 var nonceReadyNow = false; 822 if (window.hmns_ajax_router) { 823 if (window.hmns_ajax_router.ready) { 824 nonceReadyNow = true; 825 } 826 } 827 if (nonceReadyNow) { 828 runWhenNonceAndTnewReady(); 829 } else { 830 $(document).on('hmns_nonce_ready', function() { 831 runWhenNonceAndTnewReady(); 832 }); 833 } 834 835 // CONVERTED: startNewSession - Simplified with New TNEW Method 836 // Post-checkout (or post-clear) intentionally wants a brand-new TNEW session,
837 // so force a mint and bypass the reuse/coalesce short-circuit. 838 function startNewSession() { 839 if (window.sessionTimerInterval) { clearInterval(window.sessionTimerInterval); } 840 window.sessionTimerReady = false; 841 $('.sessionTimer').addClass('hide').html(''); 842 document.cookie = "sessionKey=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + hmnsCookieDomain + "; path=/;"; 843 createTNEWSession(function(newSessionKey) { 844 document.cookie = "sessionTimer=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + hmnsCookieDomain + "; path=/;"; 845 document.cookie = "promoCode=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + hmnsCookieDomain + "; path=/;"; 846 $('.promo-flex input[type="text"]').val('').removeClass('hide'); 847 $('.promo-flex .promoSubmit').removeClass('hide'); 848 $('.promo-clear-code').addClass('hide').text(''); 849 $('.promo-clear').addClass('hide'); 850 setCartPromoLink(null); 851 }, null, { forceNew: true }); 852 } 853 854 // CONVERTED: Promo form submit - New TNEW Method 855 $('.tessitura-cell.promo form').on('submit',function (e) { 856 e.preventDefault(); 857 e.stopImmediatePropagation(); 858 thisform = $(this); 859 sessionKey = getCookie('sessionKey'); 860 if (sessionKey == '' || sessionKey == null) { 861 createTNEWSession(function(newSessionKey) { 862 validate_promo(); 863 }); 864 } else { 865 validate_promo(); 866 } 867 }); 868 869 // CONVERTED: Promo submit button - New TNEW Method 870 $('.tessitura-cell.promo .promo-flex .promoSubmit').on('click tap',function (e) { 871 e.preventDefault(); 872 e.stopImmediatePropagation(); 873 sessionKey = getCookie('sessionKey'); 874 thisform = $('.tessitura-cell.promo form'); 875 if (sessionKey == '' || sessionKey == null) { 876 createTNEWSession(function(newSessionKey) { 877 validate_promo(); 878 }); 879 } else { 880 validate_promo(); 881 } 882 }); 883 884 // Handle promo code from URL parameter - wait for nonce to be ready 885 function handleQueryPromoCode() { 886 var queryPromoCode = hmnsFooterData.queryPromoCode; 887 // console.log('QueryPromo: ' + queryPromoCode); 888 if (queryPromoCode != '') { 889 sessionKey = getCookie('sessionKey'); 890 thisform = $('.tessitura-cell.promo form'); 891 var exCode = $('.tessitura-cell .promo-flex input[type="text"]').val(); 892 // console.log('currentPromo: ' + exCode); 893 var exCookie = getCookie('promoCode'); 894 if (queryPromoCode != exCode) { 895 if (queryPromoCode != exCookie) { setCookie('promoCode',queryPromoCode,30); } 896 $('.tessitura-cell .promo-flex input[type="text"]').val(queryPromoCode); 897 if (sessionKey == '' || sessionKey == null) { 898 // CONVERTED: Use TNEW session creation method 899 createTNEWSession(function(newSessionKey) { 900 sessionKey = newSessionKey; 901 validate_promo(); 902 }, function(error) { 903 // console.error('Failed to create session for promo code:', error); 904 }); 905 } else { 906 validate_promo(); 907 } 908 } 909 } 910 } 911 912 // Wait for nonce to be ready before handling promo codes 913 if (window.hmns_ajax_router && window.hmns_ajax_router.ready) { 914 handleQueryPromoCode(); 915 } else { 916 $(document).on('hmns_nonce_ready', function() { 917 handleQueryPromoCode(); 918 }); 919 } 920 921 var promo = ''; 922 function validate_promo() { 923 sessionKey = getCookie('sessionKey'); 924 var $form = $('.tessitura-cell.promo form'); 925 promo = $form.find('input[name="promoCode"]').val(); 926 // console.log('check promo: ' + promo); 927 928 // If no sessionKey, create one using TNEW method first 929 if (!sessionKey || sessionKey === '') { 930 createTNEWSession(function(newSessionKey) { 931 sessionKey = newSessionKey; 932 performPromoValidation(); 933 }, function(error) { 934 // console.error('Failed to create session for promo validation:', error); 935 $('.promo-spinner').addClass('hide'); 936 $('.tessitura-cell.promo form').removeClass('greyed-out'); 937 }); 938 return; 939 } 940 941 performPromoValidation(); 942 } 943 944 function performPromoValidation() { 945 ajaxArray = []; 946 ajaxArray.push(promo); 947 ajaxArray.push(sessionKey); 948 query = JSON.stringify(ajaxArray); 949 if (promo != '') { 950 // MODIFIED: validate_promo AJAX call 951 $.ajax({ 952 type: 'post', 953 url: hmns_ajax_router.url, 954 beforeSend: function(xhr) { 955 setAjaxHeaders(xhr); // Include nonce and security headers 956 $('.promo-spinner').removeClass('hide'); 957 $('.tessitura-cell.promo form').addClass('greyed-out'); 958 }, 959 data: { 960 action: 'tessitura_promo', 961 query: query 962 }, 963 xhrFields: { withCredentials: true }, 964 success: function(response) { 965 $('.promo-spinner').addClass('hide'); 966 promoCode = response.promoCode; 967 if (promoCode) {
968 setCookie('promoCode', promoCode, 30); 969 setCartPromoLink(promoCode); 970 document.cookie = "sessionTimer=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + hmnsCookieDomain + "; path=/;"; 971 if (window.sessionTimerInterval) { clearInterval(window.sessionTimerInterval); } 972 window.sessionTimerReady = false; 973 $('.tessitura-cell.cart').removeClass('populated'); 974 $('.tessitura-cell.cart .cart-counter span').text(0); 975 $('.sessionTimer').addClass('hide').html(''); 976 $('.hmns-tessitura-bar .promo-flex input#promoCode').addClass('hide'); 977 $('.hmns-tessitura-bar .promo-flex .promoSubmit').addClass('hide'); 978 $('.hmns-tessitura-bar .promo-clear-code').text(promoCode).removeClass('hide'); 979 var currentCartCount = window.hmns_last_session_check ? (window.hmns_last_session_check.cartCount || 0) : 0; 980 if (currentCartCount === 0) { 981 $('.hmns-tessitura-bar .promo-clear').removeClass('hide'); 982 } 983 $('.tessitura-cell.promo form').removeClass('greyed-out'); 984 } else { 985 $('.tessitura-cell.promo form').removeClass('greyed-out'); 986 $('.hmns-tessitura-bar .promo-fail').removeClass('hide'); 987 window.setTimeout(function() { 988 $('.hmns-tessitura-bar .promo-fail').addClass('hide'); 989 }, 3000); 990 } 991 }, 992 error: function(jqXHR, textStatus, errorThrown) { 993 $('.promo-spinner').addClass('hide'); 994 $('.tessitura-cell.promo form').removeClass('greyed-out'); 995 $('.hmns-tessitura-bar .promo-fail').removeClass('hide'); 996 window.setTimeout(function() { 997 $('.hmns-tessitura-bar .promo-fail').addClass('hide'); 998 }, 3000); 999 }, 1000 complete: function() { 1001 $('.promo-spinner').addClass('hide'); 1002 $('.tessitura-cell.promo form').removeClass('greyed-out'); 1003 } 1004 }); 1005 } 1006 } 1007 1008 // Control visibility of promo clear button based on cart contents 1009 // Promo codes cannot be removed when there are items in the cart 1010 function updatePromoClearVisibility(cartCount) { 1011 var hasPromo = getCookie('promoCode'); 1012 if (hasPromo && cartCount > 0) { 1013 // Cart has items - hide the X button (can't remove promo) 1014 $('.promo-clear').addClass('hide'); 1015 } else if (hasPromo && cartCount === 0) { 1016 // Cart is empty - show the X button (can remove promo) 1017 $('.promo-clear').removeClass('hide'); 1018 } 1019 } 1020 1021 // Check on initial session load 1022 if (window.hmns_last_session_check) { 1023 updatePromoClearVisibility(window.hmns_last_session_check.cartCount || 0); 1024 } 1025 $(document).on('hmns_session_checked', function(e, response) { 1026 updatePromoClearVisibility(response.cartCount || 0); 1027 // If cart has items, no need for premove on next visit 1028 if ((response.cartCount || 0) >= 1) { 1029 promoJustClearedEmptyCart = false; 1030 } 1031 }); 1032 1033 // MODIFIED: .promo-clear - Clear promo; use premove only when user next goes to cart or applies new promo 1034 $('.tessitura-cell.promo .promo-flex .promo-clear').on('click tap',function (e) { 1035 e.preventDefault(); 1036 e.stopImmediatePropagation(); 1037 // Mark that promo was cleared on empty cart so we use premove=y only on next cart visit or new promo 1038 promoJustClearedEmptyCart = true; 1039 // Update UI immediately; cart link stays base URL (no premove) until they go to cart or apply new promo 1040 $('.promo-flex input#promoCode').val(''); 1041 $('.promo-flex input#promoCode').removeClass('hide'); 1042 $('.promo-flex .promoSubmit').removeClass('hide'); 1043 $(this).addClass('hide'); 1044 $('.promo-clear-code').addClass('hide'); 1045 document.cookie = "promoCode=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + hmnsCookieDomain + "; path=/;"; 1046 setCartPromoLink(0); 1047 1048 // Also clear from TNEW cart via API 1049 var currentSessionKey = getCookie('sessionKey'); 1050 if (currentSessionKey && window.hmns_ajax_router && window.hmns_ajax_router.ready) { 1051 var clearPromoArray = [currentSessionKey]; 1052 var clearPromoQuery = JSON.stringify(clearPromoArray); 1053 $.ajax({ 1054 type: 'post', 1055 url: hmns_ajax_router.url, 1056 beforeSend: setAjaxHeaders, 1057 data: { 1058 action: 'clear_tessitura_promo', 1059 query: clearPromoQuery 1060 }, 1061 success: function(response) { 1062 // console.log('Promo cleared from TNEW:', response); 1063 }, 1064 error: function(jqXHR, textStatus, errorThrown) { 1065 // console.error('Failed to clear promo from TNEW:', textStatus, errorThrown); 1066 } 1067 }); 1068 startNewSession(function(newSessionKey) { 1069 // console.log('New session created:', newSessionKey); 1070 }); 1071 } 1072 }); 1073 1074 // Logout handler - Show confirmation modal 1075 $('.logout-link.clear-cookies').on('click', function(e) { 1076 e.preventDefault(); 1077 e.stopPropagation(); 1078 1079 // Show logout confirmation modal 1080 $('.logout-modal').removeClass('hide'); 1081 $('.logout-confirm-section').removeClass('hide'); 1082 $('.logout-success').addClass('hide'); 1083 }); 1084 1085 // Cancel logout - close modal 1086 $('.cancel-logout').on('click', function() { 1087 $('.logout-modal').addClass('hide'); 1088 }); 1089 1090 // Confirm logout - execute logout and show countdown 1091 $('.confirm-logout').on('click', function() { 1092 // Hide confirmation, show success message 1093 $('.logout-confirm-section').addClass('hide'); 1094 $('.logout-success').removeClass('hide'); 1095
1096 // Get sessionKey before clearing cookies 1097 var sessionKey = getCookie('sessionKey'); 1098 1099 // Call Tessitura logout API if we have a sessionKey and nonce is ready 1100 var canCallLogout = false; 1101 if (sessionKey) { 1102 if (window.hmns_ajax_router) { 1103 if (window.hmns_ajax_router.ready) { 1104 canCallLogout = true; 1105 } 1106 } 1107 } 1108 if (canCallLogout) { 1109 var ajaxArray = []; 1110 ajaxArray.push('/api/Web/Session/{sessionKey}/Logout'); 1111 ajaxArray.push(sessionKey); 1112 var query = JSON.stringify(ajaxArray); 1113 1114 $.ajax({ 1115 type: 'post', 1116 url: hmns_ajax_router.url, 1117 beforeSend: setAjaxHeaders, 1118 data: { 1119 action: 'tessitura_api_interactions', 1120 query: query 1121 }, 1122 success: function(response) { 1123 // console.log('Logout API call successful:', response); 1124 }, 1125 error: function(xhr, status, error) { 1126 // console.error('Logout API call failed:', status, error); 1127 // Continue with logout even if API call fails 1128 }, 1129 complete: function() { 1130 // Clear cookies and reset UI after API call (success or failure) 1131 performLogoutCleanup(); 1132 } 1133 }); 1134 } else { 1135 // No sessionKey or nonce not ready - just clear cookies and reset UI 1136 performLogoutCleanup(); 1137 } 1138 }); 1139 1140 // Function to perform logout cleanup (clear cookies then full reload so gates/blocks match server state) 1141 function performLogoutCleanup() { 1142 eat_cookies(true); 1143 1144 // Clear mode-of-sale reload flags from sessionStorage (allows reload on next page with different MOS) 1145 for (var key in sessionStorage) { 1146 if (key.indexOf('mosReload_') === 0) { 1147 sessionStorage.removeItem(key); 1148 } 1149 } 1150 1151 if (window.sessionTimerInterval) { 1152 clearInterval(window.sessionTimerInterval); 1153 } 1154 window.sessionTimerReady = false; 1155 1156 window.location.reload(); 1157 } 1158 1159 // MODIFIED: update_cart_totals AJAX call - wait for nonce to be ready 1160 function updateCartTotalsOnLoad() { 1161 sessionKey = getCookie('sessionKey'); 1162 var hasSessionKey = false; 1163 if (sessionKey != null) { 1164 if (sessionKey !== '') { 1165 hasSessionKey = true; 1166 } 1167 } 1168 if (hasSessionKey) { 1169 ajaxArray = []; 1170 ajaxArray.push('/api/Web/Session/{sessionkey}'); 1171 ajaxArray.push(sessionKey); 1172 query = JSON.stringify(ajaxArray); 1173 $.ajax({ 1174 type : 'post', 1175 url : hmns_ajax_router.url, 1176 beforeSend: setAjaxHeaders, 1177 data : { 1178 action : 'update_cart_totals', 1179 query : query 1180 }, 1181 xhrFields: { withCredentials: true }, 1182 success : function (response) { 1183 // Handle new structured response 1184 $('.tessitura-cell.cart .cart-counter span').text(response.cartCount); 1185 // Only show sessionTimer if cart has items AND timer is ready 1186 if (response.cartCount >= 1) { 1187 if (window.sessionTimerReady) { 1188 $('.sessionTimer').removeClass('hide'); 1189 } else { 1190 $('.sessionTimer').addClass('hide'); 1191 } 1192 } else { 1193 $('.sessionTimer').addClass('hide'); 1194 } 1195 $('.tessitura-cell.cart').removeClass('hide'); 1196 }, 1197 error : function(jqXHR,textStatus,errorThrown) { /* ... */ } 1198 }); 1199 } else { 1200 setCartPromoLink(null); 1201 } 1202} 1203 1204 // Cart totals on initial load are updated by checkInitialSession (no separate Session API call) 1205 // CONVERTED: Mobile promo form handlers - New TNEW Method 1206 $('#hmns-promo-form-mobile').on('submit',function (e) { 1207 e.preventDefault(); 1208 e.stopImmediatePropagation(); 1209 thisform = $(this); 1210 sessionKey = getCookie('sessionKey'); 1211 if (sessionKey == '') { 1212 createTNEWSession(function(newSessionKey) { 1213 validate_promo_mobile(); 1214 }); 1215 } else { 1216 validate_promo_mobile(); 1217 } 1218 }); 1219 1220 $('.mobile-tessitura-bar .promo-flex .promoSubmit').on('click tap',function (e) { 1221 e.preventDefault(); 1222 e.stopImmediatePropagation(); 1223 thisform = $('#hmns-promo-form-mobile'); 1224 sessionKey = getCookie('sessionKey'); 1225 if (sessionKey == '') { 1226 createTNEWSession(function(newSessionKey) { 1227 validate_promo_mobile(); 1228 }); 1229 } else { 1230 validate_promo_mobile(); 1231 } 1232 }); 1233 1234 // CACHE-COMPATIBLE: Mobile promo validation function 1235 function validate_promo_mobile() { 1236 sessionKey = getCookie('sessionKey'); 1237 var $form = $('#hmns-promo-form-mobile'); 1238 promo = $form.find('input[name="promoCode"]').val(); 1239 // console.log('check mobile promo: ' + promo); 1240 1241 // If no sessionKey, create one using TNEW method first 1242 if (!sessionKey || sessionKey === '') { 1243 createTNEWSession(function(newSessionKey) { 1244 sessionKey = newSessionKey; 1245 performPromoValidationMobile(); 1246 }, function(error) { 1247 // console.error('Failed to create session for mobile promo validation:', error); 1248 $('.mobile-tessitura-bar .promo-spinner').addClass('hide'); 1249 $('#hmns-promo-form-mobile').removeClass('greyed-out'); 1250 }); 1251 return; 1252 } 1253 1254 performPromoValidationMobile(); 1255 } 1256 1257 function performPromoValidationMobile() { 1258 ajaxArray = []; 1259 ajaxArray.push(promo); 1260 ajaxArray.push(sessionKey); 1261 query = JSON.stringify(ajaxArray); 1262 if (promo != '') { 1263 $.ajax({ 1264 type : 'post', 1265 url : hmns_ajax_router.url, 1266 data : { 1267 action : 'tessitura_promo', 1268 query : query 1269 }, 1270 xhrFields: { withCredentials: true }, 1271 beforeSend : function (xhr) { 1272 setAjaxHeaders(xhr); 1273 $('.mobile-tessitura-bar .promo-spinner').removeClass('hide'); 1274 $('#hmns-promo-form-mobile').addClass('greyed-out'); 1275 }, 1276 success : function (response) { 1277 $('.mobile-tessitura-bar .promo-spinner').addClass('hide'); 1278 promoCode = response.promoCode; 1279 if (promoCode) {
1280 setCookie('promoCode', promoCode, 30); 1281 setCartPromoLink(promoCode); 1282 $('.mobile-tessitura-bar .promo-flex input#promoCodeMobile').addClass('hide'); 1283 $('.mobile-tessitura-bar .promo-flex .promoSubmit').addClass('hide'); 1284 $('.mobile-tessitura-bar .promo-clear-code').text(promoCode).removeClass('hide'); 1285 var currentCartCount = window.hmns_last_session_check ? (window.hmns_last_session_check.cartCount || 0) : 0; 1286 if (currentCartCount === 0) { 1287 $('.mobile-tessitura-bar .promo-clear').removeClass('hide'); 1288 } 1289 $('#hmns-promo-form-mobile').removeClass('greyed-out'); 1290 } else { 1291 $('.mobile-tessitura-bar .promo-fail').removeClass('hide'); 1292 window.setTimeout(function () { 1293 $('#hmns-promo-form-mobile').removeClass('greyed-out'); 1294 $('.mobile-tessitura-bar .promo-fail').addClass('hide'); 1295 }, 3000); 1296 } 1297 }, 1298 error : function (jqXHR, textStatus, errorThrown) { 1299 $('.mobile-tessitura-bar .promo-spinner').addClass('hide'); 1300 $('#hmns-promo-form-mobile').removeClass('greyed-out'); 1301 $('.mobile-tessitura-bar .promo-fail').removeClass('hide'); 1302 window.setTimeout(function () { 1303 $('.mobile-tessitura-bar .promo-fail').addClass('hide'); 1304 }, 3000); 1305 } 1306 }); 1307 } 1308 } 1309 1310 // CACHE-COMPATIBLE: Mobile promo clear handler - Now also clears promo from TNEW cart 1311 $('.mobile-tessitura-bar .promo-flex .promo-clear').on('click tap',function (e) { 1312 e.preventDefault(); 1313 e.stopImmediatePropagation(); 1314 1315 promoJustClearedEmptyCart = true; 1316 // Update UI immediately 1317 $('.mobile-tessitura-bar .promo-flex input#promoCodeMobile').val(''); 1318 $('.mobile-tessitura-bar .promo-flex input#promoCodeMobile').removeClass('hide'); 1319 $('.mobile-tessitura-bar .promo-flex .promoSubmit').removeClass('hide'); 1320 $(this).addClass('hide'); 1321 $('.mobile-tessitura-bar .promo-clear-code').addClass('hide'); 1322 document.cookie = "promoCode=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + hmnsCookieDomain + "; path=/;"; 1323 setCartPromoLink(null); 1324 1325 // Also clear from TNEW cart via API 1326 var currentSessionKey = getCookie('sessionKey'); 1327 if (currentSessionKey && window.hmns_ajax_router && window.hmns_ajax_router.ready) { 1328 var clearPromoArray = [currentSessionKey]; 1329 var clearPromoQuery = JSON.stringify(clearPromoArray); 1330 $.ajax({ 1331 type: 'post', 1332 url: hmns_ajax_router.url, 1333 beforeSend: setAjaxHeaders, 1334 data: { 1335 action: 'clear_tessitura_promo', 1336 query: clearPromoQuery 1337 }, 1338 success: function(response) { 1339 // console.log('Promo cleared from TNEW (mobile):', response); 1340 }, 1341 error: function(jqXHR, textStatus, errorThrown) { 1342 // console.error('Failed to clear promo from TNEW (mobile):', textStatus, errorThrown); 1343 } 1344 }); 1345 } 1346 }); 1347 function eat_cookies(forceSessionClear) { 1348 // Preserve the session key only while a login is mid-flight (unless forced). 1349 if (forceSessionClear || !isLoginFlowActive()) { 1350 window.hmnsDeleteCookieEverywhere('sessionKey'); 1351 } 1352 window.hmnsDeleteCookieEverywhere('sessionTimer'); 1353 window.hmnsDeleteCookieEverywhere('promoCode'); 1354 window.hmnsDeleteCookieEverywhere('tessituraLogin'); 1355 window.hmnsDeleteCookieEverywhere('modeOfSale'); 1356 // Reset mode of sale to the public default so product pages behave predictably. 1357 document.cookie = "modeOfSale=2; path=/; domain=" + hmnsCookieDomain + ";"; 1358 } 1359 function getCookie(name) { 1360 var dc = document.cookie; 1361 var prefix = name + "="; 1362 var begin = dc.indexOf("; " + prefix); 1363 if (begin == -1) { 1364 begin = dc.indexOf(prefix); 1365 if (begin != 0) return null; 1366 } else { 1367 begin += 2; 1368 } 1369 var end = dc.indexOf(";", begin); 1370 if (end == -1) { 1371 end = dc.length; 1372 } 1373 // because unescape has been deprecated, replaced with decodeURI 1374 //return unescape(dc.substring(begin + prefix.length, end)); 1375 return decodeURI(dc.substring(begin + prefix.length, end)); 1376 } 1377 function setCookie(name, value, minutes) { 1378 let expires = ""; 1379 if (minutes) { 1380 const date = new Date(); 1381 date.setTime(date.getTime() + (minutes * 60 * 1000)); // Convert minutes to milliseconds 1382 expires = "; expires=" + date.toUTCString(); 1383 } 1384 let cookieValue = value; 1385 if (typeof cookieValue === 'boolean') { 1386 cookieValue = cookieValue ? 'true' : 'false'; 1387 } 1388 document.cookie = name + "=" + (cookieValue == null ? "" : cookieValue) + expires + "; path=/; domain=" + hmnsCookieDomain + ";"; 1389 } 1390 // CONVERTED: Add isDaylightSavingTimeCentral function (from functions.php) 1391 function isDaylightSavingTimeCentral() { 1392 const today = new Date(); 1393 const year = today.getFullYear(); 1394 1395 // DST starts on the second Sunday in March 1396 const march = new Date(year, 2, 1); // March 1st 1397 const dstStart = new Date(year, 2, 14 - march.getDay()); // Second Sunday 1398 1399 // DST ends on the first Sunday in November 1400 const november = new Date(year, 10, 1); // November 1st 1401 const dstEnd = new Date(year, 10, 7 - november.getDay()); // First Sunday 1402 1403 if (today >= dstStart) { 1404 if (today < dstEnd) { 1405 return true; 1406 } 1407 } 1408 return false;
1409} 1410});
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.