Skip to content

Instantly share code, notes, and snippets.

@Jany-M
Last active December 19, 2025 01:03
Show Gist options
  • Select an option

  • Save Jany-M/ba8bb2b6daedd14d88230ad288c4b00c to your computer and use it in GitHub Desktop.

Select an option

Save Jany-M/ba8bb2b6daedd14d88230ad288c4b00c to your computer and use it in GitHub Desktop.
[WOO] Smart multiple billing address for WooCommerce
<?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 ); ?>&nbsp;<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 ); ?>&nbsp;<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 ); ?>&nbsp;<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 ); ?>&nbsp;<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 ); ?>&nbsp;<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 ); ?>&nbsp;<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 ); ?>&nbsp;<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 ); ?>&nbsp;<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 ); ?>&nbsp;<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 ); ?>&nbsp;<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 ); ?>&nbsp;<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('&nbsp;<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 ); ?>&nbsp;<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