Files
woocommerce-subscriptions/assets/js/admin/meta-boxes-subscription.js
2025-07-10 10:19:11 +00:00

432 lines
12 KiB
JavaScript

jQuery( function ( $ ) {
var timezone = jstz.determine();
// Display the timezone for date changes
$( '#wcs-timezone' ).text( timezone.name() );
// Display times in client's timezone (based on UTC)
$( '.woocommerce-subscriptions.date-picker' ).each( function () {
var $date_input = $( this ),
date_type = $date_input.attr( 'id' ),
$hour_input = $( '#' + date_type + '_hour' ),
$minute_input = $( '#' + date_type + '_minute' ),
time = $( '#' + date_type + '_timestamp_utc' ).val(),
date = moment.unix( time );
if ( time > 0 ) {
date.local();
$date_input.val(
date.year() +
'-' +
zeroise( date.months() + 1 ) +
'-' +
date.format( 'DD' )
);
$hour_input.val( date.format( 'HH' ) );
$minute_input.val( date.format( 'mm' ) );
}
} );
// Make sure start date picker is in the past
$( '.woocommerce-subscriptions.date-picker#start' ).datepicker(
'option',
'maxDate',
moment().toDate()
);
// Make sure other date pickers are in the future
$( '.woocommerce-subscriptions.date-picker:not(#start)' ).datepicker(
'option',
'minDate',
moment().add( 1, 'hours' ).toDate()
);
// Validate date when hour/minute inputs change
$( '[name$="_hour"], [name$="_minute"]' ).on( 'change', function () {
$(
'#' +
$( this )
.attr( 'name' )
.replace( '_hour', '' )
.replace( '_minute', '' )
).trigger( 'change' );
} );
// Validate entire date
$( '.woocommerce-subscriptions.date-picker' ).on( 'change', function () {
// The date was deleted, clear hour/minute inputs values and set the UTC timestamp to 0
if ( '' == $( this ).val() ) {
$( '#' + $( this ).attr( 'id' ) + '_hour' ).val( '' );
$( '#' + $( this ).attr( 'id' ) + '_minute' ).val( '' );
$( '#' + $( this ).attr( 'id' ) + '_timestamp_utc' ).val( 0 );
return;
}
var time_now = moment(),
one_hour_from_now = moment().add( 1, 'hours' ),
minimum_date = wcs_admin_meta_boxes.is_duplicate_site
? moment().add( 2, 'minutes' )
: one_hour_from_now,
$date_input = $( this ),
date_type = $date_input.attr( 'id' ),
date_pieces = $date_input.val().split( '-' ),
$hour_input = $( '#' + date_type + '_hour' ),
$minute_input = $( '#' + date_type + '_minute' ),
chosen_hour =
0 == $hour_input.val().length
? one_hour_from_now.format( 'HH' )
: $hour_input.val(),
chosen_minute =
0 == $minute_input.val().length
? one_hour_from_now.format( 'mm' )
: $minute_input.val(),
chosen_date = moment( {
years: date_pieces[ 0 ],
months: date_pieces[ 1 ] - 1,
date: date_pieces[ 2 ],
hours: chosen_hour,
minutes: chosen_minute,
seconds: one_hour_from_now.format( 'ss' ),
} );
// Make sure start date is before now.
if (
'start' == date_type &&
false === chosen_date.isBefore( time_now )
) {
alert( wcs_admin_meta_boxes.i18n_start_date_notice );
$date_input.val(
time_now.year() +
'-' +
zeroise( time_now.months() + 1 ) +
'-' +
time_now.format( 'DD' )
);
$hour_input.val( time_now.format( 'HH' ) );
$minute_input.val( time_now.format( 'mm' ) );
}
// Make sure trial end and next payment are after start date
if (
( 'trial_end' == date_type || 'next_payment' == date_type ) &&
'' != $( '#start_timestamp_utc' ).val()
) {
var change_date = false,
start = moment.unix( $( '#start_timestamp_utc' ).val() );
// Make sure trial end is after start date
if (
'trial_end' == date_type &&
chosen_date.isBefore( start, 'minute' )
) {
if ( 'trial_end' == date_type ) {
alert( wcs_admin_meta_boxes.i18n_trial_end_start_notice );
} else if ( 'next_payment' == date_type ) {
alert(
wcs_admin_meta_boxes.i18n_next_payment_start_notice
);
}
// Change the date
$date_input.val(
start.year() +
'-' +
zeroise( start.months() + 1 ) +
'-' +
start.format( 'DD' )
);
$hour_input.val( start.format( 'HH' ) );
$minute_input.val( start.format( 'mm' ) );
}
}
// Make sure next payment is after trial end
if (
'next_payment' == date_type &&
'' != $( '#trial_end_timestamp_utc' ).val()
) {
var trial_end = moment.unix(
$( '#trial_end_timestamp_utc' ).val()
);
if ( chosen_date.isBefore( trial_end, 'minute' ) ) {
alert( wcs_admin_meta_boxes.i18n_next_payment_trial_notice );
$date_input.val(
trial_end.year() +
'-' +
zeroise( trial_end.months() + 1 ) +
'-' +
trial_end.format( 'DD' )
);
$hour_input.val( trial_end.format( 'HH' ) );
$minute_input.val( trial_end.format( 'mm' ) );
}
}
// Make sure trial end is before next payment and expiration is after next payment date
else if (
( 'trial_end' == date_type || 'end' == date_type ) &&
'' != $( '#next_payment' ).val()
) {
var change_date = false,
next_payment = moment.unix(
$( '#next_payment_timestamp_utc' ).val()
);
// Make sure trial end is before or equal to next payment
if (
'trial_end' == date_type &&
next_payment.isBefore( chosen_date, 'minute' )
) {
alert( wcs_admin_meta_boxes.i18n_trial_end_next_notice );
change_date = true;
}
// Make sure end date is after next payment date
else if (
'end' == date_type &&
chosen_date.isBefore( next_payment, 'minute' )
) {
alert( wcs_admin_meta_boxes.i18n_end_date_notice );
change_date = true;
}
if ( true === change_date ) {
$date_input.val(
next_payment.year() +
'-' +
zeroise( next_payment.months() + 1 ) +
'-' +
next_payment.format( 'DD' )
);
$hour_input.val( next_payment.format( 'HH' ) );
$minute_input.val( next_payment.format( 'mm' ) );
}
}
// Make sure the date is more than an hour in the future
if (
'trial_end' != date_type &&
'start' != date_type &&
chosen_date.unix() < minimum_date.unix()
) {
alert( wcs_admin_meta_boxes.i18n_past_date_notice );
// Set date to current day
$date_input.val(
one_hour_from_now.year() +
'-' +
zeroise( one_hour_from_now.months() + 1 ) +
'-' +
one_hour_from_now.format( 'DD' )
);
// Set time if current time is in the past
if (
chosen_date.hours() < one_hour_from_now.hours() ||
( chosen_date.hours() == one_hour_from_now.hours() &&
chosen_date.minutes() < one_hour_from_now.minutes() )
) {
$hour_input.val( one_hour_from_now.format( 'HH' ) );
$minute_input.val( one_hour_from_now.format( 'mm' ) );
}
}
if ( 0 == $hour_input.val().length ) {
$hour_input.val( one_hour_from_now.format( 'HH' ) );
}
if ( 0 == $minute_input.val().length ) {
$minute_input.val( one_hour_from_now.format( 'mm' ) );
}
// Update the UTC timestamp sent to the server
date_pieces = $date_input.val().split( '-' );
var newTimeStampValue = moment( {
years: date_pieces[ 0 ],
months: date_pieces[ 1 ] - 1,
date: date_pieces[ 2 ],
hours: $hour_input.val(),
minutes: $minute_input.val(),
seconds: one_hour_from_now.format( 'ss' ),
} )
.utc()
.unix();
// Moment will return NaN if the date is invalid, that's why we need to check for NaN only.
if ( isNaN( newTimeStampValue ) ) {
wcsShowDateFieldError( date_type );
} else {
wcsHideDateFieldError( date_type );
}
// Intentionally do not prevent timestamp updates if the date is invalid.
// This way it's easier to catch invalid fields during submit event if attempted without editing invalid values.
$( '#' + date_type + '_timestamp_utc' ).val(
newTimeStampValue
);
$( 'body' ).trigger( 'wcs-updated-date', date_type );
} );
function wcsShowDateFieldError( date_type ) {
var $fieldContainer = $( '#subscription-' + date_type + '-date' );
$fieldContainer.addClass( 'has-error' );
var $messageContainer = $fieldContainer.find( '.message' );
var $messageContent = $messageContainer.find( '.message-content' );
// Clear and set content before showing to ensure screen readers announce the new message
$messageContent.text('');
$messageContainer.show();
// Use setTimeout to ensure DOM update occurs before adding new text
setTimeout(function() {
// If the focus switched to the next field voice over skips announcing the error message.
// This is a workaround to ensure the error message is announced.
$fieldContainer
.find( `input#${date_type}` )
.trigger( 'focus' )
.trigger( 'blur' );
$messageContent.text( wcs_admin_meta_boxes.i18n_invalid_date_notice );
}, 100);
}
function wcsHideDateFieldError( date_type ) {
var $fieldContainer = $( '#subscription-' + date_type + '-date' );
$fieldContainer.removeClass( 'has-error' );
var $messageContainer = $fieldContainer.find( '.message' );
var $messageContent = $messageContainer.find( '.message-content' );
$messageContainer.hide();
$messageContent.text('');
}
function zeroise( val ) {
return val > 9 ? val : '0' + val;
}
if ( $( '#parent-order-id' ).is( 'select' ) ) {
wcs_update_parent_order_options();
$( '#customer_user' ).on( 'change', wcs_update_parent_order_options );
}
function wcs_update_parent_order_options() {
// Get user ID to load orders for
var user_id = $( '#customer_user' ).val();
if ( ! user_id ) {
return false;
}
var data = {
user_id: user_id,
action: 'wcs_get_customer_orders',
security: wcs_admin_meta_boxes.get_customer_orders_nonce,
};
$( '#parent-order-id' )
.siblings( '.select2-container' )
.block( {
message: null,
overlayCSS: {
background: '#fff',
opacity: 0.6,
},
} );
$.ajax( {
url: WCSubscriptions.ajaxUrl,
data: data,
type: 'POST',
success: function ( response ) {
if ( response ) {
var $orderlist = $( '#parent-order-id' );
$( '#parent-order-id' ).select2( 'val', '' );
$orderlist.empty(); // remove old options
$orderlist.append(
$( '<option></option>' )
.attr( 'value', '' )
.text( 'Select an order' )
);
$.each( response, function ( order_id, order_number ) {
$orderlist.append(
$( '<option></option>' )
.attr( 'value', order_id )
.text( order_number )
);
} );
$( '#parent-order-id' )
.siblings( '.select2-container' )
.unblock();
}
},
} );
return false;
}
$( 'body.post-type-shop_subscription #post, body.woocommerce_page_wc-orders--shop_subscription #order' ).on( 'submit', function ( evt ) {
var invalid_dates = [];
$( '.woocommerce-subscriptions.date-picker' ).each( function () {
var $date_input = $( this );
var date_type = $date_input.attr( 'id' );
var timestamp = $( '#' + date_type + '_timestamp_utc' ).val();
// At this point, timestamp is a string, not a number.
// We check for NaN only because everything else should be a valid timestamp set during the change event.
if ( timestamp === 'NaN' ) {
invalid_dates.push( date_type );
}
} );
if ( invalid_dates.length > 0 ) {
// Focus the first invalid date to make it noticeable.
$( '#subscription-' + invalid_dates[0] + '-date' ).find( '.wcs-date-input input' ).first().focus();
return false;
}
} )
$( 'body.post-type-shop_subscription #post, body.woocommerce_page_wc-orders--shop_subscription #order' ).on( 'submit', function () {
if (
'wcs_process_renewal' ==
$(
'body.post-type-shop_subscription select[name="wc_order_action"], body.woocommerce_page_wc-orders--shop_subscription select[name="wc_order_action"]'
).val()
) {
return confirm(
wcs_admin_meta_boxes.process_renewal_action_warning
);
}
} );
$( 'body.post-type-shop_subscription #post, body.woocommerce_page_wc-orders--shop_subscription #order' ).on( 'submit', function () {
if (
typeof wcs_admin_meta_boxes.change_payment_method_warning !=
'undefined' &&
wcs_admin_meta_boxes.payment_method != $( '#_payment_method' ).val()
) {
return confirm(
wcs_admin_meta_boxes.change_payment_method_warning
);
}
} );
/**
* When the auto-renewal is toggled on or off, show or hide the chosen payment methods meta fields.
*/
$( '#wc-subscription-auto-renew' ).on( 'change', function() {
var $payment_method_meta_elements = $( '#wcs_' + $( '#_payment_method' ).val() + '_fields' );
if ( $( this ).is( ':checked' ) ) {
$payment_method_meta_elements.fadeIn();
} else {
$payment_method_meta_elements.fadeOut();
}
} );
} );