1( function ( wc_order_attribution ) { 2 'use strict'; 3 // Cache params reference for shorter reusability. 4 const params = wc_order_attribution.params; 5 6 // Helper functions. 7 const $ = document.querySelector.bind( document ); 8 const propertyAccessor = ( obj, path ) => path.split( '.' ).reduce( ( acc, part ) => acc && acc[ part ], obj ); 9 const returnNull = () => null; 10 const stringifyFalsyInputValue = ( value ) => value === null || value === undefined ? '' : value; 11 12 // Hardcode Checkout store key (`wc.wcBlocksData.CHECKOUT_STORE_KEY`), as we no longer have `wc-blocks-checkout` as a dependency. 13 const CHECKOUT_STORE_KEY = 'wc/store/checkout'; 14 15 /** 16 * Get the order attribution data. 17 * 18 * Returns object full of `null`s if tracking is disabled or if sourcebuster.js is blocked. 19 * 20 * @returns {Object} Schema compatible object. 21 */ 22 wc_order_attribution.getAttributionData = function() { 23 const accessor = params.allowTracking && isSbjsAvailable() ? propertyAccessor : returnNull; 24 const getter = isSbjsAvailable() ? sbjs.get : {}; 25 const entries = Object.entries( wc_order_attribution.fields ) 26 .map( ( [ key, property ] ) => [ key, accessor( getter, property ) ] ); 27 return Object.fromEntries( entries ); 28 } 29 30 /** 31 * Remove duplicate `<wc-order-attribution-inputs>` elements within each owning form to prevent 32 * sending the same data multiple times. Treat groups without an enclosing form as document-owned. 33 */ 34 function removeDuplicateInputGroups() { 35 const owners = new Set(); 36 37 document.querySelectorAll( 'wc-order-attribution-inputs' ).forEach( ( group ) => { 38 const owner = group.closest( 'form' ) || document; 39 40 if ( owners.has( owner ) ) { 41 group.remove(); 42 } else { 43 owners.add( owner ); 44 } 45 } ); 46 } 47 48 /** 49 * Update `wc_order_attribution` input elements' values. 50 * 51 * @param {Object} values Object containing field values. 52 */ 53 function updateFormValues( values ) { 54 // Remove duplicates before updating to ensure only one set of elements exists. 55 removeDuplicateInputGroups(); 56 // Update `<wc-order-attribution-inputs>` elements if any exist. 57 for( const element of document.querySelectorAll( 'wc-order-attribution-inputs' ) ) { 58 element.values = values; 59 } 60 61 }; 62 63 /** 64 * Update Checkout extension data. 65 * 66 * @param {Object} values Object containing field values. 67 */ 68 function updateCheckoutBlockData( values ) { 69 // Update Checkout block data if available. 70 if ( 71 window.wp && 72 window.wp.data && 73 window.wp.data.dispatch && 74 window.wc && 75 window.wc.wcBlocksData 76 ) { 77 window.wp.data 78 .dispatch( window.wc.wcBlocksData.CHECKOUT_STORE_KEY ) 79 .setExtensionData( 80 'woocommerce/order-attribution', 81 values, 82 true 83 ); 84 } 85 } 86 87 /** 88 * Determine whether sourcebuster.js is available. 89 * 90 * @returns {boolean} Whether sourcebuster.js is available. 91 */ 92 function isSbjsAvailable() { 93 return typeof sbjs !== 'undefined'; 94 } 95 96 /** 97 * Initialize sourcebuster & set data, or clear cookies & data. 98 * 99 * @param {boolean} allow Whether to allow tracking or disable it. 100 */ 101 wc_order_attribution.setOrderTracking = function( allow ) { 102 params.allowTracking = allow; 103 if ( ! allow ) { 104 // Reset cookies, and clear form data. 105 removeTrackingCookies(); 106 } else if ( ! isSbjsAvailable() ) { 107 return; // Do nothing, as sourcebuster.js is not loaded. 108 } else { 109 // If not done yet, initialize sourcebuster.js which populates `sbjs.get` object. 110 sbjs.init( { 111 lifetime: Number( params.lifetime ), 112 session_length: Number( params.session ), 113 base64: Boolean( params.base64 ), 114 timezone_offset: '0', // utc 115 } ); 116 } 117 const values = wc_order_attribution.getAttributionData(); 118 updateFormValues( values ); 119 updateCheckoutBlockData( values ); 120 } 121 122 /** 123 * Remove sourcebuster.js cookies. 124 * To be called whenever tracking is disabled or consent is revoked. 125 */ 126 function removeTrackingCookies() { 127 const domain = window.location.hostname; 128 const sbCookies = [ 129 'sbjs_current', 130 'sbjs_current_add', 131 'sbjs_first', 132 'sbjs_first_add', 133 'sbjs_session', 134 'sbjs_udata', 135 'sbjs_migrations', 136 'sbjs_promo' 137 ]; 138
139 // Remove cookies 140 sbCookies.forEach( ( name ) => { 141 document.cookie = `${name}=; path=/; max-age=-999; domain=.${domain};`; 142 } ); 143 } 144 145 // Run init. 146 wc_order_attribution.setOrderTracking( params.allowTracking ); 147 148 // Work around the lack of explicit script dependency for the checkout block. 149 // Conditionally, wait for and use 'wp-data' & 'wc-blocks-checkout. 150 151 // Wait for (async) block checkout initialization and set source values once loaded. 152 function eventuallyInitializeCheckoutBlock() { 153 if ( 154 window.wp && window.wp.data && typeof window.wp.data.subscribe === 'function' 155 ) { 156 // Update checkout block data once more if the checkout store was loaded after this script. 157 const unsubscribe = window.wp.data.subscribe( function () { 158 unsubscribe(); 159 updateCheckoutBlockData( wc_order_attribution.getAttributionData() ); 160 }, CHECKOUT_STORE_KEY ); 161 } 162 }; 163 // Wait for DOMContentLoaded to make sure wp.data is in place, if applicable for the page. 164 if (document.readyState === "loading") { 165 document.addEventListener("DOMContentLoaded", eventuallyInitializeCheckoutBlock); 166 } else { 167 eventuallyInitializeCheckoutBlock(); 168 } 169 170 /** 171 * Define an element to contribute order attribute values to the enclosing form. 172 * To be used with the classic checkout. 173 */ 174 window.customElements.define( 'wc-order-attribution-inputs', class extends HTMLElement { 175 // Our bundler version does not support private class members, so we use a convention of `_` prefix. 176 // #values 177 // #fieldNames 178 constructor(){ 179 super(); 180 // Cache fieldNames available at the construction time, to avoid malformed behavior if they change in runtime. 181 this._fieldNames = Object.keys( wc_order_attribution.fields ); 182 // Allow values to be lazily set before CE upgrade. 183 if ( this.hasOwnProperty( '_values' ) ) { 184 let values = this.values; 185 // Restore the setter. 186 delete this.values; 187 this.values = values || {}; 188 } 189 } 190 /** 191 * Stamp input elements to the element's light DOM. 192 * 193 * We could use `.elementInternals.setFromValue` and avoid sprouting `<input>` elements, 194 * but it's not yet supported in Safari. 195 */ 196 connectedCallback() { 197 this.innerHTML = ''; 198 const inputs = new DocumentFragment(); 199 for( const fieldName of this._fieldNames ) { 200 const input = document.createElement( 'input' ); 201 input.type = 'hidden'; 202 input.name = `${params.prefix}${fieldName}`; 203 input.value = stringifyFalsyInputValue( ( this.values && this.values[ fieldName ] ) || '' ); 204 inputs.appendChild( input ); 205 } 206 this.appendChild( inputs ); 207 } 208 209 /** 210 * Update form values. 211 */ 212 set values( values ) { 213 this._values = values; 214 if( this.isConnected ) { 215 for( const fieldName of this._fieldNames ) { 216 const input = this.querySelector( `input[name="${params.prefix}${fieldName}"]` ); 217 if( input ) { 218 input.value = stringifyFalsyInputValue( this.values[ fieldName ] ); 219 } else { 220 console.warn( 221 `Field "${fieldName}" not found. ` + 222 `Most likely, the '<wc-order-attribution-inputs>' element was manipulated.` 223 ); 224 } 225 } 226 } 227 } 228 get values() { 229 return this._values; 230 } 231 } ); 232 233 234}( window.wc_order_attribution ) );
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.