PageSourceSearch

https://musicalsanfrancisco.es/wp-content/plugins/wp-marketing-aut…/assets/js/bwfan-public.js?ver=3.8.5.2

js musicalsanfrancisco.es collected 2026-10-06 15:01:13 UTC 72,535 bytes, 1,614 lines download raw bytes

1var BWFAN_Public;
2(function ($) {
3    "use strict";
4    /* Serialize the form object */
5    $.fn.bwfanac_serializeAndEncode = function () {
6        return $.map(this.serializeArray(), function (val) {
7                var field = $("input[name='" + val.name + "']");
8                if (field.attr('type') == 'checkbox') {
9                    if (field.prop("checked")) {
10                        return [val.name, encodeURIComponent('1')].join('=');
11                    } else {
12                        return [val.name, encodeURIComponent('0')].join('=');
13                    }
14                } else {
15                    return [val.name, encodeURIComponent(val.value)].join('=');
16                }
17            }
18        ).join('&');
19    };
20
21    BWFAN_Public = {
22
23        checkout_form: $('form.checkout'),
24        last_edit_field: '',
25        current_step: '',
26        checkout_fields_data: {},
27        last_posted_fields_data: {},
28        capture_email_xhr: null,
29        checkout_fields: [],
30        is_block_checkout: false,
31
32        /**
33         * Translate a WC Blocks DOM id/name to the canonical billing_ / shipping_ key
34         * that insert_abandoned_cart() expects.
35         *   'billing-first-name' -> 'billing_first_name'
36         *   'email'              -> 'billing_email'
37         */
38        bwfan_normalize_field_name: function (raw) {
39            if (!raw || !BWFAN_Public.is_block_checkout) return raw;
40            if (raw === 'email' || raw === 'contact_email' || raw === 'contact-email') return 'billing_email';
41            if (raw.indexOf('billing-') === 0)  return 'billing_'  + raw.substring(8).replace(/-/g, '_');
42            if (raw.indexOf('shipping-') === 0) return 'shipping_' + raw.substring(9).replace(/-/g, '_');
43            return raw;
44        },
45
46        init: function () {
47            /** Block checkout shim: swap form scope + selectors when WC Checkout block is active. */
48            this.is_block_checkout = !!(typeof bwfanParamspublic !== 'undefined' && parseInt(bwfanParamspublic._is_block_based_checkout)>0);
49            if (this.is_block_checkout) {
50                var $blockForm = $('div.wp-block-woocommerce-checkout, .wc-block-checkout');
51                if ($blockForm.length) {
52                    this.checkout_form = $blockForm;
53                }
54            }
55            this.checkout_fields = [
56                'billing_first_name',
57                'billing_last_name',
58                'billing_company',
59                'billing_phone',
60                'billing_country',
61                'billing_address_1',
62                'billing_address_2',
63                'billing_city',
64                'billing_state',
65                'billing_postcode',
66                'shipping_first_name',
67                'shipping_last_name',
68                'shipping_company',
69                'shipping_country',
70                'shipping_address_1',
71                'shipping_address_2',
72                'shipping_city',
73                'shipping_state',
74                'shipping_postcode',
75                'shipping_phone',
76                'ship_to_different_address',
77                'shipping_same_as_billing',
78                'billing_same_as_shipping',
79                'payment_method',
80                'bwfan_birthday_date_mm',
81                'bwfan_birthday_date_dd',
82                'bwfan_birthday_date_yy',
83            ];
84
85            if (bwfanParamspublic.bwfan_custom_checkout_field != undefined && bwfanParamspublic.bwfan_custom_checkout_field != null) {
86                this.checkout_fields = this.union_arrays(this.checkout_fields, bwfanParamspublic.bwfan_custom_checkout_field);
87            }
88
89            $.each(BWFAN_Public.checkout_fields, function (i, field_name) {
90                BWFAN_Public.checkout_fields_data[field_name] = '';
91            });
92
93            this.checkout_form.find('input,select').on('change', function () {
94                var id = $(this).attr('id');
95                if ("undefined" !== id) {
96                    BWFAN_Public.last_edit_field = id;
97                }
98            });
99
100
101        },
102        bwfan_get_cookie: function (cname) {
103            var name = cname + "=";
104            var ca = document.cookie.split(';');
105            for (var i = 0; i < ca.length; i++) {
106                var c = ca[i].trim();
107                if (c.indexOf(name) !== 0) {
108                    continue;
109                }
110                var c_val = c.substring(name.length, c.length);
111                try {
112                    c_val = decodeURIComponent(c_val);
113                } catch (e) {
114                    console.log("Error occurred while decoding (" + c + "). Error is: " + e.message);
115                }
116                return c_val;
117            }
118            return "";
119        },
120        bwfan_check_for_checkout_fields: function () {
121            if ($('.wfacp_page').length === 0) {
122                return;
123            }
124            var checkout_fields_data = bwfanParamspublic.bwfan_checkout_js_data;
125            if ('no' === checkout_fields_data) {
126                return;
127            }
128            var localstorage_data = window.localStorage.getItem('wfacp_form_values');
129            if (null == localstorage_data) {
130                localstorage_data = "";
131            }
132            var final_data = checkout_fields_data.fields;
133
134            if ('' !== localstorage_data) {
135                localstorage_data = JSON.parse(localstorage_data);
136                for (var key in localstorage_data) {
137
138                    if (localstorage_data.hasOwnProperty(key)) {
139                        final_data[key] = localstorage_data[key];
140                    }
141                }
142
143            }
144            if (final_data.hasOwnProperty('shipping_method')) {
145                delete final_data.shipping_method;
146            }
147
148            if (final_data.hasOwnProperty('ship_to_different_address')) {
149                $('#ship-to-different-address-checkbox').prop('checked', true).trigger('change');
150                delete final_data.shipping_method;
151            }
152
153            window.localStorage.setItem('wfacp_form_values', JSON.stringify(final_data));
154            populate_fields_value();
155        },
156        urlParam: function (name) {
157            var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
158            var value = 'undefined';
159
160            if (null !== results) {
161                value = results[1];
162            }
163
164            return value;
165        },
166        bwfan_capture_email: function () {
167            var emailSel = BWFAN_Public.is_block_checkout ? '#billing_email, #email' : '#billing_email';
168            $(document).on('focusout', emailSel, function () {
169                    BWFAN_Public.bwfan_get_checkout_data();
170                }
171            );
172
173            var billing_email = jQuery('#billing_email').val();
174            if ((!billing_email || billing_email === '') && BWFAN_Public.is_block_checkout) {
175                billing_email = jQuery('#email').val();
176            }
177            if (billing_email && billing_email !== '') {
178                BWFAN_Public.bwfan_capture_data_on_page_load();
179                BWFAN_Public.bwfan_process_email(billing_email);
180            }
181        },
182        bwfan_get_checkout_data: function () {
183            var email = $('#billing_email').val();
184            if ((!email || email === '') && BWFAN_Public.is_block_checkout) {
185                email = $('#email').val();
186            }
187            if (email && email !== '') {
188                BWFAN_Public.bwfan_process_email(email);
189            }
190        },
191        bwfan_capture_data_on_page_load: function () {
192            $.each(BWFAN_Public.checkout_fields, function (i, field_name) {
193                var $this = $('#' + field_name);
194                /** Block checkout uses kebab-case ids (billing-first-name) and #email for billing_email. */
195                if ((!$this.length || !$this.val()) && BWFAN_Public.is_block_checkout) {
196                    if (field_name === 'billing_email') {
197                        $this = $('#email');
198                    } else if (field_name.indexOf('billing_') === 0) {
199                        $this = $('#billing-' + field_name.substring(8).replace(/_/g, '-'));
200                    } else if (field_name.indexOf('shipping_') === 0) {
201                        $this = $('#shipping-' + field_name.substring(9).replace(/_/g, '-'));
202                    }
203                }
204                BWFAN_Public.checkout_fields_data[field_name] = $this.val();
205            });
206        },
207        bwfanUpdateOnAddressCheckbox: function ( field_name, field_value ) {
208            var checkout_formdata = BWFAN_Public.checkout_form.bwfanac_serializeAndEncode();
209            checkout_formdata = bwfanac_deserialize_obj(checkout_formdata);
210            checkout_formdata[field_name] = field_value;
211            if (
212               ! (( $('#ship-to-different-address-checkbox').length && $('#ship-to-different-address-checkbox').is(':checked') && !$('.wfacp_page').length ) ||
213                $('#billing_same_as_shipping').length && $('#billing_same_as_shipping').is(':checked') ||
214                $('#shipping_same_as_billing').length && $('#shipping_same_as_billing').is(':checked'))
215            ) {
216                for (var i = 0; i < BWFAN_Public.checkout_fields.length; i++) {
217                    let field = BWFAN_Public.checkout_fields[i];
218                    var fields = '';
219
220                    if (checkout_formdata[field] && ($('#shipping_same_as_billing').length && !$('#shipping_same_as_billing').is(':checked')) || // For Aero Checkout - if shipping same as billing checkbox exists and is not checked
221                        (!$('#ship-to-different-address-checkbox').is(':checked'))  ) {
222                        fields = field.replace('billing_', 'shipping_');
223                    }
224                    if (checkout_formdata[field] && ( $('#billing_same_as_shipping').length && !$('#billing_same_as_shipping').is(':checked' ) )) {
225                        fields = field.replace('shipping_', 'billing_');
226                    }
227
228                    checkout_formdata[fields] = checkout_formdata[field];
229                }
230
231
232            }
233            BWFAN_Public.checkout_fields_data = checkout_formdata;
234        },
235        bwfan_captureCheckoutField: function () {
236            var field_name = $(this).attr('name') || $(this).attr('id');
237            if (!field_name) {
238                return;
239            }
240            field_name = BWFAN_Public.bwfan_normalize_field_name(field_name);
241
242            var field_value = $(this).val();
243
244            /** for checking checkbox fields **/
245            if ($(this).attr('type') == 'checkbox') {
246                field_value = $(this).prop('checked') ? 1 : 0;
247            }
248
249            if (BWFAN_Public.checkout_fields.indexOf(field_name) === -1) {
250                return;
251            }
252
253            if (BWFAN_Public.checkout_fields_data[field_name] === field_value) {
254                return;
255            }
256
257            if ( ['ship_to_different_address', 'billing_same_as_shipping', 'shipping_same_as_billing'].includes( field_name ) ) {
258                setTimeout(function () {
259                    BWFAN_Public.bwfanUpdateOnAddressCheckbox(field_name, field_value);
260                }, 200);
261                return;
262            }
263            BWFAN_Public.checkout_fields_data[field_name] = field_value;
264
265            // For WooCommerce - shipping same as billing
266            if ($('#ship-to-different-address-checkbox').length) {
267                BWFAN_Public.checkout_fields_data['ship_to_different_address'] 
267= $('#ship-to-different-address-checkbox').is(':checked') ? 1 : 0;
268            }
269
270            // For WC Blocks checkout - "use shipping address as billing address" (inverted semantics)
271            if (BWFAN_Public.is_block_checkout) {
272                var $blockSame = $('.wc-block-checkout__use-address-for-billing input[type=checkbox]');
273                if ($blockSame.length) {
274                    BWFAN_Public.checkout_fields_data['ship_to_different_address'] = $blockSame.is(':checked') ? 0 : 1;
275                }
276            }
277
278            // For AeroCheckout - billing same as shipping
279            if ($('#billing_same_as_shipping').length) {
280                BWFAN_Public.checkout_fields_data['billing_same_as_shipping'] = $('#billing_same_as_shipping').is(':checked') ? 1 : 0;
281            }
282
283            // For AeroCheckout - shipping same as billing
284            if ($('#shipping_same_as_billing').length) {
285                BWFAN_Public.checkout_fields_data['shipping_same_as_billing'] = $('#shipping_same_as_billing').is(':checked') ? 1 : 0;
286            }
287
288            var field = '';
289
290            // For Aero Checkout - if billing same as shipping checkbox exists and is not checked
291            if ($('#billing_same_as_shipping').length && !$('#billing_same_as_shipping').is(':checked')) {
292                if (field_name.indexOf('shipping_') === 0) {
293                    field = field_name.replace('shipping_', 'billing_');
294                }
295            }
296
297            if (
298                ($('#shipping_same_as_billing').length && !$('#shipping_same_as_billing').is(':checked')) || // For Aero Checkout - if shipping same as billing checkbox exists and is not checked
299                (!$('#ship-to-different-address-checkbox').is(':checked')) // For WooCommerce native checkout
300            ) {
301                if (field_name.indexOf('billing_') === 0) {
302                    field = field_name.replace('billing_', 'shipping_');
303                }
304            }
305
306            // For Aero Checkout - if billing/shipping same as shipping/billing checkbox not exists
307            if(!$('#shipping_same_as_billing').length && !$('#billing_same_as_shipping').length && $('.wfacp_page').length) {
308                if (field_name.indexOf('billing_') === 0) {
309                    field = field_name.replace('billing_', 'shipping_');
310                }
311                if (field_name.indexOf('shipping_') === 0) {
312                    field = field_name.replace('shipping_', 'billing_');
313                }
314            }
315
316            BWFAN_Public.checkout_fields_data[field] = field_value;
317        },
318        bwfan_process_email: async function (email) {
319            if ('undefined' === typeof email) {
320                return;
321            }
322            if (('0' == bwfanParamspublic.bwfan_ab_enable || bwfanParamspublic.bwfan_ab_enable == "")) {
323                return;
324            }
325            /**
326             * Removed abort as it was making a loop when bonanza is enabled
327             */
328            if (null !== BWFAN_Public.capture_email_xhr) {
329                return;
330            }
331            if ('' === email) {
332                return;
333            }
334            if ($('#bwfan_gdpr_consent').length && 1 != $('#bwfan_gdpr_consent').val()) {
335                return;
336            }
337
338            var aero_id = ($('#wfacp_aero_checkout_id').length > 0) ? $('#wfacp_aero_checkout_id').attr('content') : '';
339            var step = '';
340            if (aero_id) {
341                step = (BWFAN_Public.current_step) ? BWFAN_Public.current_step : 'single_step';
342            }
343
344            var timezone = '';
345
346            if (typeof Intl === "object" || typeof Intl.DateTimeFormat() === "object") {
347                var resolved = Intl.DateTimeFormat().resolvedOptions();
348                if (resolved.hasOwnProperty('timeZone')) {
349                    timezone = resolved.timeZone;
350                }
351            }
352
353            var utm_source = this.bwfan_get_cookie('utm_source');
354            var utm_term = this.bwfan_get_cookie('utm_term');
355            var utm_campaign = this.bwfan_get_cookie('utm_campaign');
356            var utm_medium = this.bwfan_get_cookie('utm_medium');
357            var utm_content = this.bwfan_get_cookie('utm_content');
358            if ('' != utm_source || '' != utm_term || '' != utm_campaign || '' != utm_medium || '' != utm_content) {
359                BWFAN_Public.checkout_fields_data['handle_utm_grabber'] = {
360                    'utm_source': utm_source,
361                    'utm_term': utm_term,
362                    'utm_campaign': utm_campaign,
363                    'utm_medium': utm_medium,
364                    'utm_content': utm_content,
365                };
366            }
367
368            var pushengage_token = await BWFAN_Public.getPushToken();
369
370            /**
371             * Block checkout: scrape all currently-rendered block inputs into checkout_fields_data
372             * right before POST, so values land even if individual change/input events were missed
373             * (React-controlled inputs sometimes swallow them).
374             */
375            if (BWFAN_Public.is_block_checkout) {
376                $('#email, #contact_email, [name="contact_email"], [id^
376="billing-"], [id^="shipping-"], textarea[id*="order-notes"]').each(function () {
377                    var raw = $(this).attr('name') || $(this).attr('id');
378                    var key = BWFAN_Public.bwfan_normalize_field_name(raw);
379                    if (!key) return;
380                    var val = ($(this).attr('type') === 'checkbox') ? ($(this).prop('checked') ? 1 : 0) : $(this).val();
381                    if (val !== undefined && val !== null) {
382                        BWFAN_Public.checkout_fields_data[key] = val;
383                    }
384                });
385
386                /** Capture currently-selected block payment method (covers missed change events). */
387                var $payChecked = $('.wp-block-woocommerce-checkout-payment-block input[type=radio]:checked, .wc-block-components-payment-method input[type=radio]:checked').first();
388                if ($payChecked.length && $payChecked.val()) {
389                    BWFAN_Public.checkout_fields_data['payment_method'] = $payChecked.val();
390                }
391
392                /**
393                 * When "use shipping as billing" is checked (block's default), the billing form is
394                 * not rendered. Mirror shipping_* into billing_* so the cart row stores a usable
395                 * billing address for recovery emails / merge tags.
396                 */
397                var $sameAddr = $('.wc-block-checkout__use-address-for-billing input[type=checkbox]');
398                var useSameAddr = ($sameAddr.length === 0) || $sameAddr.is(':checked');
399                if (useSameAddr) {
400                    /**
401                     * Always overwrite billing_* with shipping_* — if the user previously
402                     * unchecked the box, typed billing values, then re-checked, the stale
403                     * billing values must not win. billing_email is independent of the toggle.
404                     */
405                    var fields = BWFAN_Public.checkout_fields_data;
406                    Object.keys(fields).forEach(function (k) {
407                        if (k.indexOf('shipping_') === 0) {
408                            var bk = 'billing_' + k.substring(9);
409                            if (bk === 'billing_email') return;
410                            fields[bk] = fields[k];
411                        }
412                    });
413                }
414            }
415
416            /** Snapshot the values this POST carries — the change-listener dedupe compares against posted state, not the live cache. */
417            BWFAN_Public.last_posted_fields_data = $.extend(true, {}, BWFAN_Public.checkout_fields_data);
418
419            BWFAN_Public.capture_email_xhr = $.ajax({
420                url: bwfanParamspublic.wc_ajax_url.toString().replace('%%endpoint%%', 'bwfan_insert_abandoned_cart'),
421                type: 'POST',
422                global: false,
423                data: {
424                    'email': email,
425                    'action': 'bwfan_insert_abandoned_cart',
426                    'checkout_fields_data': BWFAN_Public.checkout_fields_data,
427                    'last_edit_field': BWFAN_Public.last_edit_field,
428                    'current_step': step,
429                    'current_page_id': bwfanParamspublic.current_page_id,
430                    'timezone': timezone,
431                    'aerocheckout_page_id': aero_id,
432                    'pushengage_token': pushengage_token,
433                    '_wpnonce': bwfanParamspublic.ajax_nonce
434                },
435                success: function (res) {
436                    if (parseInt(res.id) > 0 && 0 === $('#bwfan_cart_id').length) {
437                        var cartIdHtml = '<input type="hidden" id="bwfan_cart_id" name="bwfan_cart_id" value="' + res.id + '" />';
438                        var $anchor = $('#billing_email_field');
439                        if (!$anchor.length && BWFAN_Public.is_block_checkout) {
440                            $anchor = $('div.wp-block-woocommerce-checkout, .wc-block-checkout').first();
441                        }
442                        if ($anchor.length) {
443                            $anchor.after(cartIdHtml);
444                        }
445                        console.log('Cart ID: ' + res.id + ' captured.');
446                    }
447                }
448            }).always(function () {
449                BWFAN_Public.capture_email_xhr = null;
450            });
451        },
452
453        union_arrays: function (x, y) {
454            var obj = {};
455            for (var i = x.length - 1; i >= 0; --i)
456                obj[x[i]] = x[i];
457            for (var i = y.length - 1; i >= 0; --i)
458                obj[y[i]] = y[i];
459            var res = [];
460            for (var k in obj) {
461                if (obj.hasOwnProperty(k))
462                    res.push(obj[k]);
463            }
464            return res;
465        },
466
467        getPushToken: async function () {
468            try {
469                const subscriberID = await window.PushEngage.getSubscriberId();
470                return subscriberID;
471            } catch (error) {
472
473            }
474            return null;
475        },
476    };
477
478    /**
479     * Abandoned cart JS Function Initiate here
480     */
481    BWFAN_Public.abandoned_cart = function () {
482        if ('0' != bwfanParamspublic.bwfan_ab_enable && bwfanParamspublic.bwfan_ab_enable != "") {
483            BWFAN_Public.bwfan_capture_email();
484        }
485    };
486
487    BWFAN_Public.get_selected_unsubscribe_lists = function () {
488        const enabled = $('#bwfan-unsubscribe-lists').length > 0;
489        if (!enabled) {
490            return [];
491        }
492
493        const lists = [];
494        $('.bwfan-unsubscribe-list-checkbox input').each(function () {
495            if ($(this).attr('id') === 'bwfan-list-unsubscribe-all') {
496                $(this).is(':checked') && lists.push('all');
497                return true;
498            }
499
500            if (!$(this).is(':checked')) {
501                lists.push(parseInt($(this).val()));
502            }
503        });
504        return lists;
505    };
506
507    /**
508     * Unsubscribe event to unsubscribe user on a link click
509     */
510    BWFAN_Public.unsubscribe_event = function () {
511        $('#bwfan_unsubscribe').on('click', function (event) {
512            event.preventDefault();
513            var $this = $(this);
514            if ($this.hasClass('bwfan_loading')) {
515                return;
516            }
517            $this.addClass('bwfan_loading');
518
519            var urlParams = new URLSearchParams(window.location.search);
520
521            var recipient = $('#bwfan_unsubscribe_recipient');
522            recipient = (0 === recipient.length) ? urlParams.get('subscriber_recipient') : recipient.html();
523
524            var automation_id = $('#bwfan_automation_id');
525            automation_id = (0 === automation_id.length) ? urlParams.get('automation_id') : automation_id.val();
526
527            var broadcast_id = $('#bwfan_broadcast_id');
528            broadcast_id = (0 === broadcast_id.length) ? urlParams.get('broadcast_id') : broadcast_id.val();
529
530            var form_feed_id = $('#bwfan_form_feed_id');
531            form_feed_id = (0 === form_feed_id.length) ? urlParams.get('form_feed_id') : form_feed_id.val();
532
533            var sid = $('#bwfan_sid');
534            sid = (0 === sid.length) ? urlParams.get('sid') : sid.val();
535
536            var tid = $('#bwfan_tid');
537            tid = (0 === tid.length) ? urlParams.get('tid') : tid.val();
538
539            var oneClick = $('#bwfan_one_click').val();
540
541            var uid = $('#bwfan_form_uid_id');
542            uid = (0 === uid.length) ? urlParams.get('bwfan_form_uid_id') : uid.val();
543
544            var bwfan_nonce = $('#bwfan_unsubscribe_nonce');
545
546            var oneClickArgs = urlParams.get('List-Unsubscribe');
547            oneClickArgs = (oneClickArgs == 'One-Click') ? 1 : 0;
548
549            if (uid == undefined && recipient == undefined) {
550                if ($(".bwfan_response").length === 0) {
551                    $this.after("<div class='bwfan_response'>" + bwfanParamspublic.message_no_contact + "</div>");
552                }
553                $('.bwfan_response').fadeIn();
554                $this.removeClass('bwfan_loading');
555                setTimeout(function () {
556                    $('.bwfan_response').fadeOut("slow");
557                }, 2500);
558                return;
559            }
560
561            var lists = BWFAN_Public.get_selected_unsubscribe_lists();
562            if (lists.includes('all')) {
563                $('.bwfan-unsubscribe-list-checkbox input').removeAttr('checked');
564                $('#bwfan-list-unsubscribe-all').prop('checked', true);
565            }
566            lists = JSON.stringify(lists);
567
568            $.ajax({
569                method: 'post',
570                dataType: 'json',
571                url: bwfanParamspublic.ajax_url,
572                data: $('#bwfan_unsubscribe_fields').serialize() + "&action=bwfan_unsubscribe_user" + "&recipient=" + recipient + "&sid=" + sid + "&tid=" + tid + "&unsubscribe_lists=" + lists + "&_nonce=" + bwfan_nonce.val() + "&one_click=" + oneClick + "&one_click_get=" + oneClickArgs,
573                success: function (result) {
574                    // Add the redirect handling here
575                    if (result.redirect && result.redirect_url) {
576                        window.location.href = result.redirect_url;
577                        return;
578                    }
579
580                    $this.removeClass('bwfan_loading');
581
582                    var response_generated = $this.parent().find('.bwfan_response').html();
583                    if ('undefined' === typeof response_generated) {
584                        $this.after("<div class='bwfan_response'></div>");
585                    }
586
587                    if (0 === result.success) {
588                        $('.bwfan_response').addClass('bwfan_error');
589                        $('.bwfan_response').fadeIn().html(result.message);
590
591                        if (typeof bwfan_unsubscribe_preference !== 'undefined' && bwfan_unsubscribe_preference == 'one_click') {
592                            $('.bwfan-unsubscribe-lists').hide();
593                            $('a#bwfan_unsubscribe').hide();
594                        } else {
595                            setTimeout(function () {
596                                $('.bwfan_response').fadeOut("slow");
597                            }, 2500);
598                        }
599
600                        return;
601                    }
602
603                    $('.bwfan_response').addClass('bwfan_success');
604                    $('.bwfan_response').fadeIn().html(result.message);
605                    if ('1' === $('#bwfan_one_click').val()) {
606                        $('#bwfan-list-unsubscribe-all').prop('checked', true);
607                    }
608
609                    if (typeof bwfan_unsubscribe_preference !== 'undefined' && bwfan_unsubscribe_preference == 'one_click') {
610                        $('.bwfan-unsubscribe-lists').hide();
611                        $('a#bwfan_unsubscribe').hide();
612                    } else {
613                        /** Content is refresh after success */
614                        $('#bwfan-unsubscribe-lists').load(
615                            location.href + ' #bwfan-unsubscribe-lists > *'
616                        );
617                        setTimeout(function () {
618                            $('.bwfan_response').fadeOut('slow');
619                        }, 2500);
620                    }
621                }
622            });
623        });
624    };
625
626    /**
627     * Selected profile tags
628     * */
629    BWFAN_Public.get_profile_tags = function () {
630        const enabled = $('#bwfan-profile-tags').length > 0;
631        if (!enabled) {
632            return [];
633        }
634
635        var tags = [];
636        $('.bwfan-profile-tag-checkbox input:checked').each(function () {
637            tags.push($(this).val());
638        });
639        return tags;
640    };
641
642    /**
643     * selected profile lists
644     *
645     * */
646    BWFAN_Public.get_profile_lists = function () {
647        const enabled = $('#bwfan-profile-lists').length > 0;
648        if (!enabled) {
649            return [];
650        }
651
652        const lists = [];
653        $('.bwfan-profile-list-checkbox input:checked').each(function () {
654            lists.push(parseInt($(this).val()));
655        });
656
657        return lists;
658    }
659
660    /**
661     * Selected profile fields
662     *
663     * */
664    BWFAN_Public.get_profile_fields = function () {
665        const enabled = $('#bwfan-profile-fields').length > 0;
666        if (!enabled) {
667            return [];
668        }
669
670        const fields = [];
671        const dobParts = { day: '', month: '', year: '' };
672        let hasDobField = false;
673
674        $('.bwfan-profile-field input, .bwfan-profile-field textarea, .bwfan-profile-field select').each(function () {
675            const fieldSlug = $(this).data('slug');
676            const dobPart = $(this).data('dob-part');
677
678            // Handle DOB parts separately
679            if (fieldSlug === 'dob' && dobPart) {
680                hasDobField = true;
681                dobParts[dobPart] = $(this).val();
682                return; // Skip adding to fields array
683            }
684
685            if ($(this).is(':radio') || $(this).is(':checkbox')) {
686                if ($(this).is(':checked')) {
687                    if (!fields[fieldSlug]) {
688                        fields[fieldSlug] = [];
689                    }
690                    fields[fieldSlug].push($(this).val());
691                }
692            } else if (fieldSlug === 'timezone') {
693                fields[fieldSlug] = $(this).val();
694            } else {
695                fields[fieldSlug] = $(this).val();
696            }
697        });
698
699        for (const slug in fields) {
700            if (Array.isArray(fields[slug])) {
701                fields[slug] = fields[slug].join(', ');
702            }
703        }
704
705        const result = [];
706
707        // Add DOB parts if present
708        if (hasDobField) {
709            result.push({ slug: 'dob', value: dobParts.day, dob_part: 'day' });
710            result.push({ slug: 'dob', value: dobParts.month, dob_part: 'month' });
711            result.push({ slug: 'dob', value: dobParts.year, dob_part: 'year' });
712        }
713
714        for (const slug in fields) {
715            const value = Array.isArray(fields[slug]) ? fields[slug].join(', ') : fields[slug];
716            result.push({slug: slug, value: value});
717        }
718
719        return result;
720    }
721
722    BWFAN_Public.update_profile = function () {
723        const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
724        $('#bwfan_manage_profile_update').on('click', function (event) {
725            event.preventDefault();
726            var $this = $(this);
727
728            // Clear previous DOB errors (errors are added as siblings with .after())
729            $('.bwfan-dob-wrapper').next('.bwfan_error').remove();
730            $('.bwfan-dob-field').removeClass('bwfan_error');
731
732            // Validate DOB fields if present
733            const dobFields = $('.bwfan-dob-field[data-slug="dob"]');
734            if (dobFields.length > 0) {
735                let dobDay = '';
736                let dobMonth = '';
737                let dobYear = '';
738                let dobWrapper = null;
739
740                dobFields.each(function() {
741                    const dobPart = $(this).data('dob-part');
742                    if (!dobWrapper) {
743                        dobWrapper = $(this).closest('.bwfan-dob-wrapper');
744                    }
745
746                    if (dobPart === 'day') dobDay = $(this).val();
747                    if (dobPart === 'month') dobMonth = $(this).val();
748                    if (dobPart === 'year') dobYear = $(this).val();
749                });
750
751                // Check if all parts are selected or all are empty
752                const hasAnyValue = dobDay || dobMonth || dobYear;
753                const hasAllValues = dobDay && dobMonth && dobYear;
754                const allEmpty = !dobDay && !dobMonth && !dobYear;
755
756                // If all empty, it's valid (optional field)
757                if (allEmpty) {
758                    // DOB is optional, all empty is valid
759                } else if (hasAnyValue && !hasAllValues) {
760                    // Some parts selected but not all
761                    dobFields.addClass('bwfan_error');
762                    dobWrapper.after("<p class='bwfan_response bwfan_error'>"+bwfanParamspublic.dob_not_all_parts+"</p>");
763                    return;
764                } else if (hasAllValues) {
765                    // If all values present, validate date is valid
766                    const day = parseInt(dobDay, 10);
767                    const month = parseInt(dobMonth, 10);
768                    const year = parseInt(dobYear, 10);
769
770                    // Check if date is valid
771                    const date = new Date(year, month - 1, day);
772                    if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
773                        dobFields.addClass('bwfan_error');
774                        dobWrapper.after("<p class='bwfan_response bwfan_error'>"+bwfanParamspublic.dob_not_valid+"</p>");
775                        return;
776                    }
777
778                    // Check if date is not in the future
779                    const today = new Date();
780                    if (date > today) {
781                        dobFields.addClass('bwfan_error');
782                        dobWrapper.after("<p class='bwfan_response bwfan_error'>"+bwfanParamspublic.dob_not_in_future+"</p>");
783                        return;
784                    }
785                }
786            }
787
788            var emailField = $('input[data-slug="email"]');
789            if( emailField.length > 0 ) {
790                if (emailField.val() === '') {
791                    // Check if error message already exists
792                    if (emailField.next('.bwfan_error').length === 0) {
793                        $('#bwfan-field-email').addClass('bwfan_error');
794                        emailField.after("<p class='bwfan_response bwfan_error'>"+bwfanParamspublic.email_not_fill+"</p>");
795                    } else {
796                        emailField.next('.bwfan_error').fadeIn().html( bwfanParamspublic.email_not_fill );
797                    }
798                    return; // Stop the process if email is empty
799                } else if( ! emailRegex.test(emailField.val()) ) {
800                    if (emailField.next('.bwfan_error').length === 0) {
801                        $('#bwfan-field-email').addClass('bwfan_error');
802                        emailField.after("<p class='bwfan_response bwfan_error'>"+bwfanParamspublic.email_not_valid+"</p>");
803                    } else {
804                        emailField.next('.bwfan_error').fadeIn().html(bwfanParamspublic.email_not_valid);
805                    }
806                    return;
807                } else{
808                    emailField.next('.bwfan_error').remove();
809                    $('#bwfan-field-email').removeClass('bwfan_error');
810                }
811            }
812
813            var errorExists = $('#bwfan_manage_profile_fields' ).find('.bwfan_response.bwfan_error').length > 0;
814            if( errorExists ) {
815                // Get the first error element
816                var firstError = $('#bwfan_manage_profile_fields' ).find('.bwfan_response.bwfan_error')[0];
817                setTimeout( () => {
818                    var distractionMode = $( '.bwfan-manage-profile-wrapper.bwf-distraction-free-mode' ).length > 0;
819                    if( distractionMode ) {
820                        firstError.scrollIntoView({
821                            behavior: 'smooth',
822                            block: 'start',
823                            inline: 'nearest'
824                        });
825                        return;
826                    }
827
828                    // Scroll to the position of the first error element
829                    window.scrollTo({
830                        top: firstError.getBoundingClientRect().top + window.scrollY - 100,
831                        behavior: 'smooth'
832                    });
833                }, 300 );
834            }
835
836            if ($this.hasClass('bwfan_loading')) {
837                return;
838            }
839
840
841            var bwfan_nonce = $('#bwfan_manage_profile_nonce');
842
843            var lists = BWFAN_Public.get_profile_lists();
844            lists = JSON.stringify(lists);
845
846            var tags = BWFAN_Public.get_profile_tags();
847            tags = JSON.stringify(tags);
848
849            var fields = BWFAN_Public.get_profile_fields();
850            fields = JSON.stringify(fields);
851
852            var uid = $('#bwfan_form_uid_id');
853            uid = (0 === uid.length) ? urlParams.get('bwfan_form_uid_id') : uid.val();
854
855            $this.addClass('bwfan_loading');
856
857            $.ajax({
858                method: 'post',
859                dataType: 'json',
860                url: bwfanParamspublic.ajax_url,
861                data: $('#bwfan_manage_profile_update').serialize() + "&action=bwfan_manage_profile" + "&uid=" + uid + "&profile_lists=" + lists + "&profile_tags=" + tags + "&profile_fields=" 
861+ fields + "&_nonce=" + bwfan_nonce.val(),
862                success: function (result) {
863                    $this.removeClass('bwfan_loading');
864                    var response_generated = $this.parent().find('.bwfan_response').html();
865                    if ('undefined' === typeof response_generated) {
866                        $this.after("<div class='bwfan_response api-response'></div>");
867                    }
868                    let message = result.hasOwnProperty( 'message' ) ? result.message : bwfanParamspublic.profile_success;
869                    switch (parseInt(result.success)) {
870                        case 10:
871                            if (emailField.next('.bwfan_error').length === 0) {
872                                $('#bwfan-field-email').addClass('bwfan_error');
873                                emailField.after(`<p class='bwfan_response bwfan_error'>${message}</p>`);
874                            }
875                            break;
876                        case 0:
877                            $('.bwfan_response.api-response').addClass('bwfan_error');
878                            $('.bwfan_response.api-response').fadeIn().html(message);
879                            break;
880                        default:
881                            if ($('.bwfan_response.api-response').length > 0) {
882                                $('.bwfan_response.api-response').removeClass('bwfan_error');
883                            }
884                            $('.bwfan_response.api-response').addClass('bwfan_success');
885                            $('.bwfan_response.api-response').fadeIn().html(message);
886                            break;
887                    }
888                    setTimeout(function () {
889                        $('.bwfan_response.api-response').fadeOut("slow");
890                    }, 3000);
891                }
892            });
893        });
894        $(document).ready(function () {
895            // Datetime fields
896            const dateTimeFields = document.querySelectorAll('#bwfan-profile-fields .bwfan-profile-field input[type="text"][data-type="date-time"]');
897            dateTimeFields.forEach(field => {
898                field.addEventListener('input', function () {
899                    let value = $(this).val().trim().replace(/\D/g, ''); // Strip non-digits
900                    let formatted = '';
901                    if (value.length > 0) formatted += value.substring(0, 4);           // YYYY
902                    if (value.length > 4) formatted += '-' + value.substring(4, 6);     // MM
903                    if (value.length > 6) formatted += '-' + value.substring(6, 8);     // DD
904                    if (value.length > 8) formatted += ' ' + value.substring(8, 10);    // HH
905                    if (value.length > 10) formatted += ':' + value.substring(10, 12);  // mm
906                    if (value.length > 12) formatted += ':' + value.substring(12, 14);  // ss
907
908                    $(this).val(formatted);
909                });
910
911                field.addEventListener('blur', function () {
912                    const val = $(this).val().trim();
913
914                    if( val == '' ) {
915                        $(this).next('.bwfan_error').remove();
916                        $(this).removeClass('bwfan_error');
917                        return;
918                    }
919
920                    if (val.length !== 19) {
921                        let error = 'Invalid date-time format. Please use YYYY-MM-DD HH:MM:SS';
922                        if ($(this).next('.bwfan_error').length === 0) {
923                            $(this).addClass('bwfan_error');
924                            $(this).after("<p class='bwfan_response bwfan_error'>"+error+"</p>");
925                        } else {
926                            $(this).next('.bwfan_error').fadeIn().html(error);
927                        }
928                        return;
929                    }
930
931                    const regex = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
932                    const match = val.match(regex);
933
934                    if (!match) {
935                        let error = 'Invalid format. Please use YYYY-MM-DD HH:MM:SS';
936                        if ($(this).next('.bwfan_error').length === 0) {
937                            $(this).addClass('bwfan_error');
938                            $(this).after("<p class='bwfan_response bwfan_error'>"+error+"</p>");
939                        } else {
940                            $(this).next('.bwfan_error').fadeIn().html(error);
941                        }
942                        return;
943                    }
944
945                    const [_, year, month, day, hour, minute, second] = match.map(Number);
946
947                    const isValidDate = (y, m, d, h, min, s) => {
948                        if (m < 1 || m > 12) return false;
949                        if (d < 1 || d > 31) return false;
950                        if (h < 0 || h > 23) return false;
951                        if (min < 0 || min > 59) return false;
952                        if (s < 0 || s > 59) return false;
953
954                        const date = new Date(y, m - 1, d);
955                        return date.getFullYear() === y &&
956                            date.getMonth() === m - 1 &&
957                            date.getDate() === d;
958                    };
959                    if (!isValidDate(year, month, day, hour, minute, second)) {
960                        let error = 'Invalid date or time entered.';
961                        if ($(this).next('.bwfan_error').length === 0) {
962                            $(this).addClass('bwfan_error');
963                            $(this).after("<p class='bwfan_response bwfan_error'>"+error+"</p>");
964                        } else {
965                            $(this).next('.bwfan_error').fadeIn().html(error);
966                        }
967                        return;
968                    }
969                    $(this).next('.bwfan_error').remove();
970                    $(this).removeClass('bwfan_error');
971                });
972            });
973
974            // Date field
975            const dateFields = document.querySelectorAll('#bwfan-profile-fields .bwfan-profile-field input[type="text"][data-type="date"]');
976            dateFields.forEach(field => {
977                field.addEventListener('input', function () {
978                    let value = $(this).val().trim().replace(/\D/g, ''); // Strip non-digits
979                    let formatted = '';
980                    if (value.length > 0) formatted += value.substring(0, 4);           // YYYY
981                    if (value.length > 4) formatted += '-' + value.substring(4, 6);     // MM
982                    if (value.length > 6) formatted += '-' + value.substring(6, 8);     // DD
983
984                    $(this).val(formatted);
985                });
986
987                field.addEventListener('blur', function () {
988                    const val = $(this).val().trim();
989                    if( val == '' ) {
990                        $(this).next('.bwfan_error').remove();
991                        $(this).removeClass('bwfan_error');
992                        return;
993                    }
994                    if (val.length !== 10) {
995                        let error = 'Invalid date format. Please use YYYY-MM-DD';
996                        if ($(this).next('.bwfan_error').length === 0) {
997                            $(this).addClass('bwfan_error');
998                            $(this).after("<p class='bwfan_response bwfan_error'>"+error+"</p>");
999                        } else {
1000                            $(this).next('.bwfan_error').fadeIn().html(error);
1001                        }
1002                        return;
1003                    }
1004
1005                    const regex = /^(\d{4})-(\d{2})-(\d{2})$/;
1006                    const match = val.match(regex);
1007
1008                    if (!match) {
1009                        let error = 'Invalid format. Please use YYYY-MM-DD';
1010                        if ($(this).next('.bwfan_error').length === 0) {
1011                            $(this).addClass('bwfan_error');
1012                            $(this).after("<p class='bwfan_response bwfan_error'>"+error+"</p>");
1013                        } else {
1014                            $(this).next('.bwfan_error').fadeIn().html(error);
1015                        }
1016                        return;
1017                    }
1018
1019                    const [_, year, month, day] = match.map(Number);
1020
1021                    const isValidDate = (y, m, d) => {
1022                        if (m < 1 || m > 12) return false;
1023                        if (d < 1 || d > 31) return false;
1024
1025                        const date = new Date(y, m - 1, d);
1026                        return date.getFullYear() === y &&
1027                            date.getMonth() === m - 1 &&
1028                            date.getDate() === d;
1029                    };
1030                    if (!isValidDate(year, month, day)) {
1031                        let error = 'Invalid date  entered.';
1032                        if ($(this).next('.bwfan_error').length === 0) {
1033                            $(this).addClass('bwfan_error');
1034                            $(this).after("<p class='bwfan_response bwfan_error'>"+error+"</p>");
1035                        } else {
1036                            $(this).next('.bwfan_error').fadeIn().html(error);
1037                        }
1038                        return;
1039                    }
1040                    $(this).next('.bwfan_error').remove();
1041                    $(this).removeClass('bwfan_error');
1042                });
1043            });
1044        });
1045
1046        // Time field
1047        const timeFields = document.querySelectorAll('#bwfan-profile-fields .bwfan-profile-field input[type="text"][data-type="time"]');
1048        timeFields.forEach(field => {
1049            field.addEventListener('input', function () {
1050                let value = $(this).val().trim().replace(/\D/g, ''); // Strip non-digits
1051                let formatted = '';
1052                if (value.length > 0) formatted += value.substring(0, 2);           // HH
1053                if (value.length > 2) formatted += ':' + value.substring(2, 4);     // MM
1054                if (value.length > 4) formatted += ':' + value.substring(4, 6);     // SS
1055
1056                $(this).val(formatted);
1057            });
1058
1059            field.addEventListener('blur', function () {
1060                const val = $(this).val().trim();
1061
1062                if( val == '' ) {
1063                    $(this).next('.bwfan_error').remove();
1064                    $(this).removeClass('bwfan_error');
1065                    return;
1066                }
1067
1068                if (val.length !== 8) {
1069                    let error = 'Invalid time format. Please use H
1069H:MM:SS';
1070                    if ($(this).next('.bwfan_error').length === 0) {
1071                        $(this).addClass('bwfan_error');
1072                        $(this).after("<p class='bwfan_response bwfan_error'>"+error+"</p>");
1073                    } else {
1074                        $(this).next('.bwfan_error').fadeIn().html(error);
1075                    }
1076                    return;
1077                }
1078
1079                const regex = /^(\d{2}):(\d{2}):(\d{2})$/;
1080                const match = val.match(regex);
1081
1082                if (!match) {
1083                    let error = 'Invalid format. Please use HH:MM:SS';
1084                    if ($(this).next('.bwfan_error').length === 0) {
1085                        $(this).addClass('bwfan_error');
1086                        $(this).after("<p class='bwfan_response bwfan_error'>"+error+"</p>");
1087                    } else {
1088                        $(this).next('.bwfan_error').fadeIn().html(error);
1089                    }
1090                    return;
1091                }
1092
1093                const [_, hour, minute, second] = match.map(Number);
1094
1095                const isValidDate = (h, min, s) => {
1096                    if (h < 0 || h > 23) return false;
1097                    if (min < 0 || min > 59) return false;
1098                    if (s < 0 || s > 59) return false;
1099
1100                    return true;
1101                };
1102                if (!isValidDate(hour, minute, second)) {
1103                    let error = 'Invalid time entered.';
1104                    if ($(this).next('.bwfan_error').length === 0) {
1105                        $(this).addClass('bwfan_error');
1106                        $(this).after("<p class='bwfan_response bwfan_error'>"+error+"</p>");
1107                    } else {
1108                        $(this).next('.bwfan_error').fadeIn().html(error);
1109                    }
1110                    return;
1111                }
1112                $(this).next('.bwfan_error').remove();
1113                $(this).removeClass('bwfan_error');
1114            });
1115        });
1116    }
1117
1118    /* Initialize */
1119    BWFAN_Public.init();
1120
1121    $(document).ready(function () {
1122        if ('0' != bwfanParamspublic.bwfan_ab_enable && bwfanParamspublic.bwfan_ab_enable != '') {
1123            var bwfan_email_consent_message = '';
1124            if ('0' != bwfanParamspublic.bwfan_ab_email_consent && bwfanParamspublic.bwfan_ab_email_consent != '') {
1125                var site_language = bwfanParamspublic.bwfan_site_language;
1126                var message_index = 'bwfan_ab_email_consent_message_' + site_language;
1127                var label = bwfanParamspublic.hasOwnProperty(message_index) && bwfanParamspublic[message_index] != '' ? bwfanParamspublic[message_index] : bwfanParamspublic.bwfan_ab_email_consent_message;
1128                bwfan_email_consent_message = '<label>' + label + '</label>';
1129
1130                // Remove/Strip slashes
1131                bwfan_email_consent_message = bwfan_email_consent_message.replace(new RegExp("\\\\", "g"), "");
1132                var start = bwfan_email_consent_message.search("{{no_thanks label=");
1133                var end = bwfan_email_consent_message.search("}}");
1134                if (-1 != start && -1 != end) {
1135                    var temp_start = start + 19;
1136                    var temp_end = end - 1;
1137                    var no_thanks_merge_tag = bwfan_email_consent_message.substring(start, (end + 2));
1138                    var no_thanks_label = bwfan_email_consent_message.substring(temp_start, temp_end);
1139                    no_thanks_label = no_thanks_label ? no_thanks_label : bwfanParamspublic.bwfan_no_thanks;
1140                    bwfan_email_consent_message = bwfan_email_consent_message.replace(no_thanks_merge_tag, "<a class='bwfan_email_consent_no_thanks' style='text-decoration:underline;cursor: pointer;'>" + no_thanks_label + "</a>");
1141                } else {
1142                    bwfan_email_consent_message = bwfan_email_consent_message.replace("{{no_thanks}}", "<a class='bwfan_email_consent_no_thanks' style='text-decoration:underline;cursor: pointer;'>" + bwfanParamspublic.bwfan_no_thanks + "</a>");
1143                }
1144            }
1145
1146            var emailConsentHtml = '<input type="hidden" id="bwfan_gdpr_consent" value="1" />';
1147            if ('' === bwfan_email_consent_message) {
1148                $('#billing_email_field').after(emailConsentHtml);
1149            } else {
1150                bwfan_email_consent_message += emailConsentHtml;
1151                $('#billing_email_field').after('<p class="form-row form-row-wide wfacp-form-control-wrapper wfacp-col-full wfacp-anim-wrap">' + bwfan_email_consent_message + '</p>');
1152            }
1153
1154            $('.bwfan_email_consent_no_thanks').on('click', function (event) {
1155                event.preventDefault();
1156
1157                $.post(bwfanParamspublic.wc_ajax_url.toString().replace('%%endpoint%%', 'bwfan_delete_abandoned_cart'), {
1158                        'email': $('#billing_email').val(),
1159                        'action': 'bwfan_delete_abandoned_cart',
1160                        '_wpnonce': bwfanParamspublic.ajax_nonce
1161                    }, function () {
1162                        //
1163                    }
1164                );
1165                $('#bwfan_gdpr_consent').val('0');
1166                $(this).parent().fadeOut("slow");
1167            });
1168        }
1169    });
1170    $(window).on('load', function () {
1171        BWFAN_Public.abandoned_cart();
1172        BWFAN_Public.unsubscribe_event();
1173        BWFAN_Public.update_profile();
1174        BWFAN_Public.bwfan_check_for_checkout_fields();
1175        BWFAN_Public.bwfan_capture_data_on_page_load();
1176        $(document.body).on('wfacp_step_switching', function (e, v) {
1177            BWFAN_Public.current_step = v.current_step;
1178            var email = $('#billing_email').val();
1179            if (email !== '') {
1180                BWFAN_Public.bwfan_process_email(email);
1181            }
1182        });
1183
1184        /**
1185         * Detect change and save data in database
1186         */
1187        BWFAN_Public.checkout_form.on('change', 'select', BWFAN_Public.bwfan_captureCheckoutField);
1188        BWFAN_Public.checkout_form.on('click', '.input-checkbox', BWFAN_Public.bwfan_captureCheckoutField);
1189        BWFAN_Public.checkout_form.on('change', '.input-checkbox', BWFAN_Public.bwfan_captureCheckoutField);
1190        BWFAN_Public.checkout_form.on('change', '.input-text', BWFAN_Public.bwfan_captureCheckoutField);
1191
1192        /**
1193         * WC Blocks checkout: inputs re-render on every Store API push (cart/shipping/totals),
1194         * so direct .on() bindings get detached. Delegate via document instead.
1195         * React-controlled text inputs fire 'input' reliably; 'change'/'blur' may be missed.
1196         */
1197        if (BWFAN_Public.is_block_checkout) {
1198            var blockInputs = '#email, #contact_email, [name="contact_email"], [id^="billing-"], [id^="shipping-"], textarea[id*="order-notes"], .wc-block-checkout__use-address-for-billing input[type=checkbox]';
1199            $(document).on('change input blur focusout', blockInputs, BWFAN_Public.bwfan_captureCheckoutField);
1200
1201            /**
1202             * "Use same address for billing" toggle has an auto-generated id (e.g. checkbox-control-1)
1203             * and no name attribute, so bwfan_captureCheckoutField can't classify it.
1204             * Wire a dedicated handler: translate state to ship_to_different_address and re-POST.
1205             */
1206            var sameAddrSel = '.wc-block-checkout__use-address-for-billing input[type=checkbox]';
1207            $(document).on('change', sameAddrSel, function () {
1208                var sameAddr = $(this).is(':checked');
1209                BWFAN_Public.checkout_fields_data['ship_to_different_address'] = sameAddr ? 0 : 1;
1210                /**
1211                 * When the user re-checks "use shipping as billing", the block hides the billing
1212                 * form so any previously typed billing_* values become unreachable. Drop them so
1213                 * the pre-POST mirror (shipping_* -> billing_*) is what lands on the cart row.
1214                 */
1215                if (sameAddr) {
1216                    Object.keys(BWFAN_Public.checkout_fields_data).forEach(function (k) {
1217                        if (k.indexOf('billing_') === 0 && k !== 'billing_email') {
1218                            delete BWFAN_Public.checkout_fields_data[k];
1219                        }
1220                    });
1221                } else {
1222                    Object.keys(BWFAN_Public.checkout_fields_data).forEach(function (k) {
1223                        if (k.indexOf('billing_') === 0 && k !== 'billing_email') {
1224                            BWFAN_Public.checkout_fields_data[k] = '';
1225                        }
1226                    });
1227                }
1228                if (typeof BWFAN_Public.bwfan_get_checkout_data === 'function') {
1229                    setTimeout(BWFAN_Public.bwfan_get_checkout_data, 300);
1230                }
1231            });
1232
1233            /**
1234             * Block payment method radios use auto-generated names (radio-control-X). The radio
1235             * value attribute carries the payment gateway slug (cod, bacs, stripe, ...). Map to
1236             * canonical `payment_method` key.
1237             */
1238            var paySel = '.wp-block-woocommerce-checkout-payment-block input[type=radio], .wc-block-components-payment-method input[type=radio]';
1239            $(document).on('change', paySel, function () {
1240                var val = $(this).val();
1241                if (val) {
1242                    BWFAN_Public.checkout_fields_data['payment_method'] = val;
1243                    if (typeof BWFAN_Public.bwfan_get_checkout_data === 'function') {
1244                        setTimeout(BWFAN_Public.bwfan_get_checkout_data, 300);
1245                    }
1246                }
1247            });
1248        }
1249        /**
1250         * Safety net for inputs whose `change` never fires (scripted fills, autofill).
1251         *
1252         * This previously called bwfan_captureCheckoutField() from an arrow function with no
1253         * element context, so `this` was BWFAN_Public and the call returned at its first guard —
1254         * it captured nothing. Read the field off the blurred element, then capture.
1255         */
1256        BWFAN_Public.checkout_form.on('blur focusout', '.input-text', function () {
1257            BWFAN_Public.bwfan_captureCheckoutField.call(this);
1258        });
1259        // Reset the 'triggered' flag when the input element gets focused again
1260        BWFAN_Public.checkout_form.on('focus', '.input-text', function () {
1261            $(this).removeData('triggered');
1262        });
1263
1264        var interval = null;
1265        var globalSel = '#billing_email,.input-text,.input-checkbox, select';
1266        var globalEvents = 'blur change';
1267        if (BWFAN_Public.is_block_checkout) {
1268            globalSel += ', #email, #contact_email, [name="contact_email"], [id^
1268="billing-"], [id^="shipping-"], textarea[id*="order-notes"]';
1269            /** React-controlled inputs fire 'input' reliably; blur often missed via delegation. */
1270            globalEvents = 'blur change input';
1271        }
1272        $(document).on(globalEvents, globalSel, function () {
1273            var field_name = $(this).attr('name') || $(this).attr('id');
1274            if (!field_name) {
1275                return;
1276            }
1277            field_name = BWFAN_Public.bwfan_normalize_field_name(field_name);
1278
1279            /**
1280             * Block checkout: billing_email isn't in checkout_fields but must still
1281             * trigger the POST, and React-controlled inputs can swallow discrete
1282             * change events — skip the per-field dedupe and go straight to the
1283             * debounced POST.
1284             */
1285            if (BWFAN_Public.is_block_checkout) {
1286                if (BWFAN_Public.checkout_fields.indexOf(field_name) === -1 && field_name !== 'billing_email') {
1287                    return;
1288                }
1289                if (interval !== null) clearTimeout(interval);
1290                interval = setTimeout(BWFAN_Public.bwfan_get_checkout_data, 300);
1291                return;
1292            }
1293
1294            var field_value = $(this).val();
1295
1296            /** for checking checkbox fields **/
1297            if ($(this).attr('type') == 'checkbox') {
1298                field_value = $(this).prop('checked') ? 1 : 0;
1299            }
1300
1301            if (BWFAN_Public.checkout_fields.indexOf(field_name) === -1) {
1302                return;
1303            }
1304
1305            /**
1306             * Dedupe against the last POSTed values, not checkout_fields_data:
1307             * bwfan_captureCheckoutField is delegated on the form, so it runs
1308             * earlier in the same event's bubble path and has already synced
1309             * the live cache — a live-cache comparison always reads "unchanged"
1310             * and the POST would never fire (e.g. first/last name edits).
1311             */
1312            if (BWFAN_Public.last_posted_fields_data[field_name] === field_value) {
1313                return;
1314            }
1315
1316            if (interval !== null) {
1317                clearTimeout(interval);
1318            }
1319            interval = setTimeout(BWFAN_Public.bwfan_get_checkout_data, 100);
1320        });
1321
1322        if (typeof bwfan_unsubscribe_preference !== 'undefined' && 'one_click' === bwfan_unsubscribe_preference) {
1323            var urlParams = new URLSearchParams(window.location.search);
1324            var recipient = $('#bwfan_unsubscribe_recipient');
1325            recipient = (0 === recipient.length) ? urlParams.get('subscriber_recipient') : recipient.html();
1326            var uid = $('#bwfan_form_uid_id');
1327            uid = (0 === uid.length) ? urlParams.get('bwfan_form_uid_id') : uid.val();
1328            if ((uid !== '' && uid !== null) || (recipient !== '' && recipient !== null)) {
1329                $('#bwfan_unsubscribe').click();
1330            }
1331
1332            if (uid !== '' && uid !== null) {
1333                var cookieExpireDate = new Date();
1334                cookieExpireDate.setFullYear(cookieExpireDate.getFullYear() + 1);
1335                document.cookie = '_fk_contact_uid=' + uid + '; expires=' + cookieExpireDate.toUTCString() + ';';
1336            }
1337        }
1338    });
1339    var updateCartTimeout = null;
1340    $(document).on('updated_checkout', function () {
1341        if (updateCartTimeout !== null) {
1342            clearTimeout(updateCartTimeout);
1343        }
1344        updateCartTimeout = setTimeout(function (){
1345            var email = $('#billing_email').val();
1346            if ((!email || email === '') && BWFAN_Public && BWFAN_Public.is_block_checkout) {
1347                email = $('#email').val();
1348            }
1349            if (email && email !== '') {
1350                BWFAN_Public.bwfan_process_email(email);
1351            }
1352        }, 500);
1353    });
1354
1355    /**
1356     * WC Blocks fires its own store_api_checkout / wc-blocks_checkout_render events on every Store API
1357     * push. Re-fire process_email on a debounce so the cart row gets touched on cart/shipping changes.
1358     */
1359    $(document).on('wc-blocks_checkout_render wc-blocks-checkout-update', function () {
1360        if (!BWFAN_Public || !BWFAN_Public.is_block_checkout) return;
1361        var email = $('#email').val();
1362        if (email && email !== '') {
1363            BWFAN_Public.bwfan_process_email(email);
1364        }
1365    });
1366
1367    /**
1368     * Restore the previously-selected payment method on the block checkout after recovery.
1369     *
1370     * Modern WC Blocks keeps the active payment method inside the wc/store/payment data store.
1371     * React-controlled radios ignore native DOM changes, so we must drive the store directly.
1372     * We subscribe to the store so we detect when payment methods have initialized and when
1373     * our target becomes active, then stop.
1374     */
1375    (function () {
1376        if (typeof bwfanParamspublic === 'undefined' || parseInt(bwfanParamspublic._is_block_based_checkout) <= 0) return;
1377        var slug = bwfanParamspublic.bwfan_restored_payment_method;
1378        if (!slug) return;
1379
1380        var applied = false;
1381        var attempts = 0;
1382        var MAX_ATTEMPTS = 40;
1383
1384        var setNativeValue = function (el, value) {
1385            var setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'checked').set;
1386            setter.call(el, value);
1387        };
1388
1389        var tryDataStore = function () {
1390            if (!window.wp || !wp.data) return false;
1391            try {
1392                var paymentStore = wp.data.dispatch('wc/store/payment');
1393                var paymentSelect = wp.data.select('wc/store/payment');
1394                if (!paymentStore || !paymentSelect) return false;
1395
1396                if (typeof paymentSelect.paymentMethodsInitialized === 'function' && !paymentSelect.paymentMethodsInitialized()) {
1397                    return false;
1398                }
1399
1400                var available = paymentSelect.getAvailablePaymentMethods ? paymentSelect.getAvailablePaymentMethods() : {};
1401                if (!available || !Object.prototype.hasOwnProperty.call(available, slug)) {
1402                    return false;
1403                }
1404
1405                var active = paymentSelect.getActivePaymentMethod ? paymentSelect.getActivePaymentMethod() : '';
1406                if (active === slug) {
1407                    applied = true;
1408                    return true;
1409                }
1410
1411                if (typeof paymentStore.__internalSetActivePaymentMethod === 'function') {
1412                    paymentStore.__internalSetActivePaymentMethod(slug);
1413                } else if (typeof paymentStore.setActivePaymentMethod === 'function') {
1414                    paymentStore.setActivePaymentMethod(slug);
1415                }
1416                return true; // dispatched; let subscriber confirm on next tick
1417            } catch (e) {
1418                return false;
1419            }
1420        };
1421
1422        var tryDomFallback = function () {
1423            var $radio = $('.wp-block-woocommerce-checkout-payment-block input[type=radio][value="' + slug + '"], .wc-block-components-payment-method input[type=radio][value="' + slug + '"]').first();
1424            if ($radio.length && !$radio.is(':checked')) {
1425                setNativeValue($radio[0], true);
1426                $radio[0].dispatchEvent(new Event('change', { bubbles: true }));
1427            }
1428        };
1429
1430        var tick = function () {
1431            if (applied || attempts++ > MAX_ATTEMPTS) {
1432                if (unsub) unsub();
1433                if (poll) clearInterval(poll);
1434                return;
1435            }
1436            var storeHandled = tryDataStore();
1437            if (!storeHandled) {
1438                tryDomFallback();
1439            }
1440        };
1441
1442        var unsub = null;
1443        if (window.wp && wp.data && typeof wp.data.subscribe === 'function') {
1444            unsub = wp.data.subscribe(function () {
1445                if (applied) {
1446                    if (unsub) unsub();
1447                    return;
1448                }
1449                try {
1450                    var paymentSelect = wp.data.select('wc/store/payment');
1451                    if (paymentSelect && typeof paymentSelect.getActivePaymentMethod === 'function') {
1452                        if (paymentSelect.getActivePaymentMethod() === slug) {
1453                            applied = true;
1454                            if (unsub) unsub();
1455                            if (poll) clearInterval(poll);
1456                        }
1457                    }
1458                } catch (e) {}
1459            }, 'wc/store/payment');
1460        }
1461
1462        $(window).on('load', function () { setTimeout(tick, 300); });
1463        var poll = setInterval(tick, 350);
1464    })();
1465    $("#bwfan-tyb-save-btn").on('click', function () {
1466        var $this = $(this);
1467        var $parent = $this.parents(".bwfan-tyb-wrap");
1468        var res_msg_selector = $parent.find('.bwfan-tyb-msg');
1469
1470        // Get values from the dropdown fields if they exist
1471        var yearSelect = $parent.find('select[name="bwfan_birthday_date_yy"]');
1472        var monthSelect = $parent.find('select[name="bwfan_birthday_date_mm"]');
1473        var daySelect = $parent.find('select[name="bwfan_birthday_date_dd"]');
1474
1475        // Check if the dropdowns exist
1476        var dropdownsExist = yearSelect.length && monthSelect.length && daySelect.length;
1477        var year = '', month = '', day = '';
1478        // If dropdowns exist, validate their values
1479        if (dropdownsExist) {
1480            year = yearSelect.val();
1481            month = monthSelect.val();
1482            day = daySelect.val();
1483        }
1484
1485        $this.addClass('bwfan-btn-spin');
1486        $.ajax({
1487            type: "post",
1488            dataType: "json",
1489            url: bwfanParamspublic.ajax_url,
1490            data: {
1491                "order_id": $parent.find('#bwfan-order-id').val(),
1492                "birthday": $parent.find('#bwfan-tyb-field').val(),
1493                "bwfan_birthday_date_yy": year,
1494                "bwfan_birthday_date_mm": month,
1495                "bwfan_birthday_date_dd": day,
1496                "cid": $parent.find('#bwfan-cid').val(),
1497                "nonce": $parent.find('#bwfan-tyb-nonce').val(),
1498                "action": "save_birthday_on_thankyou"
1499            },
1500            success: function (response) {
1501                $this.removeClass('bwfan-btn-spin');
1502
1503                res_msg_selector.fadeIn().html(response.msg);
1504
1505                if (2 === response.status) {
1506                    res_msg_selector.addClass('bwfan-tyb-error');
1507                } else {
1508                    res_msg_selector.removeClass('bwfan-tyb-error');
1509                }
1510
1511                setTimeout(function () {
1512                    res_msg_selector.fadeOut("slow");
1513                }, 2500);
1514            }
1515        });
1516    });
1517    $(document).on('focus', '#bwfan_birthday_date', function () {
1518        this.type = this.getAttribute("type") !== "date" ? 'date' : this.getAttribute("type");
1519        setTimeout(() => {
1520            this.showPicker();
1521        }, 500);
1522    });
1523
1524    // If unsubscribe form exists, don't run one-click unsubscribe
1525    if (0 !== $('form#bwfan_unsubscribe_fields').length) {
1526        return;
1527    }
1528
1529    // If manage profile form exists, don't run one-click unsubscribe (this is NOT the unsubscribe page)
1530    if (0 !== $('#bwfan_manage_profile_update').length || 0 !== $('#bwfan_manage_profile_fields').length) {
1531        return;
1532    }
1533
1534    let urlParams = new URLSearchParams(window.location.search);
1535    let automation_id = urlParams.get('automation_id');
1536    let broadcast_id = urlParams.get('broadcast_id');
1537    let form_feed_id = urlParams.get('form_feed_id');
1538    let sid = urlParams.get('sid');
1539    let uid = urlParams.get('uid');
1540
1541    if (uid === null || uid === '') {
1542        return;
1543    }
1544
1545    $.ajax({
1546        method: 'post',
1547        url: bwfanParamspublic.ajax_url,
1548        datatype: "JSON",
1549        data: {
1550            action: 'bwfan_unsubscribe_user',
1551            uid: uid,
1552            automation_id: automation_id ? automation_id : 0,
1553            broadcast_id: broadcast_id ? broadcast_id : 0,
1554            form_feed_id: form_feed_id ? form_feed_id : 0,
1555            sid: sid ? sid : 0,
1556            _nonce: bwfanParamspublic.unsubscribe_nonce,
1557            one_click: 1,
1558        },
1559        success: function (response) {
1560
1561        }
1562    });
1563})(jQuery);
1564
1565/* Deserialize the form object */
1566function bwfanac_deserialize_obj(query) {
1567    var setValue = function (root, path, value) {
1568        if (path.length > 1) {
1569            var dir = path.shift();
1570            if (typeof root[dir] == 'undefined') {
1571                root[dir] = path[0] === '' ? [] : {};
1572            }
1573
1574            arguments.callee(root[dir], path, value);
1575        } else {
1576            if (root instanceof Array) {
1577                root.push(value);
1578            } else {
1579                root[path] = value;
1580            }
1581        }
1582    };
1583
1584    var nvp = query.split('&');
1585    var data = {};
1586
1587    for (var i = 0; i < nvp.length; i++) {
1588        var pair = nvp[i].split('=');
1589        var name = pair[0];
1590        var value = pair[1];
1591
1592        try {
1593            name = decodeURIComponent(pair[0]);
1594            value = decodeURIComponent(pair[1]);
1595        } catch (e) {
1596            console.log("Error occurred while decoding (" + nvp[i] + "). Error is: " + e.message);
1597        }
1598
1599        var path = name.match(/(^[^\[]+)(\[.*\]$)?/);
1600        var first = path[1];
1601        if (path[2]) {
1602            //case of 'array[level1]' || 'array[level1][level2]'
1603            path = path[2].match(/(?=\[(.*)\]$)/)[1].split('][')
1604        } else {
1605            //case of 'name'
1606            path = [];
1607        }
1608        path.unshift(first);
1609
1610        setValue(data, path, value);
1611    }
1612
1613    return data;
1614}

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.