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