https://www.backsplash.com/wp-content/plugins/retainful/assets/store/js/retainful-store.js?ver=1.0.9
1if (typeof (rtfl_jquery) === 'undefined') { 2 rtfl_jquery = jQuery.noConflict(); 3} 4 5rtfl = window.rtfl || {}; 6 7(function (rtfl) { 8 9 let Url = retainful_localize_data?.ajax_url ?? ''; 10 /** 11 * Validate email address 12 * @param {string} value - Email address to validate 13 * @returns {boolean} - True if valid, false otherwise 14 */ 15 function validateEmail(value) { 16 var valid = true; 17 if (!value || value.trim() === '') { 18 return false; 19 } 20 if (value.indexOf('@') === -1) { 21 valid = false; 22 } else { 23 var parts = value.split('@'); 24 if (parts.length !== 2 || !parts[0] || !parts[1]) { 25 valid = false; 26 } else { 27 var domain = parts[1]; 28 if (domain.indexOf('.') === -1) { 29 valid = false; 30 } else { 31 var domainParts = domain.split('.'); 32 var ext = domainParts[domainParts.length - 1]; 33 if (!ext || ext.length > 14 || ext.length < 2) { 34 valid = false; 35 } 36 } 37 } 38 } 39 return valid; 40 } 41 42 /** 43 * Validate phone number 44 * @param {string} value - Phone number to validate 45 * @returns {boolean} - True if valid, false otherwise 46 */ 47 function validatePhone(value) { 48 if (!value || value.trim() === '') { 49 return false; 50 } 51 // Remove common phone number characters for validation 52 var cleaned = value.replace(/[\s\-\(\)\+]/g, ''); 53 // Check if it contains only digits and has reasonable length (at least 7 digits, max 15) 54 return /^\d+$/.test(cleaned) && cleaned.length >= 7 && cleaned.length <= 15; 55 } 56 57 /** 58 * Update checkout and track user data 59 * @param {string} email - User email 60 * @param {string} phone - User phone 61 * @param {object} guestData - Guest data object 62 */ 63 function updateCheckout(email, phone, guestData) { 64 if (!Url) { 65 return; 66 } 67 68 // Validate email if provided 69 if (email && email.trim() !== '' && !validateEmail(email)) { 70 // Invalid email, don't send request 71 return; 72 } 73 74 if ( email && validateEmail( email ) ) { 75 var previous = sessionStorage.getItem( 'rtfl_customer_email' ); 76 sessionStorage.setItem( 'rtfl_customer_email', email ); 77 // Notify product-view tracker that a customer was just identified 78 if ( previous !== email ) { 79 rtfl_jquery( document ).trigger( 'rtfl:customer_identified', [ email ] ); 80 } 81 } 82 83 // Validate phone if provided 84 if (phone && phone.trim() !== '' && !validatePhone(phone)) { 85 // Invalid phone, don't send request 86 return; 87 } 88 89 // Add nonce to the request data 90 if (typeof retainful_localize_data !== 'undefined' && retainful_localize_data.nonce) { 91 guestData.nonce = retainful_localize_data.nonce; 92 } 93 94 rtfl_jquery.ajax({ 95 url: Url, 96 type: "POST", 97 dataType: 'json', 98 data: guestData, 99 success: function(response) { 100 // Cart tracking updated successfully 101 if (response && response.success) { 102 // Optionally trigger cart sync after user data is updated 103 } 104 }, 105 error: function(xhr, status, error) { 106 // Handle error silently or log if needed 107 console.error('Retainful: Failed to track user data', error); 108 } 109 }); 110 } 111 112 // Track cart when checkout email field updates (Traditional WooCommerce Checkout) 113 // Support both old selector (rnoc_allow_gdpr) and new selector (rtfl_allow_gdpr) for backward compatibility 114 rtfl_jquery(document).on('change', 'input#billing_email,input#billing_first_name,input#billing_last_name,input#billing_phone,input#rtfl_allow_gdpr,input#rnoc_allow_gdpr,input#rtfl_sms_consent', function () { 115 var rnoc_phone = rtfl_jquery("#billing_phone").val(); 116 var rnoc_email = rtfl_jquery("#billing_email").val(); 117 var ship_to_bill = rtfl_jquery("#ship-to-different-address-checkbox:checked").length; 118 119 // Get marketing consent checkbox value (support both old and new selectors) 120 var marketing_consent_checkbox = rtfl_jquery('input#rtfl_allow_gdpr'); 121 if (marketing_consent_checkbox.length === 0) { 122 marketing_consent_checkbox = rtfl_jquery('input#rnoc_allow_gdpr'); 123 } 124 var allow_gdpr = marketing_consent_checkbox.is(':checked'); 125 126 // Get SMS consent checkbox value 127 var sms_consent_checkbox = rtfl_jquery('input#rtfl_sms_consent'); 128 var sms_consent = sms_consent_checkbox.is(':checked'); 129 var guest_data = { 130 billing_first_name: rtfl_jquery('#billing_first_name').val(), 131 billing_last_name: rtfl_jquery('#billing_last_name').val(), 132 billing_company: rtfl_jquery('#billing_company').val(), 133 billing_address_1: rtfl_jquery('#billing_address_1').val(), 134 billing_address_2: rtfl_jquery('#billing_address_2').val(), 135 billing_city: rtfl_jquery('#billing_city').val(), 136 billing_state: rtfl_jquery('#billing_state').val(), 137 billing_postcode: rtfl_jquery('#billing_postcode').val(), 138 billing_country: rtfl_jquery('#billing_country').val(), 139 billing_phone: rtfl_jquery('#billing_phone').val() || rtfl_jquery('input[type="tel"]').val(), 140 billing_email: rtfl_jquery('#billing_email').val(), 141 ship_to_billing: ship_to_bill, 142 order_notes: rtfl_jquery('#order_comments').val(),
143 shipping_first_name: rtfl_jquery('#shipping_first_name').val(), 144 shipping_last_name: rtfl_jquery('#shipping_last_name').val(), 145 shipping_company: rtfl_jquery('#shipping_company').val(), 146 shipping_address_1: rtfl_jquery('#shipping_address_1').val(), 147 shipping_address_2: rtfl_jquery('#shipping_address_2').val(), 148 shipping_city: rtfl_jquery('#shipping_city').val(), 149 shipping_state: rtfl_jquery('#shipping_state').val(), 150 shipping_postcode: rtfl_jquery('#shipping_postcode').val(), 151 shipping_country: rtfl_jquery('#shipping_country').val(), 152 allow_gdpr: allow_gdpr, 153 sms_consent: sms_consent, 154 cart_token: localStorage.getItem('retainful_ac_cart_token'), 155 action: 'rnoc_track_user_data' 156 }; 157 158 updateCheckout(rnoc_email, rnoc_phone, guest_data); 159 }); 160 161 // Track cart when checkout email field updates (WooCommerce Blocks Checkout) 162 // Block Checkout uses name attributes with namespace format: rtfl--allow_gdpr or data attributes 163 // We need to listen for multiple possible selectors to catch all cases 164 rtfl_jquery(document).on('change', '.wp-block-woocommerce-checkout input[type="email"],.wp-block-woocommerce-checkout input[type="tel"],.wp-block-woocommerce-checkout input[name*="rtfl"],.wp-block-woocommerce-checkout input[name*="rnoc"],.wp-block-woocommerce-checkout input[id*="rtfl"],.wp-block-woocommerce-checkout input[id*="rnoc"]', function () { 165 166 var rnoc_email = rtfl_jquery(".wp-block-woocommerce-checkout input[type='email']").first().val(); 167 var rnoc_phone = rtfl_jquery(".wp-block-woocommerce-checkout input[type='tel']").first().val(); 168 169 // Get marketing consent checkbox value 170 // Block Checkout converts rtfl/allow_gdpr to name="rtfl--allow_gdpr" or similar format 171 var marketing_consent_checkbox = 172 rtfl_jquery('.wp-block-woocommerce-checkout #contact-rtfl-allow_gdpr').length 173 ? rtfl_jquery('.wp-block-woocommerce-checkout #contact-rtfl-allow_gdpr') 174 : rtfl_jquery('.wp-block-woocommerce-checkout #order-rtfl-allow_gdpr'); 175 176 var allow_gdpr = marketing_consent_checkbox.is(':checked'); 177 178 // Get SMS consent checkbox value 179 // Block Checkout converts rtfl/sms_consent to name="rtfl--sms_consent" or similar format 180 var sms_consent_checkbox = 181 rtfl_jquery('.wp-block-woocommerce-checkout #billing-rtfl-sms_consent').length 182 ? rtfl_jquery('.wp-block-woocommerce-checkout #billing-rtfl-sms_consent') 183 : rtfl_jquery('.wp-block-woocommerce-checkout #order-rtfl-sms_consent'); 184 185 var sms_consent = sms_consent_checkbox.is(':checked'); 186 187 188 var guest_data = { 189 billing_first_name: rtfl_jquery('.wp-block-woocommerce-checkout #billing-first_name').first().val() || '', 190 billing_last_name: rtfl_jquery('.wp-block-woocommerce-checkout #billing-last_name').first().val() || '', 191 billing_address_1: rtfl_jquery('.wp-block-woocommerce-checkout #billing-address_1').first().val() || '', 192 billing_address_2: rtfl_jquery('.wp-block-woocommerce-checkout #billing-address_2').first().val() || '', 193 billing_city: rtfl_jquery('.wp-block-woocommerce-checkout #billing-city').first().val() || '', 194 billing_postcode: rtfl_jquery('.wp-block-woocommerce-checkout #billing-postcode').first().val() || '', 195 billing_phone: rnoc_phone || '', 196 billing_email: rnoc_email || '', 197 allow_gdpr: allow_gdpr, 198 sms_consent: sms_consent, 199 ship_to_billing: 1, 200 cart_token: localStorage.getItem('retainful_ac_cart_token'), 201 action: 'rnoc_track_user_data' 202 }; 203 204 updateCheckout(rnoc_email, rnoc_phone, guest_data); 205 }); 206 207 rtfl_jquery(document).on("retainful-form-submitted", function (e, email) { 208 if (e.email === undefined) { 209 return; 210 } 211 var guest_data = { 212 action: 'rnoc_track_user_data', 213 billing_email: e.email 214 }; 215 updateCheckout(e.email, '', guest_data); 216 }); 217 218 219 rtfl_jquery(document).ready(function(){ 220 maybeTrack(); 221 // This runs when a product page is viewed 222 /*if (typeof retainful_localize_data !== 'undefined' && 223 retainful_localize_data.viewed_product && 224 retainful_localize_data.viewed_product.product_id) { 225 226 var productData = retainful_localize_data.viewed_product; 227 var productId = productData.product_id; 228 var quantity = productData.quantity || 0; 229 230 window.wc_product = { 231 id: productId, 232 quantity: quantity 233 }; 234 235 236 }*/ 237 }); 238 var trackingConfig = (typeof retainful_localize_data !== 'undefined') 239 ? retainful_localize_data.viewed_product_tracking 240 : null; 241 242 if ( !trackingConfig || !trackingConfig.enabled ) return; 243 244 var viewedProduct = (typeof retainful_localize_data !== 'undefined') 245 ? retainful_localize_data.viewed_product 246 : null; 247 248 if ( !viewedProduct || !viewedProduct.product_id ) return; 249 250 var productId = parseInt( viewedProduct.product_id, 10 ); 251 var dedupMinutes = parseInt( trackingConfig.dedup_window || 30, 10 ); 252 253 /** 254 * Get identified email: PHP-injected (logged in / WC session) OR 255 * stored by the checkout field tracker in sessionStorage. 256 */ 257 function getIdentifiedEmail() { 258 // PHP-injected email (logged-in user or existing WC session) 259 if ( trackingConfig.customer_email && trackingConfig.customer_email !== '' ) { 260 return trackingConfig.customer_email; 261 } 262 // Written by the checkout field onChange handler when guest types email 263 return sessionStorage.getItem( 'rtfl_customer_email' ) || ''; 264 } 265 266 function isProductTrackEnabled() { 267 return trackingConfig && trackingConfig.enabled; 268 } 269 270 /** 271 * Check if this product view was already sent within the dedup window. 272 * Key: rtfl_pv_{email_hash}_{product_id} 273 * Value: timestamp of last send 274 */ 275 function isDuplicate( email, productId ) { 276 var key = 'rtfl_pv_' + hashString( email ) + '_' + productId; 277 var last = sessionStorage.getItem( key ); 278 if ( !last ) return false;
279 var elapsed = ( Date.now() - parseInt( last, 10 ) ) / 1000 / 60; // minutes 280 return elapsed < dedupMinutes; 281 } 282 283 function markAsSent( email, productId ) { 284 var key = 'rtfl_pv_' + hashString( email ) + '_' + productId; 285 sessionStorage.setItem( key, Date.now().toString() ); 286 } 287 288 /** Tiny non-cryptographic hash â just enough for a sessionStorage key */ 289 function hashString( str ) { 290 var h = 0; 291 for ( var i = 0; i < str.length; i++ ) { 292 h = ( Math.imul( 31, h ) + str.charCodeAt( i ) ) | 0; 293 } 294 return Math.abs( h ).toString( 36 ); 295 } 296 297 function sendViewedProductEvent( email ) { 298 if ( !Url || !email ) return; 299 var isEnabled = isProductTrackEnabled(); 300 if( !isEnabled ) return; 301 rtfl_jquery.ajax({ 302 url: Url, 303 type: 'POST', 304 dataType: 'json', 305 data: { 306 action: 'rtfl_track_product_viewed', 307 nonce: retainful_localize_data.nonce, 308 product_id: productId, 309 email: email, 310 }, 311 success: function ( response ) { 312 if ( response && response.success ) { 313 markAsSent( email, productId ); 314 } 315 }, 316 error: function () { 317 // Silent â non-critical tracking event 318 } 319 }); 320 } 321 322 /** 323 * Main entry: fire once on page load if customer is identified. 324 * If not yet identified (anonymous visitor), listen for the checkout 325 * field tracker to capture the email and fire then. 326 */ 327 function maybeTrack() { 328 var isEnabled = isProductTrackEnabled(); 329 if( !isEnabled ) return; 330 var email = getIdentifiedEmail(); 331 if ( email && !isDuplicate( email, productId ) ) { 332 if(typeof productId == 'undefined') return; 333 sendViewedProductEvent( email ); 334 return; 335 } 336 337 // Anonymous visitor â wait for email identification from checkout tracker. 338 // The checkout onChange handler stores email in sessionStorage as 339 // 'rtfl_customer_email'; we listen for a custom event it dispatches. 340 /*rtfl_jquery( document ).one( 'rtfl:customer_identified', function ( e, identifiedEmail ) { 341 if ( identifiedEmail && !isDuplicate( identifiedEmail, productId ) ) { 342 sendViewedProductEvent( identifiedEmail ); 343 } 344 });*/ 345 } 346})(rtfl_jquery);
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.