Last active
December 19, 2025 01:03
-
-
Save Jany-M/ba8bb2b6daedd14d88230ad288c4b00c to your computer and use it in GitHub Desktop.
[WOO] Smart multiple billing address for WooCommerce
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * ============================================================================ | |
| * SMART MULTIPLE BILLING ADDRESSES SOLUTION | |
| * ============================================================================ | |
| * 1. Store alternate addresses separately (never touch default WooCommerce billing) | |
| * 2. Populate checkout fields from alternate address when selected | |
| * 3. Save alternate address to custom storage after order | |
| * 4. Restore original billing data immediately after checkout | |
| * 5. Add and manage the alt address from the my account edit profile screen | |
| * 6. Bidirectional sync (edit profile screen <-> checkout page) | |
| * | |
| * The script also includes 2 custom billing fields (billing_cf and billing_cf2) | |
| * which can either be customized to some other custom field or removed. | |
| * Include in your theme's functions.php | |
| * ============================================================================ | |
| */ | |
| /** | |
| * Debug Mode Constant | |
| * Set to true to enable debug logging (PHP error_log and JavaScript console.log) | |
| * Set to false to disable all debug output | |
| */ | |
| if ( ! defined( 'SHAMBIX_DEBUG_MODE' ) ) { | |
| define( 'SHAMBIX_DEBUG_MODE', false ); // Temporarily enabled for debugging | |
| } | |
| /** | |
| * Debug logging wrapper function | |
| * Only logs if SHAMBIX_DEBUG_MODE is true | |
| */ | |
| function shambix_debug_log( $message ) { | |
| if ( SHAMBIX_DEBUG_MODE ) { | |
| error_log( $message ); | |
| } | |
| } | |
| /** | |
| * Get Text Domain | |
| */ | |
| $text_domain = esc_html($current_theme->get('TextDomain')); | |
| if(!defined('TEXT_DOMAIN')) { | |
| define('TEXT_DOMAIN', $text_domain); | |
| } | |
| /** | |
| * Add alternate billing address dropdown to checkout | |
| */ | |
| add_action( 'woocommerce_before_checkout_billing_form', 'shambix_add_alternate_address_dropdown' ); | |
| function shambix_add_alternate_address_dropdown() { | |
| if ( ! is_user_logged_in() ) { | |
| return; | |
| } | |
| $user_id = get_current_user_id(); | |
| $alternate_address = get_user_meta( $user_id, 'shambix_alt_billing_address', true ); | |
| $has_alternate = ! empty( $alternate_address ) && is_array( $alternate_address ); | |
| $edit_account_url = esc_url( wc_get_page_permalink( 'myaccount' ) . 'edit-account/' ); | |
| // DEBUG: Log what's loaded from database | |
| shambix_debug_log( 'WooMultiAddress DEBUG [CHECKOUT]: Loaded alternate address from DB: ' . print_r( $alternate_address, true ) ); | |
| if ( $has_alternate && isset( $alternate_address['billing_state'] ) ) { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [CHECKOUT]: billing_state value: ' . $alternate_address['billing_state'] ); | |
| } else { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [CHECKOUT]: billing_state NOT found in alternate address' ); | |
| } | |
| ?> | |
| <div class="shambix-alternate-billing-address" style="margin-bottom: 20px; padding: 15px; background: #f9f9f9; border: 1px solid #ddd;"> | |
| <!-- <h3><?php esc_html_e( 'Seleziona indirizzo di fatturazione', TEXT_DOMAIN ); ?></h3> --> | |
| <p class="form-row form-row-wide"> | |
| <label for="shambix_alt_billing_select"><?php esc_html_e( 'Indirizzo di fatturazione', TEXT_DOMAIN ); ?></label> | |
| <select name="shambix_alt_billing_select" id="shambix_alt_billing_select" class="select"> | |
| <option value="" selected><?php esc_html_e( 'Usa indirizzo predefinito', TEXT_DOMAIN ); ?></option> | |
| <?php if ( $has_alternate ) : ?> | |
| <option value="alternate"> | |
| <?php | |
| $name = trim( ( $alternate_address['billing_first_name'] ?? '' ) . ' ' . ( $alternate_address['billing_last_name'] ?? '' ) ); | |
| $addr = $alternate_address['billing_address_1'] ?? ''; | |
| echo esc_html( $name . ( $name && $addr ? ' - ' : '' ) . $addr ); | |
| ?> | |
| </option> | |
| <?php endif; ?> | |
| </select> | |
| </p> | |
| <?php if ( ! $has_alternate ) : ?> | |
| <p class="infotext"> | |
| <?php esc_html_e( 'Vuoi usare un indirizzo di fatturazione diverso?', TEXT_DOMAIN ); ?> | |
| <a href="<?php echo $edit_account_url; ?>" target="_blank"><br><?php esc_html_e( 'Crea un nuovo indirizzo dalla pagina Modifica Account', TEXT_DOMAIN ); ?></a> | |
| </p> | |
| <?php else : ?> | |
| <p class="infotext"> | |
| <?php esc_html_e( 'Vuoi creare o modificare un indirizzo alternativo?', TEXT_DOMAIN ); ?> | |
| <a href="<?php echo $edit_account_url; ?>" target="_blank"><br><?php esc_html_e( 'Vai alla pagina Modifica Account', TEXT_DOMAIN ); ?></a> | |
| </p> | |
| <?php endif; ?> | |
| </div> | |
| <script type="text/javascript"> | |
| jQuery(document).ready(function($) { | |
| // Debug Mode (from PHP constant) | |
| var SHAMBIX_DEBUG_MODE = <?php echo SHAMBIX_DEBUG_MODE ? 'true' : 'false'; ?>; | |
| // Debug logging wrapper function | |
| function shambix_debug_log() { | |
| if ( SHAMBIX_DEBUG_MODE ) { | |
| console.log.apply( console, arguments ); | |
| } | |
| } | |
| // DEBUG: Log alternate address data | |
| var alternateAddressData = <?php echo json_encode( $has_alternate ? $alternate_address : array() ); ?>; | |
| shambix_debug_log('WooMultiAddress DEBUG: Alternate address data loaded (for potential use):', alternateAddressData); | |
| if ( alternateAddressData && alternateAddressData.billing_state ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: billing_state in alternate address data:', alternateAddressData.billing_state); | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: billing_state NOT found in alternate address data or is empty'); | |
| } | |
| // Flag to prevent recursive updates | |
| var isUpdatingFields = false; | |
| var isCheckoutUpdating = false; | |
| // Track if user has manually edited the state field (so we don't overwrite their edits) | |
| var stateFieldManuallyEdited = false; | |
| // Map of billing field IDs to data keys. | |
| var fieldMap = { | |
| 'billing_first_name': 'billing_first_name', | |
| 'billing_last_name': 'billing_last_name', | |
| 'billing_company': 'billing_company', | |
| 'billing_address_1': 'billing_address_1', | |
| 'billing_address_2': 'billing_address_2', | |
| 'billing_city': 'billing_city', | |
| 'billing_state': 'billing_state', | |
| 'billing_postcode': 'billing_postcode', | |
| 'billing_country': 'billing_country', | |
| 'billing_email': 'billing_email', | |
| 'billing_phone': 'billing_phone', | |
| 'billing_cf': 'billing_cf', // this is a custom field, can be removed | |
| 'billing_cf2': 'billing_cf2' // this is a custom field, can be removed | |
| }; | |
| // Snapshot of the original default billing address values when the | |
| // checkout page first loads. Used to restore fields when the user | |
| // switches back from the alternate address to the default one. | |
| var defaultAddressData = {}; | |
| $.each(fieldMap, function(fieldId, key) { | |
| var $field = $('#' + fieldId); | |
| if ( $field.length ) { | |
| defaultAddressData[key] = $field.val(); | |
| } | |
| }); | |
| // Function to toggle billing_email readonly state | |
| function toggleBillingEmailReadonly() { | |
| var selected = $('#shambix_alt_billing_select').val(); | |
| var $billingEmail = $('#billing_email'); | |
| if ( ! selected || selected === '' ) { | |
| // Default address selected - make email readonly | |
| $billingEmail.prop('readonly', true).css('background-color', '#f5f5f5'); | |
| } else if ( selected === 'alternate' ) { | |
| // Alternate address selected - make email editable | |
| $billingEmail.prop('readonly', false).css('background-color', ''); | |
| } | |
| } | |
| // Generic function to populate billing fields from a given address object. | |
| function populateFormFieldsFromData(addressData, silent) { | |
| if ( isUpdatingFields ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Already updating fields, skipping to prevent loop'); | |
| return; | |
| } | |
| if ( ! addressData || Object.keys(addressData).length === 0 ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: No address data available for population'); | |
| return; | |
| } | |
| isUpdatingFields = true; | |
| shambix_debug_log('WooMultiAddress DEBUG: Populating form fields from address data (silent: ' + (silent || false) + ')'); | |
| var populatedFields = []; | |
| // Special handling: Set country first, then state (WooCommerce requirement) | |
| // WooCommerce's country field triggers AJAX to populate states, so we need to handle this carefully | |
| var countryValue = addressData['billing_country']; | |
| var stateValue = addressData['billing_state']; | |
| shambix_debug_log('WooMultiAddress DEBUG: populateFormFieldsFromData - countryValue:', countryValue, 'stateValue:', stateValue); | |
| shambix_debug_log('WooMultiAddress DEBUG: Full addressData object:', addressData); | |
| if ( countryValue ) { | |
| var $countryField = $('#billing_country'); | |
| if ( $countryField.length ) { | |
| var oldCountry = $countryField.val(); | |
| if ( oldCountry !== countryValue ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Setting country from "' + oldCountry + '" to "' + countryValue + '"'); | |
| $countryField.val(countryValue); | |
| populatedFields.push('billing_country: "' + oldCountry + '" -> "' + countryValue + '"'); | |
| // Trigger country change to populate states (WooCommerce requirement) | |
| // This will trigger WooCommerce's AJAX to reload state options | |
| $countryField.trigger('change'); | |
| // Wait for WooCommerce to populate states, then set state (only if country has states) | |
| if ( stateValue ) { | |
| // Use a longer timeout and check if state field is ready and visible | |
| var stateCheckAttempts = 0; | |
| var maxAttempts = 10; | |
| var setStateAfterCountry = function() { | |
| stateCheckAttempts++; | |
| var $stateField = $('#billing_state'); | |
| var $stateWrapper = $stateField.closest('.form-row'); | |
| shambix_debug_log('WooMultiAddress DEBUG: setStateAfterCountry attempt', stateCheckAttempts, '- stateValue:', stateValue, '- field exists:', $stateField.length, '- wrapper visible:', ($stateWrapper.length ? $stateWrapper.is(':visible') : false)); | |
| // Check if state field exists and is visible (not hidden by WooCommerce) | |
| // WooCommerce hides the state field for countries without states | |
| if ( $stateField.length ) { | |
| if ( $stateWrapper.is(':visible') ) { | |
| if ( ! $stateWrapper.hasClass('woocommerce-invalid') ) { | |
| // For select dropdowns, check if options are loaded | |
| if ( $stateField.is('select') ) { | |
| var hasOptions = $stateField.find('option').length > 0; | |
| var optionCount = $stateField.find('option').length; | |
| shambix_debug_log('WooMultiAddress DEBUG: State field is select, hasOptions:', hasOptions, 'optionCount:', optionCount); | |
| if ( ! hasOptions && stateCheckAttempts < maxAttempts ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Options not loaded yet, retrying...'); | |
| setTimeout(setStateAfterCountry, 200); | |
| return; | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State field is NOT a select, it is:', $stateField.prop('tagName')); | |
| } | |
| // Set the state value | |
| var oldState = $stateField.val(); | |
| shambix_debug_log('WooMultiAddress DEBUG: Attempting to set state - oldState:', oldState, 'stateValue:', stateValue); | |
| if ( oldState !== stateValue ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Setting state from "' + oldState + '" to "' + stateValue + '"'); | |
| $stateField.val(stateValue); | |
| // If Select2 is being used, trigger its update | |
| if ( $stateField.hasClass('select2-hidden-accessible') || $stateField.data('select2') ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Field uses Select2, triggering Select2 change'); | |
| $stateField.trigger('change.select2'); | |
| // Also try to update Select2 directly if available | |
| if ( $stateField.data('select2') ) { | |
| $stateField.trigger('select2:select'); | |
| } | |
| } | |
| var newStateAfterSet = $stateField.val(); | |
| shambix_debug_log('WooMultiAddress DEBUG: After setting, field value is now:', newStateAfterSet); | |
| populatedFields.push('billing_state: "' + oldState + '" -> "' + stateValue + '"'); | |
| // Trigger state change (but not if silent mode) | |
| if ( ! silent ) { | |
| $stateField.trigger('change'); | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State already set to correct value, but triggering Select2 update anyway'); | |
| // Even if value is already set, trigger Select2 to update display | |
| if ( $stateField.hasClass('select2-hidden-accessible') || $stateField.data('select2') ) { | |
| $stateField.trigger('change.select2'); | |
| if ( $stateField.data('select2') ) { | |
| $stateField.trigger('select2:select'); | |
| } | |
| } | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State wrapper has woocommerce-invalid class, skipping'); | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State wrapper is not visible'); | |
| } | |
| } else if ( stateCheckAttempts < maxAttempts ) { | |
| // State field not ready yet or country doesn't have states, try again | |
| shambix_debug_log('WooMultiAddress DEBUG: State field not found, retrying (attempt', stateCheckAttempts, 'of', maxAttempts, ')'); | |
| setTimeout(setStateAfterCountry, 200); | |
| } else { | |
| // Max attempts reached - country probably doesn't have states | |
| shambix_debug_log('WooMultiAddress DEBUG: State field not available for country "' + countryValue + '", skipping state'); | |
| } | |
| }; | |
| // Start checking after a short delay | |
| setTimeout(setStateAfterCountry, 400); | |
| } | |
| } else if ( stateValue ) { | |
| // Country already correct, check if state field is visible before setting | |
| var $stateField = $('#billing_state'); | |
| var $stateWrapper = $stateField.closest('.form-row'); | |
| // Only set state if field is visible (country has states) | |
| if ( $stateField.length ) { | |
| if ( $stateWrapper.is(':visible') ) { | |
| if ( ! $stateWrapper.hasClass('woocommerce-invalid') ) { | |
| var oldState = $stateField.val(); | |
| if ( oldState !== stateValue ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Setting state (country unchanged) from "' + oldState + '" to "' + stateValue + '"'); | |
| $stateField.val(stateValue); | |
| // If Select2 is being used, trigger its update | |
| if ( $stateField.hasClass('select2-hidden-accessible') || $stateField.data('select2') ) { | |
| $stateField.trigger('change.select2'); | |
| if ( $stateField.data('select2') ) { | |
| $stateField.trigger('select2:select'); | |
| } | |
| } | |
| populatedFields.push('billing_state: "' + oldState + '" -> "' + stateValue + '"'); | |
| if ( ! silent ) { | |
| $stateField.trigger('change'); | |
| } | |
| } | |
| } | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State field not available for country "' + countryValue + '", skipping state'); | |
| } | |
| } | |
| } | |
| } else if ( stateValue ) { | |
| // No country change, check if state field is visible before setting | |
| var $stateField = $('#billing_state'); | |
| var $stateWrapper = $stateField.closest('.form-row'); | |
| // Only set state if field is visible (country has states) | |
| // WooCommerce hides the state field for countries without states | |
| if ( $stateField.length ) { | |
| if ( $stateWrapper.is(':visible') ) { | |
| if ( ! $stateWrapper.hasClass('woocommerce-invalid') ) { | |
| var oldState = $stateField.val(); | |
| if ( oldState !== stateValue ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Setting state (no country) from "' + oldState + '" to "' + stateValue + '"'); | |
| $stateField.val(stateValue); | |
| // If Select2 is being used, trigger its update | |
| if ( $stateField.hasClass('select2-hidden-accessible') || $stateField.data('select2') ) { | |
| $stateField.trigger('change.select2'); | |
| if ( $stateField.data('select2') ) { | |
| $stateField.trigger('select2:select'); | |
| } | |
| } | |
| populatedFields.push('billing_state: "' + oldState + '" -> "' + stateValue + '"'); | |
| if ( ! silent ) { | |
| $stateField.trigger('change'); | |
| } | |
| } | |
| } | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State field not available, skipping state'); | |
| } | |
| } | |
| // Handle all other fields normally. | |
| // NOTE: When switching addresses, we always want the selected address | |
| // to fully control the field value. If the selected address has an | |
| // empty value for a field, we overwrite any previous value with the | |
| // empty string (instead of falling back to the old/default value). | |
| $.each(fieldMap, function(fieldId, key) { | |
| // Skip country and state - already handled above. | |
| if ( fieldId === 'billing_country' || fieldId === 'billing_state' ) { | |
| return; | |
| } | |
| if ( addressData.hasOwnProperty( key ) ) { | |
| var $field = $('#' + fieldId); | |
| if ( $field.length ) { | |
| var newValue = addressData[key] !== null && addressData[key] !== undefined ? addressData[key] : ''; | |
| var oldValue = $field.val(); | |
| if ( oldValue !== newValue ) { | |
| $field.val( newValue ); | |
| populatedFields.push( fieldId + ': "' + oldValue + '" -> "' + newValue + '"'); | |
| // Only trigger change if not silent (to prevent loops). | |
| if ( ! silent ) { | |
| $field.trigger( 'change' ); | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| shambix_debug_log('WooMultiAddress DEBUG: Populated fields:', populatedFields); | |
| // Reset flag after a short delay | |
| setTimeout(function() { | |
| isUpdatingFields = false; | |
| }, 500); | |
| } | |
| // Convenience wrappers. | |
| function populateFormFieldsWithAlternate(silent) { | |
| populateFormFieldsFromData(alternateAddressData, silent); | |
| } | |
| function populateFormFieldsWithDefault(silent) { | |
| populateFormFieldsFromData(defaultAddressData, silent); | |
| } | |
| // Clear any stale session data on page load if default is selected | |
| var initialValue = $('#shambix_alt_billing_select').val(); | |
| shambix_debug_log('WooMultiAddress DEBUG: Initial dropdown value:', initialValue); | |
| if ( ! initialValue || initialValue === '' ) { | |
| $('#shambix_alt_billing_hidden').val(''); | |
| } else { | |
| $('#shambix_alt_billing_hidden').val(initialValue); | |
| } | |
| // Set initial readonly state | |
| toggleBillingEmailReadonly(); | |
| // When alternate address is selected, populate checkout fields | |
| $('#shambix_alt_billing_select').on('change', function() { | |
| if ( isCheckoutUpdating ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Checkout is updating, ignoring dropdown change to prevent loop'); | |
| return; | |
| } | |
| var selected = $(this).val(); | |
| shambix_debug_log('WooMultiAddress DEBUG: Dropdown changed to:', selected); | |
| // Reset manual edit flag when switching addresses (so we can re-apply saved values) | |
| stateFieldManuallyEdited = false; | |
| shambix_debug_log('WooMultiAddress DEBUG: Reset stateFieldManuallyEdited flag (address switch)'); | |
| // Store in hidden field for form submission | |
| $('#shambix_alt_billing_hidden').val(selected); | |
| // Toggle billing_email readonly state | |
| toggleBillingEmailReadonly(); | |
| if ( selected === 'alternate' ) { | |
| // If alternate is selected, manually populate fields immediately (silent to prevent loops) | |
| shambix_debug_log('WooMultiAddress DEBUG: Alternate address selected - populating fields silently'); | |
| populateFormFieldsWithAlternate(true); // Silent mode - no change events | |
| } else { | |
| // Default address selected - restore original default values captured on page load. | |
| shambix_debug_log('WooMultiAddress DEBUG: Default address selected - restoring original default billing values (silent)'); | |
| populateFormFieldsWithDefault(true); // Silent mode - no change events | |
| } | |
| // Set flag and trigger checkout update | |
| isCheckoutUpdating = true; | |
| $('body').trigger('update_checkout'); | |
| // Reset flag after update completes | |
| setTimeout(function() { | |
| isCheckoutUpdating = false; | |
| }, 1000); | |
| }); | |
| // Track when user manually edits the state field | |
| $(document.body).on('change', '#billing_state', function() { | |
| // Only set flag if we're not currently updating fields programmatically | |
| if ( ! isUpdatingFields ) { | |
| stateFieldManuallyEdited = true; | |
| shambix_debug_log('WooMultiAddress DEBUG: User manually edited state field, setting stateFieldManuallyEdited = true'); | |
| } | |
| }); | |
| // Ensure our hidden field is included in checkout AJAX data | |
| $(document.body).on('checkout_place_order', function() { | |
| // Make sure hidden field value is set before form submission | |
| var selected = $('#shambix_alt_billing_select').val(); | |
| $('#shambix_alt_billing_hidden').val(selected || ''); | |
| }); | |
| // Also toggle when checkout is updated (AJAX). | |
| // IMPORTANT: Do NOT re-populate fields here, otherwise any manual edits | |
| // the user makes (e.g. changing CAP for this specific order) would be | |
| // overwritten after each AJAX refresh. | |
| // EXCEPTION: We need to re-apply the state field if the alternate address | |
| // is selected, because WooCommerce's AJAX may reset it when the country changes. | |
| $(document.body).on('updated_checkout', function() { | |
| shambix_debug_log('WooMultiAddress DEBUG: Checkout updated via AJAX'); | |
| toggleBillingEmailReadonly(); | |
| // If alternate address is selected, re-apply the state field | |
| // (WooCommerce's AJAX may have reset it after country change) | |
| var selected = $('#shambix_alt_billing_select').val(); | |
| shambix_debug_log('WooMultiAddress DEBUG: updated_checkout - selected:', selected, 'alternateAddressData:', alternateAddressData); | |
| if ( selected === 'alternate' && alternateAddressData && alternateAddressData['billing_state'] ) { | |
| var stateValue = alternateAddressData['billing_state']; | |
| var $stateField = $('#billing_state'); | |
| var $stateWrapper = $stateField.closest('.form-row'); | |
| shambix_debug_log('WooMultiAddress DEBUG: updated_checkout - stateValue:', stateValue, 'field exists:', $stateField.length, 'wrapper visible:', ($stateWrapper.length ? $stateWrapper.is(':visible') : false), 'stateFieldManuallyEdited:', stateFieldManuallyEdited); | |
| // Only re-apply state if user hasn't manually edited it | |
| if ( ! stateFieldManuallyEdited ) { | |
| // Only set if field is visible and has options loaded | |
| if ( $stateField.length && $stateWrapper.is(':visible') ) { | |
| if ( $stateField.is('select') ) { | |
| // Check if options are loaded | |
| var hasOptions = $stateField.find('option').length > 0; | |
| var optionCount = $stateField.find('option').length; | |
| shambix_debug_log('WooMultiAddress DEBUG: updated_checkout - State field is select, hasOptions:', hasOptions, 'optionCount:', optionCount); | |
| if ( hasOptions ) { | |
| var currentState = $stateField.val(); | |
| shambix_debug_log('WooMultiAddress DEBUG: updated_checkout - currentState:', currentState, 'stateValue:', stateValue); | |
| if ( currentState !== stateValue ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Re-applying state after AJAX update: "' + currentState + '" -> "' + stateValue + '"'); | |
| $stateField.val(stateValue); | |
| // If Select2 is being used, trigger its update | |
| if ( $stateField.hasClass('select2-hidden-accessible') || $stateField.data('select2') ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Field uses Select2, triggering Select2 change'); | |
| $stateField.trigger('change.select2'); | |
| if ( $stateField.data('select2') ) { | |
| $stateField.trigger('select2:select'); | |
| } | |
| } | |
| var newStateAfterSet = $stateField.val(); | |
| shambix_debug_log('WooMultiAddress DEBUG: After re-applying, field value is now:', newStateAfterSet); | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State already correct, but triggering Select2 update to refresh display'); | |
| // Even if value is already set, trigger Select2 to update display | |
| if ( $stateField.hasClass('select2-hidden-accessible') || $stateField.data('select2') ) { | |
| $stateField.trigger('change.select2'); | |
| if ( $stateField.data('select2') ) { | |
| $stateField.trigger('select2:select'); | |
| } | |
| } | |
| } | |
| } else { | |
| // Options not loaded yet, try again after a short delay | |
| shambix_debug_log('WooMultiAddress DEBUG: Options not loaded yet, will retry in 300ms'); | |
| setTimeout(function() { | |
| // Check flag again before retrying (user might have edited in the meantime) | |
| if ( stateFieldManuallyEdited ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: State field was manually edited, skipping delayed re-application'); | |
| return; | |
| } | |
| var $st = $('#billing_state'); | |
| if ( $st.length && $st.find('option').length > 0 ) { | |
| var curr = $st.val(); | |
| if ( curr !== stateValue ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Re-applying state after delayed AJAX update: "' + curr + '" -> "' + stateValue + '"'); | |
| $st.val(stateValue); | |
| // If Select2 is being used, trigger its update | |
| if ( $st.hasClass('select2-hidden-accessible') || $st.data('select2') ) { | |
| $st.trigger('change.select2'); | |
| if ( $st.data('select2') ) { | |
| $st.trigger('select2:select'); | |
| } | |
| } | |
| var newStateAfterDelayedSet = $st.val(); | |
| shambix_debug_log('WooMultiAddress DEBUG: After delayed re-applying, field value is now:', newStateAfterDelayedSet); | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State field still not ready after delay'); | |
| } | |
| }, 300); | |
| } | |
| } else { | |
| // Text input field | |
| var currentState = $stateField.val(); | |
| shambix_debug_log('WooMultiAddress DEBUG: State field is text input, currentState:', currentState, 'stateValue:', stateValue); | |
| if ( currentState !== stateValue ) { | |
| shambix_debug_log('WooMultiAddress DEBUG: Re-applying state (text field) after AJAX update: "' + currentState + '" -> "' + stateValue + '"'); | |
| $stateField.val(stateValue); | |
| } | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State field not visible or not found, cannot re-apply'); | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: State field was manually edited by user, skipping re-application to preserve user edits'); | |
| } | |
| } else { | |
| shambix_debug_log('WooMultiAddress DEBUG: Not re-applying state - selected:', selected, 'has alternateAddressData:', !!alternateAddressData, 'has billing_state:', !!(alternateAddressData && alternateAddressData['billing_state'])); | |
| } | |
| // Reset checkout updating flag. | |
| isCheckoutUpdating = false; | |
| }); | |
| }); | |
| </script> | |
| <input type="hidden" name="shambix_alt_billing_select" id="shambix_alt_billing_hidden" value="" /> | |
| <?php | |
| } | |
| /** | |
| * Capture alternate address selection during AJAX checkout updates | |
| * This runs when WooCommerce processes the checkout update AJAX request | |
| */ | |
| add_action( 'woocommerce_checkout_update_order_review', 'shambix_capture_alt_billing_selection_ajax', 1, 1 ); | |
| function shambix_capture_alt_billing_selection_ajax( $post_data ) { | |
| if ( ! is_user_logged_in() || ! WC()->session ) { | |
| return; | |
| } | |
| // Parse the post data string | |
| parse_str( $post_data, $posted_data ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG AJAX: POST data received: ' . print_r( $posted_data, true ) ); | |
| if ( isset( $posted_data['shambix_alt_billing_select'] ) ) { | |
| $selected = sanitize_text_field( $posted_data['shambix_alt_billing_select'] ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG AJAX: Found shambix_alt_billing_select in parsed data: ' . $selected ); | |
| if ( $selected === 'alternate' ) { | |
| WC()->session->set( 'shambix_selected_alt_billing', $selected ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG AJAX: Session set to alternate' ); | |
| } else { | |
| WC()->session->__unset( 'shambix_selected_alt_billing' ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG AJAX: Session cleared (default selected)' ); | |
| } | |
| } elseif ( isset( $_POST['shambix_alt_billing_select'] ) ) { | |
| // Also check direct POST (fallback) | |
| $selected = sanitize_text_field( $_POST['shambix_alt_billing_select'] ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG AJAX: Found shambix_alt_billing_select in $_POST: ' . $selected ); | |
| if ( $selected === 'alternate' ) { | |
| WC()->session->set( 'shambix_selected_alt_billing', $selected ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG AJAX: Session set to alternate (from $_POST)' ); | |
| } else { | |
| WC()->session->__unset( 'shambix_selected_alt_billing' ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG AJAX: Session cleared (from $_POST)' ); | |
| } | |
| } else { | |
| shambix_debug_log( 'WooMultiAddress DEBUG AJAX: shambix_alt_billing_select NOT found in POST data' ); | |
| } | |
| } | |
| /** | |
| * Also capture during form submission | |
| */ | |
| add_filter( 'woocommerce_checkout_posted_data', 'shambix_capture_alt_billing_selection', 1, 1 ); | |
| function shambix_capture_alt_billing_selection( $data ) { | |
| if ( ! is_user_logged_in() || ! WC()->session ) { | |
| return $data; | |
| } | |
| if ( isset( $data['shambix_alt_billing_select'] ) ) { | |
| $selected = sanitize_text_field( $data['shambix_alt_billing_select'] ); | |
| if ( $selected === 'alternate' ) { | |
| WC()->session->set( 'shambix_selected_alt_billing', $selected ); | |
| } else { | |
| WC()->session->__unset( 'shambix_selected_alt_billing' ); | |
| } | |
| } | |
| return $data; | |
| } | |
| /** | |
| * Clear stale session data when checkout page loads | |
| */ | |
| add_action( 'woocommerce_checkout_init', 'shambix_clear_stale_alt_billing_session', 1 ); | |
| function shambix_clear_stale_alt_billing_session() { | |
| if ( ! is_user_logged_in() || ! WC()->session ) { | |
| return; | |
| } | |
| // If no POST data and dropdown would show default, clear session | |
| if ( ! isset( $_POST['shambix_alt_billing_select'] ) ) { | |
| // Check if we have stale session data | |
| $stale_selection = WC()->session->get( 'shambix_selected_alt_billing' ); | |
| if ( $stale_selection === 'alternate' ) { | |
| // Clear it - default should be used | |
| WC()->session->__unset( 'shambix_selected_alt_billing' ); | |
| } | |
| } | |
| } | |
| /** | |
| * Populate checkout fields from alternate address when selected | |
| * CRITICAL: When default is selected, explicitly return default WooCommerce billing data | |
| */ | |
| add_filter( 'woocommerce_checkout_get_value', 'shambix_populate_checkout_from_alternate', 10, 2 ); | |
| function shambix_populate_checkout_from_alternate( $value, $input ) { | |
| if ( ! is_user_logged_in() || ! is_checkout() ) { | |
| return $value; | |
| } | |
| // Only process billing fields | |
| if ( strpos( $input, 'billing_' ) !== 0 ) { | |
| return $value; | |
| } | |
| $user_id = get_current_user_id(); | |
| // Check session first (set by woocommerce_checkout_update_order_review during AJAX) | |
| $selected = ''; | |
| $session_value = WC()->session ? WC()->session->get( 'shambix_selected_alt_billing' ) : ''; | |
| if ( $session_value === 'alternate' ) { | |
| $selected = 'alternate'; | |
| shambix_debug_log( 'WooMultiAddress DEBUG POPULATE: Using alternate from session for field: ' . $input ); | |
| } | |
| // Then check POST data (from form submission or AJAX) - this takes precedence | |
| if ( isset( $_POST['shambix_alt_billing_select'] ) ) { | |
| $selected = sanitize_text_field( $_POST['shambix_alt_billing_select'] ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG POPULATE: Found in $_POST for field ' . $input . ': ' . $selected ); | |
| // Store in session for subsequent AJAX updates | |
| if ( WC()->session ) { | |
| if ( $selected === 'alternate' ) { | |
| WC()->session->set( 'shambix_selected_alt_billing', $selected ); | |
| } else { | |
| // Clear session if default is selected | |
| WC()->session->__unset( 'shambix_selected_alt_billing' ); | |
| } | |
| } | |
| } | |
| // CRITICAL: Only use alternate address if explicitly selected | |
| if ( $selected !== 'alternate' ) { | |
| // Clear any stale session data | |
| if ( WC()->session && WC()->session->get( 'shambix_selected_alt_billing' ) ) { | |
| WC()->session->__unset( 'shambix_selected_alt_billing' ); | |
| } | |
| // CRITICAL: Explicitly return default WooCommerce billing data from user meta | |
| // Don't rely on $value which might be stale | |
| $default_value = get_user_meta( $user_id, $input, true ); | |
| // For billing_email, always use user account email for default address | |
| if ( $input === 'billing_email' ) { | |
| $user = get_userdata( $user_id ); | |
| if ( $user ) { | |
| $default_value = $user->user_email; | |
| } | |
| } | |
| return $default_value; | |
| } | |
| // Using alternate address - get from alternate address data | |
| $alternate_address = get_user_meta( $user_id, 'shambix_alt_billing_address', true ); | |
| if ( ! is_array( $alternate_address ) ) { | |
| // Fallback to default if alternate doesn't exist at all. | |
| $default_value = get_user_meta( $user_id, $input, true ); | |
| if ( $input === 'billing_email' && empty( $default_value ) ) { | |
| $user = get_userdata( $user_id ); | |
| if ( $user ) { | |
| $default_value = $user->user_email; | |
| } | |
| } | |
| return $default_value; | |
| } | |
| // If the alternate address explicitly has this field (even if it's empty), | |
| // always use that value so that selecting the alternate address fully | |
| // overrides the default address for that field. | |
| if ( array_key_exists( $input, $alternate_address ) ) { | |
| return $alternate_address[ $input ]; | |
| } | |
| // Otherwise, fall back to the default billing data. | |
| $default_value = get_user_meta( $user_id, $input, true ); | |
| if ( $input === 'billing_email' && empty( $default_value ) ) { | |
| $user = get_userdata( $user_id ); | |
| if ( $user ) { | |
| $default_value = $user->user_email; | |
| } | |
| } | |
| return $default_value; | |
| } | |
| /** | |
| * Backup original billing data on checkout page load (before any updates) | |
| * CRITICAL: This must happen before any checkout processing or AJAX updates | |
| */ | |
| add_action( 'woocommerce_checkout_init', 'shambix_backup_original_billing_on_load', 1 ); | |
| add_action( 'template_redirect', 'shambix_backup_original_billing_on_load', 1 ); // Even earlier hook | |
| function shambix_backup_original_billing_on_load() { | |
| if ( ! is_user_logged_in() || ! WC()->session || ! is_checkout() ) { | |
| return; | |
| } | |
| $user_id = get_current_user_id(); | |
| // Check if we already have a valid backup (don't overwrite if it exists and looks correct) | |
| $existing_backup = WC()->session->get( 'shambix_original_billing_backup' ); | |
| if ( $existing_backup && is_array( $existing_backup ) ) { | |
| // Verify backup doesn't match alternate address (which would indicate it's corrupted) | |
| $alternate_address = get_user_meta( $user_id, 'shambix_alt_billing_address', true ); | |
| if ( is_array( $alternate_address ) && isset( $alternate_address['billing_cf2'] ) && isset( $existing_backup['billing_cf2'] ) ) { | |
| if ( $existing_backup['billing_cf2'] === $alternate_address['billing_cf2'] ) { | |
| // Backup appears corrupted, refresh it | |
| shambix_debug_log( 'WooMultiAddress DEBUG: Existing backup appears corrupted (matches alternate), refreshing...' ); | |
| } else { | |
| // Backup looks valid, keep it | |
| return; | |
| } | |
| } else { | |
| // No alternate address to compare, backup looks fine | |
| return; | |
| } | |
| } | |
| // Backup original billing data from user meta (before any checkout updates) | |
| $backup = array( | |
| 'billing_first_name', 'billing_last_name', 'billing_company', | |
| 'billing_address_1', 'billing_address_2', 'billing_city', | |
| 'billing_state', 'billing_postcode', 'billing_country', | |
| 'billing_email', 'billing_phone', 'billing_cf', 'billing_cf2', | |
| ); | |
| $original = array(); | |
| foreach ( $backup as $field ) { | |
| $original[ $field ] = get_user_meta( $user_id, $field, true ); | |
| } | |
| // Verify backup doesn't match alternate address (safety check) | |
| $alternate_address = get_user_meta( $user_id, 'shambix_alt_billing_address', true ); | |
| if ( is_array( $alternate_address ) && isset( $alternate_address['billing_cf2'] ) && isset( $original['billing_cf2'] ) ) { | |
| if ( $original['billing_cf2'] === $alternate_address['billing_cf2'] ) { | |
| shambix_debug_log( 'WooMultiAddress DEBUG: WARNING - Backup billing_cf2 matches alternate address. User meta may have been updated before backup. Original value may be lost.' ); | |
| } | |
| } | |
| // Store backup in session | |
| WC()->session->set( 'shambix_original_billing_backup', $original ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG: Backed up original billing_cf2 on checkout init: ' . ( $original['billing_cf2'] ? $original['billing_cf2'] : '[empty]' ) ); | |
| } | |
| /** | |
| * Also backup when alternate address is selected (via AJAX) | |
| * This ensures we have the original values even if checkout init backup was missed | |
| */ | |
| add_action( 'woocommerce_checkout_update_order_review', 'shambix_backup_original_billing_on_alt_selection', 1, 1 ); | |
| function shambix_backup_original_billing_on_alt_selection( $post_data ) { | |
| if ( ! is_user_logged_in() || ! WC()->session ) { | |
| return; | |
| } | |
| // Parse the post data string | |
| parse_str( $post_data, $posted_data ); | |
| // Only backup if alternate address is being selected | |
| if ( ! isset( $posted_data['shambix_alt_billing_select'] ) || $posted_data['shambix_alt_billing_select'] !== 'alternate' ) { | |
| return; | |
| } | |
| // Only backup if not already backed up (to preserve original values) | |
| if ( WC()->session->get( 'shambix_original_billing_backup' ) ) { | |
| return; | |
| } | |
| $user_id = get_current_user_id(); | |
| // Backup original billing data from user meta (before any updates) | |
| $backup = array( | |
| 'billing_first_name', 'billing_last_name', 'billing_company', | |
| 'billing_address_1', 'billing_address_2', 'billing_city', | |
| 'billing_state', 'billing_postcode', 'billing_country', | |
| 'billing_email', 'billing_phone', 'billing_cf', 'billing_cf2', | |
| ); | |
| $original = array(); | |
| foreach ( $backup as $field ) { | |
| $original[ $field ] = get_user_meta( $user_id, $field, true ); | |
| } | |
| // Store backup in session | |
| WC()->session->set( 'shambix_original_billing_backup', $original ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG: Backed up original billing_cf2 on alt selection: ' . ( $original['billing_cf2'] ? $original['billing_cf2'] : '[empty]' ) ); | |
| } | |
| /** | |
| * Set flag when alternate address is selected during checkout process | |
| */ | |
| add_action( 'woocommerce_checkout_process', 'shambix_set_alternate_billing_flag', 1 ); | |
| function shambix_set_alternate_billing_flag() { | |
| if ( ! is_user_logged_in() || ! WC()->session ) { | |
| return; | |
| } | |
| $selected = isset( $_POST['shambix_alt_billing_select'] ) ? sanitize_text_field( $_POST['shambix_alt_billing_select'] ) : ''; | |
| // Only set flag if using alternate address | |
| if ( $selected === 'alternate' ) { | |
| WC()->session->set( 'shambix_using_alternate_billing', true ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG: Set alternate billing flag, original billing_cf2 backup: ' . ( WC()->session->get( 'shambix_original_billing_backup' )['billing_cf2'] ?? '[not set]' ) ); | |
| } | |
| } | |
| /** | |
| * Internal function to restore default billing address | |
| */ | |
| function shambix_restore_default_billing_address( $user_id ) { | |
| if ( ! WC()->session || ! WC()->session->get( 'shambix_using_alternate_billing' ) ) { | |
| return; | |
| } | |
| $original = WC()->session->get( 'shambix_original_billing_backup' ); | |
| if ( ! is_array( $original ) ) { | |
| return; | |
| } | |
| // Temporarily remove the filter to allow restore | |
| remove_filter( 'update_user_metadata', 'shambix_prevent_billing_update_for_alternate', 10 ); | |
| // CRITICAL: Verify backup contains original values, not alternate address values | |
| // If backup seems wrong (contains alternate address values), try to get original from order meta or skip restore | |
| $alternate_address = get_user_meta( $user_id, 'shambix_alt_billing_address', true ); | |
| if ( is_array( $alternate_address ) && isset( $alternate_address['billing_cf2'] ) ) { | |
| // If backup billing_cf2 matches alternate address billing_cf2, the backup is wrong | |
| // In this case, we should NOT restore (to avoid overwriting with wrong value) | |
| if ( isset( $original['billing_cf2'] ) && $original['billing_cf2'] === $alternate_address['billing_cf2'] ) { | |
| shambix_debug_log( 'WooMultiAddress DEBUG: WARNING - Backup billing_cf2 matches alternate address billing_cf2. Backup appears corrupted. Skipping restore for billing_cf2.' ); | |
| // Remove billing_cf2 from restore to prevent overwriting with wrong value | |
| unset( $original['billing_cf2'] ); | |
| } | |
| } | |
| // Log what we're about to restore | |
| shambix_debug_log( 'WooMultiAddress DEBUG: About to restore billing_cf2 from backup: ' . ( isset( $original['billing_cf2'] ) ? ( $original['billing_cf2'] ? $original['billing_cf2'] : '[empty]' ) : '[not in backup]' ) ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG: Current billing_cf2 in user meta before restore: ' . ( get_user_meta( $user_id, 'billing_cf2', true ) ? get_user_meta( $user_id, 'billing_cf2', true ) : '[empty]' ) ); | |
| foreach ( $original as $field => $value ) { | |
| update_user_meta( $user_id, $field, $value ); | |
| // Debug logging for billing_cf2 | |
| if ( $field === 'billing_cf2' ) { | |
| shambix_debug_log( 'WooMultiAddress DEBUG: Restored billing_cf2 to: ' . ( $value ? $value : '[empty]' ) ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG: billing_cf2 in user meta after restore: ' . ( get_user_meta( $user_id, 'billing_cf2', true ) ? get_user_meta( $user_id, 'billing_cf2', true ) : '[empty]' ) ); | |
| } | |
| } | |
| // Re-add the filter | |
| add_filter( 'update_user_metadata', 'shambix_prevent_billing_update_for_alternate', 10, 5 ); | |
| // Update customer object to sync restored data | |
| $customer = new WC_Customer( $user_id ); | |
| $customer->save(); | |
| // Clean up session | |
| WC()->session->__unset( 'shambix_original_billing_backup' ); | |
| WC()->session->__unset( 'shambix_using_alternate_billing' ); | |
| WC()->session->__unset( 'shambix_selected_alt_billing' ); | |
| } | |
| /** | |
| * Save new alternate address and restore original billing data after order | |
| * CRITICAL: This must run AFTER WooCommerce has finished all customer updates | |
| */ | |
| add_action( 'woocommerce_checkout_order_processed', 'shambix_handle_alternate_address_after_order', 999, 3 ); | |
| function shambix_handle_alternate_address_after_order( $order_id, $posted_data, $order ) { | |
| if ( ! is_user_logged_in() ) { | |
| return; | |
| } | |
| $user_id = get_current_user_id(); | |
| $selected = isset( $_POST['shambix_alt_billing_select'] ) ? sanitize_text_field( $_POST['shambix_alt_billing_select'] ) : ''; | |
| // Only process if using alternate address | |
| if ( $selected !== 'alternate' ) { | |
| return; | |
| } | |
| // Restore original billing data | |
| shambix_restore_default_billing_address( $user_id ); | |
| } | |
| /** | |
| * Additional restore hook after payment completes (for payment gateways) | |
| */ | |
| add_action( 'woocommerce_payment_complete', 'shambix_restore_default_address_after_payment', 999, 1 ); | |
| function shambix_restore_default_address_after_payment( $order_id ) { | |
| if ( ! is_user_logged_in() ) { | |
| return; | |
| } | |
| $order = wc_get_order( $order_id ); | |
| if ( ! $order ) { | |
| return; | |
| } | |
| $user_id = $order->get_user_id(); | |
| if ( ! $user_id ) { | |
| return; | |
| } | |
| // Check if alternate address was used (from order meta or session) | |
| $selected = ''; | |
| if ( WC()->session && WC()->session->get( 'shambix_using_alternate_billing' ) ) { | |
| $selected = 'alternate'; | |
| } | |
| if ( $selected === 'alternate' ) { | |
| shambix_restore_default_billing_address( $user_id ); | |
| } | |
| } | |
| /** | |
| * After successful payment, sync the final billing details from the order | |
| * back into the stored alternate billing address (when it was used). | |
| * | |
| * This lets the user: | |
| * - Start from the saved alternate address. | |
| * - Tweak fields on the checkout form for a specific order. | |
| * - Have those successful edits become the new saved alternate address. | |
| */ | |
| add_action( 'woocommerce_payment_complete', 'shambix_update_alt_billing_from_order', 900, 1 ); | |
| function shambix_update_alt_billing_from_order( $order_id ) { | |
| if ( ! $order_id ) { | |
| return; | |
| } | |
| $order = wc_get_order( $order_id ); | |
| if ( ! $order ) { | |
| return; | |
| } | |
| $user_id = $order->get_user_id(); | |
| if ( ! $user_id ) { | |
| return; // Guests cannot have a stored alternate address. | |
| } | |
| // Only proceed if this order actually used the alternate billing address. | |
| $used_alt = $order->get_meta( 'shambix_alt_billing_used' ); | |
| if ( 'yes' !== $used_alt ) { | |
| return; | |
| } | |
| // Build new alternate address from the FINAL billing data on the order. | |
| $alternate_address = array( | |
| 'billing_first_name' => $order->get_billing_first_name(), | |
| 'billing_last_name' => $order->get_billing_last_name(), | |
| 'billing_company' => $order->get_billing_company(), | |
| 'billing_address_1' => $order->get_billing_address_1(), | |
| 'billing_address_2' => $order->get_billing_address_2(), | |
| 'billing_city' => $order->get_billing_city(), | |
| 'billing_state' => $order->get_billing_state(), | |
| 'billing_postcode' => $order->get_billing_postcode(), | |
| 'billing_country' => $order->get_billing_country(), | |
| 'billing_email' => $order->get_billing_email(), | |
| 'billing_phone' => $order->get_billing_phone(), | |
| // Custom fields stored on the order. | |
| 'billing_cf' => $order->get_meta( 'billing_cf' ), | |
| 'billing_cf2' => $order->get_meta( 'billing_cf2' ), | |
| ); | |
| // If all fields are empty for some reason, don't wipe the stored alt address. | |
| $has_data = false; | |
| foreach ( $alternate_address as $value ) { | |
| if ( ! empty( $value ) ) { | |
| $has_data = true; | |
| break; | |
| } | |
| } | |
| if ( ! $has_data ) { | |
| return; | |
| } | |
| update_user_meta( $user_id, 'shambix_alt_billing_address', $alternate_address ); | |
| } | |
| /** | |
| * Additional restore hook on thankyou page (catches any late updates) | |
| */ | |
| add_action( 'woocommerce_thankyou', 'shambix_restore_default_address_on_thankyou', 999, 1 ); | |
| function shambix_restore_default_address_on_thankyou( $order_id ) { | |
| if ( ! is_user_logged_in() ) { | |
| return; | |
| } | |
| $order = wc_get_order( $order_id ); | |
| if ( ! $order ) { | |
| return; | |
| } | |
| $user_id = $order->get_user_id(); | |
| if ( ! $user_id || $user_id !== get_current_user_id() ) { | |
| return; | |
| } | |
| // Check if alternate address was used (from session) | |
| if ( WC()->session && WC()->session->get( 'shambix_using_alternate_billing' ) ) { | |
| shambix_restore_default_billing_address( $user_id ); | |
| } | |
| } | |
| /** | |
| * Ensure order has correct billing data from alternate address | |
| */ | |
| add_action( 'woocommerce_checkout_create_order', 'shambix_ensure_order_has_alternate_billing', 10, 2 ); | |
| function shambix_ensure_order_has_alternate_billing( $order, $data ) { | |
| // - Here we only mark the order when the alternate address was used so | |
| // we can later sync the FINAL billing data back into the stored | |
| // alternate address once the order is successfully paid. | |
| if ( ! is_user_logged_in() ) { | |
| return; | |
| } | |
| $selected = isset( $_POST['shambix_alt_billing_select'] ) ? sanitize_text_field( wp_unslash( $_POST['shambix_alt_billing_select'] ) ) : ''; | |
| if ( $selected === 'alternate' ) { | |
| $order->update_meta_data( 'shambix_alt_billing_used', 'yes' ); | |
| } | |
| } | |
| /** | |
| * Ensure customer object has alternate billing data for payment gateways | |
| */ | |
| add_action( 'woocommerce_before_checkout_process', 'shambix_update_customer_for_alternate', 5 ); | |
| function shambix_update_customer_for_alternate() { | |
| if ( ! is_user_logged_in() ) { | |
| return; | |
| } | |
| $selected = isset( $_POST['shambix_alt_billing_select'] ) ? sanitize_text_field( $_POST['shambix_alt_billing_select'] ) : ''; | |
| if ( $selected !== 'alternate' ) { | |
| return; | |
| } | |
| $user_id = get_current_user_id(); | |
| $customer = new WC_Customer( $user_id ); | |
| // Use the values the customer actually submitted on the checkout form | |
| // (which may be based on the saved alternate address, but can include | |
| // per-order edits) so that payment gateways see the correct data. | |
| $billing_fields = array( | |
| 'billing_first_name', 'billing_last_name', 'billing_company', | |
| 'billing_address_1', 'billing_address_2', 'billing_city', | |
| 'billing_state', 'billing_postcode', 'billing_country', | |
| 'billing_email', 'billing_phone', | |
| ); | |
| foreach ( $billing_fields as $field ) { | |
| if ( isset( $_POST[ $field ] ) && $_POST[ $field ] !== '' ) { | |
| $setter = 'set_' . $field; | |
| if ( method_exists( $customer, $setter ) ) { | |
| $customer->$setter( wc_clean( wp_unslash( $_POST[ $field ] ) ) ); | |
| } | |
| } | |
| } | |
| // Handle custom fields | |
| if ( isset( $_POST['billing_cf'] ) ) { | |
| $customer->update_meta_data( 'billing_cf', wc_clean( wp_unslash( $_POST['billing_cf'] ) ) ); | |
| } | |
| if ( isset( $_POST['billing_cf2'] ) ) { | |
| $customer->update_meta_data( 'billing_cf2', wc_clean( wp_unslash( $_POST['billing_cf2'] ) ) ); | |
| } | |
| $customer->save(); | |
| WC()->customer = $customer; | |
| } | |
| /** | |
| * Enqueue WooCommerce address-i18n script on edit account page for dynamic country/state updates | |
| */ | |
| add_action( 'wp_enqueue_scripts', 'shambix_enqueue_address_i18n_on_edit_account' ); | |
| function shambix_enqueue_address_i18n_on_edit_account() { | |
| // Only on the edit account page | |
| if ( ! is_account_page() || ! is_wc_endpoint_url( 'edit-account' ) ) { | |
| return; | |
| } | |
| // Enqueue WooCommerce's address-i18n script (handles dynamic state updates) | |
| if ( function_exists( 'WC' ) && class_exists( 'WC_Countries' ) ) { | |
| $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; | |
| wp_enqueue_script( | |
| 'wc-address-i18n', | |
| WC()->plugin_url() . '/assets/js/frontend/address-i18n' . $suffix . '.js', | |
| array( 'jquery', 'woocommerce' ), | |
| WC()->version, | |
| true | |
| ); | |
| // Localize script with country/state data | |
| $countries_obj = new WC_Countries(); | |
| wp_localize_script( | |
| 'wc-address-i18n', | |
| 'wc_address_i18n_params', | |
| array( | |
| 'locale' => wp_json_encode( $countries_obj->get_country_locale() ), | |
| 'locale_fields' => wp_json_encode( $countries_obj->get_country_locale_field_selectors() ), | |
| 'i18n_required_text' => esc_attr__( 'required', 'woocommerce' ), | |
| 'i18n_optional_text' => esc_attr__( 'optional', 'woocommerce' ), | |
| ) | |
| ); | |
| } | |
| } | |
| /** | |
| * Add default WooCommerce billing fields to My Account > Edit Account page | |
| */ | |
| add_action( 'woocommerce_edit_account_form', 'shambix_add_billing_fields_to_edit_account' ); | |
| function shambix_add_billing_fields_to_edit_account() { | |
| $user_id = get_current_user_id(); | |
| // Use submitted values after validation errors, otherwise fallback to saved meta. | |
| $submitted = isset( $_POST['save_account_details'] ); | |
| $billing_keys = array( | |
| 'billing_first_name', | |
| 'billing_last_name', | |
| 'billing_company', | |
| 'billing_address_1', | |
| 'billing_address_2', | |
| 'billing_city', | |
| 'billing_postcode', | |
| 'billing_state', | |
| 'billing_country', | |
| 'billing_phone', | |
| 'billing_cf2', // this is a custom field, can be removed | |
| 'billing_cf', // this is a custom field, can be removed | |
| ); | |
| $billing_values = array(); | |
| foreach ( $billing_keys as $key ) { | |
| if ( isset( $_POST[ $key ] ) ) { | |
| $billing_values[ $key ] = wc_clean( wp_unslash( $_POST[ $key ] ) ); | |
| } else { | |
| $billing_values[ $key ] = get_user_meta( $user_id, $key, true ); | |
| } | |
| } | |
| // Required fields for inline error messages. | |
| $required_fields = array( | |
| 'billing_first_name' => __( 'Nome', TEXT_DOMAIN ), | |
| 'billing_last_name' => __( 'Cognome', TEXT_DOMAIN ), | |
| 'billing_address_1' => __( 'Indirizzo', TEXT_DOMAIN ), | |
| 'billing_city' => __( 'Città', TEXT_DOMAIN ), | |
| 'billing_postcode' => __( 'CAP', TEXT_DOMAIN ), | |
| 'billing_state' => __( 'Provincia', TEXT_DOMAIN ), | |
| 'billing_country' => __( 'Paese', TEXT_DOMAIN ), | |
| 'billing_phone' => __( 'Telefono', TEXT_DOMAIN ), | |
| ); | |
| $inline_errors = array(); | |
| if ( $submitted ) { | |
| foreach ( $required_fields as $field => $label ) { | |
| $value = isset( $billing_values[ $field ] ) ? trim( (string) $billing_values[ $field ] ) : ''; | |
| if ( '' === $value ) { | |
| $inline_errors[ $field ] = sprintf( | |
| __( '%s è un campo obbligatorio.', TEXT_DOMAIN ), | |
| '<strong>' . $label . '</strong>' | |
| ); | |
| } | |
| } | |
| } | |
| ?> | |
| <fieldset class="billing_default"> | |
| <legend><?php esc_html_e( 'Indirizzo di Fatturazione Predefinito', TEXT_DOMAIN ); ?></legend> | |
| <p class="woocommerce-form-row woocommerce-form-row--first form-row form-row-first"> | |
| <label for="billing_first_name"><?php esc_html_e( 'Nome', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_first_name" id="billing_first_name" value="<?php echo esc_attr( $billing_values['billing_first_name'] ); ?>" /> | |
| <?php if ( isset( $inline_errors['billing_first_name'] ) ) : ?> | |
| <span class="woocommerce-error" style="margin-top:4px;display:block;"><?php echo wp_kses_post( $inline_errors['billing_first_name'] ); ?></span> | |
| <?php endif; ?> | |
| </p> | |
| <p class="woocommerce-form-row woocommerce-form-row--last form-row form-row-last"> | |
| <label for="billing_last_name"><?php esc_html_e( 'Cognome', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_last_name" id="billing_last_name" value="<?php echo esc_attr( $billing_values['billing_last_name'] ); ?>" /> | |
| <?php if ( isset( $inline_errors['billing_last_name'] ) ) : ?> | |
| <span class="woocommerce-error" style="margin-top:4px;display:block;"><?php echo wp_kses_post( $inline_errors['billing_last_name'] ); ?></span> | |
| <?php endif; ?> | |
| </p> | |
| <div class="clear"></div> | |
| <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"> | |
| <label for="billing_company"><?php esc_html_e( 'Azienda', TEXT_DOMAIN ); ?></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_company" id="billing_company" value="<?php echo esc_attr( $billing_values['billing_company'] ); ?>" /> | |
| </p> | |
| <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"> | |
| <label for="billing_address_1"><?php esc_html_e( 'Indirizzo', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_address_1" id="billing_address_1" value="<?php echo esc_attr( $billing_values['billing_address_1'] ); ?>" /> | |
| <?php if ( isset( $inline_errors['billing_address_1'] ) ) : ?> | |
| <span class="woocommerce-error" style="margin-top:4px;display:block;"><?php echo wp_kses_post( $inline_errors['billing_address_1'] ); ?></span> | |
| <?php endif; ?> | |
| </p> | |
| <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"> | |
| <label for="billing_address_2"><?php esc_html_e( 'Indirizzo 2', TEXT_DOMAIN ); ?></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_address_2" id="billing_address_2" value="<?php echo esc_attr( $billing_values['billing_address_2'] ); ?>" /> | |
| </p> | |
| <p class="woocommerce-form-row woocommerce-form-row--first form-row form-row-first"> | |
| <label for="billing_city"><?php esc_html_e( 'Città', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_city" id="billing_city" value="<?php echo esc_attr( $billing_values['billing_city'] ); ?>" /> | |
| <?php if ( isset( $inline_errors['billing_city'] ) ) : ?> | |
| <span class="woocommerce-error" style="margin-top:4px;display:block;"><?php echo wp_kses_post( $inline_errors['billing_city'] ); ?></span> | |
| <?php endif; ?> | |
| </p> | |
| <p class="woocommerce-form-row woocommerce-form-row--last form-row form-row-last"> | |
| <label for="billing_postcode"><?php esc_html_e( 'CAP', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_postcode" id="billing_postcode" value="<?php echo esc_attr( $billing_values['billing_postcode'] ); ?>" /> | |
| <?php if ( isset( $inline_errors['billing_postcode'] ) ) : ?> | |
| <span class="woocommerce-error" style="margin-top:4px;display:block;"><?php echo wp_kses_post( $inline_errors['billing_postcode'] ); ?></span> | |
| <?php endif; ?> | |
| </p> | |
| <div class="clear"></div> | |
| <?php | |
| // Use WooCommerce dynamic country/state fields so behaviour matches checkout | |
| $countries_obj = new WC_Countries(); | |
| $address_fields = $countries_obj->get_address_fields( '', 'billing_' ); | |
| // Billing state (Provincia) | |
| // Get saved country and state values | |
| $saved_country = isset( $billing_values['billing_country'] ) ? $billing_values['billing_country'] : ''; | |
| $saved_state = isset( $billing_values['billing_state'] ) ? $billing_values['billing_state'] : ''; | |
| if ( isset( $address_fields['billing_state'] ) ) { | |
| $state_args = $address_fields['billing_state']; | |
| $state_args['label'] = __( 'Provincia', TEXT_DOMAIN ); | |
| $state_args['required'] = true; | |
| // Force layout: first half-width column | |
| if ( ! empty( $state_args['class'] ) && is_array( $state_args['class'] ) ) { | |
| $state_args['class'] = array_diff( $state_args['class'], array( 'form-row-wide' ) ); | |
| } | |
| $state_args['class'][] = 'form-row-first'; | |
| $state_args['clear'] = false; | |
| // CRITICAL: Set the 'country' parameter so woocommerce_form_field() knows which country's states to show | |
| // Without this, it will use the shop's base country (probably Italy) or checkout session value | |
| if ( $saved_country ) { | |
| $state_args['country'] = $saved_country; | |
| } | |
| woocommerce_form_field( | |
| 'billing_state', | |
| $state_args, | |
| $saved_state | |
| ); | |
| if ( isset( $inline_errors['billing_state'] ) ) { | |
| echo '<span class="woocommerce-error" style="margin-top:4px;display:block;">' . wp_kses_post( $inline_errors['billing_state'] ) . '</span>'; | |
| } | |
| } | |
| // Billing country (Paese) | |
| if ( isset( $address_fields['billing_country'] ) ) { | |
| $country_args = $address_fields['billing_country']; | |
| $country_args['label'] = __( 'Paese', TEXT_DOMAIN ); | |
| $country_args['required'] = true; | |
| // Force layout: last half-width column | |
| if ( ! empty( $country_args['class'] ) && is_array( $country_args['class'] ) ) { | |
| $country_args['class'] = array_diff( $country_args['class'], array( 'form-row-wide' ) ); | |
| } | |
| $country_args['class'][] = 'form-row-last'; | |
| $country_args['clear'] = true; | |
| woocommerce_form_field( | |
| 'billing_country', | |
| $country_args, | |
| $billing_values['billing_country'] | |
| ); | |
| if ( isset( $inline_errors['billing_country'] ) ) { | |
| echo '<span class="woocommerce-error" style="margin-top:4px;display:block;">' . wp_kses_post( $inline_errors['billing_country'] ) . '</span>'; | |
| } | |
| } | |
| ?> | |
| <div class="clear"></div> | |
| <?php | |
| // Add JavaScript to ensure saved state value is set on page load | |
| // The state field should already have the correct states from PHP, but we need to set the value | |
| // Also ensure the state field wrapper maintains form-row-first class after dynamic updates | |
| $saved_state = isset( $billing_values['billing_state'] ) ? $billing_values['billing_state'] : ''; | |
| ?> | |
| <script type="text/javascript"> | |
| jQuery(document).ready(function($) { | |
| var $stateField = $('#billing_state'); | |
| var $stateWrapper = $stateField.closest('.form-row'); | |
| var savedState = '<?php echo esc_js( $saved_state ); ?>'; | |
| // Function to ensure state field wrapper has correct classes | |
| function ensureStateFieldLayout() { | |
| if ( $stateWrapper.length ) { | |
| // Remove form-row-wide if present | |
| $stateWrapper.removeClass('form-row-wide'); | |
| // Ensure form-row-first is present | |
| if ( ! $stateWrapper.hasClass('form-row-first') ) { | |
| $stateWrapper.addClass('form-row-first'); | |
| } | |
| } | |
| } | |
| // Ensure correct layout on page load | |
| ensureStateFieldLayout(); | |
| // Ensure correct layout after WooCommerce updates the state field (when country changes) | |
| // Listen for when the state field is updated by WooCommerce's address-i18n script | |
| if ( $stateField.length ) { | |
| // Watch for changes to the state field wrapper's classes | |
| var observer = new MutationObserver(function(mutations) { | |
| ensureStateFieldLayout(); | |
| }); | |
| if ( $stateWrapper.length ) { | |
| observer.observe($stateWrapper[0], { | |
| attributes: true, | |
| attributeFilter: ['class'] | |
| }); | |
| } | |
| // Also ensure layout when country changes (WooCommerce updates state field via AJAX) | |
| $('#billing_country').on('change', function() { | |
| // Wait for WooCommerce to update the state field, then fix layout | |
| setTimeout(function() { | |
| ensureStateFieldLayout(); | |
| }, 300); | |
| }); | |
| } | |
| // Set saved state value if it exists | |
| if ( $stateField.length && savedState ) { | |
| // Wait a moment for the field to be fully rendered | |
| setTimeout(function() { | |
| // Check if the state option exists | |
| if ( $stateField.find('option[value="' + savedState + '"]').length > 0 ) { | |
| $stateField.val(savedState); | |
| // Trigger change to update any Select2 or other UI | |
| $stateField.trigger('change'); | |
| } | |
| }, 100); | |
| } | |
| }); | |
| </script> | |
| <p class="woocommerce-form-row woocommerce-form-row--first form-row form-row-first"> | |
| <label for="billing_phone"><?php esc_html_e( 'Telefono', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="tel" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_phone" id="billing_phone" value="<?php echo esc_attr( $billing_values['billing_phone'] ); ?>" /> | |
| <?php if ( isset( $inline_errors['billing_phone'] ) ) : ?> | |
| <span class="woocommerce-error" style="margin-top:4px;display:block;"><?php echo wp_kses_post( $inline_errors['billing_phone'] ); ?></span> | |
| <?php endif; ?> | |
| </p> | |
| <p class="woocommerce-form-row woocommerce-form-row--last form-row form-row-last"> | |
| <label for="billing_cf2"><?php esc_html_e( 'Codice Fiscale', TEXT_DOMAIN ); ?></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_cf2" id="billing_cf2" value="<?php echo esc_attr( $billing_values['billing_cf2'] ); ?>" /> | |
| </p> | |
| <div class="clear"></div> | |
| <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"> | |
| <label for="billing_cf"><?php esc_html_e( 'Partita IVA', TEXT_DOMAIN ); ?></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="billing_cf" id="billing_cf" value="<?php echo esc_attr( $billing_values['billing_cf'] ); ?>" /> | |
| </p> | |
| </fieldset> | |
| <?php | |
| } | |
| /** | |
| * Save default WooCommerce billing fields from My Account > Edit Account page | |
| */ | |
| add_action( 'woocommerce_save_account_details', 'shambix_save_billing_fields_from_edit_account', 5, 1 ); | |
| function shambix_save_billing_fields_from_edit_account( $user_id ) { | |
| // Only process if we're on the My Account edit account page | |
| if ( ! is_account_page() ) { | |
| return; | |
| } | |
| // Define billing fields to save | |
| $billing_fields = array( | |
| 'billing_first_name', | |
| 'billing_last_name', | |
| 'billing_company', | |
| 'billing_address_1', | |
| 'billing_address_2', | |
| 'billing_city', | |
| 'billing_postcode', | |
| 'billing_state', | |
| 'billing_country', | |
| 'billing_phone', | |
| 'billing_cf', // this is a custom field, can be removed | |
| 'billing_cf2', // this is a custom field, can be removed | |
| ); | |
| // Save each billing field - ALWAYS allow updates from My Account page | |
| foreach ( $billing_fields as $field ) { | |
| if ( isset( $_POST[ $field ] ) ) { | |
| update_user_meta( $user_id, $field, sanitize_text_field( $_POST[ $field ] ) ); | |
| } | |
| } | |
| // Also update WordPress first_name and last_name | |
| if ( isset( $_POST['billing_first_name'] ) ) { | |
| update_user_meta( $user_id, 'first_name', sanitize_text_field( $_POST['billing_first_name'] ) ); | |
| } | |
| if ( isset( $_POST['billing_last_name'] ) ) { | |
| update_user_meta( $user_id, 'last_name', sanitize_text_field( $_POST['billing_last_name'] ) ); | |
| } | |
| } | |
| /** | |
| * Validate billing fields in edit account form | |
| */ | |
| add_action( 'woocommerce_save_account_details_errors', 'shambix_validate_billing_fields_edit_account', 10, 1 ); | |
| function shambix_validate_billing_fields_edit_account( $args ) { | |
| // Required fields | |
| $required_fields = array( | |
| 'billing_first_name' => __( 'Nome', TEXT_DOMAIN ), | |
| 'billing_last_name' => __( 'Cognome', TEXT_DOMAIN ), | |
| 'billing_address_1' => __( 'Indirizzo', TEXT_DOMAIN ), | |
| 'billing_city' => __( 'Città', TEXT_DOMAIN ), | |
| 'billing_postcode' => __( 'CAP', TEXT_DOMAIN ), | |
| 'billing_state' => __( 'Provincia', TEXT_DOMAIN ), | |
| 'billing_country' => __( 'Paese', TEXT_DOMAIN ), | |
| 'billing_phone' => __( 'Telefono', TEXT_DOMAIN ), | |
| ); | |
| foreach ( $required_fields as $field => $label ) { | |
| if ( empty( $_POST[ $field ] ) ) { | |
| $args->add( $field, sprintf( __( '%s è un campo obbligatorio.', TEXT_DOMAIN ), '<strong>' . $label . '</strong>' ) ); | |
| } | |
| } | |
| } | |
| /** | |
| * Validate alternate billing fields in edit account form | |
| * The alternate address is optional, but if the user fills it, core fields must be present. | |
| */ | |
| add_action( 'woocommerce_save_account_details_errors', 'shambix_validate_alt_billing_fields_edit_account', 11, 1 ); | |
| function shambix_validate_alt_billing_fields_edit_account( $errors ) { | |
| // If user requested deletion of the alt address, skip validation. | |
| if ( isset( $_POST['shambix_delete_alt_address'] ) && $_POST['shambix_delete_alt_address'] === '1' ) { | |
| return; | |
| } | |
| // Detect if user is actually trying to save an alternate address (any alt_billing_* field filled). | |
| $has_alt_data = false; | |
| foreach ( $_POST as $key => $value ) { | |
| if ( strpos( $key, 'alt_billing_' ) === 0 && trim( (string) $value ) !== '' ) { | |
| $has_alt_data = true; | |
| break; | |
| } | |
| } | |
| if ( ! $has_alt_data ) { | |
| return; | |
| } | |
| // Required fields for alternate address. | |
| $required_fields = array( | |
| 'alt_billing_first_name' => __( 'Nome', TEXT_DOMAIN ), | |
| 'alt_billing_last_name' => __( 'Cognome', TEXT_DOMAIN ), | |
| 'alt_billing_address_1' => __( 'Indirizzo', TEXT_DOMAIN ), | |
| 'alt_billing_city' => __( 'Città', TEXT_DOMAIN ), | |
| 'alt_billing_postcode' => __( 'CAP', TEXT_DOMAIN ), | |
| 'alt_billing_state' => __( 'Provincia', TEXT_DOMAIN ), | |
| 'alt_billing_country' => __( 'Paese', TEXT_DOMAIN ), | |
| 'alt_billing_email' => __( 'Email', TEXT_DOMAIN ), | |
| ); | |
| // Make state/province required only if the selected country actually has states (like Woo checkout). | |
| $alt_country = isset( $_POST['alt_billing_country'] ) ? wc_clean( wp_unslash( $_POST['alt_billing_country'] ) ) : ''; | |
| if ( function_exists( 'WC' ) && $alt_country ) { | |
| $wc_countries = WC()->countries; | |
| if ( $wc_countries ) { | |
| $states_for_country = $wc_countries->get_states( $alt_country ); | |
| if ( empty( $states_for_country ) || ! is_array( $states_for_country ) ) { | |
| unset( $required_fields['alt_billing_state'] ); | |
| } | |
| } | |
| } | |
| foreach ( $required_fields as $field => $label ) { | |
| $value = isset( $_POST[ $field ] ) ? trim( (string) $_POST[ $field ] ) : ''; | |
| if ( $value === '' ) { | |
| $errors->add( | |
| $field, | |
| sprintf( | |
| __( '%s è un campo obbligatorio per l\'indirizzo di fatturazione alternativo.', TEXT_DOMAIN ), | |
| '<strong>' . $label . '</strong>' | |
| ) | |
| ); | |
| } | |
| } | |
| } | |
| /** | |
| * Add alternate billing address section to My Account > Edit Account page | |
| */ | |
| add_action( 'woocommerce_edit_account_form_end', 'shambix_add_alternate_address_to_edit_account' ); | |
| function shambix_add_alternate_address_to_edit_account() { | |
| if ( ! is_user_logged_in() ) { | |
| return; | |
| } | |
| $user_id = get_current_user_id(); | |
| $alternate_address = get_user_meta( $user_id, 'shambix_alt_billing_address', true ); | |
| $has_alternate = ! empty( $alternate_address ) && is_array( $alternate_address ); | |
| // DEBUG: Log loaded alternate address | |
| shambix_debug_log( 'WooMultiAddress DEBUG [EDIT ACCOUNT]: Loaded alternate address from DB: ' . print_r( $alternate_address, true ) ); | |
| if ( isset( $alternate_address['billing_state'] ) ) { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [EDIT ACCOUNT]: Saved billing_state value: ' . $alternate_address['billing_state'] ); | |
| } else { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [EDIT ACCOUNT]: No billing_state in saved alternate address' ); | |
| } | |
| // Preserve submitted alternate address values after validation errors. | |
| $alt_keys = array( | |
| 'billing_first_name', | |
| 'billing_last_name', | |
| 'billing_company', | |
| 'billing_address_1', | |
| 'billing_address_2', | |
| 'billing_city', | |
| 'billing_postcode', | |
| 'billing_state', | |
| 'billing_country', | |
| 'billing_phone', | |
| 'billing_email', | |
| 'billing_cf2', // this is a custom field, can be removed | |
| 'billing_cf', // this is a custom field, can be removed | |
| ); | |
| $alt_values = array(); | |
| foreach ( $alt_keys as $key ) { | |
| $post_key = 'alt_' . $key; | |
| if ( isset( $_POST[ $post_key ] ) ) { | |
| $alt_values[ $key ] = wc_clean( wp_unslash( $_POST[ $post_key ] ) ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [EDIT ACCOUNT]: Using POST value for ' . $key . ': ' . $alt_values[ $key ] ); | |
| } else { | |
| $alt_values[ $key ] = isset( $alternate_address[ $key ] ) ? $alternate_address[ $key ] : ''; | |
| shambix_debug_log( 'WooMultiAddress DEBUG [EDIT ACCOUNT]: Using saved value for ' . $key . ': ' . $alt_values[ $key ] ); | |
| } | |
| } | |
| // DEBUG: Log final alt_values array | |
| shambix_debug_log( 'WooMultiAddress DEBUG [EDIT ACCOUNT]: Final alt_values array: ' . print_r( $alt_values, true ) ); | |
| ?> | |
| <fieldset style="margin-top: 40px; padding-top: 30px; border-top: 2px solid #ddd;"> | |
| <legend><?php esc_html_e( 'Indirizzo di Fatturazione Alternativo', TEXT_DOMAIN ); ?></legend> | |
| <p style="margin-bottom: 20px; color: #666;"> | |
| <?php esc_html_e( 'Puoi salvare un indirizzo di fatturazione alternativo da utilizzare durante il checkout. Questo indirizzo non sostituirà mai il tuo indirizzo predefinito.', TEXT_DOMAIN ); ?> | |
| </p> | |
| <?php if ( $has_alternate ) : ?> | |
| <p class="form-row form-row-wide"> | |
| <label> | |
| <input type="checkbox" name="shambix_delete_alt_address" value="1" /> | |
| <?php esc_html_e( 'Elimina indirizzo alternativo', TEXT_DOMAIN ); ?> | |
| </label> | |
| </p> | |
| <?php endif; ?> | |
| <p class="form-row form-row-first"> | |
| <label for="alt_billing_first_name"><?php esc_html_e( 'Nome', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_first_name" id="alt_billing_first_name" value="<?php echo esc_attr( $alt_values['billing_first_name'] ); ?>" /> | |
| </p> | |
| <p class="form-row form-row-last"> | |
| <label for="alt_billing_last_name"><?php esc_html_e( 'Cognome', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_last_name" id="alt_billing_last_name" value="<?php echo esc_attr( $alt_values['billing_last_name'] ); ?>" /> | |
| </p> | |
| <div class="clear"></div> | |
| <p class="form-row form-row-wide"> | |
| <label for="alt_billing_company"><?php esc_html_e( 'Azienda', TEXT_DOMAIN ); ?></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_company" id="alt_billing_company" value="<?php echo esc_attr( $alt_values['billing_company'] ); ?>" /> | |
| </p> | |
| <p class="form-row form-row-wide"> | |
| <label for="alt_billing_address_1"><?php esc_html_e( 'Indirizzo', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_address_1" id="alt_billing_address_1" value="<?php echo esc_attr( $alt_values['billing_address_1'] ); ?>" /> | |
| </p> | |
| <p class="form-row form-row-wide"> | |
| <label for="alt_billing_address_2"><?php esc_html_e( 'Indirizzo 2', TEXT_DOMAIN ); ?></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_address_2" id="alt_billing_address_2" value="<?php echo esc_attr( $alt_values['billing_address_2'] ); ?>" /> | |
| </p> | |
| <p class="form-row form-row-first"> | |
| <label for="alt_billing_city"><?php esc_html_e( 'Città', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_city" id="alt_billing_city" value="<?php echo esc_attr( $alt_values['billing_city'] ); ?>" /> | |
| </p> | |
| <p class="form-row form-row-last"> | |
| <label for="alt_billing_postcode"><?php esc_html_e( 'CAP', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_postcode" id="alt_billing_postcode" value="<?php echo esc_attr( $alt_values['billing_postcode'] ); ?>" /> | |
| </p> | |
| <div class="clear"></div> | |
| <?php | |
| // Use WooCommerce dynamic country/state fields for alternate address (same as default billing) | |
| $countries_obj_alt = new WC_Countries(); | |
| $address_fields_alt = $countries_obj_alt->get_address_fields( '', 'billing_' ); | |
| // Determine if the currently selected alt country has states (for required validation) | |
| $alt_country_code = isset( $alt_values['billing_country'] ) ? $alt_values['billing_country'] : ''; | |
| $alt_country_has_state = false; | |
| if ( $alt_country_code && $countries_obj_alt ) { | |
| $states_for_alt_country = $countries_obj_alt->get_states( $alt_country_code ); | |
| if ( ! empty( $states_for_alt_country ) && is_array( $states_for_alt_country ) ) { | |
| $alt_country_has_state = true; | |
| } | |
| } | |
| // Get inline errors for alt address fields | |
| $alt_inline_errors = array(); | |
| if ( isset( $_POST['save_account_details'] ) ) { | |
| $alt_required_fields = array( | |
| 'alt_billing_first_name' => __( 'Nome', TEXT_DOMAIN ), | |
| 'alt_billing_last_name' => __( 'Cognome', TEXT_DOMAIN ), | |
| 'alt_billing_address_1' => __( 'Indirizzo', TEXT_DOMAIN ), | |
| 'alt_billing_city' => __( 'Città', TEXT_DOMAIN ), | |
| 'alt_billing_postcode' => __( 'CAP', TEXT_DOMAIN ), | |
| 'alt_billing_country' => __( 'Paese', TEXT_DOMAIN ), | |
| 'alt_billing_email' => __( 'Email', TEXT_DOMAIN ), | |
| ); | |
| // State is only required if country has states | |
| if ( $alt_country_has_state ) { | |
| $alt_required_fields['alt_billing_state'] = __( 'Provincia', TEXT_DOMAIN ); | |
| } | |
| foreach ( $alt_required_fields as $field => $label ) { | |
| $value = isset( $_POST[ $field ] ) ? trim( (string) $_POST[ $field ] ) : ''; | |
| if ( '' === $value ) { | |
| $alt_inline_errors[ $field ] = sprintf( | |
| __( '%s è un campo obbligatorio per l\'indirizzo di fatturazione alternativo.', TEXT_DOMAIN ), | |
| '<strong>' . $label . '</strong>' | |
| ); | |
| } | |
| } | |
| } | |
| // Alternate billing state (Provincia) - uses WooCommerce standard form field | |
| if ( isset( $address_fields_alt['billing_state'] ) ) { | |
| $alt_state_args = $address_fields_alt['billing_state']; | |
| $alt_state_args['label'] = __( 'Provincia', TEXT_DOMAIN ); | |
| $alt_state_args['required'] = $alt_country_has_state; | |
| $alt_state_args['id'] = 'alt_billing_state'; | |
| $alt_state_args['name'] = 'alt_billing_state'; | |
| // Link to country field for dynamic updates | |
| $alt_state_args['country_field'] = 'alt_billing_country'; | |
| // Force layout: first half-width column | |
| if ( ! empty( $alt_state_args['class'] ) && is_array( $alt_state_args['class'] ) ) { | |
| $alt_state_args['class'] = array_diff( $alt_state_args['class'], array( 'form-row-wide' ) ); | |
| } | |
| $alt_state_args['class'][] = 'form-row-first'; | |
| $alt_state_args['class'][] = 'alt-billing-state'; | |
| $alt_state_args['clear'] = false; | |
| // DEBUG: Log state field rendering | |
| shambix_debug_log( 'WooMultiAddress DEBUG [EDIT ACCOUNT]: Rendering alt_billing_state field with value: ' . $alt_values['billing_state'] ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [EDIT ACCOUNT]: Current country: ' . $alt_values['billing_country'] . ', Has states: ' . ( $alt_country_has_state ? 'yes' : 'no' ) ); | |
| woocommerce_form_field( | |
| 'alt_billing_state', | |
| $alt_state_args, | |
| $alt_values['billing_state'] | |
| ); | |
| if ( isset( $alt_inline_errors['alt_billing_state'] ) ) { | |
| echo '<span class="woocommerce-error" style="margin-top:4px;display:block;">' . wp_kses_post( $alt_inline_errors['alt_billing_state'] ) . '</span>'; | |
| } | |
| // DEBUG: Print debug info on page | |
| if ( SHAMBIX_DEBUG_MODE ) { | |
| echo '<div style="background: #fff3cd; padding: 10px; margin: 10px 0; border: 1px solid #ffc107; font-size: 12px;">'; | |
| echo '<strong>DEBUG [State Field]:</strong><br>'; | |
| echo 'Saved state value: ' . esc_html( $alt_values['billing_state'] ) . '<br>'; | |
| echo 'Current country: ' . esc_html( $alt_values['billing_country'] ) . '<br>'; | |
| echo 'Country has states: ' . ( $alt_country_has_state ? 'Yes' : 'No' ) . '<br>'; | |
| echo 'Field ID: alt_billing_state<br>'; | |
| echo 'Field name: alt_billing_state'; | |
| echo '</div>'; | |
| } | |
| } | |
| // Alternate billing country (Paese) - uses WooCommerce standard form field | |
| if ( isset( $address_fields_alt['billing_country'] ) ) { | |
| $alt_country_args = $address_fields_alt['billing_country']; | |
| $alt_country_args['label'] = __( 'Paese', TEXT_DOMAIN ); | |
| $alt_country_args['required'] = true; | |
| $alt_country_args['id'] = 'alt_billing_country'; | |
| $alt_country_args['name'] = 'alt_billing_country'; | |
| // Force layout: last half-width column | |
| if ( ! empty( $alt_country_args['class'] ) && is_array( $alt_country_args['class'] ) ) { | |
| $alt_country_args['class'] = array_diff( $alt_country_args['class'], array( 'form-row-wide' ) ); | |
| } | |
| $alt_country_args['class'][] = 'form-row-last'; | |
| $alt_country_args['class'][] = 'alt-billing-country'; | |
| $alt_country_args['clear'] = false; | |
| woocommerce_form_field( | |
| 'alt_billing_country', | |
| $alt_country_args, | |
| $alt_values['billing_country'] | |
| ); | |
| if ( isset( $alt_inline_errors['alt_billing_country'] ) ) { | |
| echo '<span class="woocommerce-error" style="margin-top:4px;display:block;">' . wp_kses_post( $alt_inline_errors['alt_billing_country'] ) . '</span>'; | |
| } | |
| } | |
| ?> | |
| <div class="clear"></div> | |
| <script type="text/javascript"> | |
| jQuery(function($) { | |
| // Debug Mode (from PHP constant) | |
| var SHAMBIX_DEBUG_MODE = <?php echo SHAMBIX_DEBUG_MODE ? 'true' : 'false'; ?>; | |
| // Debug logging wrapper function | |
| function shambix_debug_log() { | |
| if ( SHAMBIX_DEBUG_MODE ) { | |
| console.log.apply( console, arguments ); | |
| } | |
| } | |
| // Get states data from WooCommerce (same format as checkout uses) | |
| var altStatesData = <?php echo wp_json_encode( $countries_obj_alt->get_states() ); ?>; | |
| // Get the saved state value from PHP (so we can preserve it when rebuilding options) | |
| var savedStateValue = <?php echo wp_json_encode( $alt_values['billing_state'] ); ?>; | |
| var savedCountryValue = <?php echo wp_json_encode( $alt_values['billing_country'] ); ?>; | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Initialized alt address state field handler' ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: savedStateValue =', savedStateValue ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: savedCountryValue =', savedCountryValue ); | |
| // Function to update alt billing state field when country changes | |
| function updateAltBillingState(preserveValue) { | |
| var $countryField = $('#alt_billing_country'); | |
| var $stateField = $('#alt_billing_state'); | |
| var $stateWrapper = $('#alt_billing_state_field'); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: updateAltBillingState called, preserveValue =', preserveValue ); | |
| if ( ! $countryField.length || ! $stateField.length ) { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: State or country field not found!' ); | |
| return; | |
| } | |
| var selectedCountry = $countryField.val(); | |
| var states = {}; | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Selected country =', selectedCountry ); | |
| // Get states for selected country | |
| if ( selectedCountry && altStatesData && altStatesData[selectedCountry] ) { | |
| states = altStatesData[selectedCountry]; | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Found', Object.keys(states).length, 'states for country', selectedCountry ); | |
| } else { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: No states found for country', selectedCountry ); | |
| } | |
| // If country has states, show field and populate options | |
| if ( Object.keys(states).length > 0 ) { | |
| $stateWrapper.show(); | |
| // Determine which value to preserve: | |
| // - If preserveValue is true (initial load), use savedStateValue if country matches | |
| // - Otherwise, use current field value | |
| var valueToPreserve = ''; | |
| if ( preserveValue && savedStateValue && selectedCountry === savedCountryValue ) { | |
| valueToPreserve = savedStateValue; | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Preserving saved state value:', valueToPreserve, '(country matches)' ); | |
| } else { | |
| valueToPreserve = $stateField.val(); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Using current field value:', valueToPreserve, '(preserveValue =', preserveValue, ', country match =', (selectedCountry === savedCountryValue), ')' ); | |
| } | |
| // Clear and rebuild options | |
| $stateField.empty(); | |
| $stateField.append( | |
| $('<option></option>') | |
| .attr('value', '') | |
| .text('<?php echo esc_js( __( "Seleziona un'opzione…", 'woocommerce' ) ); ?>') | |
| ); | |
| // Add state options | |
| $.each(states, function(code, name) { | |
| var $option = $('<option></option>') | |
| .attr('value', code) | |
| .text(name); | |
| // Select if this matches the value we want to preserve | |
| if ( code === valueToPreserve ) { | |
| $option.prop('selected', true); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Marking option as selected:', code, name ); | |
| } | |
| $stateField.append($option); | |
| }); | |
| // Explicitly set the value after options are added (in case selected prop didn't work) | |
| if ( valueToPreserve ) { | |
| $stateField.val(valueToPreserve); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Explicitly set field value to:', valueToPreserve, '(current field value after set:', $stateField.val(), ')' ); | |
| } else { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: No value to preserve, leaving field empty' ); | |
| } | |
| // Make required | |
| $stateField.prop('required', true); | |
| // Update label to show required asterisk | |
| var $label = $('label[for="alt_billing_state"]'); | |
| if ( $label.length && $label.find('span.required').length === 0 ) { | |
| $label.append(' <span class="required">*</span>'); | |
| } | |
| } else { | |
| // No states for this country - hide field and clear value | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: No states for country, hiding field' ); | |
| $stateWrapper.hide(); | |
| $stateField.val(''); | |
| $stateField.prop('required', false); | |
| // Remove required asterisk from label | |
| var $label = $('label[for="alt_billing_state"]'); | |
| if ( $label.length ) { | |
| $label.find('span.required').remove(); | |
| } | |
| } | |
| } | |
| // Update state field when country changes (don't preserve saved value on manual change) | |
| $('#alt_billing_country').on('change', function() { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Country field changed' ); | |
| updateAltBillingState(false); | |
| }); | |
| // Monitor form submission to log state value | |
| $('form.woocommerce-EditAccountForm').on('submit', function() { | |
| var stateValue = $('#alt_billing_state').val(); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Form submitting, alt_billing_state value =', stateValue ); | |
| }); | |
| // Run once on page load to set initial state (preserve saved value) | |
| // Use a small delay to ensure the field is fully rendered | |
| setTimeout(function() { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [JS]: Running initial state update after page load' ); | |
| updateAltBillingState(true); | |
| }, 100); | |
| }); | |
| </script> | |
| <p class="form-row form-row-first"> | |
| <label for="alt_billing_phone"><?php esc_html_e( 'Telefono', TEXT_DOMAIN ); ?></label> | |
| <input type="tel" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_phone" id="alt_billing_phone" value="<?php echo esc_attr( $alt_values['billing_phone'] ); ?>" /> | |
| </p> | |
| <p class="form-row form-row-last"> | |
| <label for="alt_billing_email"><?php esc_html_e( 'Email', TEXT_DOMAIN ); ?> <span class="required">*</span></label> | |
| <input type="email" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_email" id="alt_billing_email" value="<?php echo esc_attr( $alt_values['billing_email'] ); ?>" /> | |
| </p> | |
| <div class="clear"></div> | |
| <p class="form-row form-row-first"> | |
| <label for="alt_billing_cf2"><?php esc_html_e( 'Codice Fiscale', TEXT_DOMAIN ); ?></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_cf2" id="alt_billing_cf2" value="<?php echo esc_attr( $alt_values['billing_cf2'] ); ?>" /> | |
| </p> | |
| <p class="form-row form-row-last"> | |
| <label for="alt_billing_cf"><?php esc_html_e( 'Partita IVA', TEXT_DOMAIN ); ?></label> | |
| <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="alt_billing_cf" id="alt_billing_cf" value="<?php echo esc_attr( $alt_values['billing_cf'] ); ?>" /> | |
| </p> | |
| <div class="clear"></div> | |
| <p style="margin-top: 20px;"> | |
| <button type="submit" class="woocommerce-Button button<?php echo esc_attr( wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '' ); ?>" name="save_account_details" value="<?php esc_attr_e( 'Salva cambiamenti', 'woocommerce' ); ?>"><?php esc_html_e( 'Salva cambiamenti', 'woocommerce' ); ?></button> | |
| </p> | |
| </fieldset> | |
| <?php | |
| } | |
| /** | |
| * Save alternate billing address from My Account > Edit Account page | |
| */ | |
| add_action( 'woocommerce_save_account_details', 'shambix_save_alternate_address_from_edit_account', 15, 1 ); | |
| function shambix_save_alternate_address_from_edit_account( $user_id ) { | |
| // DEBUG: Log save function called | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: Save function called for user ' . $user_id ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: POST data keys: ' . print_r( array_keys( $_POST ), true ) ); | |
| // Check if user wants to delete alternate address | |
| if ( isset( $_POST['shambix_delete_alt_address'] ) && $_POST['shambix_delete_alt_address'] == '1' ) { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: User requested deletion of alt address' ); | |
| delete_user_meta( $user_id, 'shambix_alt_billing_address' ); | |
| return; | |
| } | |
| // Start from existing alternate address so we don't accidentally drop fields | |
| // (e.g. if a field is missing from POST for some reason). | |
| $existing_address = get_user_meta( $user_id, 'shambix_alt_billing_address', true ); | |
| $alternate_address = is_array( $existing_address ) ? $existing_address : array(); | |
| // DEBUG: Log existing address | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: Existing alternate address: ' . print_r( $existing_address, true ) ); | |
| // Save alternate address (only if at least one field is filled) | |
| $has_data = false; | |
| $fields = array( | |
| 'billing_first_name', 'billing_last_name', 'billing_company', | |
| 'billing_address_1', 'billing_address_2', 'billing_city', | |
| 'billing_state', 'billing_postcode', 'billing_country', | |
| 'billing_email', 'billing_phone', 'billing_cf', 'billing_cf2', | |
| ); | |
| foreach ( $fields as $field ) { | |
| $alt_field = 'alt_' . $field; | |
| // If the field is present in POST, update it. Otherwise, keep the existing value. | |
| if ( array_key_exists( $alt_field, $_POST ) ) { | |
| $value = sanitize_text_field( wp_unslash( $_POST[ $alt_field ] ) ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: Field ' . $alt_field . ' in POST with value: "' . $value . '"' ); | |
| $alternate_address[ $field ] = $value; | |
| } else { | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: Field ' . $alt_field . ' NOT in POST, keeping existing: "' . ( isset( $alternate_address[ $field ] ) ? $alternate_address[ $field ] : '[not set]' ) . '"' ); | |
| } | |
| // Track whether at least one field has a non-empty value. | |
| if ( ! empty( $alternate_address[ $field ] ) ) { | |
| $has_data = true; | |
| } | |
| } | |
| // DEBUG: Log final alternate address before saving | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: Final alternate address to save: ' . print_r( $alternate_address, true ) ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: billing_state value: "' . ( isset( $alternate_address['billing_state'] ) ? $alternate_address['billing_state'] : '[not set]' ) . '"' ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: Has data: ' . ( $has_data ? 'yes' : 'no' ) ); | |
| // Only save if at least one field has data | |
| if ( $has_data ) { | |
| // WooCommerce form fields already output the correct state/country codes, | |
| // so we can save them directly without normalization | |
| update_user_meta( $user_id, 'shambix_alt_billing_address', $alternate_address ); | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: Successfully saved alternate address to user meta' ); | |
| } else { | |
| // If all fields are empty, delete the alternate address | |
| shambix_debug_log( 'WooMultiAddress DEBUG [SAVE]: No data, deleting alternate address' ); | |
| delete_user_meta( $user_id, 'shambix_alt_billing_address' ); | |
| } | |
| } | |
| /** | |
| * Prevent WooCommerce from updating user meta when using alternate address | |
| * This is critical to prevent default address from being overwritten | |
| */ | |
| add_filter( 'update_user_metadata', 'shambix_prevent_billing_update_for_alternate', 10, 5 ); | |
| function shambix_prevent_billing_update_for_alternate( $check, $user_id, $meta_key, $meta_value, $prev_value ) { | |
| // Only for logged-in users | |
| if ( ! is_user_logged_in() ) { | |
| return $check; | |
| } | |
| // Only for current user | |
| if ( $user_id !== get_current_user_id() ) { | |
| return $check; | |
| } | |
| // Check if using alternate address (check both POST and session) | |
| $selected = ''; | |
| if ( isset( $_POST['shambix_alt_billing_select'] ) ) { | |
| $selected = sanitize_text_field( $_POST['shambix_alt_billing_select'] ); | |
| } elseif ( WC()->session && WC()->session->get( 'shambix_using_alternate_billing' ) ) { | |
| $selected = 'alternate'; | |
| } | |
| // Also check if we're in an AJAX request and session indicates alternate | |
| if ( wp_doing_ajax() && WC()->session && WC()->session->get( 'shambix_selected_alt_billing' ) === 'alternate' ) { | |
| $selected = 'alternate'; | |
| } | |
| if ( $selected !== 'alternate' ) { | |
| return $check; // Allow updates for default address | |
| } | |
| // Block billing field updates when using alternate address | |
| // This includes checkout, order processing, payment completion, and AJAX updates | |
| $billing_fields = array( | |
| 'billing_first_name', 'billing_last_name', 'billing_company', | |
| 'billing_address_1', 'billing_address_2', 'billing_city', | |
| 'billing_state', 'billing_postcode', 'billing_country', | |
| 'billing_email', 'billing_phone', 'billing_cf', 'billing_cf2', | |
| ); | |
| if ( in_array( $meta_key, $billing_fields ) ) { | |
| // Return true to prevent the update | |
| shambix_debug_log( 'WooMultiAddress DEBUG: Blocked update_user_meta for ' . $meta_key . ' (alternate address in use, value: ' . ( $meta_value ? $meta_value : '[empty]' ) . ')' ); | |
| return true; | |
| } | |
| return $check; | |
| } | |
| ?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment