installed plugin Easy Digital Downloads
version 3.1.0.3
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
/* global jQuery */
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
$( '.edd-advanced-filters-button' ).on( 'click', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
$( this ).closest( '#edd-advanced-filters' ).toggleClass( 'open' );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,147 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { getChosenVars } from 'utils/chosen.js';
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
|
||||
// Globally apply to elements on the page.
|
||||
$( '.edd-select-chosen' ).each( function() {
|
||||
const el = $( this );
|
||||
el.chosen( getChosenVars( el ) );
|
||||
} );
|
||||
|
||||
$( '.edd-select-chosen .chosen-search input' ).each( function() {
|
||||
// Bail if placeholder already set
|
||||
if ( $( this ).attr( 'placeholder' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectElem = $( this ).parent().parent().parent().prev( 'select.edd-select-chosen' ),
|
||||
placeholder = selectElem.data( 'search-placeholder' );
|
||||
|
||||
if ( placeholder ) {
|
||||
$( this ).attr( 'placeholder', placeholder );
|
||||
}
|
||||
} );
|
||||
|
||||
// Add placeholders for Chosen input fields
|
||||
$( '.chosen-choices' ).on( 'click', function() {
|
||||
let placeholder = $( this ).parent().prev().data( 'search-placeholder' );
|
||||
if ( typeof placeholder === 'undefined' ) {
|
||||
placeholder = edd_vars.type_to_search;
|
||||
}
|
||||
$( this ).children( 'li' ).children( 'input' ).attr( 'placeholder', placeholder );
|
||||
} );
|
||||
|
||||
// This fixes the Chosen box being 0px wide when the thickbox is opened
|
||||
$( '#post' ).on( 'click', '.edd-thickbox', function() {
|
||||
$( '.edd-select-chosen', '#choose-download' ).css( 'width', '100%' );
|
||||
} );
|
||||
|
||||
// Variables for setting up the typing timer
|
||||
// Time in ms, Slow - 521ms, Moderate - 342ms, Fast - 300ms
|
||||
let userInteractionInterval = 342,
|
||||
typingTimerElements = '.edd-select-chosen .chosen-search input, .edd-select-chosen .search-field input',
|
||||
typingTimer;
|
||||
|
||||
// Replace options with search results
|
||||
$( document.body ).on( 'keyup', typingTimerElements, _.debounce( function( e ) {
|
||||
let element = $( this ),
|
||||
val = element.val(),
|
||||
container = element.closest( '.edd-select-chosen' ),
|
||||
|
||||
select = container.prev(),
|
||||
select_type = select.data( 'search-type' ),
|
||||
no_bundles = container.hasClass( 'no-bundles' ),
|
||||
variations = container.hasClass( 'variations' ),
|
||||
variations_only = container.hasClass( 'variations-only' ),
|
||||
|
||||
lastKey = e.which,
|
||||
search_type = 'edd_download_search';
|
||||
|
||||
// String replace the chosen container IDs
|
||||
container.attr( 'id' ).replace( '_chosen', '' );
|
||||
|
||||
// Detect if we have a defined search type, otherwise default to downloads
|
||||
if ( typeof select_type !== 'undefined' ) {
|
||||
// Don't trigger AJAX if this select has all options loaded
|
||||
if ( 'no_ajax' === select_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
search_type = 'edd_' + select_type + '_search';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't fire if short or is a modifier key (shift, ctrl, apple command key, or arrow keys)
|
||||
if (
|
||||
( val.length <= 3 && 'edd_download_search' === search_type ) ||
|
||||
(
|
||||
lastKey === 16 ||
|
||||
lastKey === 13 ||
|
||||
lastKey === 91 ||
|
||||
lastKey === 17 ||
|
||||
lastKey === 37 ||
|
||||
lastKey === 38 ||
|
||||
lastKey === 39 ||
|
||||
lastKey === 40
|
||||
)
|
||||
) {
|
||||
container.children( '.spinner' ).remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// Maybe append a spinner
|
||||
if ( ! container.children( '.spinner' ).length ) {
|
||||
container.append( '<span class="spinner is-active"></span>' );
|
||||
}
|
||||
|
||||
$.ajax( {
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
url: ajaxurl,
|
||||
data: {
|
||||
s: val,
|
||||
action: search_type,
|
||||
no_bundles: no_bundles,
|
||||
variations: variations,
|
||||
variations_only: variations_only,
|
||||
},
|
||||
|
||||
beforeSend: function() {
|
||||
select.closest( 'ul.chosen-results' ).empty();
|
||||
},
|
||||
|
||||
success: function( data ) {
|
||||
// Remove all options but those that are selected
|
||||
$( 'option:not(:selected)', select ).remove();
|
||||
|
||||
// Add any option that doesn't already exist
|
||||
$.each( data, function( key, item ) {
|
||||
if ( ! $( 'option[value="' + item.id + '"]', select ).length ) {
|
||||
select.prepend( '<option value="' + item.id + '">' + item.name + '</option>' );
|
||||
}
|
||||
} );
|
||||
|
||||
// Get the text immediately before triggering an update.
|
||||
// Any sooner will cause the text to jump around.
|
||||
const val = element.val();
|
||||
|
||||
// Update the options
|
||||
select.trigger( 'chosen:updated' );
|
||||
|
||||
element.val( val );
|
||||
},
|
||||
} ).fail( function( response ) {
|
||||
if ( window.console && window.console.log ) {
|
||||
console.log( response );
|
||||
}
|
||||
} ).done( function( response ) {
|
||||
container.children( '.spinner' ).remove();
|
||||
} );
|
||||
}, userInteractionInterval ) );
|
||||
} );
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Date picker
|
||||
*
|
||||
* This juggles a few CSS classes to avoid styling collisions with other
|
||||
* third-party plugins.
|
||||
*/
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
const edd_datepicker = $( 'input.edd_datepicker' );
|
||||
|
||||
if ( edd_datepicker.length > 0 ) {
|
||||
edd_datepicker
|
||||
|
||||
// Disable autocomplete to avoid it covering the calendar
|
||||
.attr( 'autocomplete', 'off' )
|
||||
|
||||
// Invoke the datepickers
|
||||
.datepicker( {
|
||||
dateFormat: edd_vars.date_picker_format,
|
||||
beforeShow: function() {
|
||||
$( '#ui-datepicker-div' )
|
||||
.removeClass( 'ui-datepicker' )
|
||||
.addClass( 'edd-datepicker' );
|
||||
},
|
||||
} );
|
||||
}
|
||||
} );
|
@ -0,0 +1,23 @@
|
||||
jQuery( document ).ready( function ( $ ) {
|
||||
$( '.edd_countries_filter' ).on( 'change', function () {
|
||||
const select = $( this ),
|
||||
data = {
|
||||
action: 'edd_get_shop_states',
|
||||
country: select.val(),
|
||||
nonce: select.data( 'nonce' ),
|
||||
field_name: 'edd_regions_filter',
|
||||
};
|
||||
|
||||
$.post( ajaxurl, data, function ( response ) {
|
||||
$( 'select.edd_regions_filter' ).find( 'option:gt(0)' ).remove();
|
||||
|
||||
if ( 'nostates' !== response ) {
|
||||
$( response ).find( 'option:gt(0)' ).appendTo( 'select.edd_regions_filter' );
|
||||
}
|
||||
|
||||
$( 'select.edd_regions_filter' ).trigger( 'chosen:updated' );
|
||||
} );
|
||||
|
||||
return false;
|
||||
} );
|
||||
} );
|
@ -0,0 +1,125 @@
|
||||
/* global edd_vars */
|
||||
|
||||
document.addEventListener( 'alpine:init', () => {
|
||||
Alpine.store( 'eddNotifications', {
|
||||
isPanelOpen: false,
|
||||
notificationsLoaded: false,
|
||||
numberActiveNotifications: 0,
|
||||
activeNotifications: [],
|
||||
inactiveNotifications: [],
|
||||
|
||||
init: function() {
|
||||
const eddNotifications = this;
|
||||
|
||||
/*
|
||||
* The bubble starts out hidden until AlpineJS is initialized. Once it is, we remove
|
||||
* the hidden class. This prevents a flash of the bubble's visibility in the event that there
|
||||
* are no notifications.
|
||||
*/
|
||||
const notificationCountBubble = document.querySelector( '#edd-notification-button .edd-number' );
|
||||
if ( notificationCountBubble ) {
|
||||
notificationCountBubble.classList.remove( 'edd-hidden' );
|
||||
}
|
||||
|
||||
document.addEventListener( 'keydown', function( e ) {
|
||||
if ( e.key === 'Escape' ) {
|
||||
eddNotifications.closePanel();
|
||||
}
|
||||
} );
|
||||
|
||||
const params = new URLSearchParams( window.location.search );
|
||||
|
||||
const triggerNotifications = params.has( 'notifications' );
|
||||
if ( triggerNotifications && 'true' === params.get( 'notifications' ) ) {
|
||||
eddNotifications.openPanel();
|
||||
}
|
||||
},
|
||||
|
||||
openPanel: function() {
|
||||
const panelHeader = document.getElementById( 'edd-notifications-header' );
|
||||
|
||||
if ( this.notificationsLoaded ) {
|
||||
this.isPanelOpen = true;
|
||||
if ( panelHeader ) {
|
||||
setTimeout( function() {
|
||||
panelHeader.focus();
|
||||
} );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isPanelOpen = true;
|
||||
|
||||
this.apiRequest( '/notifications', 'GET' )
|
||||
.then( data => {
|
||||
this.activeNotifications = data.active;
|
||||
this.inactiveNotifications = data.dismissed;
|
||||
this.notificationsLoaded = true;
|
||||
|
||||
if ( panelHeader ) {
|
||||
panelHeader.focus();
|
||||
}
|
||||
} )
|
||||
.catch( error => {
|
||||
console.log( 'Notification error', error );
|
||||
} );
|
||||
},
|
||||
|
||||
closePanel: function() {
|
||||
if ( ! this.isPanelOpen ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isPanelOpen = false;
|
||||
|
||||
const notificationButton = document.getElementById( 'edd-notification-button' );
|
||||
if ( notificationButton ) {
|
||||
notificationButton.focus();
|
||||
}
|
||||
},
|
||||
|
||||
apiRequest: function( endpoint, method ) {
|
||||
return fetch( edd_vars.restBase + endpoint, {
|
||||
method: method,
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': edd_vars.restNonce
|
||||
}
|
||||
} ).then( response => {
|
||||
if ( ! response.ok ) {
|
||||
return Promise.reject( response );
|
||||
}
|
||||
|
||||
/*
|
||||
* Returning response.text() instead of response.json() because dismissing
|
||||
* a notification doesn't return a JSON response, so response.json() will break.
|
||||
*/
|
||||
return response.text();
|
||||
//return response.json();
|
||||
} ).then( data => {
|
||||
return data ? JSON.parse( data ) : null;
|
||||
} );
|
||||
} ,
|
||||
|
||||
dismiss: function( event, index ) {
|
||||
if ( 'undefined' === typeof this.activeNotifications[ index ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.target.disabled = true;
|
||||
|
||||
const notification = this.activeNotifications[ index ];
|
||||
|
||||
this.apiRequest( '/notifications/' + notification.id, 'DELETE' )
|
||||
.then( response => {
|
||||
this.activeNotifications.splice( index, 1 );
|
||||
this.numberActiveNotifications = this.activeNotifications.length;
|
||||
} )
|
||||
.catch( error => {
|
||||
console.log( 'Dismiss error', error );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
@ -0,0 +1,42 @@
|
||||
/* global ajaxurl */
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
/**
|
||||
* Display notices
|
||||
*/
|
||||
const topOfPageNotice = $( '.edd-admin-notice-top-of-page' );
|
||||
if ( topOfPageNotice ) {
|
||||
const topOfPageNoticeEl = topOfPageNotice.detach();
|
||||
|
||||
$( '#wpbody-content' ).prepend( topOfPageNoticeEl );
|
||||
topOfPageNotice.delay( 1000 ).slideDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss notices
|
||||
*/
|
||||
$( '.edd-promo-notice' ).each( function() {
|
||||
const notice = $( this );
|
||||
|
||||
notice.on( 'click', '.edd-promo-notice-dismiss', function( e ) {
|
||||
// Only prevent default behavior for buttons, not links.
|
||||
if ( ! $( this ).attr( 'href' ) ) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'edd_dismiss_promo_notice',
|
||||
notice_id: notice.data( 'id' ),
|
||||
nonce: notice.data( 'nonce' ),
|
||||
lifespan: notice.data( 'lifespan' )
|
||||
},
|
||||
url: ajaxurl,
|
||||
success: function( response ) {
|
||||
notice.slideUp();
|
||||
}
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Sortables
|
||||
*
|
||||
* This makes certain settings sortable, and attempts to stash the results
|
||||
* in the nearest .edd-order input value.
|
||||
*/
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
const edd_sortables = $( 'ul.edd-sortable-list' );
|
||||
|
||||
if ( edd_sortables.length > 0 ) {
|
||||
edd_sortables.sortable( {
|
||||
axis: 'y',
|
||||
items: 'li',
|
||||
cursor: 'move',
|
||||
tolerance: 'pointer',
|
||||
containment: 'parent',
|
||||
distance: 2,
|
||||
opacity: 0.7,
|
||||
scroll: true,
|
||||
|
||||
/**
|
||||
* When sorting stops, assign the value to the previous input.
|
||||
* This input should be a hidden text field
|
||||
*/
|
||||
stop: function() {
|
||||
const keys = $.map( $( this ).children( 'li' ), function( el ) {
|
||||
return $( el ).data( 'key' );
|
||||
} );
|
||||
|
||||
$( this ).prev( 'input.edd-order' ).val( keys );
|
||||
},
|
||||
} );
|
||||
}
|
||||
} );
|
@ -0,0 +1,7 @@
|
||||
/* global jQuery */
|
||||
|
||||
jQuery( document ).ready( function ( $ ) {
|
||||
if ( $( 'body' ).hasClass( 'taxonomy-download_category' ) || $( 'body' ).hasClass( 'taxonomy-download_tag' ) ) {
|
||||
$( '.nav-tab-wrapper, .nav-tab-wrapper + br' ).detach().insertAfter( '.wp-header-end' );
|
||||
}
|
||||
} );
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Attach tooltips
|
||||
*
|
||||
* @param {string} selector
|
||||
*/
|
||||
export const edd_attach_tooltips = function( selector ) {
|
||||
selector.tooltip( {
|
||||
content: function() {
|
||||
return $( this ).prop( 'title' );
|
||||
},
|
||||
tooltipClass: 'edd-ui-tooltip',
|
||||
position: {
|
||||
my: 'center top',
|
||||
at: 'center bottom+10',
|
||||
collision: 'flipfit',
|
||||
},
|
||||
hide: {
|
||||
duration: 200,
|
||||
},
|
||||
show: {
|
||||
duration: 200,
|
||||
},
|
||||
} );
|
||||
};
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
edd_attach_tooltips( $( '.edd-help-tip' ) );
|
||||
} );
|
@ -0,0 +1,74 @@
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
// AJAX user search
|
||||
$( '.edd-ajax-user-search' )
|
||||
|
||||
// Search
|
||||
.keyup( function() {
|
||||
let user_search = $( this ).val(),
|
||||
exclude = '';
|
||||
|
||||
if ( $( this ).data( 'exclude' ) ) {
|
||||
exclude = $( this ).data( 'exclude' );
|
||||
}
|
||||
|
||||
$( '.edd_user_search_wrap' ).addClass( 'loading' );
|
||||
|
||||
const data = {
|
||||
action: 'edd_search_users',
|
||||
user_name: user_search,
|
||||
exclude: exclude,
|
||||
};
|
||||
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
url: ajaxurl,
|
||||
|
||||
success: function( search_response ) {
|
||||
$( '.edd_user_search_wrap' ).removeClass( 'loading' );
|
||||
$( '.edd_user_search_results' ).removeClass( 'hidden' );
|
||||
$( '.edd_user_search_results span' ).html( '' );
|
||||
if ( search_response.results ) {
|
||||
$( search_response.results ).appendTo( '.edd_user_search_results span' );
|
||||
}
|
||||
},
|
||||
} );
|
||||
} )
|
||||
|
||||
// Hide
|
||||
.blur( function() {
|
||||
if ( edd_user_search_mouse_down ) {
|
||||
edd_user_search_mouse_down = false;
|
||||
} else {
|
||||
$( this ).removeClass( 'loading' );
|
||||
$( '.edd_user_search_results' ).addClass( 'hidden' );
|
||||
}
|
||||
} )
|
||||
|
||||
// Show
|
||||
.focus( function() {
|
||||
$( this ).keyup();
|
||||
} );
|
||||
|
||||
$( document.body ).on( 'click.eddSelectUser', '.edd_user_search_results span a', function( e ) {
|
||||
e.preventDefault();
|
||||
const login = $( this ).data( 'login' );
|
||||
$( '.edd-ajax-user-search' ).val( login );
|
||||
$( '.edd_user_search_results' ).addClass( 'hidden' );
|
||||
$( '.edd_user_search_results span' ).html( '' );
|
||||
} );
|
||||
|
||||
$( document.body ).on( 'click.eddCancelUserSearch', '.edd_user_search_results a.edd-ajax-user-cancel', function( e ) {
|
||||
e.preventDefault();
|
||||
$( '.edd-ajax-user-search' ).val( '' );
|
||||
$( '.edd_user_search_results' ).addClass( 'hidden' );
|
||||
$( '.edd_user_search_results span' ).html( '' );
|
||||
} );
|
||||
|
||||
// Cancel user-search.blur when picking a user
|
||||
var edd_user_search_mouse_down = false;
|
||||
$( '.edd_user_search_results' ).mousedown( function() {
|
||||
edd_user_search_mouse_down = true;
|
||||
} );
|
||||
} );
|
@ -0,0 +1,61 @@
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
|
||||
const sectionSelector = '.edd-vertical-sections.use-js';
|
||||
// If the current screen doesn't have JS sections, return.
|
||||
if ( 0 === $( sectionSelector ).length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hides the section content.
|
||||
$( `${ sectionSelector } .section-content` ).hide();
|
||||
|
||||
const hash = window.location.hash;
|
||||
if ( hash && hash.includes( 'edd_' ) ) {
|
||||
// Show the section content related to the URL.
|
||||
$( sectionSelector ).find( hash ).show();
|
||||
|
||||
// Set the aria-selected for section titles to be false
|
||||
$( `${ sectionSelector } .section-title` ).attr( 'aria-selected', 'false' ).removeClass( 'section-title--is-active' );
|
||||
|
||||
// Set aria-selected true on the related link.
|
||||
$( sectionSelector ).find( '.section-title a[href="' + hash + '"]' ).parents( '.section-title' ).attr( 'aria-selected', 'true' ).addClass( 'section-title--is-active' );
|
||||
|
||||
} else {
|
||||
// Shows the first section's content.
|
||||
$( `${ sectionSelector } .section-content:first-child` ).show();
|
||||
|
||||
// Makes the 'aria-selected' attribute true for the first section nav item.
|
||||
$( `${ sectionSelector } .section-nav li:first-child` ).attr( 'aria-selected', 'true' ).addClass( 'section-title--is-active' );
|
||||
}
|
||||
|
||||
// When a section nav item is clicked.
|
||||
$( `${ sectionSelector } .section-nav li a` ).on( 'click',
|
||||
function( j ) {
|
||||
// Prevent the default browser action when a link is clicked.
|
||||
j.preventDefault();
|
||||
|
||||
// Get the `href` attribute of the item.
|
||||
const them = $( this ),
|
||||
href = them.attr( 'href' ),
|
||||
rents = them.parents( '.edd-vertical-sections' );
|
||||
|
||||
// Hide all section content.
|
||||
rents.find( '.section-content' ).hide();
|
||||
|
||||
// Find the section content that matches the section nav item and show it.
|
||||
rents.find( href ).show();
|
||||
|
||||
// Set the `aria-selected` attribute to false for all section nav items.
|
||||
rents.find( '.section-title' ).attr( 'aria-selected', 'false' ).removeClass( 'section-title--is-active' );
|
||||
|
||||
// Set the `aria-selected` attribute to true for this section nav item.
|
||||
them.parent().attr( 'aria-selected', 'true' ).addClass( 'section-title--is-active' );
|
||||
|
||||
// Maybe re-Chosen
|
||||
rents.find( 'div.chosen-container' ).css( 'width', '100%' );
|
||||
|
||||
// Add the current "link" to the page URL
|
||||
window.history.pushState( 'object or string', '', href );
|
||||
}
|
||||
); // click()
|
||||
} );
|
@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Customer management screen JS
|
||||
*/
|
||||
var EDD_Customer = {
|
||||
|
||||
vars: {
|
||||
customer_card_wrap_editable: $( '#edit-customer-info .editable' ),
|
||||
customer_card_wrap_edit_item: $( '#edit-customer-info .edit-item' ),
|
||||
user_id: $( 'input[name="customerinfo[user_id]"]' ),
|
||||
},
|
||||
init: function() {
|
||||
this.edit_customer();
|
||||
this.add_email();
|
||||
this.user_search();
|
||||
this.remove_user();
|
||||
this.cancel_edit();
|
||||
this.change_country();
|
||||
this.delete_checked();
|
||||
},
|
||||
edit_customer: function() {
|
||||
$( document.body ).on( 'click', '#edit-customer', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
EDD_Customer.vars.customer_card_wrap_editable.hide();
|
||||
EDD_Customer.vars.customer_card_wrap_edit_item.show().css( 'display', 'block' );
|
||||
} );
|
||||
},
|
||||
add_email: function() {
|
||||
$( document.body ).on( 'click', '#add-customer-email', function( e ) {
|
||||
e.preventDefault();
|
||||
const button = $( this ),
|
||||
wrapper = button.parent().parent().parent().parent(),
|
||||
customer_id = wrapper.find( 'input[name="customer-id"]' ).val(),
|
||||
email = wrapper.find( 'input[name="additional-email"]' ).val(),
|
||||
primary = wrapper.find( 'input[name="make-additional-primary"]' ).is( ':checked' ),
|
||||
nonce = wrapper.find( 'input[name="add_email_nonce"]' ).val(),
|
||||
postData = {
|
||||
edd_action: 'customer-add-email',
|
||||
customer_id: customer_id,
|
||||
email: email,
|
||||
primary: primary,
|
||||
_wpnonce: nonce,
|
||||
};
|
||||
|
||||
wrapper.parent().find( '.notice-container' ).remove();
|
||||
wrapper.find( '.spinner' ).css( 'visibility', 'visible' );
|
||||
button.attr( 'disabled', true );
|
||||
|
||||
$.post( ajaxurl, postData, function( response ) {
|
||||
setTimeout( function() {
|
||||
if ( true === response.success ) {
|
||||
window.location.href = response.redirect;
|
||||
} else {
|
||||
button.attr( 'disabled', false );
|
||||
wrapper.before( '<div class="notice-container"><div class="notice notice-error inline"><p>' + response.message + '</p></div></div>' );
|
||||
wrapper.find( '.spinner' ).css( 'visibility', 'hidden' );
|
||||
}
|
||||
}, 342 );
|
||||
}, 'json' );
|
||||
} );
|
||||
},
|
||||
user_search: function() {
|
||||
// Upon selecting a user from the dropdown, we need to update the User ID
|
||||
$( document.body ).on( 'click.eddSelectUser', '.edd_user_search_results a', function( e ) {
|
||||
e.preventDefault();
|
||||
const user_id = $( this ).data( 'userid' );
|
||||
EDD_Customer.vars.user_id.val( user_id );
|
||||
} );
|
||||
},
|
||||
remove_user: function() {
|
||||
$( document.body ).on( 'click', '#disconnect-customer', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
if ( confirm( edd_vars.disconnect_customer ) ) {
|
||||
const customer_id = $( 'input[name="customerinfo[id]"]' ).val(),
|
||||
postData = {
|
||||
edd_action: 'disconnect-userid',
|
||||
customer_id: customer_id,
|
||||
_wpnonce: $( '#edit-customer-info #_wpnonce' ).val(),
|
||||
};
|
||||
|
||||
$.post( ajaxurl, postData, function( response ) {
|
||||
// Weird
|
||||
window.location.href = window.location.href;
|
||||
}, 'json' );
|
||||
}
|
||||
} );
|
||||
},
|
||||
cancel_edit: function() {
|
||||
$( document.body ).on( 'click', '#edd-edit-customer-cancel', function( e ) {
|
||||
e.preventDefault();
|
||||
EDD_Customer.vars.customer_card_wrap_edit_item.hide();
|
||||
EDD_Customer.vars.customer_card_wrap_editable.show();
|
||||
|
||||
$( '.edd_user_search_results' ).html( '' );
|
||||
} );
|
||||
},
|
||||
change_country: function() {
|
||||
$( 'select[name="customerinfo[country]"]' ).change( function() {
|
||||
const select = $( this ),
|
||||
state_input = $( ':input[name="customerinfo[region]"]' ),
|
||||
data = {
|
||||
action: 'edd_get_shop_states',
|
||||
country: select.val(),
|
||||
nonce: select.data( 'nonce' ),
|
||||
field_name: 'customerinfo[region]',
|
||||
};
|
||||
|
||||
$.post( ajaxurl, data, function( response ) {
|
||||
console.log( response );
|
||||
if ( 'nostates' === response ) {
|
||||
state_input.replaceWith( '<input type="text" name="' + data.field_name + '" value="" class="edd-edit-toggles medium-text"/>' );
|
||||
} else {
|
||||
state_input.replaceWith( response );
|
||||
}
|
||||
} );
|
||||
|
||||
return false;
|
||||
} );
|
||||
},
|
||||
delete_checked: function() {
|
||||
$( '#edd-customer-delete-confirm' ).change( function() {
|
||||
const records_input = $( '#edd-customer-delete-records' );
|
||||
const submit_button = $( '#edd-delete-customer' );
|
||||
|
||||
if ( $( this ).prop( 'checked' ) ) {
|
||||
records_input.attr( 'disabled', false );
|
||||
submit_button.attr( 'disabled', false );
|
||||
} else {
|
||||
records_input.attr( 'disabled', true );
|
||||
records_input.prop( 'checked', false );
|
||||
submit_button.attr( 'disabled', true );
|
||||
}
|
||||
} );
|
||||
},
|
||||
};
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
EDD_Customer.init();
|
||||
} );
|
@ -0,0 +1,14 @@
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
if ( $( '#edd_dashboard_sales' ).length ) {
|
||||
$.ajax( {
|
||||
type: 'GET',
|
||||
data: {
|
||||
action: 'edd_load_dashboard_widget',
|
||||
},
|
||||
url: ajaxurl,
|
||||
success: function( response ) {
|
||||
$( '#edd_dashboard_sales .edd-loading' ).html( response );
|
||||
},
|
||||
} );
|
||||
}
|
||||
} );
|
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
/**
|
||||
* DOM ready.
|
||||
*/
|
||||
jQueryReady( () => {
|
||||
const products = $( '#edd_products' );
|
||||
if ( ! products ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show/hide conditions based on input value.
|
||||
*/
|
||||
products.change( function() {
|
||||
$( '#edd-discount-product-conditions' ).toggle( null !== products.val() );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,43 @@
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
$( 'body' ).on( 'click', '#the-list .editinline', function() {
|
||||
let post_id = $( this ).closest( 'tr' ).attr( 'id' );
|
||||
|
||||
post_id = post_id.replace( 'post-', '' );
|
||||
|
||||
const $edd_inline_data = $( '#post-' + post_id );
|
||||
|
||||
const regprice = $edd_inline_data.find( '.column-price .downloadprice-' + post_id ).val();
|
||||
|
||||
// If variable priced product disable editing, otherwise allow price changes
|
||||
if ( regprice !== $( '#post-' + post_id + '.column-price .downloadprice-' + post_id ).val() ) {
|
||||
$( '.regprice', '#edd-download-data' ).val( regprice ).attr( 'disabled', false );
|
||||
} else {
|
||||
$( '.regprice', '#edd-download-data' ).val( edd_vars.quick_edit_warning ).attr( 'disabled', 'disabled' );
|
||||
}
|
||||
} );
|
||||
|
||||
// Bulk edit save
|
||||
$( document.body ).on( 'click', '#bulk_edit', function() {
|
||||
// define the bulk edit row
|
||||
const $bulk_row = $( '#bulk-edit' );
|
||||
|
||||
// get the selected post ids that are being edited
|
||||
const $post_ids = new Array();
|
||||
$bulk_row.find( '#bulk-titles' ).children().each( function() {
|
||||
$post_ids.push( $( this ).attr( 'id' ).replace( /^(ttle)/i, '' ) );
|
||||
} );
|
||||
|
||||
// get the stock and price values to save for all the product ID's
|
||||
const $price = $( '#edd-download-data input[name="_edd_regprice"]' ).val();
|
||||
|
||||
const data = {
|
||||
action: 'edd_save_bulk_edit',
|
||||
edd_bulk_nonce: $post_ids,
|
||||
post_ids: $post_ids,
|
||||
price: $price,
|
||||
};
|
||||
|
||||
// save the data
|
||||
$.post( ajaxurl, data );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,409 @@
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { getChosenVars } from 'utils/chosen.js';
|
||||
import { edd_attach_tooltips } from 'admin/components/tooltips';
|
||||
import './bulk-edit.js';
|
||||
|
||||
/**
|
||||
* Download Configuration Metabox
|
||||
*/
|
||||
var EDD_Download_Configuration = {
|
||||
init: function() {
|
||||
this.add();
|
||||
this.move();
|
||||
this.remove();
|
||||
this.type();
|
||||
this.prices();
|
||||
this.files();
|
||||
this.updatePrices();
|
||||
this.showAdvanced();
|
||||
},
|
||||
clone_repeatable: function( row ) {
|
||||
// Retrieve the highest current key
|
||||
let key = 1;
|
||||
let highest = 1;
|
||||
row.parent().find( '.edd_repeatable_row' ).each( function() {
|
||||
const current = $( this ).data( 'key' );
|
||||
if ( parseInt( current ) > highest ) {
|
||||
highest = current;
|
||||
}
|
||||
} );
|
||||
key = highest += 1;
|
||||
|
||||
const clone = row.clone();
|
||||
|
||||
clone.removeClass( 'edd_add_blank' );
|
||||
|
||||
clone.attr( 'data-key', key );
|
||||
clone.find( 'input, select, textarea' ).val( '' ).each( function() {
|
||||
let elem = $( this ),
|
||||
name = elem.attr( 'name' ),
|
||||
id = elem.attr( 'id' );
|
||||
|
||||
if ( name ) {
|
||||
name = name.replace( /\[(\d+)\]/, '[' + parseInt( key ) + ']' );
|
||||
elem.attr( 'name', name );
|
||||
}
|
||||
|
||||
elem.attr( 'data-key', key );
|
||||
|
||||
if ( typeof id !== 'undefined' ) {
|
||||
id = id.replace( /(\d+)/, parseInt( key ) );
|
||||
elem.attr( 'id', id );
|
||||
}
|
||||
} );
|
||||
|
||||
/** manually update any select box values */
|
||||
clone.find( 'select' ).each( function() {
|
||||
$( this ).val( row.find( 'select[name="' + $( this ).attr( 'name' ) + '"]' ).val() );
|
||||
} );
|
||||
|
||||
/** manually uncheck any checkboxes */
|
||||
clone.find( 'input[type="checkbox"]' ).each( function() {
|
||||
// Make sure checkboxes are unchecked when cloned
|
||||
const checked = $( this ).is( ':checked' );
|
||||
if ( checked ) {
|
||||
$( this ).prop( 'checked', false );
|
||||
}
|
||||
|
||||
// reset the value attribute to 1 in order to properly save the new checked state
|
||||
$( this ).val( 1 );
|
||||
} );
|
||||
|
||||
clone.find( 'span.edd_price_id' ).each( function() {
|
||||
$( this ).text( parseInt( key ) );
|
||||
} );
|
||||
|
||||
clone.find( 'input.edd_repeatable_index' ).each( function() {
|
||||
$( this ).val( parseInt( $( this ).data( 'key' ) ) );
|
||||
} );
|
||||
|
||||
clone.find( 'span.edd_file_id' ).each( function() {
|
||||
$( this ).text( parseInt( key ) );
|
||||
} );
|
||||
|
||||
clone.find( '.edd_repeatable_default_input' ).each( function() {
|
||||
$( this ).val( parseInt( key ) ).removeAttr( 'checked' );
|
||||
} );
|
||||
|
||||
clone.find( '.edd_repeatable_condition_field' ).each( function() {
|
||||
$( this ).find( 'option:eq(0)' ).prop( 'selected', 'selected' );
|
||||
} );
|
||||
|
||||
clone.find( 'label' ).each( function () {
|
||||
var labelFor = $( this ).attr( 'for' );
|
||||
if ( labelFor ) {
|
||||
$( this ).attr( 'for', labelFor.replace( /(\d+)/, parseInt( key ) ) );
|
||||
}
|
||||
} );
|
||||
|
||||
// Remove Chosen elements
|
||||
clone.find( '.search-choice' ).remove();
|
||||
clone.find( '.chosen-container' ).remove();
|
||||
edd_attach_tooltips( clone.find( '.edd-help-tip' ) );
|
||||
|
||||
return clone;
|
||||
},
|
||||
|
||||
add: function() {
|
||||
$( document.body ).on( 'click', '.edd_add_repeatable', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const button = $( this ),
|
||||
row = button.closest( '.edd_repeatable_table' ).find( '.edd_repeatable_row' ).last(),
|
||||
clone = EDD_Download_Configuration.clone_repeatable( row );
|
||||
|
||||
clone.insertAfter( row ).find( 'input, textarea, select' ).filter( ':visible' ).eq( 0 ).focus();
|
||||
|
||||
// Setup chosen fields again if they exist
|
||||
clone.find( '.edd-select-chosen' ).each( function() {
|
||||
const el = $( this );
|
||||
el.chosen( getChosenVars( el ) );
|
||||
} );
|
||||
clone.find( '.edd-select-chosen' ).css( 'width', '100%' );
|
||||
clone.find( '.edd-select-chosen .chosen-search input' ).attr( 'placeholder', edd_vars.search_placeholder );
|
||||
} );
|
||||
},
|
||||
|
||||
move: function() {
|
||||
$( '.edd_repeatable_table .edd-repeatables-wrap' ).sortable( {
|
||||
axis: 'y',
|
||||
handle: '.edd-draghandle-anchor',
|
||||
items: '.edd_repeatable_row',
|
||||
cursor: 'move',
|
||||
tolerance: 'pointer',
|
||||
containment: 'parent',
|
||||
distance: 2,
|
||||
opacity: 0.7,
|
||||
scroll: true,
|
||||
|
||||
update: function() {
|
||||
let count = 0;
|
||||
$( this ).find( '.edd_repeatable_row' ).each( function() {
|
||||
$( this ).find( 'input.edd_repeatable_index' ).each( function() {
|
||||
$( this ).val( count );
|
||||
} );
|
||||
count++;
|
||||
} );
|
||||
},
|
||||
start: function( e, ui ) {
|
||||
ui.placeholder.height( ui.item.height() - 2 );
|
||||
},
|
||||
} );
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
$( document.body ).on( 'click', '.edd-remove-row, .edd_remove_repeatable', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
let row = $( this ).parents( '.edd_repeatable_row' ),
|
||||
count = row.parent().find( '.edd_repeatable_row' ).length,
|
||||
type = $( this ).data( 'type' ),
|
||||
repeatable = 'div.edd_repeatable_' + type + 's',
|
||||
focusElement,
|
||||
focusable,
|
||||
firstFocusable;
|
||||
|
||||
// Set focus on next element if removing the first row. Otherwise set focus on previous element.
|
||||
if ( $( this ).is( '.ui-sortable .edd_repeatable_row:first-child .edd-remove-row, .ui-sortable .edd_repeatable_row:first-child .edd_remove_repeatable' ) ) {
|
||||
focusElement = row.next( '.edd_repeatable_row' );
|
||||
} else {
|
||||
focusElement = row.prev( '.edd_repeatable_row' );
|
||||
}
|
||||
|
||||
focusable = focusElement.find( 'select, input, textarea, button' ).filter( ':visible' );
|
||||
firstFocusable = focusable.eq( 0 );
|
||||
|
||||
if ( type === 'price' ) {
|
||||
const price_row_id = row.data( 'key' );
|
||||
/** remove from price condition */
|
||||
$( '.edd_repeatable_condition_field option[value="' + price_row_id + '"]' ).remove();
|
||||
}
|
||||
|
||||
if ( count > 1 ) {
|
||||
$( 'input, select', row ).val( '' );
|
||||
row.fadeOut( 'fast' ).remove();
|
||||
firstFocusable.focus();
|
||||
} else {
|
||||
switch ( type ) {
|
||||
case 'price' :
|
||||
alert( edd_vars.one_price_min );
|
||||
break;
|
||||
case 'file' :
|
||||
$( 'input, select', row ).val( '' );
|
||||
break;
|
||||
default:
|
||||
alert( edd_vars.one_field_min );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* re-index after deleting */
|
||||
$( repeatable ).each( function( rowIndex ) {
|
||||
$( this ).find( 'input, select' ).each( function() {
|
||||
let name = $( this ).attr( 'name' );
|
||||
name = name.replace( /\[(\d+)\]/, '[' + rowIndex + ']' );
|
||||
$( this ).attr( 'name', name ).attr( 'id', name );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
},
|
||||
|
||||
type: function() {
|
||||
$( document.body ).on( 'change', '#_edd_product_type', function( e ) {
|
||||
const edd_products = $( '#edd_products' ),
|
||||
edd_download_files = $( '#edd_download_files' ),
|
||||
edd_download_limit_wrap = $( '#edd_download_limit_wrap' );
|
||||
|
||||
if ( 'bundle' === $( this ).val() ) {
|
||||
edd_products.show();
|
||||
edd_download_files.hide();
|
||||
edd_download_limit_wrap.hide();
|
||||
} else {
|
||||
edd_products.hide();
|
||||
edd_download_files.show();
|
||||
edd_download_limit_wrap.show();
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
prices: function() {
|
||||
$( document.body ).on( 'change', '#edd_variable_pricing', function( e ) {
|
||||
const checked = $( this ).is( ':checked' ),
|
||||
single = $( '#edd_regular_price_field' ),
|
||||
variable = $( '#edd_variable_price_fields, .edd_repeatable_table .pricing' ),
|
||||
bundleRow = $( '.edd-bundled-product-row, .edd-repeatable-row-standard-fields' );
|
||||
|
||||
if ( checked ) {
|
||||
single.hide();
|
||||
variable.show();
|
||||
bundleRow.addClass( 'has-variable-pricing' );
|
||||
} else {
|
||||
single.show();
|
||||
variable.hide();
|
||||
bundleRow.removeClass( 'has-variable-pricing' );
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
files: function() {
|
||||
var file_frame;
|
||||
window.formfield = '';
|
||||
|
||||
$( document.body ).on( 'click', '.edd_upload_file_button', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const button = $( this );
|
||||
|
||||
window.formfield = button.closest( '.edd_repeatable_upload_wrapper' );
|
||||
|
||||
// If the media frame already exists, reopen it.
|
||||
if ( file_frame ) {
|
||||
file_frame.open();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the media frame.
|
||||
file_frame = wp.media.frames.file_frame = wp.media( {
|
||||
title: button.data( 'uploader-title' ),
|
||||
frame: 'post',
|
||||
state: 'insert',
|
||||
button: { text: button.data( 'uploader-button-text' ) },
|
||||
multiple: $( this ).data( 'multiple' ) === '0' ? false : true, // Set to true to allow multiple files to be selected
|
||||
} );
|
||||
|
||||
file_frame.on( 'menu:render:default', function( view ) {
|
||||
// Store our views in an object.
|
||||
const views = {};
|
||||
|
||||
// Unset default menu items
|
||||
view.unset( 'library-separator' );
|
||||
view.unset( 'gallery' );
|
||||
view.unset( 'featured-image' );
|
||||
view.unset( 'embed' );
|
||||
|
||||
// Initialize the views in our view object.
|
||||
view.set( views );
|
||||
} );
|
||||
|
||||
// When an image is selected, run a callback.
|
||||
file_frame.on( 'insert', function() {
|
||||
const selection = file_frame.state().get( 'selection' );
|
||||
selection.each( function( attachment, index ) {
|
||||
attachment = attachment.toJSON();
|
||||
|
||||
let selectedSize = 'image' === attachment.type ? $( '.attachment-display-settings .size option:selected' ).val() : false,
|
||||
selectedURL = attachment.url,
|
||||
selectedName = attachment.title.length > 0 ? attachment.title : attachment.filename;
|
||||
|
||||
if ( selectedSize && typeof attachment.sizes[ selectedSize ] !== 'undefined' ) {
|
||||
selectedURL = attachment.sizes[ selectedSize ].url;
|
||||
}
|
||||
|
||||
if ( 'image' === attachment.type ) {
|
||||
if ( selectedSize && typeof attachment.sizes[ selectedSize ] !== 'undefined' ) {
|
||||
selectedName = selectedName + '-' + attachment.sizes[ selectedSize ].width + 'x' + attachment.sizes[ selectedSize ].height;
|
||||
} else {
|
||||
selectedName = selectedName + '-' + attachment.width + 'x' + attachment.height;
|
||||
}
|
||||
}
|
||||
|
||||
if ( 0 === index ) {
|
||||
// place first attachment in field
|
||||
window.formfield.find( '.edd_repeatable_attachment_id_field' ).val( attachment.id );
|
||||
window.formfield.find( '.edd_repeatable_thumbnail_size_field' ).val( selectedSize );
|
||||
window.formfield.find( '.edd_repeatable_upload_field' ).val( selectedURL );
|
||||
window.formfield.find( '.edd_repeatable_name_field' ).val( selectedName );
|
||||
} else {
|
||||
// Create a new row for all additional attachments
|
||||
const row = window.formfield,
|
||||
clone = EDD_Download_Configuration.clone_repeatable( row );
|
||||
|
||||
clone.find( '.edd_repeatable_attachment_id_field' ).val( attachment.id );
|
||||
clone.find( '.edd_repeatable_thumbnail_size_field' ).val( selectedSize );
|
||||
clone.find( '.edd_repeatable_upload_field' ).val( selectedURL );
|
||||
clone.find( '.edd_repeatable_name_field' ).val( selectedName );
|
||||
clone.insertAfter( row );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
// Finally, open the modal
|
||||
file_frame.open();
|
||||
} );
|
||||
|
||||
// @todo Break this out and remove jQuery.
|
||||
$( '.edd_repeatable_upload_field' )
|
||||
.on( 'focus', function() {
|
||||
const input = $( this );
|
||||
|
||||
input.data( 'originalFile', input.val() );
|
||||
} )
|
||||
.on( 'change', function() {
|
||||
const input = $( this );
|
||||
const originalFile = input.data( 'originalFile' );
|
||||
|
||||
if ( originalFile !== input.val() ) {
|
||||
input
|
||||
.closest( '.edd-repeatable-row-standard-fields' )
|
||||
.find( '.edd_repeatable_attachment_id_field' )
|
||||
.val( 0 );
|
||||
}
|
||||
} );
|
||||
|
||||
var file_frame;
|
||||
window.formfield = '';
|
||||
},
|
||||
|
||||
updatePrices: function() {
|
||||
$( '#edd_price_fields' ).on( 'keyup', '.edd_variable_prices_name', function() {
|
||||
const key = $( this ).parents( '.edd_repeatable_row' ).data( 'key' ),
|
||||
name = $( this ).val(),
|
||||
field_option = $( '.edd_repeatable_condition_field option[value=' + key + ']' );
|
||||
|
||||
if ( field_option.length > 0 ) {
|
||||
field_option.text( name );
|
||||
} else {
|
||||
$( '.edd_repeatable_condition_field' ).append(
|
||||
$( '<option></option>' )
|
||||
.attr( 'value', key )
|
||||
.text( name )
|
||||
);
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
showAdvanced: function() {
|
||||
// Toggle display of entire custom settings section for a price option
|
||||
$( document.body ).on( 'click', '.toggle-custom-price-option-section', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const toggle = $( this ),
|
||||
show = toggle.html() === edd_vars.show_advanced_settings ?
|
||||
true :
|
||||
false;
|
||||
|
||||
if ( show ) {
|
||||
toggle.html( edd_vars.hide_advanced_settings );
|
||||
} else {
|
||||
toggle.html( edd_vars.show_advanced_settings );
|
||||
}
|
||||
|
||||
const header = toggle.parents( '.edd-repeatable-row-header' );
|
||||
header.siblings( '.edd-custom-price-option-sections-wrap' ).slideToggle();
|
||||
|
||||
let first_input;
|
||||
if ( show ) {
|
||||
first_input = $( ':input:not(input[type=button],input[type=submit],button):visible:first', header.siblings( '.edd-custom-price-option-sections-wrap' ) );
|
||||
} else {
|
||||
first_input = $( ':input:not(input[type=button],input[type=submit],button):visible:first', header.siblings( '.edd-repeatable-row-standard-fields' ) );
|
||||
}
|
||||
first_input.focus();
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
EDD_Download_Configuration.init();
|
||||
} );
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import './components/date-picker';
|
||||
import './components/chosen';
|
||||
import './components/tooltips';
|
||||
import './components/vertical-sections';
|
||||
import './components/sortable-list';
|
||||
import './components/user-search';
|
||||
import './components/advanced-filters';
|
||||
import './components/taxonomies';
|
||||
import './components/location';
|
||||
import './components/promos';
|
||||
import './components/notifications';
|
||||
// Note: This is not common across all admin pages and at some point this code will be moved to a new file that only loads on the orders table page.
|
||||
import './orders/list-table';
|
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Notes
|
||||
*/
|
||||
const EDD_Notes = {
|
||||
init: function() {
|
||||
this.enter_key();
|
||||
this.add_note();
|
||||
this.remove_note();
|
||||
},
|
||||
|
||||
enter_key: function() {
|
||||
$( document.body ).on( 'keydown', '#edd-note', function( e ) {
|
||||
if ( e.keyCode === 13 && ( e.metaKey || e.ctrlKey ) ) {
|
||||
e.preventDefault();
|
||||
$( '#edd-add-note' ).click();
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Ajax handler for adding new notes
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
add_note: function() {
|
||||
$( '#edd-add-note' ).on( 'click', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const edd_button = $( this ),
|
||||
edd_note = $( '#edd-note' ),
|
||||
edd_notes = $( '.edd-notes' ),
|
||||
edd_no_notes = $( '.edd-no-notes' ),
|
||||
edd_spinner = $( '.edd-add-note .spinner' ),
|
||||
edd_note_nonce = $( '#edd_note_nonce' );
|
||||
|
||||
const postData = {
|
||||
action: 'edd_add_note',
|
||||
nonce: edd_note_nonce.val(),
|
||||
object_id: edd_button.data( 'object-id' ),
|
||||
object_type: edd_button.data( 'object-type' ),
|
||||
note: edd_note.val(),
|
||||
};
|
||||
|
||||
if ( postData.note ) {
|
||||
edd_button.prop( 'disabled', true );
|
||||
edd_spinner.css( 'visibility', 'visible' );
|
||||
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
data: postData,
|
||||
url: ajaxurl,
|
||||
success: function( response ) {
|
||||
let res = wpAjax.parseAjaxResponse( response );
|
||||
res = res.responses[ 0 ];
|
||||
|
||||
edd_notes.append( res.data );
|
||||
edd_no_notes.hide();
|
||||
edd_button.prop( 'disabled', false );
|
||||
edd_spinner.css( 'visibility', 'hidden' );
|
||||
edd_note.val( '' );
|
||||
},
|
||||
} ).fail( function( data ) {
|
||||
if ( window.console && window.console.log ) {
|
||||
console.log( data );
|
||||
}
|
||||
edd_button.prop( 'disabled', false );
|
||||
edd_spinner.css( 'visibility', 'hidden' );
|
||||
} );
|
||||
} else {
|
||||
const border_color = edd_note.css( 'border-color' );
|
||||
|
||||
edd_note.css( 'border-color', 'red' );
|
||||
|
||||
setTimeout( function() {
|
||||
edd_note.css( 'border-color', border_color );
|
||||
}, userInteractionInterval );
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Ajax handler for deleting existing notes
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
remove_note: function() {
|
||||
$( document.body ).on( 'click', '.edd-delete-note', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const edd_link = $( this ),
|
||||
edd_notes = $( '.edd-note' ),
|
||||
edd_note = edd_link.parents( '.edd-note' ),
|
||||
edd_no_notes = $( '.edd-no-notes' ),
|
||||
edd_note_nonce = $( '#edd_note_nonce' );
|
||||
|
||||
if ( confirm( edd_vars.delete_note ) ) {
|
||||
const postData = {
|
||||
action: 'edd_delete_note',
|
||||
nonce: edd_note_nonce.val(),
|
||||
note_id: edd_link.data( 'note-id' ),
|
||||
};
|
||||
|
||||
edd_note.addClass( 'deleting' );
|
||||
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
data: postData,
|
||||
url: ajaxurl,
|
||||
success: function( response ) {
|
||||
if ( '1' === response ) {
|
||||
edd_note.remove();
|
||||
}
|
||||
|
||||
if ( edd_notes.length === 1 ) {
|
||||
edd_no_notes.show();
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
} ).fail( function( data ) {
|
||||
if ( window.console && window.console.log ) {
|
||||
console.log( data );
|
||||
}
|
||||
edd_note.removeClass( 'deleting' );
|
||||
} );
|
||||
return true;
|
||||
}
|
||||
} );
|
||||
},
|
||||
};
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
EDD_Notes.init();
|
||||
} );
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Deletes the debug log file and disables logging.
|
||||
*/
|
||||
; ( function ( document, $ ) {
|
||||
'use strict';
|
||||
|
||||
$( '#edd-disable-debug-log' ).on( 'click', function ( e ) {
|
||||
e.preventDefault();
|
||||
$( this ).attr( 'disabled', true );
|
||||
var notice = $( '#edd-debug-log-notice' );
|
||||
$.ajax( {
|
||||
type: "GET",
|
||||
data: {
|
||||
action: 'edd_disable_debugging',
|
||||
nonce: $( '#edd_debug_log_delete' ).val(),
|
||||
},
|
||||
url: ajaxurl,
|
||||
success: function ( response ) {
|
||||
notice.empty().append( response.data );
|
||||
setTimeout( function () {
|
||||
notice.slideUp();
|
||||
}, 3000 );
|
||||
}
|
||||
} ).fail( function ( response ) {
|
||||
notice.empty().append( response.responseJSON.data );
|
||||
} );
|
||||
} );
|
||||
} )( document, jQuery );
|
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import OrderOverview from './order-overview';
|
||||
import './order-details';
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
jQueryReady( () => {
|
||||
// Order Overview.
|
||||
if ( window.eddAdminOrderOverview ) {
|
||||
OrderOverview.render();
|
||||
|
||||
/**
|
||||
* Add validation to Add/Edit Order form.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
( () => {
|
||||
const overview = OrderOverview.options.state;
|
||||
const orderItems = overview.get( 'items' );
|
||||
|
||||
const noItemErrorEl = document.getElementById( 'edd-add-order-no-items-error' );
|
||||
const noCustomerErrorEl = document.getElementById( 'edd-add-order-customer-error' );
|
||||
|
||||
const assignCustomerEl = document.getElementById( 'customer_id' );
|
||||
const newCustomerEmailEl = document.getElementById( 'edd_new_customer_email' );
|
||||
|
||||
[
|
||||
'edd-add-order-form',
|
||||
'edd-edit-order-form',
|
||||
].forEach( ( form ) => {
|
||||
const formEl = document.getElementById( form );
|
||||
|
||||
if ( ! formEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
formEl.addEventListener( 'submit', submitForm );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Submits an Order form.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} event Submit event.
|
||||
*/
|
||||
function submitForm( event ) {
|
||||
let hasError = false;
|
||||
|
||||
// Ensure `OrderItem`s.
|
||||
if ( noItemErrorEl ) {
|
||||
if ( 0 === orderItems.length ) {
|
||||
noItemErrorEl.style.display = 'block';
|
||||
hasError = true;
|
||||
} else {
|
||||
noItemErrorEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure Customer.
|
||||
if ( noCustomerErrorEl ) {
|
||||
if ( '0' === assignCustomerEl.value && '' === newCustomerEmailEl.value ) {
|
||||
noCustomerErrorEl.style.display = 'block';
|
||||
hasError = true;
|
||||
} else {
|
||||
noCustomerErrorEl.style.display = 'none';
|
||||
}
|
||||
|
||||
if ( true === hasError ) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove `OrderItem` notice when an `OrderItem` is added.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
orderItems.on( 'add', function() {
|
||||
noItemErrorEl.style.display = 'none';
|
||||
} );
|
||||
|
||||
/**
|
||||
* Remove Customer notice when a Customer is changed.
|
||||
*
|
||||
* Uses a jQuery binding for Chosen support.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} event Change event.
|
||||
*/
|
||||
$( assignCustomerEl ).on( 'change', ( event ) => {
|
||||
const val = event.target.value;
|
||||
|
||||
if ( '0' !== val ) {
|
||||
noCustomerErrorEl.style.display = 'none';
|
||||
}
|
||||
} )
|
||||
|
||||
if ( newCustomerEmailEl ) {
|
||||
/**
|
||||
* Remove Customer notice when a Customer is set.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} event Input event.
|
||||
*/
|
||||
newCustomerEmailEl.addEventListener( 'input', ( event ) => {
|
||||
const val = event.target.value;
|
||||
|
||||
if ( '' !== val ) {
|
||||
noCustomerErrorEl.style.display = 'none';
|
||||
}
|
||||
} );
|
||||
}
|
||||
} )();
|
||||
}
|
||||
|
||||
// Move `.update-nag` items below the top header.
|
||||
// `#update-nag` is legacy styling, which core still supports.
|
||||
//
|
||||
// `.notice` items are properly moved, but WordPress core
|
||||
// does not move `.update-nag`.
|
||||
if ( 0 !== $( '.edit-post-editor-regions__header' ).length ) {
|
||||
$( 'div.update-nag, div#update-nag' ).insertAfter( $( '.edit-post-editor-regions__header' ) );
|
||||
}
|
||||
|
||||
} );
|
@ -0,0 +1,17 @@
|
||||
/* global $, ajaxurl */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
jQueryReady( () => {
|
||||
|
||||
$( '.download_page_edd-payment-history .row-actions .delete a' ).on( 'click', function() {
|
||||
if( confirm( edd_vars.delete_payment ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
} );
|
@ -0,0 +1,256 @@
|
||||
/* global $, ajaxurl, _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import OrderOverview from './../order-overview';
|
||||
import { getChosenVars } from 'utils/chosen.js';
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
// Store customer search results to help prefill address data.
|
||||
let CUSTOMER_SEARCH_RESULTS = {
|
||||
addresses: {
|
||||
'0': {
|
||||
address: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
region: '',
|
||||
postal_code: '',
|
||||
country: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jQueryReady( () => {
|
||||
|
||||
/**
|
||||
* Adjusts Overview tax configuration when the Customer's address changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
( () => {
|
||||
const { state: overviewState } = OrderOverview.options;
|
||||
|
||||
// No tax, do nothing.
|
||||
if ( false === overviewState.get( 'hasTax' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Editing, do nothing.
|
||||
if ( false === overviewState.get( 'isAdding' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const countryInput = document.getElementById(
|
||||
'edd_order_address_country'
|
||||
);
|
||||
const regionInput = document.getElementById(
|
||||
'edd_order_address_region'
|
||||
);
|
||||
|
||||
if ( ! ( countryInput && regionInput ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a tax rate based on the currently selected Address.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
function getTaxRate() {
|
||||
const country = $( '#edd_order_address_country' ).val();
|
||||
const region = $( '#edd_order_address_region' ).val();
|
||||
|
||||
const nonce = document.getElementById( 'edd_get_tax_rate_nonce' )
|
||||
.value;
|
||||
|
||||
wp.ajax.send( 'edd_get_tax_rate', {
|
||||
data: {
|
||||
nonce,
|
||||
country,
|
||||
region,
|
||||
},
|
||||
/**
|
||||
* Updates the Overview's tax configuration on successful retrieval.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} response AJAX response.
|
||||
*/
|
||||
success( response ) {
|
||||
let { tax_rate: rate } = response;
|
||||
|
||||
// Make a percentage.
|
||||
rate = rate * 100;
|
||||
|
||||
overviewState.set( 'hasTax', {
|
||||
...overviewState.get( 'hasTax' ),
|
||||
country,
|
||||
region,
|
||||
rate,
|
||||
} );
|
||||
},
|
||||
/*
|
||||
* Updates the Overview's tax configuration on failed retrieval.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
error() {
|
||||
overviewState.set( 'hasTax', 'none' );
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
// Update rate on Address change.
|
||||
//
|
||||
// Wait for Region field to be replaced when Country changes.
|
||||
// Wait for typing when Regino field changes.
|
||||
// jQuery listeners for Chosen compatibility.
|
||||
$( '#edd_order_address_country' ).on( 'change', _.debounce( getTaxRate, 250 ) );
|
||||
|
||||
$( '#edd-order-address' ).on( 'change', '#edd_order_address_region', getTaxRate );
|
||||
$( '#edd-order-address' ).on( 'keyup', '#edd_order_address_region', _.debounce( getTaxRate, 250 ) );
|
||||
} )();
|
||||
|
||||
$( '.edd-payment-change-customer-input' ).on( 'change', function() {
|
||||
const $this = $( this ),
|
||||
data = {
|
||||
action: 'edd_customer_addresses',
|
||||
customer_id: $this.val(),
|
||||
nonce: $( '#edd_add_order_nonce' ).val(),
|
||||
};
|
||||
|
||||
$.post( ajaxurl, data, function( response ) {
|
||||
const { success, data } = response;
|
||||
|
||||
if ( ! success ) {
|
||||
$( '.customer-address-select-wrap' ).hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Store response for later use.
|
||||
CUSTOMER_SEARCH_RESULTS = {
|
||||
...CUSTOMER_SEARCH_RESULTS,
|
||||
...data,
|
||||
addresses: {
|
||||
...CUSTOMER_SEARCH_RESULTS.addresses,
|
||||
...data.addresses,
|
||||
},
|
||||
};
|
||||
|
||||
if ( data.html ) {
|
||||
$( '.customer-address-select-wrap' ).show();
|
||||
$( '.customer-address-select-wrap .edd-form-group__control' ).html( data.html );
|
||||
} else {
|
||||
$( '.customer-address-select-wrap' ).hide();
|
||||
}
|
||||
}, 'json' );
|
||||
|
||||
return false;
|
||||
} );
|
||||
|
||||
/**
|
||||
* Retrieves a list of states based on a Country HTML <select>.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {HTMLElement} countryEl Element containing country information.
|
||||
* @param {string} fieldName the name of the field to use in response.
|
||||
* @return {$.promise} Region data response.
|
||||
*/
|
||||
function getStates( countryEl, fieldName, fieldId ) {
|
||||
const data = {
|
||||
action: 'edd_get_shop_states',
|
||||
country: countryEl.val(),
|
||||
nonce: countryEl.data( 'nonce' ),
|
||||
field_name: fieldName,
|
||||
field_id: fieldId,
|
||||
};
|
||||
|
||||
return $.post( ajaxurl, data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the Region area with the appropriate field type.
|
||||
*
|
||||
* @todo This is hacky and blindly picks elements from the DOM.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {string} regions Regions response.
|
||||
*/
|
||||
function replaceRegionField( regions ) {
|
||||
const state_wrapper = $( '#edd_order_address_region' );
|
||||
|
||||
$( '#edd_order_address_region_chosen' ).remove();
|
||||
|
||||
if ( 'nostates' === regions ) {
|
||||
state_wrapper
|
||||
.replaceWith( '<input type="text" name="edd_order_address[region]" id="edd_order_address_region" value="" class="wide-fat" style="max-width: none; width: 100%;" />' );
|
||||
} else {
|
||||
state_wrapper
|
||||
.replaceWith( regions );
|
||||
|
||||
$( '#edd_order_address_region' ).chosen( getChosenVars( $( '#edd_order_address_region' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles replacing a Region field when a Country field changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
function updateRegionFieldOnChange() {
|
||||
getStates(
|
||||
$( this ),
|
||||
'edd_order_address[region]',
|
||||
'edd_order_address_region'
|
||||
)
|
||||
.done( replaceRegionField );
|
||||
}
|
||||
|
||||
$( document.body ).on( 'change', '.customer-address-select-wrap .add-order-customer-address-select', function() {
|
||||
const $this = $( this ),
|
||||
val = $this.val(),
|
||||
address = CUSTOMER_SEARCH_RESULTS.addresses[ val ];
|
||||
|
||||
$( '#edd-add-order-form input[name="edd_order_address[address]"]' ).val( address.address );
|
||||
$( '#edd-add-order-form input[name="edd_order_address[address2]"]' ).val( address.address2 );
|
||||
$( '#edd-add-order-form input[name="edd_order_address[postal_code]"]' ).val( address.postal_code );
|
||||
$( '#edd-add-order-form input[name="edd_order_address[city]"]' ).val( address.city );
|
||||
$( '#edd-add-order-form input[name="edd_order_address[address_id]"]' ).val( val );
|
||||
|
||||
// Remove global `change` event handling to prevent loop.
|
||||
$( '#edd_order_address_country' ).off( 'change', updateRegionFieldOnChange );
|
||||
|
||||
// Set Country.
|
||||
$( '#edd_order_address_country' )
|
||||
.val( address.country )
|
||||
.trigger( 'change' )
|
||||
.trigger( 'chosen:updated' );
|
||||
|
||||
// Set Region.
|
||||
getStates(
|
||||
$( '#edd_order_address_country' ),
|
||||
'edd_order_address[region]',
|
||||
'edd_order_address_region'
|
||||
)
|
||||
.done( replaceRegionField )
|
||||
.done( ( response ) => {
|
||||
$( '#edd_order_address_region' )
|
||||
.val( address.region )
|
||||
.trigger( 'change' )
|
||||
.trigger( 'chosen:updated' );
|
||||
} );
|
||||
|
||||
// Add back global `change` event handling.
|
||||
$( '#edd_order_address_country' ).on( 'change', updateRegionFieldOnChange );
|
||||
|
||||
return false;
|
||||
} );
|
||||
|
||||
// Country change.
|
||||
$( '#edd_order_address_country' ).on( 'change', updateRegionFieldOnChange );
|
||||
|
||||
} );
|
@ -0,0 +1,69 @@
|
||||
/* global $ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
jQueryReady( () => {
|
||||
|
||||
// Change Customer.
|
||||
$( '.edd-payment-change-customer-input' ).on( 'change', function() {
|
||||
const $this = $( this ),
|
||||
data = {
|
||||
action: 'edd_customer_details',
|
||||
customer_id: $this.val(),
|
||||
nonce: $( '#edd_customer_details_nonce' ).val(),
|
||||
};
|
||||
|
||||
if ( '' === data.customer_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$( '.customer-details' ).css( 'display', 'none' );
|
||||
$( '#customer-avatar' ).html( '<span class="spinner is-active"></span>' );
|
||||
|
||||
$.post( ajaxurl, data, function( response ) {
|
||||
const { success, data } = response;
|
||||
|
||||
if ( success ) {
|
||||
$( '.customer-details' ).css( 'display', 'flex' );
|
||||
$( '.customer-details-wrap' ).css( 'display', 'flex' );
|
||||
|
||||
$( '#customer-avatar' ).html( data.avatar );
|
||||
$( '.customer-name' ).html( data.name );
|
||||
$( '.customer-since span' ).html( data.date_created_i18n );
|
||||
$( '.customer-record a' ).prop( 'href', data._links.self );
|
||||
} else {
|
||||
$( '.customer-details-wrap' ).css( 'display', 'none' );
|
||||
}
|
||||
}, 'json' );
|
||||
} );
|
||||
|
||||
$( '.edd-payment-change-customer-input' ).trigger( 'change' );
|
||||
|
||||
// New Customer.
|
||||
$( '#edd-customer-details' ).on( 'click', '.edd-payment-new-customer, .edd-payment-new-customer-cancel', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
var new_customer = $( this ).hasClass( 'edd-payment-new-customer' ),
|
||||
cancel = $( this ).hasClass( 'edd-payment-new-customer-cancel' );
|
||||
|
||||
if ( new_customer ) {
|
||||
$( '.order-customer-info' ).hide();
|
||||
$( '.new-customer' ).show();
|
||||
} else if ( cancel ) {
|
||||
$( '.order-customer-info' ).show();
|
||||
$( '.new-customer' ).hide();
|
||||
}
|
||||
|
||||
var new_customer = $( '#edd-new-customer' );
|
||||
|
||||
if ( $( '.new-customer' ).is( ':visible' ) ) {
|
||||
new_customer.val( 1 );
|
||||
} else {
|
||||
new_customer.val( 0 );
|
||||
}
|
||||
} );
|
||||
|
||||
} );
|
@ -0,0 +1,3 @@
|
||||
import './address.js';
|
||||
import './customer.js';
|
||||
import './receipt.js';
|
@ -0,0 +1,32 @@
|
||||
/* global $, ajaxurl */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
jQueryReady( () => {
|
||||
const emails_wrap = $( '.edd-order-resend-receipt-addresses' );
|
||||
|
||||
$( document.body ).on( 'click', '#edd-select-receipt-email', function( e ) {
|
||||
e.preventDefault();
|
||||
emails_wrap.slideDown();
|
||||
} );
|
||||
|
||||
$( document.body ).on( 'change', '.edd-order-resend-receipt-email', function() {
|
||||
const selected = $('input:radio.edd-order-resend-receipt-email:checked').val();
|
||||
|
||||
$( '#edd-select-receipt-email').data( 'email', selected );
|
||||
} );
|
||||
|
||||
$( document.body).on( 'click', '#edd-select-receipt-email', function () {
|
||||
if ( confirm( edd_vars.resend_receipt ) ) {
|
||||
const href = $( this ).prop( 'href' ) + '&email=' + $( this ).data( 'email' );
|
||||
window.location = href;
|
||||
}
|
||||
} );
|
||||
|
||||
$( document.body ).on( 'click', '#edd-resend-receipt', function() {
|
||||
return confirm( edd_vars.resend_receipt );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,279 @@
|
||||
import { NumberFormat } from '@easy-digital-downloads/currency';
|
||||
|
||||
const number = new NumberFormat();
|
||||
|
||||
/* global eddAdminOrderOverview */
|
||||
|
||||
// Loads the modal when the refund button is clicked.
|
||||
$(document.body).on('click', '.edd-refund-order', function (e) {
|
||||
e.preventDefault();
|
||||
var link = $(this),
|
||||
postData = {
|
||||
action : 'edd_generate_refund_form',
|
||||
order_id: $('input[name="edd_payment_id"]').val(),
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
data : postData,
|
||||
url : ajaxurl,
|
||||
success: function success(data) {
|
||||
let modal_content = '';
|
||||
if (data.success) {
|
||||
modal_content = data.html;
|
||||
} else {
|
||||
modal_content = data.message;
|
||||
}
|
||||
|
||||
$('#edd-refund-order-dialog').dialog({
|
||||
position: { my: 'top center', at: 'center center-25%' },
|
||||
width : '75%',
|
||||
modal : true,
|
||||
resizable: false,
|
||||
draggable: false,
|
||||
classes: {
|
||||
'ui-dialog': 'edd-dialog',
|
||||
},
|
||||
closeText: eddAdminOrderOverview.i18n.closeText,
|
||||
open: function( event, ui ) {
|
||||
$(this).html( modal_content );
|
||||
},
|
||||
close: function( event, ui ) {
|
||||
$( this ).html( '' );
|
||||
if ( $( this ).hasClass( 'did-refund' ) ) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}).fail(function (data) {
|
||||
$('#edd-refund-order-dialog').dialog({
|
||||
position: { my: 'top center', at: 'center center-25%' },
|
||||
width : '75%',
|
||||
modal : true,
|
||||
resizable: false,
|
||||
draggable: false
|
||||
}).html(data.message);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
$( document.body ).on( 'click', '.ui-widget-overlay', function ( e ) {
|
||||
$( '#edd-refund-order-dialog' ).dialog( 'close' );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Listen for the bulk actions checkbox, since WP doesn't trigger a change on sub-items.
|
||||
*/
|
||||
$( document.body ).on( 'change', '#edd-refund-order-dialog #cb-select-all-1', function () {
|
||||
const itemCheckboxes = $( '.edd-order-item-refund-checkbox' );
|
||||
const isChecked = $( this ).prop( 'checked' );
|
||||
|
||||
itemCheckboxes.each( function() {
|
||||
$( this ).prop( 'checked', isChecked ).trigger( 'change' );
|
||||
} );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Listen for individual checkbox changes.
|
||||
* When it does, trigger a quantity change.
|
||||
*/
|
||||
$( document.body ).on( 'change', '.edd-order-item-refund-checkbox', function () {
|
||||
const parent = $( this ).parent().parent();
|
||||
const quantityField = parent.find( '.edd-order-item-refund-quantity' );
|
||||
|
||||
if ( quantityField.length ) {
|
||||
if ( $( this ).prop( 'checked' ) ) {
|
||||
// Triggering a change on the quantity field handles enabling the inputs.
|
||||
quantityField.trigger( 'change' );
|
||||
} else {
|
||||
// Disable inputs and recalculate total.
|
||||
parent.find( '.edd-order-item-refund-input' ).prop( 'disabled', true );
|
||||
recalculateRefundTotal();
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* Handles quantity changes, which includes items in the refund.
|
||||
*/
|
||||
$( document.body ).on( 'change', '#edd-refund-order-dialog .edd-order-item-refund-input', function () {
|
||||
let parent = $( this ).closest( '.refunditem' ),
|
||||
quantityField = parent.find( '.edd-order-item-refund-quantity' ),
|
||||
quantity = parseInt( quantityField.val() );
|
||||
|
||||
if ( quantity > 0 ) {
|
||||
parent.addClass( 'refunded' );
|
||||
} else {
|
||||
parent.removeClass( 'refunded' );
|
||||
}
|
||||
|
||||
// Only auto calculate subtotal / tax if we've adjusted the quantity.
|
||||
if ( $( this ).hasClass( 'edd-order-item-refund-quantity' ) ) {
|
||||
// Enable/disable amount fields.
|
||||
parent.find( '.edd-order-item-refund-input:not(.edd-order-item-refund-quantity)' ).prop( 'disabled', quantity === 0 );
|
||||
if ( quantity > 0 ) {
|
||||
quantityField.prop( 'disabled', false );
|
||||
}
|
||||
|
||||
let subtotalField = parent.find( '.edd-order-item-refund-subtotal' ),
|
||||
taxField = parent.find( '.edd-order-item-refund-tax' ),
|
||||
originalSubtotal = number.unformat( subtotalField.data( 'original' ) ),
|
||||
originalTax = taxField.length ? number.unformat( taxField.data( 'original' ) ) : 0.00,
|
||||
originalQuantity = parseInt( quantityField.data( 'max' ) ),
|
||||
calculatedSubtotal = ( originalSubtotal / originalQuantity ) * quantity,
|
||||
calculatedTax = taxField.length ? ( originalTax / originalQuantity ) * quantity : 0.00;
|
||||
|
||||
// Make sure totals don't go over maximums.
|
||||
if ( calculatedSubtotal > parseFloat( subtotalField.data( 'max' ) ) ) {
|
||||
calculatedSubtotal = subtotalField.data( 'max' );
|
||||
}
|
||||
if ( taxField.length && calculatedTax > parseFloat( taxField.data( 'max' ) ) ) {
|
||||
calculatedTax = taxField.data( 'max' );
|
||||
}
|
||||
|
||||
// Guess the subtotal and tax for the selected quantity.
|
||||
subtotalField.val( number.format( calculatedSubtotal ) );
|
||||
if ( taxField.length ) {
|
||||
taxField.val( number.format( calculatedTax ) );
|
||||
}
|
||||
}
|
||||
|
||||
recalculateRefundTotal();
|
||||
} );
|
||||
|
||||
/**
|
||||
* Calculates all the final refund values.
|
||||
*/
|
||||
function recalculateRefundTotal() {
|
||||
let newSubtotal = 0,
|
||||
newTax = 0,
|
||||
newTotal = 0,
|
||||
canRefund = false,
|
||||
allInputBoxes = $( '#edd-refund-order-dialog .edd-order-item-refund-input' ),
|
||||
allReadOnly = $( '#edd-refund-order-dialog .edd-order-item-refund-input.readonly' );
|
||||
|
||||
// Set a readonly while we recalculate, to avoid race conditions in the browser.
|
||||
allInputBoxes.prop( 'readonly', true );
|
||||
|
||||
// Loop over all order items.
|
||||
$( '#edd-refund-order-dialog .edd-order-item-refund-quantity' ).each( function() {
|
||||
const thisItemQuantity = parseInt( $( this ).val() );
|
||||
|
||||
if ( ! thisItemQuantity ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thisItemParent = $( this ).closest( '.refunditem' );
|
||||
const thisItemSelected = thisItemParent.find( '.edd-order-item-refund-checkbox' ).prop( 'checked' );
|
||||
|
||||
if ( ! thisItemSelected ) {
|
||||
thisItemParent.removeClass( 'refunded' );
|
||||
return;
|
||||
}
|
||||
|
||||
// Values for this item.
|
||||
let thisItemTax = 0.00;
|
||||
|
||||
let thisItemSubtotal = number.unformat( thisItemParent.find( '.edd-order-item-refund-subtotal' ).val() );
|
||||
|
||||
if ( thisItemParent.find( '.edd-order-item-refund-tax' ).length ) {
|
||||
thisItemTax = number.unformat( thisItemParent.find( '.edd-order-item-refund-tax' ).val() );
|
||||
}
|
||||
|
||||
let thisItemTotal = thisItemSubtotal + thisItemTax;
|
||||
|
||||
thisItemParent.find( '.column-total span' ).text( number.format( thisItemTotal ) );
|
||||
|
||||
// Negate amounts if working with credit.
|
||||
if ( thisItemParent.data( 'credit' ) ) {
|
||||
thisItemSubtotal = thisItemSubtotal * -1;
|
||||
thisItemTax = thisItemTax * -1;
|
||||
thisItemTotal = thisItemTotal * -1;
|
||||
}
|
||||
|
||||
// Only include order items in the subtotal.
|
||||
if ( thisItemParent.data( 'orderItem' ) ) {
|
||||
newSubtotal += thisItemSubtotal;
|
||||
}
|
||||
|
||||
newTax += thisItemTax;
|
||||
newTotal += thisItemTotal;
|
||||
} );
|
||||
|
||||
if ( parseFloat( newTotal ) > 0 ) {
|
||||
canRefund = true;
|
||||
}
|
||||
|
||||
$( '#edd-refund-submit-subtotal-amount' ).text( number.format( newSubtotal ) );
|
||||
$( '#edd-refund-submit-tax-amount' ).text( number.format( newTax ) );
|
||||
$( '#edd-refund-submit-total-amount' ).text( number.format( newTotal ) );
|
||||
|
||||
$( '#edd-submit-refund-submit' ).attr( 'disabled', ! canRefund );
|
||||
|
||||
// Remove the readonly.
|
||||
allInputBoxes.prop( 'readonly', false );
|
||||
allReadOnly.prop( 'readonly', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the refund form after the button is clicked.
|
||||
*/
|
||||
$(document.body).on( 'click', '#edd-submit-refund-submit', function(e) {
|
||||
e.preventDefault();
|
||||
$('.edd-submit-refund-message').removeClass('success').removeClass('fail');
|
||||
$( this ).removeClass( 'button-primary' ).attr( 'disabled', true ).addClass( 'updating-message' );
|
||||
$('#edd-submit-refund-status').hide();
|
||||
|
||||
const refundForm = $( '#edd-submit-refund-form' );
|
||||
const refundData = refundForm.serialize();
|
||||
|
||||
var postData = {
|
||||
action: 'edd_process_refund_form',
|
||||
data: refundData,
|
||||
order_id: $('input[name="edd_payment_id"]').val()
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
data : postData,
|
||||
url : ajaxurl,
|
||||
success: function success(response) {
|
||||
const message_target = $('.edd-submit-refund-message'),
|
||||
url_target = $('.edd-submit-refund-url');
|
||||
|
||||
if ( response.success ) {
|
||||
message_target.text(response.data.message).addClass('success');
|
||||
url_target.attr( 'href', response.data.refund_url ).show();
|
||||
|
||||
$( '#edd-submit-refund-status' ).show();
|
||||
url_target.focus();
|
||||
$( '#edd-refund-order-dialog' ).addClass( 'did-refund' );
|
||||
} else {
|
||||
message_target.html(response.data).addClass('fail');
|
||||
url_target.hide();
|
||||
|
||||
$('#edd-submit-refund-status').show();
|
||||
$( '#edd-submit-refund-submit' ).attr( 'disabled', false ).removeClass( 'updating-message' ).addClass( 'button-primary' );
|
||||
}
|
||||
}
|
||||
} ).fail( function ( data ) {
|
||||
const message_target = $('.edd-submit-refund-message'),
|
||||
url_target = $('.edd-submit-refund-url'),
|
||||
json = data.responseJSON;
|
||||
|
||||
|
||||
message_target.text( json.data ).addClass( 'fail' );
|
||||
url_target.hide();
|
||||
|
||||
$( '#edd-submit-refund-status' ).show();
|
||||
$( '#edd-submit-refund-submit' ).attr( 'disabled', false ).removeClass( 'updating-message' ).addClass( 'button-primary' );
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize WP toggle behavior for the modal.
|
||||
$( document.body ).on( 'click', '.refund-items .toggle-row', function () {
|
||||
$( this ).closest( 'tr' ).toggleClass( 'is-expanded' );
|
||||
} );
|
@ -0,0 +1,101 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustment } from './../models/order-adjustment.js';
|
||||
import { OrderAdjustmentDiscount } from './../models/order-adjustment-discount.js';
|
||||
|
||||
/**
|
||||
* Collection of `OrderAdjustment`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Adjustments
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
export const OrderAdjustments = Backbone.Collection.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
comparator: 'type',
|
||||
|
||||
/**
|
||||
* Initializes the `OrderAdjustments` collection.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderAdjustments
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
initialize() {
|
||||
this.getByType = this.getByType.bind( this );
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines which Model to use and instantiates it.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} attributes Model attributes.
|
||||
* @param {Object} options Model options.
|
||||
*/
|
||||
model( attributes, options ) {
|
||||
let model;
|
||||
|
||||
switch ( attributes.type ) {
|
||||
case 'discount':
|
||||
model = new OrderAdjustmentDiscount( attributes, options );
|
||||
break;
|
||||
default:
|
||||
model = new OrderAdjustment( attributes, options );
|
||||
}
|
||||
|
||||
return model;
|
||||
},
|
||||
|
||||
/**
|
||||
* Defines the model's attribute that defines it's ID.
|
||||
*
|
||||
* Uses the `OrderAdjustment`'s Type ID.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} attributes Model attributes.
|
||||
* @return {number}
|
||||
*/
|
||||
modelId( attributes ) {
|
||||
return `${ attributes.type }-${ attributes.typeId }-${ attributes.description }`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if `OrderAdjustments` contains a specific `OrderAdjustment`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderAdjustment} model Model to look for.
|
||||
* @return {bool} True if the Collection contains the Model.
|
||||
*/
|
||||
has( model ) {
|
||||
return (
|
||||
undefined !==
|
||||
this.findWhere( {
|
||||
typeId: model.get( 'typeId' ),
|
||||
} )
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of `OrderAdjustment`s by type.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {string} type Type of adjustment to retrieve. `fee`, `credit`, or `discount`.
|
||||
* @return {Array} List of type-specific adjustments.
|
||||
*/
|
||||
getByType( type ) {
|
||||
return this.where( {
|
||||
type,
|
||||
} );
|
||||
},
|
||||
} );
|
@ -0,0 +1,137 @@
|
||||
/* global Backbone, $, _ */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import uuid from 'uuid-random';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustments } from './../collections/order-adjustments.js';
|
||||
import { OrderAdjustmentDiscount } from './../models/order-adjustment-discount.js';
|
||||
import { OrderItem } from './../models/order-item.js';
|
||||
|
||||
/**
|
||||
* Collection of `OrderItem`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderItems
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
export const OrderItems = Backbone.Collection.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @type {OrderItem}
|
||||
*/
|
||||
model: OrderItem,
|
||||
|
||||
/**
|
||||
* Ensures `OrderItems` has access to the current state through a similar
|
||||
* interface as Views. BackBone.Collection does not automatically set
|
||||
* passed options as a property.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {null|Array} models List of Models.
|
||||
* @param {Object} options Collection options.
|
||||
*/
|
||||
preinitialize( models, options ) {
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if `OrderItems` contains a specific `OrderItem`.
|
||||
*
|
||||
* Uses the `OrderItem`s Product ID and Price ID to create a unique
|
||||
* value to check against.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderItem} model Model to look for.
|
||||
* @return {bool} True if the Collection contains the Model.
|
||||
*/
|
||||
has( model ) {
|
||||
const duplicates = this.filter( ( item ) => {
|
||||
const itemId =
|
||||
item.get( 'productId' ) + '_' + item.get( 'priceId' );
|
||||
const modelId =
|
||||
model.get( 'productId' ) + '_' + model.get( 'priceId' );
|
||||
|
||||
return itemId === modelId;
|
||||
} );
|
||||
|
||||
return duplicates.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the amounts for all current `OrderItem`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {$.promise} A jQuery promise representing zero or more requests.
|
||||
*/
|
||||
updateAmounts() {
|
||||
const { options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const items = state.get( 'items' );
|
||||
const discounts = new Backbone.Collection(
|
||||
state.get( 'adjustments' ).getByType( 'discount' )
|
||||
);
|
||||
|
||||
const args = {
|
||||
country: state.getTaxCountry(),
|
||||
region: state.getTaxRegion(),
|
||||
products: items.map( ( item ) => ( {
|
||||
id: item.get( 'productId' ),
|
||||
quantity: item.get( 'quantity' ),
|
||||
options: {
|
||||
price_id: item.get( 'priceId' ),
|
||||
}
|
||||
} ) ),
|
||||
discountIds: discounts.pluck( 'typeId' ),
|
||||
};
|
||||
|
||||
// Keep track of all jQuery Promises.
|
||||
const promises = [];
|
||||
|
||||
// Find each `OrderItem`'s amounts.
|
||||
items.models.forEach( ( item ) => {
|
||||
const getItemAmounts = item.getAmounts( args );
|
||||
|
||||
getItemAmounts
|
||||
// Update `OrderItem`-level Adjustments.
|
||||
.done( ( { adjustments } ) => {
|
||||
// Map returned Discounts to `OrderAdjustmentDiscount`.
|
||||
const orderItemDiscounts = adjustments.map( ( adjustment ) => {
|
||||
return new OrderAdjustmentDiscount( {
|
||||
...adjustment,
|
||||
id: uuid(),
|
||||
objectId: item.get( 'id' ),
|
||||
} );
|
||||
} );
|
||||
|
||||
// Gather existing `fee` and `credit` `OrderItem`-level Adjustments.
|
||||
const orderItemAdjustments = item.get( 'adjustments' ).filter( ( adjustment ) => {
|
||||
return [ 'fee', 'credit' ].includes( adjustment.type );
|
||||
} );
|
||||
|
||||
// Reset `OrderAdjustments` collection with new data.
|
||||
item.set( 'adjustments', new OrderAdjustments( [
|
||||
...orderItemDiscounts,
|
||||
...orderItemAdjustments,
|
||||
] ) );
|
||||
} )
|
||||
// Update individual `OrderItem`s and `OrderAdjustment`s with new amounts.
|
||||
.done( ( response ) => item.setAmounts( response ) );
|
||||
|
||||
// Track jQuery Promise.
|
||||
promises.push( getItemAmounts );
|
||||
} );
|
||||
|
||||
return $.when.apply( $, promises );
|
||||
},
|
||||
} );
|
@ -0,0 +1,21 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderRefund } from './../models/order-refund.js';
|
||||
|
||||
/**
|
||||
* Collection of `OrderRefund`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderRefunds
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
export const OrderRefunds = Backbone.Collection.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
model: OrderRefund,
|
||||
} );
|
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Currency, NumberFormat } from '@easy-digital-downloads/currency';
|
||||
import { Overview } from './views/overview.js';
|
||||
import { OrderItems } from './collections/order-items.js';
|
||||
import { OrderItem } from './models/order-item.js';
|
||||
import { OrderAdjustments } from './collections/order-adjustments.js';
|
||||
import { OrderRefunds } from './collections/order-refunds.js';
|
||||
import { State } from './models/state.js';
|
||||
|
||||
// Temporarily include old Refund flow.
|
||||
import './_refund.js';
|
||||
|
||||
let overview;
|
||||
|
||||
( () => {
|
||||
if ( ! window.eddAdminOrderOverview ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
isAdding,
|
||||
isRefund,
|
||||
hasTax,
|
||||
hasQuantity,
|
||||
hasDiscounts,
|
||||
order,
|
||||
items,
|
||||
adjustments,
|
||||
refunds,
|
||||
} = window.eddAdminOrderOverview;
|
||||
|
||||
const currencyFormatter = new Currency( {
|
||||
currency: order.currency,
|
||||
currencySymbol: order.currencySymbol,
|
||||
} );
|
||||
|
||||
// Create and hydrate state.
|
||||
const state = new State( {
|
||||
isAdding: '1' === isAdding,
|
||||
isRefund: '1' === isRefund,
|
||||
hasTax: '0' === hasTax ? false : hasTax,
|
||||
hasQuantity: '1' === hasQuantity,
|
||||
hasDiscounts: '1' === hasDiscounts,
|
||||
formatters: {
|
||||
currency: currencyFormatter,
|
||||
// Backbone doesn't merge nested defaults.
|
||||
number: new NumberFormat(),
|
||||
},
|
||||
order,
|
||||
} );
|
||||
|
||||
// Create collections and add to state.
|
||||
state.set( {
|
||||
items: new OrderItems( null, {
|
||||
state,
|
||||
} ),
|
||||
adjustments: new OrderAdjustments( null, {
|
||||
state,
|
||||
} ),
|
||||
refunds: new OrderRefunds( null, {
|
||||
state,
|
||||
} ),
|
||||
} );
|
||||
|
||||
// Create Overview.
|
||||
overview = new Overview( {
|
||||
state,
|
||||
} );
|
||||
|
||||
// Hydrate collections.
|
||||
|
||||
// Hydrate `OrderItem`s.
|
||||
//
|
||||
// Models are created manually before being added to the collection to
|
||||
// ensure attributes maintain schema with deep model attributes.
|
||||
items.forEach( ( item ) => {
|
||||
const orderItemAdjustments = new OrderAdjustments( item.adjustments );
|
||||
const orderItem = new OrderItem( {
|
||||
...item,
|
||||
adjustments: orderItemAdjustments,
|
||||
state,
|
||||
} );
|
||||
|
||||
state.get( 'items' ).add( orderItem );
|
||||
} );
|
||||
|
||||
// Hyrdate `Order`-level `Adjustments`.
|
||||
adjustments.forEach( ( adjustment ) => {
|
||||
state.get( 'adjustments' ).add( {
|
||||
state,
|
||||
...adjustment,
|
||||
} )
|
||||
} );
|
||||
|
||||
// Hydrate `OrderRefund`s.
|
||||
refunds.forEach( ( refund ) => {
|
||||
state.get( 'refunds' ).add( {
|
||||
state,
|
||||
...refund,
|
||||
} );
|
||||
} );
|
||||
} ) ();
|
||||
|
||||
export default overview;
|
@ -0,0 +1,76 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustment } from './order-adjustment.js';
|
||||
|
||||
/**
|
||||
* OrderAdjustmentDiscount
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderAdjustmentDiscount
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const OrderAdjustmentDiscount = OrderAdjustment.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} OrderAdjustmentDiscount
|
||||
*/
|
||||
defaults: {
|
||||
...OrderAdjustment.prototype.defaults,
|
||||
type: 'discount',
|
||||
},
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
idAttribute: 'typeId',
|
||||
|
||||
/**
|
||||
* Returns the `OrderAdjustmentDiscount`'s amount based on the current values
|
||||
* of all `OrderItems` discounts.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} `OrderAdjustmentDiscount` amount.
|
||||
*/
|
||||
getAmount() {
|
||||
let amount = 0;
|
||||
|
||||
const state = this.get( 'state' );
|
||||
|
||||
// Return stored amount if viewing an existing Order.
|
||||
if ( false === state.get( 'isAdding' ) ) {
|
||||
return OrderAdjustment.prototype.getAmount.apply( this, arguments );
|
||||
}
|
||||
|
||||
const { models: items } = state.get( 'items' );
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
items.forEach( ( item ) => {
|
||||
const discount = item.get( 'adjustments' ).findWhere( {
|
||||
typeId: this.get( 'typeId' ),
|
||||
} );
|
||||
|
||||
if ( undefined !== discount ) {
|
||||
amount += number.unformat(
|
||||
number.format( discount.get( 'subtotal' ) )
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
||||
return amount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the `OrderAdjustment` total.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
getTotal() {
|
||||
return this.getAmount();
|
||||
},
|
||||
} );
|
@ -0,0 +1,101 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* OrderAdjustment
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderAdjustment
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const OrderAdjustment = Backbone.Model.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} OrderAdjustment
|
||||
*/
|
||||
defaults: {
|
||||
id: 0,
|
||||
objectId: 0,
|
||||
objectType: '',
|
||||
typeId: 0,
|
||||
type: '',
|
||||
description: '',
|
||||
subtotal: 0,
|
||||
tax: 0,
|
||||
total: 0,
|
||||
dateCreated: '',
|
||||
dateModified: '',
|
||||
uuid: '',
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the `OrderAdjustment` amount.
|
||||
*
|
||||
* Separate from subtotal or total calculation so `OrderAdjustmentDiscount`
|
||||
* can be calculated independently.
|
||||
*
|
||||
* @see OrderAdjustmentDiscount.prototype.getAmount()
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
getAmount() {
|
||||
return this.get( 'subtotal' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the `OrderAdjustment` tax.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return {number} Total amount.
|
||||
*/
|
||||
getTax() {
|
||||
return this.get( 'tax' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the `OrderAdjustment` total.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
getTotal() {
|
||||
// Fees always have tax added exclusively.
|
||||
// @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/2445#issuecomment-53215087
|
||||
// @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/f97f4f6f5454921a2014dc1fa8f4caa5f550108c/includes/cart/class-edd-cart.php#L1306-L1311
|
||||
return this.get( 'subtotal' ) + this.get( 'tax' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Recalculates the tax amount based on the current tax rate.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
updateTax() {
|
||||
const state = this.get( 'state' );
|
||||
const hasTax = state.get( 'hasTax' );
|
||||
|
||||
if (
|
||||
'none' === hasTax ||
|
||||
'' === hasTax.country ||
|
||||
'' === hasTax.rate
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { number } = state.get( 'formatters' );
|
||||
const taxRate = hasTax.rate / 100;
|
||||
const adjustments = state.get( 'adjustments' ).getByType( 'fee' );
|
||||
|
||||
adjustments.forEach( ( adjustment ) => {
|
||||
if ( false === adjustment.get( 'isTaxable' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const taxableAmount = adjustment.getAmount();
|
||||
const taxAmount = number.unformat( taxableAmount * taxRate );
|
||||
|
||||
adjustment.set( 'tax', taxAmount );
|
||||
} );
|
||||
}
|
||||
} );
|
@ -0,0 +1,250 @@
|
||||
/* global Backbone, _, $ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustments } from './../collections/order-adjustments.js';
|
||||
|
||||
/**
|
||||
* OrderItem
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderItem
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const OrderItem = Backbone.Model.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} OrderItem
|
||||
*/
|
||||
defaults: {
|
||||
id: 0,
|
||||
orderId: 0,
|
||||
productId: 0,
|
||||
productName: '',
|
||||
priceId: null,
|
||||
cartIndex: 0,
|
||||
type: 'download',
|
||||
status: '',
|
||||
statusLabel: '',
|
||||
quantity: 1,
|
||||
amount: 0,
|
||||
subtotal: 0,
|
||||
discount: 0,
|
||||
tax: 0,
|
||||
total: 0,
|
||||
dateCreated: '',
|
||||
dateModified: '',
|
||||
uuid: '',
|
||||
|
||||
// Track manually set amounts.
|
||||
amountManual: 0,
|
||||
taxManual: 0,
|
||||
subtotalManual: 0,
|
||||
|
||||
// Track if the amounts have been adjusted manually on addition.
|
||||
_isAdjustingManually: false,
|
||||
|
||||
// Track `OrderItem`-level adjustments.
|
||||
//
|
||||
// The handling of Adjustments in the API is currently somewhat
|
||||
// fragmented with certain extensions creating Adjustments at the
|
||||
// `Order` level, some at a duplicate `OrderItem` level, and some both.
|
||||
adjustments: new OrderAdjustments(),
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the `OrderItem` subtotal amount.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param {bool} includeTax If taxes should be included when retrieving the subtotal.
|
||||
* This is needed in some scenarios with inclusive taxes.
|
||||
* @return {number} Subtotal amount.
|
||||
*/
|
||||
getSubtotal( includeTax = false ) {
|
||||
const state = this.get( 'state' );
|
||||
const subtotal = this.get( 'subtotal' );
|
||||
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === state.get( 'isAdding' ) ) {
|
||||
return subtotal;
|
||||
}
|
||||
|
||||
// Calculate subtotal.
|
||||
if ( true === state.hasInclusiveTax() && false === includeTax ) {
|
||||
return subtotal - this.getTax();
|
||||
}
|
||||
|
||||
return subtotal;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the Discount amount.
|
||||
*
|
||||
* If an Order is being added the amount is calculated based
|
||||
* on the total of `OrderItem`-level Adjustments that are
|
||||
* currently applied.
|
||||
*
|
||||
* If an Order has already been added use the amount stored
|
||||
* directly in the database.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Discount amount.
|
||||
*/
|
||||
getDiscountAmount() {
|
||||
let amount = 0;
|
||||
|
||||
const discounts = this.get( 'adjustments' ).getByType( 'discount' );
|
||||
|
||||
if ( 0 === discounts.length ) {
|
||||
return this.get( 'discount' );
|
||||
}
|
||||
|
||||
discounts.forEach( ( discount ) => {
|
||||
amount += +discount.get( 'subtotal' );
|
||||
} );
|
||||
|
||||
return amount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the rounded Tax for the order item.
|
||||
*
|
||||
* Rounded to match storefront checkout.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return {number} Total amount.
|
||||
*/
|
||||
getTax() {
|
||||
const state = this.get( 'state' );
|
||||
const tax = this.get( 'tax' );
|
||||
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === state.get( 'isAdding' ) ) {
|
||||
return tax;
|
||||
}
|
||||
|
||||
// Calculate tax.
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
return number.unformat( number.format( tax ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Total for the order item.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return {number} Total amount.
|
||||
*/
|
||||
getTotal() {
|
||||
const state = this.get( 'state' );
|
||||
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === state.get( 'isAdding' ) ) {
|
||||
return this.get( 'total' );
|
||||
}
|
||||
|
||||
// Calculate total.
|
||||
if ( true === state.hasInclusiveTax() ) {
|
||||
return this.get( 'subtotal' ) - this.getDiscountAmount();
|
||||
}
|
||||
|
||||
return ( this.get( 'subtotal' ) - this.getDiscountAmount() ) + this.getTax();
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves amounts for the `OrderItem` based on other `OrderItem`s and `OrderAdjustment`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} args Arguments to pass as data in the XHR request.
|
||||
* @param {string} args.country Country code to determine tax rate.
|
||||
* @param {string} args.region Region to determine tax rate.
|
||||
* @param {Array} args.products List of current products added to the order.
|
||||
* @param {Array} args.discountIds List of `OrderAdjustmentDiscount`s to calculate amounts against.
|
||||
* @return {$.promise} A jQuery promise that represents the request.
|
||||
*/
|
||||
getAmounts( {
|
||||
country = '',
|
||||
region = '',
|
||||
products = [],
|
||||
discountIds = [],
|
||||
} ) {
|
||||
const {
|
||||
nonces: { edd_admin_order_get_item_amounts: nonce },
|
||||
} = window.eddAdminOrderOverview;
|
||||
|
||||
const { productId, priceId, quantity, amount, tax, subtotal } = _.clone(
|
||||
this.attributes
|
||||
);
|
||||
|
||||
return wp.ajax.send( 'edd-admin-order-get-item-amounts', {
|
||||
data: {
|
||||
nonce,
|
||||
productId,
|
||||
priceId,
|
||||
quantity,
|
||||
amount,
|
||||
tax,
|
||||
subtotal,
|
||||
country,
|
||||
region,
|
||||
products: _.uniq( [
|
||||
...products,
|
||||
{
|
||||
id: productId,
|
||||
quantity,
|
||||
options: {
|
||||
price_id: priceId,
|
||||
},
|
||||
},
|
||||
], function( { id, options: { price_id } } ) {
|
||||
return `${ id }_${ price_id }`
|
||||
} ),
|
||||
discounts: _.uniq( discountIds ),
|
||||
},
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Bulk sets amounts.
|
||||
*
|
||||
* Only adjusts the Discount amount if adjusting manually.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} amounts Amounts to set.
|
||||
* @param {number} amounts.amount `OrderItem` unit price.
|
||||
* @param {number} amounts.discount `OrderItem` discount amount.
|
||||
* @param {number} amounts.tax `OrderItem` tax amount.
|
||||
* @param {number} amounts.subtotal `OrderItem` subtotal amount.
|
||||
* @param {number} amounts.total `OrderItem` total amount.
|
||||
*/
|
||||
setAmounts( {
|
||||
amount = 0,
|
||||
discount = 0,
|
||||
tax = 0,
|
||||
subtotal = 0,
|
||||
total = 0,
|
||||
} ) {
|
||||
if ( true === this.get( '_isAdjustingManually' ) ) {
|
||||
this.set( {
|
||||
discount,
|
||||
} );
|
||||
} else {
|
||||
this.set( {
|
||||
amount,
|
||||
discount,
|
||||
tax,
|
||||
subtotal,
|
||||
total,
|
||||
} );
|
||||
}
|
||||
},
|
||||
} );
|
@ -0,0 +1,24 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* OrderRefund
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderRefund
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const OrderRefund = Backbone.Model.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} OrderAdjustment
|
||||
*/
|
||||
defaults: {
|
||||
id: 0,
|
||||
number: '',
|
||||
total: 0,
|
||||
dateCreated: '',
|
||||
dateCreatedi18n: '',
|
||||
},
|
||||
} );
|
@ -0,0 +1,233 @@
|
||||
/* global Backbone, _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Currency, NumberFormat } from '@easy-digital-downloads/currency';
|
||||
|
||||
/**
|
||||
* State
|
||||
*
|
||||
* Leverages `Backbone.Model` and subsequently `Backbone.Events`
|
||||
* to easily track changes to top level state changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class State
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const State = Backbone.Model.extend(
|
||||
/** Lends State.prototype */ {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} State
|
||||
*/
|
||||
defaults: {
|
||||
isAdding: false,
|
||||
isFetching: false,
|
||||
hasQuantity: false,
|
||||
hasTax: false,
|
||||
items: [],
|
||||
adjustments: [],
|
||||
refunds: [],
|
||||
formatters: {
|
||||
currency: new Currency(),
|
||||
number: new NumberFormat(),
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current tax rate's country code.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {string} Tax rate country code.
|
||||
*/
|
||||
getTaxCountry() {
|
||||
return false !== this.get( 'hasTax' )
|
||||
? this.get( 'hasTax' ).country
|
||||
: '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current tax rate's region.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {string} Tax rate region.
|
||||
*/
|
||||
getTaxRegion() {
|
||||
return false !== this.get( 'hasTax' )
|
||||
? this.get( 'hasTax' ).region
|
||||
: '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order subtotal.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {bool} includeTax If taxes should be included when retrieving the subtotal.
|
||||
* This is needed in some scenarios with inclusive taxes.
|
||||
* @return {number} Order subtotal.
|
||||
*/
|
||||
getSubtotal( includeTax = false ) {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).subtotal;
|
||||
}
|
||||
|
||||
const { models: items } = this.get( 'items' );
|
||||
|
||||
return items.reduce(
|
||||
( amount, item ) => {
|
||||
return amount += +item.getSubtotal( includeTax );
|
||||
},
|
||||
0
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order discount.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Order discount.
|
||||
*/
|
||||
getDiscount() {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).discount;
|
||||
}
|
||||
|
||||
const adjustments = this.get( 'adjustments' ).getByType( 'discount' );
|
||||
|
||||
return adjustments.reduce(
|
||||
( amount, adjustment ) => {
|
||||
return amount += +adjustment.getAmount();
|
||||
},
|
||||
0
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order tax.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Order tax.
|
||||
*/
|
||||
getTax() {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).tax;
|
||||
}
|
||||
|
||||
const items = this.get( 'items' ).models;
|
||||
const feesTax = this.getFeesTax();
|
||||
|
||||
return items.reduce(
|
||||
( amount, item ) => {
|
||||
return amount += +item.getTax();
|
||||
},
|
||||
feesTax
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order tax amount for fees.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Order tax amount for fees.
|
||||
*/
|
||||
getFeesTax() {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).tax;
|
||||
}
|
||||
|
||||
const adjustments = this.get( 'adjustments' ).getByType( 'fee' );
|
||||
|
||||
return adjustments.reduce(
|
||||
( amount, item ) => {
|
||||
return amount += +item.getTax();
|
||||
},
|
||||
0
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order total.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Order total.
|
||||
*/
|
||||
getTotal() {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).total;
|
||||
}
|
||||
|
||||
// Calculate all adjustments that affect the total.
|
||||
const { models: adjustments } = this.get( 'adjustments' );
|
||||
const includeTaxInSubtotal = true;
|
||||
|
||||
const adjustedSubtotal = adjustments.reduce(
|
||||
( amount, adjustment ) => {
|
||||
if (
|
||||
[ 'discount', 'credit' ].includes(
|
||||
adjustment.get( 'type' )
|
||||
)
|
||||
) {
|
||||
return amount -= +adjustment.getAmount();
|
||||
} else {
|
||||
return amount += +adjustment.get( 'subtotal' );
|
||||
}
|
||||
},
|
||||
this.getSubtotal( includeTaxInSubtotal )
|
||||
);
|
||||
|
||||
if ( true === this.hasInclusiveTax() ) {
|
||||
// Fees always have tax added exclusively.
|
||||
// @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/2445#issuecomment-53215087
|
||||
// @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/f97f4f6f5454921a2014dc1fa8f4caa5f550108c/includes/cart/class-edd-cart.php#L1306-L1311
|
||||
return adjustedSubtotal + this.getFeesTax();
|
||||
}
|
||||
|
||||
return adjustedSubtotal + this.getTax();
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if the state has a new, valid, tax rate.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {bool} True if the rate has changed.
|
||||
*/
|
||||
hasNewTaxRate() {
|
||||
const hasTax = this.get( 'hasTax' );
|
||||
|
||||
if ( false === hasTax ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const prevHasTax = this.previous( 'hasTax' );
|
||||
|
||||
return ! _.isEqual( hasTax, prevHasTax );
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if the state has prices entered inclusive of tax.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @returns {bool} True if prices are entered inclusive of tax.
|
||||
*/
|
||||
hasInclusiveTax() {
|
||||
return this.get( 'hasTax' ).inclusive;
|
||||
}
|
||||
}
|
||||
);
|
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { edd_attach_tooltips as setupTooltips } from 'admin/components/tooltips';
|
||||
import { FormAddOrderItem } from './form-add-order-item.js';
|
||||
import { FormAddOrderDiscount } from './form-add-order-discount.js';
|
||||
import { FormAddOrderAdjustment } from './form-add-order-adjustment.js';
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Actions
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const Actions = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-order-overview-actions',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-actions' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click #add-item': 'onAddOrderItem',
|
||||
'click #add-discount': 'onAddOrderDiscount',
|
||||
'click #add-adjustment': 'onAddOrderAdjustment',
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures tooltips can be used after render.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
render() {
|
||||
wp.Backbone.View.prototype.render.apply( this, arguments );
|
||||
|
||||
// Setup Tooltips after render.
|
||||
setupTooltips( $( '.edd-help-tip' ) );
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the "Add Item" flow.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onAddOrderItem( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
new FormAddOrderItem( this.options ).openDialog().render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the "Add Discount" flow.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onAddOrderDiscount( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
new FormAddOrderDiscount( this.options ).openDialog().render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the "Add Adjustment" flow.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onAddOrderAdjustment( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
new FormAddOrderAdjustment( this.options ).openDialog().render();
|
||||
},
|
||||
} );
|
@ -0,0 +1,255 @@
|
||||
/* global _, $ */
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { focus } from '@wordpress/dom';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getChosenVars } from 'utils/chosen.js';
|
||||
|
||||
// Set noconflict when using Lodash (@wordpress packages) and Underscores.
|
||||
// @todo Find a better place to set this up. Webpack?
|
||||
window.lodash = _.noConflict();
|
||||
|
||||
/**
|
||||
* Base
|
||||
*
|
||||
* Supplies additional functionality and helpers beyond
|
||||
* what is provided by `wp.Backbone.View`.
|
||||
*
|
||||
* - Maintains focus and caret positioning on rendering.
|
||||
* - Extends events via `addEvents()`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Base
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const Base = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* Defines base events to help maintain focus and caret position.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'keydown input': 'handleTabBehavior',
|
||||
'keydown textarea': 'handleTabBehavior',
|
||||
|
||||
'focus input': 'onFocus',
|
||||
'focus textarea': 'onFocus',
|
||||
'focus select': 'onFocus',
|
||||
|
||||
'change input': 'onChange',
|
||||
'change textarea': 'onChange',
|
||||
'change select': 'onChange',
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up additional properties.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
preinitialize() {
|
||||
this.focusedEl = null;
|
||||
this.focusedElCaretPos = 0;
|
||||
|
||||
wp.Backbone.View.prototype.preinitialize.apply( this, arguments );
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges additional events with existing events.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} events Hash of events to add.
|
||||
*/
|
||||
addEvents( events ) {
|
||||
this.delegateEvents( {
|
||||
...this.events,
|
||||
...events,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Moves the focus when dealing with tabbing.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Keydown event.
|
||||
*/
|
||||
handleTabBehavior( e ) {
|
||||
const { keyCode, shiftKey, target } = e;
|
||||
|
||||
// 9 = TAB
|
||||
if ( 9 !== keyCode ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabbables = focus.tabbable.find( this.el );
|
||||
|
||||
if ( ! tabbables.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTabbable = tabbables[ 0 ];
|
||||
const lastTabbable = tabbables[ tabbables.length - 1 ];
|
||||
let toFocus;
|
||||
|
||||
if ( shiftKey && target === firstTabbable ) {
|
||||
toFocus = lastTabbable;
|
||||
} else if ( ! shiftKey && target === lastTabbable ) {
|
||||
toFocus = firstTabbable;
|
||||
} else if ( shiftKey ) {
|
||||
toFocus = focus.tabbable.findPrevious( target );
|
||||
} else {
|
||||
toFocus = focus.tabbable.findNext( target );
|
||||
}
|
||||
|
||||
if ( 'undefined' !== typeof toFocus ) {
|
||||
this.focusedEl = toFocus;
|
||||
this.focusedElCartetPos = toFocus.value.length;
|
||||
} else {
|
||||
this.focusedEl = null;
|
||||
this.focusedElCartetPos = 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks the current element when focusing.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onFocus( e ) {
|
||||
this.focusedEl = e.target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks the current cursor position when editing.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChange( e ) {
|
||||
const { target, keyCode } = e;
|
||||
|
||||
// 9 = TAB
|
||||
if ( undefined !== typeof keyCode && 9 === keyCode ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if ( target.selectionStart ) {
|
||||
this.focusedElCaretPos = target.selectionStart;
|
||||
}
|
||||
} catch ( error ) {
|
||||
this.focusedElCaretPos = target.value.length;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
return this.model
|
||||
? {
|
||||
...this.model.toJSON(),
|
||||
state: this.model.get( 'state' ).toJSON(),
|
||||
}
|
||||
: {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds additional handling after initial render.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
wp.Backbone.View.prototype.render.apply( this, arguments );
|
||||
|
||||
this.initializeSelects();
|
||||
this.setFocus();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reinitializes special <select> fields.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
initializeSelects() {
|
||||
const selects = this.el.querySelectorAll( '.edd-select-chosen' );
|
||||
|
||||
// Reinialize Chosen.js
|
||||
_.each( selects, ( el ) => {
|
||||
$( el ).chosen( {
|
||||
...getChosenVars( $( el ) ),
|
||||
width: '100%',
|
||||
} );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focus and caret position.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
setFocus() {
|
||||
const { el, focusedEl, focusedElCaretPos } = this;
|
||||
|
||||
// Do nothing extra if nothing is focused.
|
||||
if ( null === focusedEl || 'undefined' === typeof focusedEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert full element in to a usable selector.
|
||||
// We can't search for the actual HTMLElement because
|
||||
// the DOM has since changed.
|
||||
let selector = null;
|
||||
|
||||
if ( '' !== focusedEl.id ) {
|
||||
selector = `#${ focusedEl.id }`;
|
||||
} else if ( '' !== focusedEl.name ) {
|
||||
selector = `[name="${ focusedEl.name }"]`;
|
||||
} else if ( focusedEl.classList.length > 0 ) {
|
||||
selector = `.${ [ ...focusedEl.classList ].join( '.' ) }`;
|
||||
}
|
||||
|
||||
// Do nothing if we can't generate a selector.
|
||||
if ( null === selector ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus element.
|
||||
const elToFocus = el.querySelector( selector );
|
||||
|
||||
if ( ! elToFocus ) {
|
||||
return;
|
||||
}
|
||||
|
||||
elToFocus.focus();
|
||||
|
||||
// Attempt to set the caret position.
|
||||
try {
|
||||
if ( elToFocus.setSelectionRange ) {
|
||||
elToFocus.setSelectionRange(
|
||||
focusedElCaretPos,
|
||||
focusedElCaretPos
|
||||
);
|
||||
}
|
||||
} catch ( error ) {}
|
||||
},
|
||||
} );
|
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
import { Dialog } from './dialog.js';
|
||||
|
||||
/**
|
||||
* "Copy Download Link" view
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class FormAddOrderItem
|
||||
* @augments Dialog
|
||||
*/
|
||||
export const CopyDownloadLink = Dialog.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-admin-order-copy-download-link-dialog',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-copy-download-link' ),
|
||||
|
||||
/**
|
||||
* "Copy Download Link" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs CopyDownloadLink
|
||||
* @augments Base
|
||||
*/
|
||||
initialize() {
|
||||
Dialog.prototype.initialize.apply( this, arguments );
|
||||
|
||||
this.link = false;
|
||||
|
||||
this.addEvents( {
|
||||
'click #close': 'closeDialog',
|
||||
} );
|
||||
|
||||
this.fetchLink.call( this );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { link } = this;
|
||||
|
||||
return {
|
||||
link,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the view.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
Base.prototype.render.apply( this, arguments );
|
||||
|
||||
const { el, link } = this;
|
||||
|
||||
// Select the contents if a link is available.
|
||||
if ( false !== link && '' !== link ) {
|
||||
el.querySelector( '#link' ).select();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches the Download's file URLs.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
fetchLink() {
|
||||
const { orderId, productId, priceId } = this.options;
|
||||
|
||||
// Retrieve and set link.
|
||||
//
|
||||
// We can't use wp.ajax.send because the `edd_ajax_generate_file_download_link()`
|
||||
// does not send back JSON responses.
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
url: ajaxurl,
|
||||
data: {
|
||||
action: 'edd_get_file_download_link',
|
||||
payment_id: orderId,
|
||||
download_id: productId,
|
||||
price_id: priceId,
|
||||
},
|
||||
} )
|
||||
.done( ( link ) => {
|
||||
link = link.trim();
|
||||
|
||||
if ( [ '-1', '-2', '-3', '-4', '' ].includes( link ) ) {
|
||||
this.link = '';
|
||||
} else {
|
||||
this.link = link.trim();
|
||||
}
|
||||
} )
|
||||
.done( () => this.render() );
|
||||
},
|
||||
} );
|
@ -0,0 +1,79 @@
|
||||
/* global eddAdminOrderOverview */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
|
||||
/**
|
||||
* "Dialog" view
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Dialog
|
||||
* @augments Base
|
||||
*/
|
||||
export const Dialog = Base.extend( {
|
||||
/**
|
||||
* "Dialog" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs Dialog
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
this.$el.dialog( {
|
||||
position: {
|
||||
my: 'top center',
|
||||
at: 'center center-25%',
|
||||
},
|
||||
classes: {
|
||||
'ui-dialog': 'edd-dialog',
|
||||
},
|
||||
closeText: eddAdminOrderOverview.i18n.closeText,
|
||||
width: '350px',
|
||||
modal: true,
|
||||
resizable: false,
|
||||
draggable: false,
|
||||
autoOpen: false,
|
||||
create: function() {
|
||||
$( this ).css( 'maxWidth', '90vw' );
|
||||
},
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the jQuery UI Dialog containing this view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {Dialog} Current view.
|
||||
*/
|
||||
openDialog() {
|
||||
this.$el.dialog( 'open' );
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the jQuery UI Dialog containing this view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object=} e Event that triggered the close.
|
||||
* @return {Dialog} Current view.
|
||||
*/
|
||||
closeDialog( e ) {
|
||||
if ( e && e.preventDefault ) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.$el.dialog( 'close' );
|
||||
|
||||
// Prevent events from stacking.
|
||||
this.undelegateEvents();
|
||||
|
||||
return this;
|
||||
},
|
||||
} );
|
@ -0,0 +1,289 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import uuid from 'uuid-random';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
import { Dialog } from './dialog.js';
|
||||
import { OrderAdjustment } from './../models/order-adjustment.js';
|
||||
|
||||
/**
|
||||
* FormAddOrderAdjustment
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class FormAddOrderAdjustment
|
||||
* @augments Dialog
|
||||
*/
|
||||
export const FormAddOrderAdjustment = Dialog.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-admin-order-add-adjustment-dialog',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-form-add-order-adjustment' ),
|
||||
|
||||
/**
|
||||
* "Add Adjustment" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs FormAddOrderAdjustment
|
||||
* @augments Dialog
|
||||
*/
|
||||
initialize() {
|
||||
Dialog.prototype.initialize.apply( this, arguments );
|
||||
|
||||
// Delegate additional events.
|
||||
this.addEvents( {
|
||||
'change #object_type': 'onChangeObjectType',
|
||||
'change [name="type"]': 'onChangeType',
|
||||
|
||||
'keyup #amount': 'onChangeAmount',
|
||||
'change #no-tax': 'onHasTaxToggle',
|
||||
'click #set-address': 'onSetAddress',
|
||||
|
||||
'keyup #description': 'onChangeDescription',
|
||||
|
||||
'submit form': 'onAdd',
|
||||
} );
|
||||
|
||||
const { state } = this.options;
|
||||
|
||||
// Create a model `OrderAdjustment` to be added.
|
||||
this.model = new OrderAdjustment( {
|
||||
id: uuid(),
|
||||
objectId: uuid(),
|
||||
typeId: uuid(),
|
||||
objectType: 'order',
|
||||
type: 'fee',
|
||||
amountManual: '',
|
||||
isTaxed: true,
|
||||
|
||||
state,
|
||||
} );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add', this.closeDialog );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
// Pass existing OrderItems so we can apply a fee at OrderItem level.
|
||||
orderItems: state.get( 'items' ).models.map( ( item ) => ( {
|
||||
id: item.get( 'id' ),
|
||||
productName: item.get( 'productName' ),
|
||||
} ) ),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderAdjustment` when the Object Type changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event
|
||||
*/
|
||||
onChangeObjectType( e ) {
|
||||
const {
|
||||
target: { options, selectedIndex },
|
||||
} = e;
|
||||
|
||||
const selected = options[ selectedIndex ];
|
||||
|
||||
const objectType = selected.value;
|
||||
let objectId = this.model.get( 'objectId' );
|
||||
|
||||
// Apply to a specific `OrderItem`.
|
||||
if ( 'order_item' === objectType ) {
|
||||
objectId = selected.dataset.orderItemId;
|
||||
|
||||
this.model.set( {
|
||||
objectId,
|
||||
objectType,
|
||||
} );
|
||||
|
||||
// Apply to the whole order.
|
||||
} else {
|
||||
this.model.set( {
|
||||
objectType,
|
||||
objectId,
|
||||
} );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderAdjustment` when the Type changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event
|
||||
*/
|
||||
onChangeType( e ) {
|
||||
const type = e.target.value;
|
||||
|
||||
this.model.set( 'type', type );
|
||||
|
||||
if ( 'credit' === type ) {
|
||||
this.model.set( 'objectId', 0 );
|
||||
this.model.set( 'objectType', 'order' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderAdjustment` when the Amount changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event
|
||||
*/
|
||||
onChangeAmount( e ) {
|
||||
const { target } = e;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const { state } = this.options;
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
const amountManual = target.value;
|
||||
const amountNumber = number.unformat( amountManual );
|
||||
|
||||
let taxNumber = 0;
|
||||
|
||||
const hasTax = state.get( 'hasTax' );
|
||||
|
||||
if (
|
||||
true === this.model.get( 'isTaxed' ) &&
|
||||
'fee' === this.model.get( 'type' ) &&
|
||||
'none' !== hasTax &&
|
||||
'' !== hasTax.country &&
|
||||
'' !== hasTax.rate
|
||||
) {
|
||||
taxNumber = amountNumber * ( hasTax.rate / 100 );
|
||||
}
|
||||
|
||||
this.model.set( {
|
||||
amountManual,
|
||||
subtotal: amountNumber,
|
||||
total: amountNumber,
|
||||
tax: number.unformat( number.format( taxNumber ) ),
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles if the fee should be taxed.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onHasTaxToggle( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const checked = e.target.checked;
|
||||
const args = {
|
||||
isTaxed: checked,
|
||||
}
|
||||
|
||||
// Reset tax amount if it should not be taxed.
|
||||
if ( false === checked ) {
|
||||
args.tax = 0;
|
||||
}
|
||||
|
||||
this.model.set( args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes dialog and opens "Order Details - Address" section.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onSetAddress( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
this.closeDialog();
|
||||
|
||||
const button = $( '[href="#edd_general_address"]' );
|
||||
|
||||
if ( ! button ) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.trigger( 'click' );
|
||||
|
||||
$( '#edd_order_address_country' ).trigger( 'focus' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderAdjustment` when the Description changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event
|
||||
*/
|
||||
onChangeDescription( e ) {
|
||||
this.model.set( 'description', e.target.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderAdjustment` to `OrderAdjustments`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Submit event.
|
||||
*/
|
||||
onAdd( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const adjustments = state.get( 'adjustments' );
|
||||
const items = state.get( 'items' );
|
||||
|
||||
// Add at `OrderItem` level if necessary.
|
||||
if ( 'order_item' === model.get( 'objectType' ) ) {
|
||||
const orderItem = items.findWhere( {
|
||||
id: model.get( 'objectId' ),
|
||||
} );
|
||||
|
||||
orderItem.get( 'adjustments' ).add( model );
|
||||
// Adding to the Collection doesn't bubble up a change event.
|
||||
orderItem.trigger( 'change' );
|
||||
model.set( 'objectType', 'order_item' );
|
||||
} else {
|
||||
|
||||
// Add to `Order` level.
|
||||
model.set( 'objectType', 'order' );
|
||||
}
|
||||
|
||||
adjustments.add( model );
|
||||
|
||||
// Stop listening to the model in this view.
|
||||
this.stopListening( model );
|
||||
},
|
||||
} );
|
@ -0,0 +1,155 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import uuid from 'uuid-random';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Dialog } from './dialog.js';
|
||||
import { Base } from './base.js';
|
||||
import { OrderAdjustmentDiscount } from './../models/order-adjustment-discount.js';
|
||||
|
||||
/**
|
||||
* "Add Discount" view
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class FormAddOrderDiscount
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const FormAddOrderDiscount = Dialog.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-admin-order-add-discount-dialog',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-form-add-order-discount' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'submit form': 'onAdd',
|
||||
|
||||
'change #discount': 'onChangeDiscount',
|
||||
},
|
||||
|
||||
/**
|
||||
* "Add Discount" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs FormAddOrderAdjustment
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
Dialog.prototype.initialize.apply( this, arguments );
|
||||
|
||||
const { state } = this.options;
|
||||
|
||||
// Create a fresh `OrderAdjustmentDiscount` to be added.
|
||||
this.model = new OrderAdjustmentDiscount( {
|
||||
id: uuid(),
|
||||
typeId: uuid(),
|
||||
|
||||
state,
|
||||
} );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add', this.closeDialog );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const _isDuplicate = state.get( 'adjustments' ).has( model );
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
_isDuplicate,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderDiscounts` when the Discount changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeDiscount( e ) {
|
||||
const { target: { selectedIndex, options } } = e;
|
||||
const { model } = this;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const discount = options[ selectedIndex ];
|
||||
const adjustment = discount.dataset;
|
||||
|
||||
if ( '' === discount.value ) {
|
||||
return model.set( OrderAdjustmentDiscount.prototype.defaults );
|
||||
}
|
||||
|
||||
model.set( {
|
||||
typeId: parseInt( discount.value ),
|
||||
description: adjustment.code,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderAdjustmentDiscount` to `OrderAdjustments`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Submit event.
|
||||
*/
|
||||
onAdd( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
state.set( 'isFetching', true );
|
||||
|
||||
const items = state.get( 'items' );
|
||||
const adjustments = state.get( 'adjustments' );
|
||||
|
||||
// Add to collection but do not alert.
|
||||
adjustments.add( model, {
|
||||
silent: true,
|
||||
} );
|
||||
|
||||
// Update all amounts with new item and alert when done.
|
||||
items
|
||||
.updateAmounts()
|
||||
.done( () => {
|
||||
// Stop listening to the model in this view.
|
||||
this.stopListening( model );
|
||||
|
||||
// Alert of succesful addition.
|
||||
adjustments.trigger( 'add', model );
|
||||
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false ) ;
|
||||
} );
|
||||
},
|
||||
} );
|
@ -0,0 +1,364 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import uuid from 'uuid-random';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
import { Dialog } from './dialog.js';
|
||||
import { OrderItem } from './../models/order-item.js';
|
||||
|
||||
/**
|
||||
* "Add Item" view
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class FormAddOrderItem
|
||||
* @augments Dialog
|
||||
*/
|
||||
export const FormAddOrderItem = Dialog.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-admin-order-add-item-dialog',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-form-add-order-item' ),
|
||||
|
||||
/**
|
||||
* "Add Item" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs FormAddOrderItem
|
||||
* @augments Base
|
||||
*/
|
||||
initialize() {
|
||||
Dialog.prototype.initialize.apply( this, arguments );
|
||||
|
||||
// Delegate additional events.
|
||||
this.addEvents( {
|
||||
'change #download': 'onChangeDownload',
|
||||
'change #quantity': 'onChangeQuantity',
|
||||
'change #auto-calculate': 'onAutoCalculateToggle',
|
||||
|
||||
'keyup #amount': 'onChangeAmount',
|
||||
'keyup #tax': 'onChangeTax',
|
||||
'keyup #subtotal': 'onChangeSubtotal',
|
||||
|
||||
'click #set-address': 'onSetAddress',
|
||||
|
||||
'submit form': 'onAdd',
|
||||
} );
|
||||
|
||||
const { state } = this.options;
|
||||
const id = uuid();
|
||||
|
||||
// Create a fresh `OrderItem` to be added.
|
||||
this.model = new OrderItem( {
|
||||
id,
|
||||
orderId: id,
|
||||
|
||||
state,
|
||||
|
||||
error: false,
|
||||
} );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
this.listenTo( state, 'change:isFetching', this.render );
|
||||
this.listenTo( state.get( 'items' ), 'add', this.closeDialog );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
const quantity = model.get( 'quantity' );
|
||||
|
||||
let amount = number.format( model.get( 'amount' ) * quantity );
|
||||
let tax = number.format( model.get( 'tax' ) * quantity );
|
||||
let subtotal = number.format( model.get( 'subtotal' ) * quantity );
|
||||
|
||||
if ( true === model.get( '_isAdjustingManually' ) ) {
|
||||
amount = model.get( 'amountManual' );
|
||||
tax = model.get( 'taxManual' );
|
||||
subtotal = model.get( 'subtotalManual' );
|
||||
}
|
||||
|
||||
const isDuplicate = false === state.get( 'isFetching' ) && true === state.get( 'items' ).has( model );
|
||||
const isAdjustingManually = model.get( '_isAdjustingManually' );
|
||||
const error = model.get( 'error' );
|
||||
|
||||
const defaults = Base.prototype.prepare.apply( this, arguments );
|
||||
|
||||
return {
|
||||
...defaults,
|
||||
|
||||
amountManual: amount,
|
||||
taxManual: tax,
|
||||
subtotalManual: subtotal,
|
||||
|
||||
state: {
|
||||
...defaults.state,
|
||||
isAdjustingManually,
|
||||
isDuplicate,
|
||||
error,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the OrderItem when the Download changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event for Download selector.
|
||||
*/
|
||||
onChangeDownload( e ) {
|
||||
const {
|
||||
target: { options: selectOptions, selectedIndex },
|
||||
} = e;
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
// Find the selected Download.
|
||||
const selected = selectOptions[ selectedIndex ];
|
||||
|
||||
// Set ID and Price ID.
|
||||
let productId = selected.value;
|
||||
let priceId = null;
|
||||
|
||||
const parts = productId.split( '_' );
|
||||
|
||||
productId = parseInt( parts[ 0 ] );
|
||||
|
||||
if ( parts[ 1 ] ) {
|
||||
priceId = parseInt( parts[ 1 ] );
|
||||
}
|
||||
|
||||
state.set( 'isFetching', true );
|
||||
|
||||
// Update basic attributes.
|
||||
model.set( {
|
||||
productId,
|
||||
priceId,
|
||||
productName: selected.text,
|
||||
error: false,
|
||||
} );
|
||||
|
||||
// Update amount attributes.
|
||||
model
|
||||
.getAmounts( {
|
||||
country: state.getTaxCountry(),
|
||||
region: state.getTaxRegion(),
|
||||
products: state.get( 'items' ).map( ( item ) => ( {
|
||||
id: item.get( 'productId' ),
|
||||
quantity: item.get( 'quantity' ),
|
||||
options: {
|
||||
price_id: item.get( 'priceId' ),
|
||||
}
|
||||
} ) ),
|
||||
discountIds: state.get( 'adjustments' ).pluck( 'typeId' ),
|
||||
} )
|
||||
.fail( ( { message: error } ) => {
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false );
|
||||
|
||||
// Set error and reset model.
|
||||
model.set( {
|
||||
error,
|
||||
productId: 0,
|
||||
priceId: null,
|
||||
productName: '',
|
||||
} );
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
const { amount, tax, subtotal, total } = response;
|
||||
|
||||
model.set( {
|
||||
amount,
|
||||
tax,
|
||||
subtotal,
|
||||
total,
|
||||
|
||||
amountManual: number.format( amount ),
|
||||
taxManual: number.format( tax ),
|
||||
subtotalManual: number.format( subtotal ),
|
||||
} );
|
||||
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderItem`'s when the Quantity changes.
|
||||
*
|
||||
* @since 3.0
|
||||
* @todo Validate.
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeQuantity( e ) {
|
||||
this.model.set( 'quantity', parseInt( e.target.value ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderItem`'s when the manually managed Amount changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeAmount( e ) {
|
||||
this.model.set( 'amountManual', e.target.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderItem`'s when the manually managed Tax changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeTax( e ) {
|
||||
this.model.set( 'taxManual', e.target.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderItem`'s when the manually managed Subtotal changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeSubtotal( e ) {
|
||||
this.model.set( 'subtotalManual', e.target.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles manual amount adjustments.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onAutoCalculateToggle( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
this.model.set( {
|
||||
_isAdjustingManually: ! e.target.checked,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes dialog and opens "Order Details - Address" section.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onSetAddress( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
this.closeDialog();
|
||||
|
||||
const button = $( '[href="#edd_general_address"]' );
|
||||
|
||||
if ( ! button ) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.trigger( 'click' );
|
||||
|
||||
$( '#edd_order_address_country' ).trigger( 'focus' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderItem` to `OrderItems`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Submit event.
|
||||
*/
|
||||
onAdd( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
state.set( 'isFetching', true );
|
||||
|
||||
// Use manual amounts if adjusting manually.
|
||||
if ( true === model.get( '_isAdjustingManually' ) ) {
|
||||
model.set( {
|
||||
amount: number.unformat( model.get( 'amountManual' ) ),
|
||||
tax: number.unformat( model.get( 'taxManual' ) ),
|
||||
subtotal: number.unformat( model.get( 'subtotalManual' ) ),
|
||||
} );
|
||||
|
||||
// Duplicate base amounts by the quantity set.
|
||||
} else {
|
||||
const quantity = model.get( 'quantity' );
|
||||
|
||||
model.set( {
|
||||
tax: model.get( 'tax' ) * quantity,
|
||||
subtotal: model.get( 'subtotal' ) * quantity,
|
||||
} );
|
||||
}
|
||||
|
||||
const items = state.get( 'items' );
|
||||
|
||||
// Add to collection but do not alert.
|
||||
items.add( model, {
|
||||
silent: true,
|
||||
} );
|
||||
|
||||
// Update all amounts with new item and alert when done.
|
||||
items
|
||||
.updateAmounts()
|
||||
.fail( ( { message: error } ) => {
|
||||
// Remove added model on failure.
|
||||
// It is is added previously to calculate Discounts
|
||||
// as if adding would be successful.
|
||||
items.remove( model, {
|
||||
silent: true,
|
||||
} );
|
||||
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false );
|
||||
|
||||
// Set error.
|
||||
model.set( 'error', error );
|
||||
} )
|
||||
.done( () => {
|
||||
// Stop listening to the model in this view.
|
||||
this.stopListening( model );
|
||||
|
||||
// Alert of succesful addition.
|
||||
items.trigger( 'add', model );
|
||||
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false );
|
||||
} );
|
||||
},
|
||||
} );
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
|
||||
/**
|
||||
* NoOrderItems
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class NoOrderItems
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const NoOrderItems = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tr',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-no-items' ),
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = this.options;
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 4 : 3;
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
};
|
||||
},
|
||||
} );
|
@ -0,0 +1,118 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
|
||||
/**
|
||||
* OrderAdjustment
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderAdjustment
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderAdjustment = Base.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tr',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'is-expanded',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click .delete': 'onDelete',
|
||||
},
|
||||
|
||||
initialize() {
|
||||
Base.prototype.initialize.apply( this );
|
||||
|
||||
// Set template depending on type.
|
||||
switch ( this.model.get( 'type' ) ) {
|
||||
case 'credit':
|
||||
case 'fee':
|
||||
this.template = wp.template( 'edd-admin-order-adjustment' );
|
||||
break;
|
||||
default:
|
||||
this.template = wp.template(
|
||||
'edd-admin-order-adjustment-discount'
|
||||
);
|
||||
}
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = this.options;
|
||||
|
||||
const { currency, number } = state.get( 'formatters' );
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
let orderItem;
|
||||
|
||||
if ( 'order_item' === model.get( 'objectType' ) ) {
|
||||
orderItem = _.first( state.get( 'items' ).filter( ( item ) => {
|
||||
return undefined !== item.get( 'adjustments' ).findWhere( {
|
||||
objectId: item.get( 'id' ),
|
||||
} );
|
||||
} ) );
|
||||
}
|
||||
|
||||
const subtotal = model.getAmount();
|
||||
const total = model.getTotal();
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
total,
|
||||
subtotal,
|
||||
orderItem: orderItem ? orderItem.toJSON() : false,
|
||||
totalCurrency: currency.format( total ),
|
||||
subtotalCurrency: currency.format( subtotal )
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the current Adjustment from Adjustments.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onDelete( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { state } = this.options;
|
||||
|
||||
// Remove `OrderAdjustment`.
|
||||
state.get( 'adjustments' ).remove( this.model );
|
||||
|
||||
// Update `OrderItem` amounts.
|
||||
state.get( 'items' ).updateAmounts();
|
||||
},
|
||||
} );
|
@ -0,0 +1,101 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustment } from './order-adjustment.js';
|
||||
|
||||
/**
|
||||
* OrderAdjustments
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderAdjustments
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderAdjustments = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__adjustments',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
const items = state.get( 'items' );
|
||||
const adjustments = state.get( 'adjustments' );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( state, 'change:hasTax', this.render );
|
||||
this.listenTo( items, 'change', this.render );
|
||||
this.listenTo( adjustments, 'add', this.render );
|
||||
this.listenTo( adjustments, 'remove', this.remove );
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders initial view.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
const adjustments = this.getAdjustments();
|
||||
|
||||
this.views.remove();
|
||||
|
||||
_.each( adjustments, ( adjustment ) => this.add( adjustment ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderAdjustment` subview.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderAdjustment} model OrderAdjustment to add to view.
|
||||
*/
|
||||
add( model ) {
|
||||
this.views.add(
|
||||
new OrderAdjustment( {
|
||||
...this.options,
|
||||
model,
|
||||
} )
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an `OrderAdjustment` subview.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderAdjustment} model OrderAdjustment to remove from view.
|
||||
*/
|
||||
remove( model ) {
|
||||
let subview = null;
|
||||
const views = this.views.get();
|
||||
|
||||
if ( ! views ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the Subview containing the model.
|
||||
views.forEach( ( view ) => {
|
||||
const { model: viewModel } = view;
|
||||
|
||||
if ( viewModel.id === model.id ) {
|
||||
subview = view;
|
||||
}
|
||||
} );
|
||||
|
||||
// Remove Subview if found.
|
||||
if ( null !== subview ) {
|
||||
subview.remove();
|
||||
}
|
||||
},
|
||||
} );
|
@ -0,0 +1,27 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustments } from './order-adjustments.js';
|
||||
|
||||
/**
|
||||
* OrderCredits
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderCredits
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderCredits = OrderAdjustments.extend( {
|
||||
/**
|
||||
* Returns Credit adjustments.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
getAdjustments() {
|
||||
const { state } = this.options;
|
||||
|
||||
return state.get( 'adjustments' ).getByType( 'credit' );
|
||||
},
|
||||
} );
|
@ -0,0 +1,31 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustments } from './order-adjustments.js';
|
||||
|
||||
/**
|
||||
* OrderDiscountsFees
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderDiscountsFees
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderDiscountsFees = OrderAdjustments.extend( {
|
||||
/**
|
||||
* Returns Discount and Fee adjustments.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
getAdjustments() {
|
||||
const { state } = this.options;
|
||||
|
||||
return state.get( 'adjustments' ).filter(
|
||||
( adjustment ) => {
|
||||
return [ 'discount', 'fee' ].includes( adjustment.get( 'type' ) );
|
||||
}
|
||||
);
|
||||
},
|
||||
} );
|
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
import { CopyDownloadLink } from './copy-download-link.js';
|
||||
|
||||
/**
|
||||
* OrderItem
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderItem
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderItem = Base.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tr',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-item' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click .delete': 'onDelete',
|
||||
'click .copy-download-link': 'onCopyDownloadLink',
|
||||
},
|
||||
|
||||
/**
|
||||
* "Order Item" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderItem
|
||||
* @augments Base
|
||||
*/
|
||||
initialize() {
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const { currency } = state.get( 'formatters' );
|
||||
|
||||
const subtotal = model.getSubtotal();
|
||||
const discountAmount = model.getDiscountAmount();
|
||||
const isAdjustingManually = model.get( '_isAdjustingManually' );
|
||||
const tax = model.getTax();
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
discount: discountAmount,
|
||||
amountCurrency: currency.format( model.get( 'amount' ) ),
|
||||
subtotal,
|
||||
subtotalCurrency: currency.format( subtotal ),
|
||||
tax,
|
||||
taxCurrency: currency.format( tax ),
|
||||
total: model.getTotal(),
|
||||
|
||||
config: {
|
||||
isAdjustingManually,
|
||||
},
|
||||
|
||||
adjustments: model.get( 'adjustments' ).toJSON(),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the current Item from Items.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onDelete( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
// Remove OrderItem.
|
||||
state.get( 'items' ).remove( model );
|
||||
|
||||
// Update remaining OrderItem amounts.
|
||||
state.get( 'items' ).updateAmounts();
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens a Dialog that fetches Download File URLs.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onCopyDownloadLink( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { options, model } = this;
|
||||
|
||||
new CopyDownloadLink( {
|
||||
orderId: model.get( 'orderId' ),
|
||||
productId: model.get( 'productId' ),
|
||||
priceId: model.get( 'priceId' ),
|
||||
} )
|
||||
.openDialog()
|
||||
.render();
|
||||
},
|
||||
} );
|
@ -0,0 +1,120 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderItem } from './order-item.js';
|
||||
import { NoOrderItems } from './no-order-items.js';
|
||||
|
||||
/**
|
||||
* OrderItems
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderItems
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderItems = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__items',
|
||||
|
||||
/**
|
||||
* "Order Items" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderItem
|
||||
* @augments Base
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
const items = state.get( 'items' );
|
||||
const adjustments = state.get( 'adjustments' );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( items, 'add', this.render );
|
||||
this.listenTo( items, 'remove', this.remove );
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders initial view.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
const { state } = this.options;
|
||||
const items = state.get( 'items' );
|
||||
|
||||
this.views.remove();
|
||||
|
||||
// Nothing available.
|
||||
if ( 0 === items.length ) {
|
||||
this.views.set(
|
||||
new NoOrderItems( {
|
||||
...this.options,
|
||||
} )
|
||||
);
|
||||
// Render each item.
|
||||
} else {
|
||||
_.each( items.models, ( model ) => this.add( model ) );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderItem` subview.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderItem} model OrderItem
|
||||
*/
|
||||
add( model ) {
|
||||
this.views.add(
|
||||
new OrderItem( {
|
||||
...this.options,
|
||||
model,
|
||||
} )
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an `OrderItem` subview.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderItem} model OrderItem
|
||||
*/
|
||||
remove( model ) {
|
||||
let subview = null;
|
||||
|
||||
// Find the subview containing the model.
|
||||
this.views.get().forEach( ( view ) => {
|
||||
const { model: viewModel } = view;
|
||||
|
||||
if ( viewModel.get( 'id' ) === model.id ) {
|
||||
subview = view;
|
||||
}
|
||||
} );
|
||||
|
||||
// Remove subview if found.
|
||||
if ( null !== subview ) {
|
||||
subview.remove();
|
||||
}
|
||||
|
||||
// Last item was removed, show "No items".
|
||||
if ( 0 === this.views.get().length ) {
|
||||
this.views.set(
|
||||
new NoOrderItems( {
|
||||
...this.options,
|
||||
} )
|
||||
);
|
||||
}
|
||||
},
|
||||
} );
|
@ -0,0 +1,57 @@
|
||||
/* global wp */
|
||||
|
||||
/**
|
||||
* OrderRefund
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderRefund
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderRefund = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-refund' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tr',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'is-expanded',
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const { currency } = state.get( 'formatters' );
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
return {
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
id: model.get( 'id' ),
|
||||
number: model.get( 'number' ),
|
||||
dateCreated: model.get( 'dateCreatedi18n' ),
|
||||
totalCurrency: currency.format( model.get( 'total' ) ),
|
||||
};
|
||||
},
|
||||
} );
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderRefund } from './order-refund.js';
|
||||
|
||||
/**
|
||||
* Order refunds
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderRefunds
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderRefunds = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__refunds',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-refunds' ),
|
||||
|
||||
/**
|
||||
* Renders initial view.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
const { state } = this.options;
|
||||
const { models: refunds } = state.get( 'refunds' );
|
||||
|
||||
_.each( refunds, ( model ) => (
|
||||
this.views.add(
|
||||
new OrderRefund( {
|
||||
...this.options,
|
||||
model,
|
||||
} )
|
||||
)
|
||||
) );
|
||||
},
|
||||
} );
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Order subtotal
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Subtotal
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderSubtotal = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__subtotal',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-subtotal' ),
|
||||
|
||||
/**
|
||||
* Order subtotal view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderSubtotal
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( state.get( 'items' ), 'add remove change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add remove', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { state } = this.options;
|
||||
const { currency, number } = state.get( 'formatters' );
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
const subtotal = state.getSubtotal();
|
||||
|
||||
return {
|
||||
state: state.toJSON(),
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
subtotal,
|
||||
subtotalCurrency: currency.format( subtotal ),
|
||||
};
|
||||
},
|
||||
} );
|
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Order tax
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderTax
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderTax = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__tax',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-tax' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click #notice-tax-change .notice-dismiss': 'onDismissTaxRateChange',
|
||||
'click #notice-tax-change .update-amounts': 'onUpdateAmounts',
|
||||
},
|
||||
|
||||
/**
|
||||
* Order total view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderTotal
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( state, 'change:hasTax', this.render );
|
||||
this.listenTo( state.get( 'items' ), 'add remove change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add remove', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { state } = this.options;
|
||||
const { currency, number } = state.get( 'formatters' );
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
const tax = state.getTax();
|
||||
const hasNewTaxRate = state.hasNewTaxRate();
|
||||
|
||||
const taxableItems = [
|
||||
...state.get( 'items' ).models,
|
||||
...state.get( 'adjustments' ).getByType( 'fee' ),
|
||||
];
|
||||
|
||||
return {
|
||||
state: {
|
||||
...state.toJSON(),
|
||||
hasNewTaxRate,
|
||||
},
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
tax,
|
||||
taxCurrency: currency.format( tax ),
|
||||
|
||||
hasTaxableItems: taxableItems.length > 0,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismisses Tax Rate change notice.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
onDismissTaxRateChange() {
|
||||
const { state } = this.options;
|
||||
// Reset amount
|
||||
state.set( 'hasTax', state.get( 'hasTax' ) );
|
||||
|
||||
// Manually trigger change because new and previous attributes
|
||||
// are the same so Backbone will not.
|
||||
state.trigger( 'change:hasTax' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates amounts for existing Order Items.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
onUpdateAmounts( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { state } = this.options;
|
||||
|
||||
// Manually recalculate taxed fees.
|
||||
state.get( 'adjustments' ).getByType( 'fee' ).forEach(
|
||||
( fee ) => {
|
||||
fee.updateTax();
|
||||
}
|
||||
);
|
||||
|
||||
// Request updated tax amounts for orders from the server.
|
||||
state.get( 'items' )
|
||||
.updateAmounts()
|
||||
.done( () => {
|
||||
this.onDismissTaxRateChange();
|
||||
} );
|
||||
},
|
||||
} );
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Order total
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderTotal
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderTotal = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__total',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-total' ),
|
||||
|
||||
/**
|
||||
* Order tax view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderTax
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( state, 'change:hasTax', this.render );
|
||||
this.listenTo( state.get( 'items' ), 'add remove change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add remove', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { state } = this.options;
|
||||
const { currency, number } = state.get( 'formatters' );
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
const total = state.getTotal();
|
||||
const discount = state.getDiscount();
|
||||
const hasManualAdjustment = undefined !== state.get( 'items' ).findWhere( {
|
||||
_isAdjustingManually: true,
|
||||
} );
|
||||
|
||||
return {
|
||||
state: {
|
||||
...state.toJSON(),
|
||||
hasManualAdjustment,
|
||||
},
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
total,
|
||||
discount,
|
||||
|
||||
discountCurrency: currency.format( discount ),
|
||||
totalCurrency: currency.format( total ),
|
||||
};
|
||||
},
|
||||
} );
|
@ -0,0 +1,66 @@
|
||||
/** global wp */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Summary } from './summary.js';
|
||||
import { Actions } from './actions.js';
|
||||
|
||||
/**
|
||||
* Overview
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Overview
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const Overview = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-order-overview',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click .toggle-row': 'onToggleRow',
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {Overview} Current view.
|
||||
*/
|
||||
render() {
|
||||
// Add "Summary".
|
||||
//
|
||||
// Contains `OrderItems`, `OrderAdjustments`, and `Totals` subviews.
|
||||
this.views.add( new Summary( this.options ) );
|
||||
|
||||
// "Actions".
|
||||
if ( document.getElementById( 'edd-order-overview-actions' ) ) {
|
||||
this.views.add( new Actions( this.options ) );
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles a row's other columns.
|
||||
*
|
||||
* Core does not support the dynamically added items.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see https://github.com/WordPress/WordPress/blob/001ffe81fbec4438a9f594f330e18103d21fbcd7/wp-admin/js/common.js#L908
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onToggleRow( e ) {
|
||||
e.preventDefault();
|
||||
$( e.target ).closest( 'tr' ).toggleClass( 'is-expanded' );
|
||||
},
|
||||
} );
|
@ -0,0 +1,46 @@
|
||||
/** global wp */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderItems } from './order-items.js';
|
||||
import { OrderSubtotal } from './order-subtotal.js';
|
||||
import { OrderDiscountsFees } from './order-discounts-fees.js';
|
||||
import { OrderTax } from './order-tax.js';
|
||||
import { OrderCredits } from './order-credits.js';
|
||||
import { OrderTotal } from './order-total.js';
|
||||
import { OrderRefunds } from './order-refunds.js';
|
||||
|
||||
/**
|
||||
* Overview summary
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Summary
|
||||
* @augments wp.Backbone.view
|
||||
*/
|
||||
export const Summary = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-order-overview-summary',
|
||||
|
||||
/**
|
||||
* Renders the view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {Summary} Current view.
|
||||
*/
|
||||
render() {
|
||||
this.views.add( new OrderItems( this.options ) );
|
||||
this.views.add( new OrderSubtotal( this.options ) );
|
||||
this.views.add( new OrderDiscountsFees( this.options ) );
|
||||
this.views.add( new OrderTax( this.options ) );
|
||||
this.views.add( new OrderCredits( this.options ) );
|
||||
this.views.add( new OrderTotal( this.options ) );
|
||||
this.views.add( new OrderRefunds( this.options ) );
|
||||
|
||||
return this;
|
||||
},
|
||||
} );
|
@ -0,0 +1,2 @@
|
||||
// Empty file for backwards compatibility.
|
||||
// @see assets/js/admin/orders/index.js
|
@ -0,0 +1,31 @@
|
||||
/* global eddAdminReportsCharts */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import moment from 'moment';
|
||||
import { render as lineChartRender } from './line.js';
|
||||
import { render as pieChartRender } from './pie.js';
|
||||
import { isPieChart } from './utils.js';
|
||||
|
||||
// Access existing global `edd` variable, or create a new object.
|
||||
window.edd = window.edd || {};
|
||||
|
||||
/**
|
||||
* Render a chart based on config.
|
||||
*
|
||||
* This function is attached to the `edd` property attached to the `window`.
|
||||
*
|
||||
* @param {Object} config Chart config.
|
||||
*/
|
||||
window.edd.renderChart = ( config ) => {
|
||||
const isPie = isPieChart( config );
|
||||
|
||||
Chart.defaults.global.pointHitDetectionRadius = 5;
|
||||
|
||||
if ( isPieChart( config ) ) {
|
||||
pieChartRender( config );
|
||||
} else {
|
||||
lineChartRender( config );
|
||||
}
|
||||
};
|
@ -0,0 +1,94 @@
|
||||
/* global Chart */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { NumberFormat } from '@easy-digital-downloads/currency';
|
||||
import moment, { utc } from 'moment';
|
||||
import momentTimezone from 'moment-timezone';
|
||||
import { getLabelWithTypeCondition, toolTipBaseConfig } from './utils';
|
||||
|
||||
/**
|
||||
* Render a line chart.
|
||||
*
|
||||
* @param {Object} config Global chart config.
|
||||
* @return {Chart}
|
||||
*/
|
||||
export const render = ( config ) => {
|
||||
const { target } = config;
|
||||
const {
|
||||
dates: {
|
||||
utc_offset: utcOffset,
|
||||
hour_by_hour: hourByHour,
|
||||
day_by_day: dayByDay,
|
||||
},
|
||||
} = config;
|
||||
const number = new NumberFormat();
|
||||
|
||||
const lineConfig = {
|
||||
...config,
|
||||
options: {
|
||||
...config.options,
|
||||
maintainAspectRatio: false,
|
||||
tooltips: tooltipConfig( config ),
|
||||
scales: {
|
||||
...config.options.scales,
|
||||
yAxes: [
|
||||
{
|
||||
...config.options.scales.yAxes[0],
|
||||
ticks: {
|
||||
callback: ( value, index, values ) => {
|
||||
return number.format( value );
|
||||
},
|
||||
suggestedMin: 0,
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
xAxes: [
|
||||
{
|
||||
...config.options.scales.xAxes[0],
|
||||
ticks: {
|
||||
...config.options.scales.xAxes[0].ticks,
|
||||
maxTicksLimit:12,
|
||||
autoSkip: true,
|
||||
callback( value, index, ticks ) {
|
||||
return moment.tz( ticks[index].value, config.dates.timezone ).format( config.dates.time_format );
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Render
|
||||
return new Chart( document.getElementById( target ), lineConfig );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get custom tooltip config for line charts.
|
||||
*
|
||||
* @param {Object} config Global chart config.
|
||||
* @return {Object}
|
||||
*/
|
||||
export const tooltipConfig = ( config ) => ( {
|
||||
...toolTipBaseConfig,
|
||||
|
||||
callbacks: {
|
||||
/**
|
||||
* Generate a label.
|
||||
*
|
||||
* @param {Object} t
|
||||
* @param {Object} d
|
||||
*/
|
||||
label: function( t, d ) {
|
||||
const { options: { datasets } } = config;
|
||||
|
||||
const datasetConfig = datasets[ Object.keys( datasets )[ t.datasetIndex ] ];
|
||||
const label = getLabelWithTypeCondition( t.yLabel, datasetConfig );
|
||||
|
||||
return `${ d.datasets[ t.datasetIndex ].label }: ${ label }`;
|
||||
},
|
||||
},
|
||||
} );
|
@ -0,0 +1,56 @@
|
||||
/* global Chart */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { toolTipBaseConfig, getLabelWithTypeCondition } from './utils';
|
||||
|
||||
/**
|
||||
* Render a line chart.
|
||||
*
|
||||
* @param {Object} config Global chart config.
|
||||
* @return {Chart}
|
||||
*/
|
||||
export const render = ( config ) => {
|
||||
const { target } = config;
|
||||
|
||||
// Config tooltips.
|
||||
config.options.tooltips = tooltipConfig( config );
|
||||
|
||||
// Render
|
||||
return new Chart( document.getElementById( target ), config );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get custom tooltip config for pie charts.
|
||||
*
|
||||
* @param {Object} config Global chart config.
|
||||
* @return {Object}
|
||||
*/
|
||||
export const tooltipConfig = ( config ) => ( {
|
||||
...toolTipBaseConfig,
|
||||
|
||||
callbacks: {
|
||||
/**
|
||||
* Generate a label.
|
||||
*
|
||||
* @param {Object} t
|
||||
* @param {Object} d
|
||||
*/
|
||||
label: function( t, d ) {
|
||||
const { options: { datasets } } = config;
|
||||
const datasetConfig = datasets[ Object.keys( datasets )[ t.datasetIndex ] ];
|
||||
const dataset = d.datasets[ t.datasetIndex ];
|
||||
|
||||
const total = dataset.data.reduce( function( previousValue, currentValue, currentIndex, array ) {
|
||||
return previousValue + currentValue;
|
||||
} );
|
||||
|
||||
const currentValue = dataset.data[ t.index ];
|
||||
const label = getLabelWithTypeCondition( currentValue, datasetConfig );
|
||||
const precentage = Math.floor( ( ( currentValue / total ) * 100 ) + 0.5 );
|
||||
|
||||
return `${ d.labels[ t.index ] }: ${ label } (${ precentage }%)`;
|
||||
},
|
||||
},
|
||||
} );
|
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Currency } from '@easy-digital-downloads/currency';
|
||||
|
||||
/**
|
||||
* Determine if a pie graph.
|
||||
*
|
||||
* @todo maybe pass from data?
|
||||
*
|
||||
* @param {Object} config Global chart config.
|
||||
* @return {Bool}
|
||||
*/
|
||||
export const isPieChart = ( config ) => {
|
||||
const { type } = config;
|
||||
|
||||
return type === 'pie' || type === 'doughnut';
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a chart's dataset has a special conditional type.
|
||||
*
|
||||
* Currently just checks for currency.
|
||||
*
|
||||
* @param {string} label Current label.
|
||||
* @param {Object} config Global chart config.
|
||||
*/
|
||||
export const getLabelWithTypeCondition = ( label, datasetConfig ) => {
|
||||
let newLabel = label;
|
||||
const { type } = datasetConfig;
|
||||
|
||||
if ( 'currency' === type ) {
|
||||
const currency = new Currency();
|
||||
|
||||
newLabel = currency.format( label, false );
|
||||
}
|
||||
|
||||
return newLabel;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared tooltip configuration.
|
||||
*/
|
||||
export const toolTipBaseConfig = {
|
||||
enabled: false,
|
||||
mode: 'index',
|
||||
position: 'nearest',
|
||||
|
||||
/**
|
||||
* Output a a custom tooltip.
|
||||
*
|
||||
* @param {Object} tooltip Tooltip data.
|
||||
*/
|
||||
custom: function( tooltip ) {
|
||||
// Tooltip element.
|
||||
let tooltipEl = document.getElementById( 'edd-chartjs-tooltip' );
|
||||
|
||||
if ( ! tooltipEl ) {
|
||||
tooltipEl = document.createElement( 'div' );
|
||||
tooltipEl.id = 'edd-chartjs-tooltip';
|
||||
tooltipEl.innerHTML = '<table></table>';
|
||||
|
||||
this._chart.canvas.parentNode.appendChild( tooltipEl );
|
||||
}
|
||||
|
||||
// Hide if no tooltip.
|
||||
if ( tooltip.opacity === 0 ) {
|
||||
tooltipEl.style.opacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set caret position.
|
||||
tooltipEl.classList.remove( 'above', 'below', 'no-transform' );
|
||||
|
||||
if ( tooltip.yAlign ) {
|
||||
tooltipEl.classList.add( tooltip.yAlign );
|
||||
} else {
|
||||
tooltipEl.classList.add( 'no-transform' );
|
||||
}
|
||||
|
||||
function getBody( bodyItem ) {
|
||||
return bodyItem.lines;
|
||||
}
|
||||
|
||||
// Set Text
|
||||
if ( tooltip.body ) {
|
||||
const titleLines = tooltip.title || [];
|
||||
const bodyLines = tooltip.body.map( getBody );
|
||||
|
||||
let innerHtml = '<thead>';
|
||||
|
||||
innerHtml += '</thead><tbody>';
|
||||
|
||||
bodyLines.forEach( function( body, i ) {
|
||||
const colors = tooltip.labelColors[ i ];
|
||||
const { borderColor, backgroundColor } = colors;
|
||||
|
||||
// Super dirty check to use the legend's color.
|
||||
let fill = borderColor;
|
||||
|
||||
if ( fill === 'rgb(230, 230, 230)' || fill === '#fff' ) {
|
||||
fill = backgroundColor;
|
||||
}
|
||||
|
||||
const style = [
|
||||
`background: ${ fill }`,
|
||||
`border-color: ${ fill }`,
|
||||
'border-width: 2px',
|
||||
];
|
||||
|
||||
const span = '<span class="edd-chartjs-tooltip-key" style="' + style.join( ';' ) + '"></span>';
|
||||
|
||||
innerHtml += '<tr><td>' + span + body + '</td></tr>';
|
||||
} );
|
||||
|
||||
innerHtml += '</tbody>';
|
||||
|
||||
const tableRoot = tooltipEl.querySelector( 'table' );
|
||||
tableRoot.innerHTML = innerHtml;
|
||||
}
|
||||
|
||||
const positionY = this._chart.canvas.offsetTop;
|
||||
const positionX = this._chart.canvas.offsetLeft;
|
||||
|
||||
// Display, position, and set styles for font
|
||||
tooltipEl.style.opacity = 1;
|
||||
tooltipEl.style.left = positionX + tooltip.caretX + 'px';
|
||||
tooltipEl.style.top = positionY + tooltip.caretY + 'px';
|
||||
tooltipEl.style.fontFamily = tooltip._bodyFontFamily;
|
||||
tooltipEl.style.fontSize = tooltip.bodyFontSize + 'px';
|
||||
tooltipEl.style.fontStyle = tooltip._bodyFontStyle;
|
||||
tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
|
||||
},
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
|
||||
export var eddLabelFormatter = function( label, series ) {
|
||||
return '<div style="font-size:12px; text-align:center; padding:2px">' + label + '</div>';
|
||||
};
|
||||
|
||||
export var eddLegendFormatterSales = function( label, series ) {
|
||||
const slug = label.toLowerCase().replace( /\s/g, '-' ),
|
||||
color = '<div class="edd-legend-color" style="background-color: ' + series.color + '"></div>',
|
||||
value = '<div class="edd-pie-legend-item">' + label + ': ' + Math.round( series.percent ) + '% (' + eddFormatNumber( series.data[ 0 ][ 1 ] ) + ')</div>',
|
||||
item = '<div id="' + series.edd_vars.id + slug + '" class="edd-legend-item-wrapper">' + color + value + '</div>';
|
||||
|
||||
jQuery( '#edd-pie-legend-' + series.edd_vars.id ).append( item );
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
export var eddLegendFormatterEarnings = function( label, series ) {
|
||||
const slug = label.toLowerCase().replace( /\s/g, '-' ),
|
||||
color = '<div class="edd-legend-color" style="background-color: ' + series.color + '"></div>',
|
||||
value = '<div class="edd-pie-legend-item">' + label + ': ' + Math.round( series.percent ) + '% (' + eddFormatCurrency( series.data[ 0 ][ 1 ] ) + ')</div>',
|
||||
item = '<div id="' + series.edd_vars.id + slug + '" class="edd-legend-item-wrapper">' + color + value + '</div>';
|
||||
|
||||
jQuery( '#edd-pie-legend-' + series.edd_vars.id ).append( item );
|
||||
|
||||
return item;
|
||||
};
|
@ -0,0 +1,85 @@
|
||||
/* global pagenow, postboxes */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { eddLabelFormatter, eddLegendFormatterSales, eddLegendFormatterEarnings } from './formatting.js';
|
||||
import './charts';
|
||||
|
||||
// Enable reports meta box toggle states.
|
||||
if ( typeof postboxes !== 'undefined' && /edd-reports/.test( pagenow ) ) {
|
||||
postboxes.add_postbox_toggles( pagenow );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports / Exports screen JS
|
||||
*/
|
||||
const EDD_Reports = {
|
||||
|
||||
init: function() {
|
||||
this.meta_boxes();
|
||||
this.date_options();
|
||||
this.customers_export();
|
||||
},
|
||||
|
||||
meta_boxes: function() {
|
||||
$( '.edd-reports-wrapper .postbox .handlediv' ).remove();
|
||||
$( '.edd-reports-wrapper .postbox' ).removeClass( 'closed' );
|
||||
|
||||
// Use a timeout to ensure this happens after core binding
|
||||
setTimeout( function() {
|
||||
$( '.edd-reports-wrapper .postbox .hndle' ).unbind( 'click.postboxes' );
|
||||
}, 1 );
|
||||
},
|
||||
|
||||
date_options: function() {
|
||||
// Show hide extended date options
|
||||
$( 'select.edd-graphs-date-options' ).on( 'change', function( event ) {
|
||||
const select = $( this ),
|
||||
date_range_options = select.parent().siblings( '.edd-date-range-options' );
|
||||
|
||||
if ( 'other' === select.val() ) {
|
||||
date_range_options.removeClass( 'screen-reader-text' );
|
||||
} else {
|
||||
date_range_options.addClass( 'screen-reader-text' );
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
customers_export: function() {
|
||||
// Show / hide Download option when exporting customers
|
||||
$( '#edd_customer_export_download' ).change( function() {
|
||||
const $this = $( this ),
|
||||
download_id = $( 'option:selected', $this ).val(),
|
||||
customer_export_option = $( '#edd_customer_export_option' );
|
||||
|
||||
if ( '0' === $this.val() ) {
|
||||
customer_export_option.show();
|
||||
} else {
|
||||
customer_export_option.hide();
|
||||
}
|
||||
|
||||
// On Download Select, Check if Variable Prices Exist
|
||||
if ( parseInt( download_id ) !== 0 ) {
|
||||
const data = {
|
||||
action: 'edd_check_for_download_price_variations',
|
||||
download_id: download_id,
|
||||
all_prices: true,
|
||||
};
|
||||
|
||||
var price_options_select = $( '.edd_price_options_select' );
|
||||
|
||||
$.post( ajaxurl, data, function( response ) {
|
||||
price_options_select.remove();
|
||||
$( '#edd_customer_export_download_chosen' ).after( response );
|
||||
} );
|
||||
} else {
|
||||
price_options_select.remove();
|
||||
}
|
||||
} );
|
||||
},
|
||||
};
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
EDD_Reports.init();
|
||||
} );
|
@ -0,0 +1,76 @@
|
||||
/* global eddEmailTagsInserter, tb_remove, tb_position, send_to_editor, _, window, document */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { searchItems } from './utils.js';
|
||||
|
||||
/**
|
||||
* Make tags clickable and send them to the email content (wp_editor()).
|
||||
*/
|
||||
function setupEmailTags() {
|
||||
// Find all of the buttons.
|
||||
const insertButtons = document.querySelectorAll( '.edd-email-tags-list-button' );
|
||||
if ( ! insertButtons ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for clicks on tag buttons.
|
||||
*
|
||||
* @param {object} node Button node.
|
||||
*/
|
||||
_.each( insertButtons, function( node ) {
|
||||
/**
|
||||
* Listen for clicks on tag buttons.
|
||||
*/
|
||||
node.addEventListener( 'click', function() {
|
||||
// Close Thickbox.
|
||||
tb_remove();
|
||||
|
||||
window.send_to_editor( node.dataset.to_insert );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter tags.
|
||||
*/
|
||||
function filterEmailTags() {
|
||||
const filterInput = document.querySelector( '.edd-email-tags-filter-search' );
|
||||
const tagItems = document.querySelectorAll( '.edd-email-tags-list-item' );
|
||||
|
||||
if ( ! filterInput ) {
|
||||
return;
|
||||
}
|
||||
|
||||
filterInput.addEventListener( 'keyup', function( event ) {
|
||||
const searchTerm = event.target.value;
|
||||
const foundTags = searchItems( eddEmailTagsInserter.items, searchTerm );
|
||||
|
||||
_.each( tagItems, function( node ) {
|
||||
const found = _.findWhere( foundTags, { tag: node.dataset.tag } );
|
||||
|
||||
node.style.display = ! found ? 'none' : 'block';
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM ready.
|
||||
*/
|
||||
document.addEventListener( 'DOMContentLoaded', function() {
|
||||
// Resize Thickbox when media button is clicked.
|
||||
const mediaButton = document.querySelector( '.edd-email-tags-inserter' );
|
||||
if ( ! mediaButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
mediaButton.addEventListener( 'click', tb_position );
|
||||
|
||||
// Clickable tags.
|
||||
setupEmailTags();
|
||||
|
||||
// Filterable tags.
|
||||
filterEmailTags();
|
||||
} );
|
@ -0,0 +1,40 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Filters an item list given a search term.
|
||||
*
|
||||
* @param {Array} items Item list
|
||||
* @param {string} searchTerm Search term.
|
||||
*
|
||||
* @return {Array} Filtered item list.
|
||||
*/
|
||||
export const searchItems = function( items, searchTerm ) {
|
||||
const normalizedSearchTerm = normalizeTerm( searchTerm );
|
||||
|
||||
const matchSearch = function( string ) {
|
||||
return normalizeTerm( string ).indexOf( normalizedSearchTerm ) !== -1;
|
||||
};
|
||||
|
||||
return _.filter( items, function( item ) {
|
||||
return matchSearch( item.title ) || _.some( item.keywords, matchSearch );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the search term into a normalized term.
|
||||
*
|
||||
* @param {string} term The search term to normalize.
|
||||
*
|
||||
* @return {string} The normalized search term.
|
||||
*/
|
||||
export const normalizeTerm = function( term ) {
|
||||
// Lowercase.
|
||||
// Input: "MEDIA"
|
||||
term = term.toLowerCase();
|
||||
|
||||
// Strip leading and trailing whitespace.
|
||||
// Input: " media "
|
||||
term = term.trim();
|
||||
|
||||
return term;
|
||||
};
|
@ -0,0 +1,75 @@
|
||||
/* global EDDExtensionManager, ajaxurl */
|
||||
|
||||
; ( function ( document, $ ) {
|
||||
'use strict';
|
||||
|
||||
$( '.edd-extension-manager__action' ).on( 'click', function ( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
var $btn = $( this ),
|
||||
action = $btn.attr( 'data-action' ),
|
||||
plugin = $btn.attr( 'data-plugin' ),
|
||||
type = $btn.attr( 'data-type' ),
|
||||
ajaxAction = '';
|
||||
|
||||
if ( $btn.attr( 'disabled' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( action ) {
|
||||
case 'activate':
|
||||
ajaxAction = 'edd_activate_extension';
|
||||
$btn.text( EDDExtensionManager.activating );
|
||||
break;
|
||||
|
||||
case 'install':
|
||||
ajaxAction = 'edd_install_extension';
|
||||
$btn.text( EDDExtensionManager.installing );
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
$btn.removeClass( 'button-primary' ).attr( 'disabled', true ).addClass( 'updating-message' );
|
||||
|
||||
var data = {
|
||||
action: ajaxAction,
|
||||
nonce: EDDExtensionManager.extension_manager_nonce,
|
||||
plugin: plugin,
|
||||
type: type,
|
||||
pass: $btn.attr( 'data-pass' ),
|
||||
id: $btn.attr( 'data-id' ),
|
||||
product: $btn.attr( 'data-product' ),
|
||||
};
|
||||
|
||||
$.post( ajaxurl, data )
|
||||
.done( function ( res ) {
|
||||
console.log( res );
|
||||
var thisStep = $btn.closest( '.edd-extension-manager__step' );
|
||||
if ( res.success ) {
|
||||
var nextStep = thisStep.next();
|
||||
if ( nextStep.length ) {
|
||||
thisStep.fadeOut();
|
||||
nextStep.prepend( '<div class="notice inline-notice notice-success"><p>' + res.data.message + '</p></div>' );
|
||||
nextStep.fadeIn();
|
||||
}
|
||||
} else {
|
||||
thisStep.fadeOut();
|
||||
var message = res.data.message;
|
||||
/**
|
||||
* The install class returns an array of error messages, and res.data.message will be undefined.
|
||||
* In that case, we'll use the standard failure messages.
|
||||
*/
|
||||
if ( ! message ) {
|
||||
if ( 'plugin' !== type ) {
|
||||
message = EDDExtensionManager.extension_install_failed;
|
||||
} else {
|
||||
message = EDDExtensionManager.plugin_install_failed;
|
||||
}
|
||||
}
|
||||
thisStep.after( '<div class="notice inline-notice notice-warning"><p>' + message + '</p></div>' );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
} )( document, jQuery );
|
@ -0,0 +1,130 @@
|
||||
jQuery( document ).ready( function ( $ ) {
|
||||
/**
|
||||
* Connect to PayPal
|
||||
*/
|
||||
$( '#edd-paypal-commerce-connect' ).on( 'click', function ( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
// Clear errors.
|
||||
var errorContainer = $( '#edd-paypal-commerce-errors' );
|
||||
errorContainer.empty().removeClass( 'notice notice-error' );
|
||||
|
||||
var button = document.getElementById( 'edd-paypal-commerce-connect' );
|
||||
button.classList.add( 'updating-message' );
|
||||
button.disabled = true;
|
||||
|
||||
$.post( ajaxurl, {
|
||||
action: 'edd_paypal_commerce_connect',
|
||||
_ajax_nonce: $( this ).data( 'nonce' )
|
||||
}, function( response ) {
|
||||
if ( ! response.success ) {
|
||||
console.log( 'Connection failure', response.data );
|
||||
button.classList.remove( 'updating-message' );
|
||||
button.disabled = false;
|
||||
|
||||
// Set errors.
|
||||
errorContainer.html( '<p>' + response.data + '</p>' ).addClass( 'notice notice-error' );
|
||||
return;
|
||||
}
|
||||
|
||||
var paypalLinkEl = document.getElementById( 'edd-paypal-commerce-link' );
|
||||
paypalLinkEl.href = response.data.signupLink + '&displayMode=minibrowser';
|
||||
|
||||
paypalLinkEl.click();
|
||||
} );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Checks the PayPal connection & webhook status.
|
||||
*/
|
||||
function eddPayPalGetAccountStatus() {
|
||||
var accountInfoEl = document.getElementById( 'edd-paypal-commerce-connect-wrap' );
|
||||
if ( accountInfoEl ) {
|
||||
$.post( ajaxurl, {
|
||||
action: 'edd_paypal_commerce_get_account_info',
|
||||
_ajax_nonce: accountInfoEl.getAttribute( 'data-nonce' )
|
||||
}, function( response ) {
|
||||
var newHtml = '<p>' + eddPayPalConnectVars.defaultError + '</p>';
|
||||
|
||||
if ( response.success ) {
|
||||
newHtml = response.data.account_status;
|
||||
|
||||
if ( response.data.actions && response.data.actions.length ) {
|
||||
newHtml += '<p class="edd-paypal-connect-actions">' + response.data.actions.join( ' ' ) + '</p>';
|
||||
}
|
||||
} else if ( response.data && response.data.message ) {
|
||||
newHtml = response.data.message;
|
||||
}
|
||||
|
||||
accountInfoEl.innerHTML = newHtml;
|
||||
|
||||
// Remove old status messages.
|
||||
accountInfoEl.classList.remove( 'notice-success', 'notice-warning', 'notice-error' );
|
||||
|
||||
// Add new one.
|
||||
var newClass = response.success && response.data.status ? 'notice-' + response.data.status : 'notice-error';
|
||||
accountInfoEl.classList.add( newClass );
|
||||
} );
|
||||
}
|
||||
}
|
||||
eddPayPalGetAccountStatus();
|
||||
|
||||
/**
|
||||
* Create webhook
|
||||
*/
|
||||
$( document ).on( 'click', '.edd-paypal-connect-action', function ( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
var button = $( this );
|
||||
button.prop( 'disabled', true );
|
||||
button.addClass( 'updating-message' );
|
||||
|
||||
var errorWrap = $( '#edd-paypal-commerce-connect-wrap' ).find( '.edd-paypal-actions-error-wrap' );
|
||||
if ( errorWrap.length ) {
|
||||
errorWrap.remove();
|
||||
}
|
||||
|
||||
$.post( ajaxurl, {
|
||||
action: button.data( 'action' ),
|
||||
_ajax_nonce: button.data( 'nonce' )
|
||||
}, function( response ) {
|
||||
button.prop( 'disabled', false );
|
||||
button.removeClass( 'updating-message' );
|
||||
|
||||
if ( response.success ) {
|
||||
button.addClass( 'updated-message' );
|
||||
|
||||
// Refresh account status.
|
||||
eddPayPalGetAccountStatus();
|
||||
} else {
|
||||
button.parent().after( '<p class="edd-paypal-actions-error-wrap">' + response.data + '</p>' );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
window.eddPayPalOnboardingCallback = function eddPayPalOnboardingCallback( authCode, shareId ) {
|
||||
var connectButton = document.getElementById( 'edd-paypal-commerce-connect' );
|
||||
var errorContainer = document.getElementById( 'edd-paypal-commerce-errors' );
|
||||
|
||||
jQuery.post( ajaxurl, {
|
||||
action: 'edd_paypal_commerce_get_access_token',
|
||||
auth_code: authCode,
|
||||
share_id: shareId,
|
||||
_ajax_nonce: connectButton.getAttribute( 'data-nonce' )
|
||||
}, function( response ) {
|
||||
connectButton.classList.remove( 'updating-message' );
|
||||
|
||||
if ( ! response.success ) {
|
||||
connectButton.disabled = false;
|
||||
|
||||
errorContainer.innerHTML = '<p>' + response.data + '</p>';
|
||||
errorContainer.classList.add( 'notice notice-error' );
|
||||
return;
|
||||
}
|
||||
|
||||
connectButton.classList.add( 'updated-message' );
|
||||
|
||||
window.location.reload();
|
||||
} );
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
import { recaptureRemoteInstall } from './recapture';
|
||||
import './gateways/paypal';
|
||||
|
||||
/**
|
||||
* Settings screen JS
|
||||
*/
|
||||
const EDD_Settings = {
|
||||
init: function() {
|
||||
this.general();
|
||||
this.misc();
|
||||
this.gateways();
|
||||
this.emails();
|
||||
},
|
||||
|
||||
general: function() {
|
||||
const edd_color_picker = $( '.edd-color-picker' );
|
||||
|
||||
if ( edd_color_picker.length ) {
|
||||
edd_color_picker.wpColorPicker();
|
||||
}
|
||||
|
||||
// Settings Upload field JS
|
||||
if ( typeof wp === 'undefined' || '1' !== edd_vars.new_media_ui ) {
|
||||
// Old Thickbox uploader
|
||||
const edd_settings_upload_button = $( '.edd_settings_upload_button' );
|
||||
if ( edd_settings_upload_button.length > 0 ) {
|
||||
window.formfield = '';
|
||||
|
||||
$( document.body ).on( 'click', edd_settings_upload_button, function( e ) {
|
||||
e.preventDefault();
|
||||
window.formfield = $( this ).parent().prev();
|
||||
window.tbframe_interval = setInterval( function() {
|
||||
jQuery( '#TB_iframeContent' ).contents().find( '.savesend .button' ).val( edd_vars.use_this_file ).end().find( '#insert-gallery, .wp-post-thumbnail' ).hide();
|
||||
}, 2000 );
|
||||
tb_show( edd_vars.add_new_download, 'media-upload.php?TB_iframe=true' );
|
||||
} );
|
||||
|
||||
window.edd_send_to_editor = window.send_to_editor;
|
||||
window.send_to_editor = function( html ) {
|
||||
if ( window.formfield ) {
|
||||
imgurl = $( 'a', '<div>' + html + '</div>' ).attr( 'href' );
|
||||
window.formfield.val( imgurl );
|
||||
window.clearInterval( window.tbframe_interval );
|
||||
tb_remove();
|
||||
} else {
|
||||
window.edd_send_to_editor( html );
|
||||
}
|
||||
window.send_to_editor = window.edd_send_to_editor;
|
||||
window.formfield = '';
|
||||
window.imagefield = false;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// WP 3.5+ uploader
|
||||
var file_frame;
|
||||
window.formfield = '';
|
||||
|
||||
$( document.body ).on( 'click', '.edd_settings_upload_button', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const button = $( this );
|
||||
|
||||
window.formfield = $( this ).parent().prev();
|
||||
|
||||
// If the media frame already exists, reopen it.
|
||||
if ( file_frame ) {
|
||||
//file_frame.uploader.uploader.param( 'post_id', set_to_post_id );
|
||||
file_frame.open();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the media frame.
|
||||
file_frame = wp.media.frames.file_frame = wp.media( {
|
||||
title: button.data( 'uploader_title' ),
|
||||
library: { type: 'image' },
|
||||
button: { text: button.data( 'uploader_button_text' ) },
|
||||
multiple: false,
|
||||
} );
|
||||
|
||||
file_frame.on( 'menu:render:default', function( view ) {
|
||||
// Store our views in an object.
|
||||
const views = {};
|
||||
|
||||
// Unset default menu items
|
||||
view.unset( 'library-separator' );
|
||||
view.unset( 'gallery' );
|
||||
view.unset( 'featured-image' );
|
||||
view.unset( 'embed' );
|
||||
|
||||
// Initialize the views in our view object.
|
||||
view.set( views );
|
||||
} );
|
||||
|
||||
// When an image is selected, run a callback.
|
||||
file_frame.on( 'select', function() {
|
||||
const selection = file_frame.state().get( 'selection' );
|
||||
selection.each( function( attachment, index ) {
|
||||
attachment = attachment.toJSON();
|
||||
window.formfield.val( attachment.url );
|
||||
} );
|
||||
} );
|
||||
|
||||
// Finally, open the modal
|
||||
file_frame.open();
|
||||
} );
|
||||
|
||||
// WP 3.5+ uploader
|
||||
var file_frame;
|
||||
window.formfield = '';
|
||||
}
|
||||
},
|
||||
|
||||
misc: function() {
|
||||
const downloadMethod = $( 'select[name="edd_settings[download_method]"]' ),
|
||||
symlink = downloadMethod.parent().parent().next();
|
||||
|
||||
// Hide Symlink option if Download Method is set to Direct
|
||||
if ( downloadMethod.val() === 'direct' ) {
|
||||
symlink.css( 'opacity', '0.4' );
|
||||
symlink.find( 'input' ).prop( 'checked', false ).prop( 'disabled', true );
|
||||
}
|
||||
|
||||
// Toggle download method option
|
||||
downloadMethod.on( 'change', function() {
|
||||
if ( $( this ).val() === 'direct' ) {
|
||||
symlink.css( 'opacity', '0.4' );
|
||||
symlink.find( 'input' ).prop( 'checked', false ).prop( 'disabled', true );
|
||||
} else {
|
||||
symlink.find( 'input' ).prop( 'disabled', false );
|
||||
symlink.css( 'opacity', '1' );
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
gateways: function() {
|
||||
$( '#edd-payment-gateways input[type="checkbox"]' ).on( 'change', function() {
|
||||
const gateway = $( this ),
|
||||
gateway_key = gateway.data( 'gateway-key' ),
|
||||
default_gateway = $( '#edd_settings\\[default_gateway\\]' ),
|
||||
option = default_gateway.find( 'option[value="' + gateway_key + '"]' );
|
||||
|
||||
// Toggle enable/disable based
|
||||
option.prop( 'disabled', function( i, v ) {
|
||||
return ! v;
|
||||
} );
|
||||
|
||||
// Maybe deselect
|
||||
if ( option.prop( 'selected' ) ) {
|
||||
option.prop( 'selected', false );
|
||||
}
|
||||
|
||||
default_gateway.trigger( 'chosen:updated' );
|
||||
} );
|
||||
},
|
||||
|
||||
emails: function() {
|
||||
$('#edd-recapture-connect').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$(this).html( edd_vars.wait + ' <span class="edd-loading"></span>' );
|
||||
document.body.style.cursor = 'wait';
|
||||
recaptureRemoteInstall();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
EDD_Settings.init();
|
||||
} );
|
@ -0,0 +1,18 @@
|
||||
export const recaptureRemoteInstall = () => {
|
||||
var data = {
|
||||
'action': 'edd_recapture_remote_install',
|
||||
};
|
||||
|
||||
jQuery.post( ajaxurl, data, function( response ) {
|
||||
|
||||
if( ! response.success ) {
|
||||
|
||||
if( confirm( response.data.error ) ) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
window.location.href = 'https://recapture.io/register';
|
||||
});
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import TaxRate from './../models/tax-rate.js';
|
||||
|
||||
/**
|
||||
* A collection of multiple tax rates.
|
||||
*/
|
||||
const TaxRates = Backbone.Collection.extend( {
|
||||
// Map the model.
|
||||
model: TaxRate,
|
||||
|
||||
/**
|
||||
* Set initial state.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.showAll = false;
|
||||
this.selected = [];
|
||||
},
|
||||
} );
|
||||
|
||||
export default TaxRates;
|
@ -0,0 +1,47 @@
|
||||
/* global _, eddTaxRates */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import TaxRate from './models/tax-rate.js';
|
||||
import TaxRates from './collections/tax-rates.js';
|
||||
import Manager from './views/manager.js';
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
/**
|
||||
* DOM ready.
|
||||
*/
|
||||
jQueryReady( () => {
|
||||
// Show notice if taxes are not enabled.
|
||||
const noticeEl = document.getElementById( 'edd-tax-disabled-notice' );
|
||||
|
||||
if ( noticeEl ) {
|
||||
noticeEl.classList.add( 'notice' );
|
||||
noticeEl.classList.add( 'notice-warning' );
|
||||
}
|
||||
|
||||
// Start manager with a blank collection.
|
||||
const manager = new Manager( {
|
||||
collection: new TaxRates(),
|
||||
} );
|
||||
|
||||
const rates = [];
|
||||
|
||||
// Normalize rate data.
|
||||
_.each( eddTaxRates.rates, ( rate ) => rates.push( {
|
||||
id: rate.id,
|
||||
country: rate.name,
|
||||
region: rate.description,
|
||||
global: 'country' === rate.scope,
|
||||
amount: rate.amount,
|
||||
status: rate.status,
|
||||
} ) );
|
||||
|
||||
// Add initial rates.
|
||||
manager.collection.set( rates, {
|
||||
silent: true,
|
||||
} );
|
||||
|
||||
// Render manager.
|
||||
manager.render();
|
||||
} );
|
@ -0,0 +1,34 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* Model a tax rate.
|
||||
*/
|
||||
const TaxRate = Backbone.Model.extend( {
|
||||
defaults: {
|
||||
id: '',
|
||||
country: '',
|
||||
region: '',
|
||||
global: true,
|
||||
amount: 0,
|
||||
status: 'active',
|
||||
unsaved: false,
|
||||
selected: false,
|
||||
},
|
||||
|
||||
/**
|
||||
* Format a rate amount (adds a %)
|
||||
*
|
||||
* @todo This should support dynamic decimal types.
|
||||
*/
|
||||
formattedAmount: function() {
|
||||
let amount = 0;
|
||||
|
||||
if ( this.get( 'amount' ) ) {
|
||||
amount = parseFloat( this.get( 'amount' ) ).toFixed( 2 );
|
||||
}
|
||||
|
||||
return `${ amount }%`;
|
||||
},
|
||||
} );
|
||||
|
||||
export default TaxRate;
|
@ -0,0 +1,55 @@
|
||||
/* global wp, _ */
|
||||
|
||||
/**
|
||||
* Apply bulk actions.
|
||||
*/
|
||||
const BulkActions = wp.Backbone.View.extend( {
|
||||
// See https://codex.wordpress.org/Javascript_Reference/wp.template
|
||||
template: wp.template( 'edd-admin-tax-rates-table-bulk-actions' ),
|
||||
|
||||
// Watch events.
|
||||
events: {
|
||||
'click .edd-admin-tax-rates-table-filter': 'filter',
|
||||
'change .edd-admin-tax-rates-table-hide input': 'showHide',
|
||||
},
|
||||
|
||||
/**
|
||||
* Bulk actions for selected items.
|
||||
*
|
||||
* Currently only supports changing the status.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
filter: function( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
// @hack - need to access the DOM directly here because the dropdown is not tied to the button event.
|
||||
const status = document.getElementById( 'edd-admin-tax-rates-table-bulk-actions' );
|
||||
|
||||
_.each( this.collection.selected, ( cid ) => {
|
||||
const model = this.collection.get( {
|
||||
cid: cid,
|
||||
} );
|
||||
|
||||
model.set( 'status', status.value );
|
||||
} );
|
||||
|
||||
this.collection.trigger( 'filtered' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle show active/inactive rates.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
showHide: function( event ) {
|
||||
this.collection.showAll = event.target.checked;
|
||||
|
||||
// @hack -- shouldn't access this table directly.
|
||||
document.getElementById( 'edd_tax_rates' ).classList.toggle( 'has-inactive', this.collection.showAll );
|
||||
|
||||
this.collection.trigger( 'filtered' );
|
||||
},
|
||||
} );
|
||||
|
||||
export default BulkActions;
|
@ -0,0 +1,65 @@
|
||||
/* global wp */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import Table from './table.js';
|
||||
import BulkActions from './bulk-actions.js';
|
||||
|
||||
/**
|
||||
* Manage tax rates.
|
||||
*/
|
||||
const Manager = wp.Backbone.View.extend( {
|
||||
// Append to this element.
|
||||
el: '#edd-admin-tax-rates',
|
||||
|
||||
/**
|
||||
* Set bind changes to collection.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.listenTo( this.collection, 'add change', this.makeDirty );
|
||||
|
||||
// Clear unload confirmation when submitting parent form.
|
||||
document.querySelector( '.edd-settings-form #submit' ).addEventListener( 'click', this.makeClean );
|
||||
},
|
||||
|
||||
/**
|
||||
* Output the manager.
|
||||
*/
|
||||
render: function() {
|
||||
this.views.add( new BulkActions( {
|
||||
collection: this.collection,
|
||||
} ) );
|
||||
|
||||
this.views.add( new Table( {
|
||||
collection: this.collection,
|
||||
} ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Collection has changed so warn the user before exiting.
|
||||
*/
|
||||
makeDirty: function() {
|
||||
window.onbeforeunload = this.confirmUnload;
|
||||
},
|
||||
|
||||
/**
|
||||
* When submitting the main form remove the dirty check.
|
||||
*/
|
||||
makeClean: function() {
|
||||
window.onbeforeunload = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm page unload.
|
||||
*
|
||||
* @param {Object} event Close event.
|
||||
*/
|
||||
confirmUnload: function( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
return '';
|
||||
},
|
||||
} );
|
||||
|
||||
export default Manager;
|
@ -0,0 +1,38 @@
|
||||
/* global wp, _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { getChosenVars } from 'utils/chosen.js';
|
||||
|
||||
const RegionField = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* Bind passed arguments.
|
||||
*
|
||||
* @param {Object} options Extra options passed.
|
||||
*/
|
||||
initialize: function( options ) {
|
||||
_.extend( this, options );
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a list of options.
|
||||
*/
|
||||
render: function() {
|
||||
if ( this.global ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'nostates' === this.states ) {
|
||||
this.setElement( '<input type="text" id="tax_rate_region" />' );
|
||||
} else {
|
||||
this.$el.html( this.states );
|
||||
this.$el.find( 'select' ).each( function() {
|
||||
const el = $( this );
|
||||
el.chosen( getChosenVars( el ) );
|
||||
} );
|
||||
}
|
||||
},
|
||||
} );
|
||||
|
||||
export default RegionField;
|
@ -0,0 +1,236 @@
|
||||
/* global wp */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import TaxRate from './../models/tax-rate.js';
|
||||
import RegionField from './../views/region-field.js';
|
||||
import { getChosenVars } from 'utils/chosen.js';
|
||||
|
||||
/**
|
||||
* Add a new rate "form".
|
||||
*/
|
||||
const TableAdd = wp.Backbone.View.extend( {
|
||||
// Use <tfoot>
|
||||
tagName: 'tfoot',
|
||||
|
||||
// Set class.
|
||||
className: 'add-new',
|
||||
|
||||
// See https://codex.wordpress.org/Javascript_Reference/wp.template
|
||||
template: wp.template( 'edd-admin-tax-rates-table-add' ),
|
||||
|
||||
// Watch events.
|
||||
events: {
|
||||
'click button': 'addTaxRate',
|
||||
'keypress': 'maybeAddTaxRate',
|
||||
|
||||
'change #tax_rate_country': 'setCountry',
|
||||
|
||||
// Can be select or input.
|
||||
'keyup #tax_rate_region': 'setRegion',
|
||||
'change #tax_rate_region': 'setRegion',
|
||||
|
||||
'change input[type="checkbox"]': 'setGlobal',
|
||||
|
||||
// Can be click increase or keyboard.
|
||||
'keyup #tax_rate_amount': 'setAmount',
|
||||
'change #tax_rate_amount': 'setAmount',
|
||||
},
|
||||
|
||||
/**
|
||||
* Set initial state and bind changes to model.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.model = new TaxRate( {
|
||||
global: true,
|
||||
unsaved: true,
|
||||
} );
|
||||
|
||||
this.listenTo( this.model, 'change:country', this.updateRegion );
|
||||
this.listenTo( this.model, 'change:global', this.updateRegion );
|
||||
},
|
||||
|
||||
/**
|
||||
* Render. Only overwritten so we can reinit chosen once cleared.
|
||||
*/
|
||||
render: function() {
|
||||
wp.Backbone.View.prototype.render.apply( this, arguments );
|
||||
|
||||
this.$el.find( 'select' ).each( function() {
|
||||
const el = $( this );
|
||||
el.chosen( getChosenVars( el ) );
|
||||
} );
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a list of states or an input field.
|
||||
*/
|
||||
updateRegion: function() {
|
||||
const self = this;
|
||||
|
||||
const data = {
|
||||
action: 'edd_get_shop_states',
|
||||
country: this.model.get( 'country' ),
|
||||
nonce: eddTaxRates.nonce,
|
||||
field_name: 'tax_rate_region',
|
||||
};
|
||||
|
||||
$.post( ajaxurl, data, function( response ) {
|
||||
self.views.set( '#tax_rate_region_wrapper', new RegionField( {
|
||||
states: response,
|
||||
global: self.model.get( 'global' ),
|
||||
} ) );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a country value.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
setCountry: function( event ) {
|
||||
let country = event.target.options[ event.target.selectedIndex ].value;
|
||||
let regionGlobalCheckbox = document.getElementById( "tax_rate_region_global" );
|
||||
if ( 'all' === country ) {
|
||||
country = '*';
|
||||
regionGlobalCheckbox.checked = true;
|
||||
this.model.set( 'region', '' );
|
||||
this.model.set( 'global', true );
|
||||
regionGlobalCheckbox.readOnly = true;
|
||||
regionGlobalCheckbox.disabled = true;
|
||||
} else {
|
||||
regionGlobalCheckbox.disabled = false;
|
||||
regionGlobalCheckbox.readOnly = false;
|
||||
}
|
||||
|
||||
this.model.set( 'country', country );
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a region value.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
setRegion: function( event ) {
|
||||
let value = false;
|
||||
|
||||
if ( event.target.value ) {
|
||||
value = event.target.value;
|
||||
} else {
|
||||
value = event.target.options[ event.target.selectedIndex ].value;
|
||||
}
|
||||
|
||||
this.model.set( 'region', value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a global scope.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
setGlobal: function( event ) {
|
||||
let isChecked = event.target.checked;
|
||||
this.model.set( 'global', isChecked );
|
||||
if ( true === isChecked ) {
|
||||
this.model.set( 'region', '' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set an amount value.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
setAmount: function( event ) {
|
||||
this.model.set( 'amount', event.target.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Monitors keyepress for "Enter" key.
|
||||
*
|
||||
* We cannot use the `submit` event because we cannot nest <form>
|
||||
* elements inside the settings API.
|
||||
*
|
||||
* @param {Object} event Keypress event.
|
||||
*/
|
||||
maybeAddTaxRate: function( event ) {
|
||||
if ( 13 !== event.keyCode ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addTaxRate( event );
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a single rate when the "form" is submitted.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
addTaxRate: function( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
const { i18n } = eddTaxRates;
|
||||
|
||||
if ( ! this.model.get( 'country' ) ) {
|
||||
alert( i18n.emptyCountry );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let addingRegion = this.model.get( 'region' );
|
||||
let addingCountry = this.model.get( 'country' );
|
||||
let addingGlobal = '' === this.model.get( 'region' );
|
||||
|
||||
// For the purposes of this query, the * is really an empty query.
|
||||
if ( '*' === addingCountry ) {
|
||||
addingCountry = '';
|
||||
addingRegion = '';
|
||||
addingGlobal = false;
|
||||
}
|
||||
|
||||
const existingCountryWide = this.collection.where( {
|
||||
region: addingRegion,
|
||||
country: addingCountry,
|
||||
global: addingGlobal,
|
||||
status: 'active',
|
||||
} );
|
||||
|
||||
if ( existingCountryWide.length > 0 ) {
|
||||
const countryString = '' === addingCountry
|
||||
? '*'
|
||||
: addingCountry;
|
||||
|
||||
const regionString = '' === addingRegion
|
||||
? ''
|
||||
: ': ' + addingRegion;
|
||||
|
||||
const taxRateString = countryString + regionString;
|
||||
|
||||
alert( i18n.duplicateRate.replace( '%s', `"${ taxRateString }"` ) );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.model.get( 'amount' ) <= 0 ) {
|
||||
alert( i18n.emptyTax );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge cid as ID to make this a unique model.
|
||||
this.collection.add( _.extend(
|
||||
this.model.attributes,
|
||||
{
|
||||
id: this.model.cid,
|
||||
}
|
||||
) );
|
||||
|
||||
this.render();
|
||||
this.initialize();
|
||||
},
|
||||
} );
|
||||
|
||||
export default TableAdd;
|
@ -0,0 +1,33 @@
|
||||
/* global wp, _ */
|
||||
|
||||
/**
|
||||
* Output a table header and footer.
|
||||
*/
|
||||
const TableMeta = wp.Backbone.View.extend( {
|
||||
// See https://codex.wordpress.org/Javascript_Reference/wp.template
|
||||
template: wp.template( 'edd-admin-tax-rates-table-meta' ),
|
||||
|
||||
// Watch events.
|
||||
events: {
|
||||
'change [type="checkbox"]': 'selectAll',
|
||||
},
|
||||
|
||||
/**
|
||||
* Select all items in the collection.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
selectAll: function( event ) {
|
||||
const checked = event.target.checked;
|
||||
|
||||
_.each( this.collection.models, ( model ) => {
|
||||
// Check individual models.
|
||||
model.set( 'selected', checked );
|
||||
|
||||
// Add to global selection.
|
||||
this.collection.selected.push( model.cid );
|
||||
} );
|
||||
},
|
||||
} );
|
||||
|
||||
export default TableMeta;
|
@ -0,0 +1,17 @@
|
||||
/* global wp */
|
||||
|
||||
/**
|
||||
* Empty tax rates table.
|
||||
*/
|
||||
const TableRowEmpty = wp.Backbone.View.extend( {
|
||||
// Insert as a <tr>
|
||||
tagName: 'tr',
|
||||
|
||||
// Set class.
|
||||
className: 'edd-tax-rate-row edd-tax-rate-row--is-empty',
|
||||
|
||||
// See https://codex.wordpress.org/Javascript_Reference/wp.template
|
||||
template: wp.template( 'edd-admin-tax-rates-table-row-empty' ),
|
||||
} );
|
||||
|
||||
export default TableRowEmpty;
|
@ -0,0 +1,119 @@
|
||||
/* global wp, _ */
|
||||
|
||||
/**
|
||||
* A row inside a table of rates.
|
||||
*/
|
||||
const TableRow = wp.Backbone.View.extend( {
|
||||
// Insert as a <tr>
|
||||
tagName: 'tr',
|
||||
|
||||
// Set class.
|
||||
className: function() {
|
||||
return 'edd-tax-rate-row edd-tax-rate-row--' + this.model.get( 'status' );
|
||||
},
|
||||
|
||||
// See https://codex.wordpress.org/Javascript_Reference/wp.template
|
||||
template: wp.template( 'edd-admin-tax-rates-table-row' ),
|
||||
|
||||
// Watch events.
|
||||
events: {
|
||||
'click .remove': 'removeRow',
|
||||
'click .activate': 'activateRow',
|
||||
'click .deactivate': 'deactivateRow',
|
||||
'change [type="checkbox"]': 'selectRow',
|
||||
},
|
||||
|
||||
/**
|
||||
* Bind model to view.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Render
|
||||
*/
|
||||
render: function() {
|
||||
this.$el.html( this.template( {
|
||||
...this.model.toJSON(),
|
||||
formattedAmount: this.model.formattedAmount(),
|
||||
} ) );
|
||||
|
||||
// Ensure the wrapper class has the new name.
|
||||
this.$el.attr( 'class', _.result( this, 'className' ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a rate (can only be done if it has not been saved to the database).
|
||||
*
|
||||
* Don't use this.model.destroy() to avoid sending a DELETE request.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
removeRow: function( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
this.collection.remove( this.model );
|
||||
},
|
||||
|
||||
/**
|
||||
* Activate a rate.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
activateRow: function( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
const { i18n } = eddTaxRates;
|
||||
const existingCountryWide = this.collection.where( {
|
||||
region: this.model.get( 'region' ),
|
||||
country: this.model.get( 'country' ),
|
||||
global: '' === this.model.get( 'region' ),
|
||||
status: 'active',
|
||||
} );
|
||||
|
||||
if ( existingCountryWide.length > 0 ) {
|
||||
const regionString = '' === this.model.get( 'region' )
|
||||
? ''
|
||||
: ': ' + this.model.get( 'region' );
|
||||
|
||||
const taxRateString = this.model.get( 'country' ) + regionString;
|
||||
|
||||
alert( i18n.duplicateRate.replace( '%s', `"${ taxRateString }"` ) );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.set( 'status', 'active' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Deactivate a rate.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
deactivateRow: function( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
this.model.set( 'status', 'inactive' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Select or deselect for bulk actions.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
*/
|
||||
selectRow: function( event ) {
|
||||
const checked = event.target.checked;
|
||||
|
||||
if ( ! checked ) {
|
||||
this.collection.selected = _.reject( this.collection.selected, ( cid ) => {
|
||||
return cid === this.model.cid;
|
||||
} );
|
||||
} else {
|
||||
this.collection.selected.push( this.model.cid );
|
||||
}
|
||||
},
|
||||
} );
|
||||
|
||||
export default TableRow;
|
@ -0,0 +1,65 @@
|
||||
/* global wp, _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import TableRowEmpty from './table-row-empty.js';
|
||||
import TableRow from './table-row.js';
|
||||
|
||||
/**
|
||||
* A bunch of rows inside a table of rates.
|
||||
*/
|
||||
const TableRows = wp.Backbone.View.extend( {
|
||||
// Insert as a <tbody>
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* Bind events to collection.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.listenTo( this.collection, 'add', this.render );
|
||||
this.listenTo( this.collection, 'remove', this.render );
|
||||
this.listenTo( this.collection, 'filtered change', this.filtered );
|
||||
},
|
||||
|
||||
/**
|
||||
* Render a collection of rows.
|
||||
*/
|
||||
render: function() {
|
||||
// Clear to handle sorting.
|
||||
this.views.remove();
|
||||
|
||||
// Show empty placeholder.
|
||||
if ( 0 === this.collection.models.length ) {
|
||||
return this.views.add( new TableRowEmpty() );
|
||||
}
|
||||
|
||||
// Add items.
|
||||
_.each( this.collection.models, ( rate ) => {
|
||||
this.views.add( new TableRow( {
|
||||
collection: this.collection,
|
||||
model: rate,
|
||||
} ) );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Show an empty state if all items are deactivated.
|
||||
*/
|
||||
filtered: function() {
|
||||
const disabledRates = this.collection.where( {
|
||||
status: 'inactive',
|
||||
} );
|
||||
|
||||
// Check if all rows are invisible, and show the "No Items" row if so
|
||||
if ( disabledRates.length === this.collection.models.length && ! this.collection.showAll ) {
|
||||
this.views.add( new TableRowEmpty() );
|
||||
|
||||
// Possibly re-render the view
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
} );
|
||||
|
||||
export default TableRows;
|
@ -0,0 +1,50 @@
|
||||
/* global wp */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import TableMeta from './table-meta.js';
|
||||
import TableRows from './table-rows.js';
|
||||
import TableAdd from './table-add.js';
|
||||
|
||||
/**
|
||||
* Manage the tax rate rows in a table.
|
||||
*/
|
||||
const Table = wp.Backbone.View.extend( {
|
||||
// Render as a <table> tag.
|
||||
tagName: 'table',
|
||||
|
||||
// Set class.
|
||||
className: 'wp-list-table widefat fixed tax-rates',
|
||||
|
||||
// Set ID.
|
||||
id: 'edd_tax_rates',
|
||||
|
||||
/**
|
||||
* Output a table with a header, body, and footer.
|
||||
*/
|
||||
render: function() {
|
||||
this.views.add( new TableMeta( {
|
||||
tagName: 'thead',
|
||||
collection: this.collection,
|
||||
} ) );
|
||||
|
||||
this.views.add( new TableRows( {
|
||||
collection: this.collection,
|
||||
} ) );
|
||||
|
||||
this.views.add( new TableAdd( {
|
||||
collection: this.collection,
|
||||
} ) );
|
||||
|
||||
this.views.add( new TableMeta( {
|
||||
tagName: 'tfoot',
|
||||
collection: this.collection,
|
||||
} ) );
|
||||
|
||||
// Trigger the `filtered` action to show/hide rows accordingly
|
||||
this.collection.trigger( 'filtered' );
|
||||
},
|
||||
} );
|
||||
|
||||
export default Table;
|
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Export screen JS
|
||||
*/
|
||||
const EDD_Export = {
|
||||
|
||||
init: function() {
|
||||
this.submit();
|
||||
},
|
||||
|
||||
submit: function() {
|
||||
const self = this;
|
||||
|
||||
$( document.body ).on( 'submit', '.edd-export-form', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const form = $( this ),
|
||||
submitButton = form.find( 'button[type="submit"]' ).first();
|
||||
|
||||
if ( submitButton.hasClass( 'button-disabled' ) || submitButton.is( ':disabled' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = form.serialize();
|
||||
|
||||
if ( submitButton.hasClass( 'button-primary' ) ) {
|
||||
submitButton.removeClass( 'button-primary' ).addClass( 'button-secondary' );
|
||||
}
|
||||
submitButton.attr( 'disabled', true ).addClass( 'updating-message' );
|
||||
form.find( '.notice-wrap' ).remove();
|
||||
form.append( '<div class="notice-wrap"><div class="edd-progress"><div></div></div></div>' );
|
||||
|
||||
// start the process
|
||||
self.process_step( 1, data, self );
|
||||
} );
|
||||
},
|
||||
|
||||
process_step: function( step, data, self ) {
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
url: ajaxurl,
|
||||
data: {
|
||||
form: data,
|
||||
action: 'edd_do_ajax_export',
|
||||
step: step,
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function( response ) {
|
||||
if ( 'done' === response.step || response.error || response.success ) {
|
||||
// We need to get the actual in progress form, not all forms on the page
|
||||
const export_form = $( '.edd-export-form' ).find( '.edd-progress' ).parent().parent();
|
||||
const notice_wrap = export_form.find( '.notice-wrap' );
|
||||
|
||||
export_form.find( 'button' ).attr( 'disabled', false ).removeClass( 'updating-message' ).addClass( 'updated-message' );
|
||||
export_form.find( 'button .spinner' ).hide().css( 'visibility', 'visible' );
|
||||
|
||||
if ( response.error ) {
|
||||
const error_message = response.message;
|
||||
notice_wrap.html( '<div class="updated error"><p>' + error_message + '</p></div>' );
|
||||
} else if ( response.success ) {
|
||||
const success_message = response.message;
|
||||
notice_wrap.html( '<div id="edd-batch-success" class="updated notice"><p>' + success_message + '</p></div>' );
|
||||
if ( response.data ) {
|
||||
$.each( response.data, function ( key, value ) {
|
||||
$( '.edd_' + key ).html( value );
|
||||
} );
|
||||
}
|
||||
} else {
|
||||
notice_wrap.remove();
|
||||
window.location = response.url;
|
||||
}
|
||||
} else {
|
||||
$( '.edd-progress div' ).animate( {
|
||||
width: response.percentage + '%',
|
||||
}, 50, function() {
|
||||
// Animation complete.
|
||||
} );
|
||||
self.process_step( parseInt( response.step ), data, self );
|
||||
}
|
||||
},
|
||||
} ).fail( function( response ) {
|
||||
if ( window.console && window.console.log ) {
|
||||
console.log( response );
|
||||
}
|
||||
} );
|
||||
},
|
||||
};
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
EDD_Export.init();
|
||||
} );
|
@ -0,0 +1,174 @@
|
||||
/**
|
||||
* Import screen JS
|
||||
*/
|
||||
var EDD_Import = {
|
||||
|
||||
init: function() {
|
||||
this.submit();
|
||||
},
|
||||
|
||||
submit: function() {
|
||||
const self = this;
|
||||
|
||||
$( '.edd-import-form' ).ajaxForm( {
|
||||
beforeSubmit: self.before_submit,
|
||||
success: self.success,
|
||||
complete: self.complete,
|
||||
dataType: 'json',
|
||||
error: self.error,
|
||||
} );
|
||||
},
|
||||
|
||||
before_submit: function( arr, form, options ) {
|
||||
form.find( '.notice-wrap' ).remove();
|
||||
form.append( '<div class="notice-wrap"><div class="edd-progress"><div></div></div></div>' );
|
||||
|
||||
//check whether client browser fully supports all File API
|
||||
if ( window.File && window.FileReader && window.FileList && window.Blob ) {
|
||||
|
||||
// HTML5 File API is supported by browser
|
||||
|
||||
} else {
|
||||
const import_form = $( '.edd-import-form' ).find( '.edd-progress' ).parent().parent();
|
||||
const notice_wrap = import_form.find( '.notice-wrap' );
|
||||
|
||||
import_form.find( '.button:disabled' ).attr( 'disabled', false );
|
||||
|
||||
//Error for older unsupported browsers that doesn't support HTML5 File API
|
||||
notice_wrap.html( '<div class="update error"><p>' + edd_vars.unsupported_browser + '</p></div>' );
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
success: function( responseText, statusText, xhr, form ) {},
|
||||
|
||||
complete: function( xhr ) {
|
||||
const self = $( this ),
|
||||
response = jQuery.parseJSON( xhr.responseText );
|
||||
|
||||
if ( response.success ) {
|
||||
const form = $( '.edd-import-form .notice-wrap' ).parent();
|
||||
|
||||
form.find( '.edd-import-file-wrap,.notice-wrap' ).remove();
|
||||
form.find( '.edd-import-options' ).slideDown();
|
||||
|
||||
// Show column mapping
|
||||
let select = form.find( 'select.edd-import-csv-column' ),
|
||||
row = select.parents( 'tr' ).first(),
|
||||
options = '',
|
||||
columns = response.data.columns.sort( function( a, b ) {
|
||||
if ( a < b ) {
|
||||
return -1;
|
||||
}
|
||||
if ( a > b ) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} );
|
||||
|
||||
$.each( columns, function( key, value ) {
|
||||
options += '<option value="' + value + '">' + value + '</option>';
|
||||
} );
|
||||
|
||||
select.append( options );
|
||||
|
||||
select.on( 'change', function() {
|
||||
const key = $( this ).val();
|
||||
|
||||
if ( ! key ) {
|
||||
$( this ).parent().next().html( '' );
|
||||
} else if ( false !== response.data.first_row[ key ] ) {
|
||||
$( this ).parent().next().html( response.data.first_row[ key ] );
|
||||
} else {
|
||||
$( this ).parent().next().html( '' );
|
||||
}
|
||||
} );
|
||||
|
||||
$.each( select, function() {
|
||||
$( this ).val( $( this ).attr( 'data-field' ) ).change();
|
||||
} );
|
||||
|
||||
$( document.body ).on( 'click', '.edd-import-proceed', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
form.find( '.edd-import-proceed.button-primary' ).addClass( 'updating-message' );
|
||||
form.append( '<div class="notice-wrap"><div class="edd-progress"><div></div></div></div>' );
|
||||
|
||||
response.data.mapping = form.serialize();
|
||||
|
||||
EDD_Import.process_step( 1, response.data, self );
|
||||
} );
|
||||
} else {
|
||||
EDD_Import.error( xhr );
|
||||
}
|
||||
},
|
||||
|
||||
error: function( xhr ) {
|
||||
// Something went wrong. This will display error on form
|
||||
|
||||
const response = jQuery.parseJSON( xhr.responseText );
|
||||
const import_form = $( '.edd-import-form' ).find( '.edd-progress' ).parent().parent();
|
||||
const notice_wrap = import_form.find( '.notice-wrap' );
|
||||
|
||||
import_form.find( '.button:disabled' ).attr( 'disabled', false );
|
||||
|
||||
if ( response.data.error ) {
|
||||
notice_wrap.html( '<div class="update error"><p>' + response.data.error + '</p></div>' );
|
||||
} else {
|
||||
notice_wrap.remove();
|
||||
}
|
||||
},
|
||||
|
||||
process_step: function( step, import_data, self ) {
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
url: ajaxurl,
|
||||
data: {
|
||||
form: import_data.form,
|
||||
nonce: import_data.nonce,
|
||||
class: import_data.class,
|
||||
upload: import_data.upload,
|
||||
mapping: import_data.mapping,
|
||||
action: 'edd_do_ajax_import',
|
||||
step: step,
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function( response ) {
|
||||
if ( 'done' === response.data.step || response.data.error ) {
|
||||
// We need to get the actual in progress form, not all forms on the page
|
||||
const import_form = $( '.edd-import-form' ).find( '.edd-progress' ).parent().parent();
|
||||
const notice_wrap = import_form.find( '.notice-wrap' );
|
||||
|
||||
import_form.find( '.button:disabled' ).attr( 'disabled', false );
|
||||
|
||||
if ( response.data.error ) {
|
||||
notice_wrap.html( '<div class="update error"><p>' + response.data.error + '</p></div>' );
|
||||
} else {
|
||||
import_form.find( '.edd-import-options' ).hide();
|
||||
$( 'html, body' ).animate( {
|
||||
scrollTop: import_form.parent().offset().top,
|
||||
}, 500 );
|
||||
|
||||
notice_wrap.html( '<div class="updated"><p>' + response.data.message + '</p></div>' );
|
||||
}
|
||||
} else {
|
||||
$( '.edd-progress div' ).animate( {
|
||||
width: response.data.percentage + '%',
|
||||
}, 50, function() {
|
||||
// Animation complete.
|
||||
} );
|
||||
|
||||
EDD_Import.process_step( parseInt( response.data.step ), import_data, self );
|
||||
}
|
||||
},
|
||||
} ).fail( function( response ) {
|
||||
if ( window.console && window.console.log ) {
|
||||
console.log( response );
|
||||
}
|
||||
} );
|
||||
},
|
||||
};
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
EDD_Import.init();
|
||||
} );
|
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Tools screen JS
|
||||
*/
|
||||
const EDD_Tools = {
|
||||
|
||||
init: function() {
|
||||
this.revoke_api_key();
|
||||
this.regenerate_api_key();
|
||||
this.create_api_key();
|
||||
this.recount_stats();
|
||||
},
|
||||
|
||||
revoke_api_key: function() {
|
||||
$( document.body ).on( 'click', '.edd-revoke-api-key', function( e ) {
|
||||
return confirm( edd_vars.revoke_api_key );
|
||||
} );
|
||||
},
|
||||
regenerate_api_key: function() {
|
||||
$( document.body ).on( 'click', '.edd-regenerate-api-key', function( e ) {
|
||||
return confirm( edd_vars.regenerate_api_key );
|
||||
} );
|
||||
},
|
||||
create_api_key: function() {
|
||||
$( document.body ).on( 'submit', '#api-key-generate-form', function( e ) {
|
||||
const input = $( 'input[type="text"][name="user_id"]' );
|
||||
|
||||
input.css( 'border-color', '#ddd' );
|
||||
|
||||
const user_id = input.val();
|
||||
if ( user_id.length < 1 || user_id === 0 ) {
|
||||
input.css( 'border-color', '#ff0000' );
|
||||
return false;
|
||||
}
|
||||
} );
|
||||
},
|
||||
recount_stats: function() {
|
||||
$( document.body ).on( 'change', '#recount-stats-type', function() {
|
||||
const export_form = $( '#edd-tools-recount-form' ),
|
||||
selected_type = $( 'option:selected', this ).data( 'type' ),
|
||||
submit_button = $( '#recount-stats-submit' ),
|
||||
products = $( '#tools-product-dropdown' );
|
||||
|
||||
// Reset the form
|
||||
export_form.find( '.notice-wrap' ).remove();
|
||||
submit_button.attr( 'disabled', false ).removeClass( 'updated-message' );
|
||||
products.hide();
|
||||
$( '.edd-recount-stats-descriptions span' ).hide();
|
||||
|
||||
if ( 'recount-download' === selected_type ) {
|
||||
products.show();
|
||||
products.find( '.edd-select-chosen' ).css( 'width', 'auto' );
|
||||
} else if ( 'reset-stats' === selected_type ) {
|
||||
export_form.append( '<div class="notice-wrap"></div>' );
|
||||
const notice_wrap = export_form.find( '.notice-wrap' );
|
||||
notice_wrap.html( '<div class="notice notice-warning"><p><input type="checkbox" id="confirm-reset" name="confirm_reset_store" value="1" /> <label for="confirm-reset">' + edd_vars.reset_stats_warn + '</label></p></div>' );
|
||||
|
||||
$( '#recount-stats-submit' ).attr( 'disabled', true );
|
||||
} else {
|
||||
products.hide();
|
||||
products.val( 0 );
|
||||
}
|
||||
|
||||
$( '#' + selected_type ).show();
|
||||
} );
|
||||
|
||||
$( document.body ).on( 'change', '#confirm-reset', function() {
|
||||
const checked = $( this ).is( ':checked' );
|
||||
if ( checked ) {
|
||||
$( '#recount-stats-submit' ).attr( 'disabled', false );
|
||||
} else {
|
||||
$( '#recount-stats-submit' ).attr( 'disabled', true );
|
||||
}
|
||||
} );
|
||||
|
||||
$( '#edd-tools-recount-form' ).submit( function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const selection = $( '#recount-stats-type' ).val(),
|
||||
export_form = $( this ),
|
||||
selected_type = $( 'option:selected', this ).data( 'type' );
|
||||
|
||||
if ( 'reset-stats' === selected_type ) {
|
||||
const is_confirmed = $( '#confirm-reset' ).is( ':checked' );
|
||||
if ( is_confirmed ) {
|
||||
return true;
|
||||
}
|
||||
has_errors = true;
|
||||
}
|
||||
|
||||
export_form.find( '.notice-wrap' ).remove();
|
||||
export_form.append( '<div class="notice-wrap"></div>' );
|
||||
|
||||
var notice_wrap = export_form.find( '.notice-wrap' ),
|
||||
has_errors = false;
|
||||
|
||||
if ( null === selection || 0 === selection ) {
|
||||
// Needs to pick a method edd_vars.batch_export_no_class
|
||||
notice_wrap.html( '<div class="updated error"><p>' + edd_vars.batch_export_no_class + '</p></div>' );
|
||||
has_errors = true;
|
||||
}
|
||||
|
||||
if ( 'recount-download' === selected_type ) {
|
||||
const selected_download = $( 'select[name="download_id"]' ).val();
|
||||
if ( selected_download === 0 ) {
|
||||
// Needs to pick download edd_vars.batch_export_no_reqs
|
||||
notice_wrap.html( '<div class="updated error"><p>' + edd_vars.batch_export_no_reqs + '</p></div>' );
|
||||
has_errors = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( has_errors ) {
|
||||
export_form.find( 'button:disabled' ).attr( 'disabled', false ).removeClass( 'updated-message' );
|
||||
return false;
|
||||
}
|
||||
} );
|
||||
},
|
||||
};
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
EDD_Tools.init();
|
||||
} );
|
@ -0,0 +1 @@
|
||||
import './v3';
|
@ -0,0 +1,211 @@
|
||||
const EDD_v3_Upgrades = {
|
||||
inProgress: false,
|
||||
|
||||
init: function() {
|
||||
// Listen for toggle on the checkbox.
|
||||
$( '.edd-v3-migration-confirmation' ).on( 'change', function( e ) {
|
||||
const wrapperForm = $( this ).closest( '.edd-v3-migration' );
|
||||
const formSubmit = wrapperForm.find( 'button' );
|
||||
|
||||
if ( e.target.checked ) {
|
||||
formSubmit.removeClass( 'disabled' ).prop( 'disabled', false );
|
||||
} else {
|
||||
formSubmit.addClass( 'disabled' ).prop( 'disabled', true );
|
||||
}
|
||||
} );
|
||||
|
||||
$( '.edd-v3-migration' ).on( 'submit', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
if ( EDD_v3_Upgrades.inProgress ) {
|
||||
return;
|
||||
}
|
||||
|
||||
EDD_v3_Upgrades.inProgress = true;
|
||||
|
||||
const migrationForm = $( this );
|
||||
const upgradeKeyField = migrationForm.find( 'input[name="upgrade_key"]' );
|
||||
let upgradeKey = false;
|
||||
|
||||
if ( upgradeKeyField.length && upgradeKeyField.val() ) {
|
||||
upgradeKey = upgradeKeyField.val();
|
||||
}
|
||||
|
||||
// Disable submit button.
|
||||
migrationForm.find( 'button' )
|
||||
.removeClass( 'button-primary' )
|
||||
.addClass( 'button-secondary disabled updating-message' )
|
||||
.prop( 'disabled', true );
|
||||
|
||||
// Disable checkbox.
|
||||
migrationForm.find( 'input' ).prop( 'disabled', true );
|
||||
|
||||
// If this is the main migration, reveal the steps & mark the first non-complete item as in progress.
|
||||
if ( 'edd-v3-migration' === migrationForm.attr( 'id' ) ) {
|
||||
$( '#edd-migration-progress' ).removeClass( 'edd-hidden' );
|
||||
const firstNonCompleteUpgrade = $( '#edd-migration-progress li:not(.edd-upgrade-complete)' );
|
||||
if ( firstNonCompleteUpgrade.length && ! upgradeKey ) {
|
||||
upgradeKey = firstNonCompleteUpgrade.data( 'upgrade' );
|
||||
}
|
||||
}
|
||||
|
||||
EDD_v3_Upgrades.processStep( upgradeKey, 1, migrationForm.find( 'input[name="_wpnonce"]' ).val() );
|
||||
} )
|
||||
},
|
||||
|
||||
processStep: function( upgrade_key, step, nonce ) {
|
||||
let data = {
|
||||
action: 'edd_process_v3_upgrade',
|
||||
_ajax_nonce: nonce,
|
||||
upgrade_key: upgrade_key,
|
||||
step: step
|
||||
}
|
||||
|
||||
EDD_v3_Upgrades.clearErrors();
|
||||
|
||||
if ( upgrade_key ) {
|
||||
EDD_v3_Upgrades.markUpgradeInProgress( upgrade_key );
|
||||
}
|
||||
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
data: data,
|
||||
url: ajaxurl,
|
||||
success: function( response ) {
|
||||
if ( ! response.success ) {
|
||||
EDD_v3_Upgrades.showError( upgrade_key, response.data );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( response.data.upgrade_completed ) {
|
||||
EDD_v3_Upgrades.markUpgradeComplete( response.data.upgrade_processed );
|
||||
|
||||
// If we just completed legacy data removal then we're all done!
|
||||
if ( 'v30_legacy_data_removed' === response.data.upgrade_processed ) {
|
||||
EDD_v3_Upgrades.legacyDataRemovalComplete();
|
||||
|
||||
return;
|
||||
}
|
||||
} else if( response.data.percentage ) {
|
||||
// Update percentage for the upgrade we just processed.
|
||||
EDD_v3_Upgrades.updateUpgradePercentage( response.data.upgrade_processed, response.data.percentage );
|
||||
}
|
||||
|
||||
if ( response.data.next_upgrade && 'v30_legacy_data_removed' === response.data.next_upgrade && 'v30_legacy_data_removed' !== response.data.upgrade_processed ) {
|
||||
EDD_v3_Upgrades.inProgress = false;
|
||||
|
||||
// Legacy data removal is next, which we do not start automatically.
|
||||
EDD_v3_Upgrades.showLegacyDataRemoval();
|
||||
} else if ( response.data.next_upgrade ) {
|
||||
// Start the next upgrade (or continuation of current) automatically.
|
||||
EDD_v3_Upgrades.processStep( response.data.next_upgrade, response.data.next_step, response.data.nonce );
|
||||
} else {
|
||||
EDD_v3_Upgrades.inProgress = false;
|
||||
EDD_v3_Upgrades.stopAllSpinners();
|
||||
}
|
||||
}
|
||||
} ).fail( ( data ) => {
|
||||
// @todo
|
||||
} )
|
||||
},
|
||||
|
||||
clearErrors: function() {
|
||||
$( '.edd-v3-migration-error' ).addClass( 'edd-hidden' ).html( '' );
|
||||
},
|
||||
|
||||
showError: function( upgradeKey, message ) {
|
||||
let container = $( '#edd-v3-migration' );
|
||||
if ( 'v30_legacy_data_removed' === upgradeKey ) {
|
||||
container = $( '#edd-v3-remove-legacy-data' );
|
||||
}
|
||||
const errorWrapper = container.find( '.edd-v3-migration-error' );
|
||||
|
||||
errorWrapper.html( '<p>' + message + '</p>' ).removeClass( 'edd-hidden' );
|
||||
|
||||
// Stop processing and allow form resubmission.
|
||||
EDD_v3_Upgrades.inProgress = false;
|
||||
container.find( 'input' ).prop( 'disabled', false );
|
||||
container.find( 'button' )
|
||||
.prop( 'disabled', false )
|
||||
.addClass( 'button-primary' )
|
||||
.removeClass( 'button-secondary disabled updating-message' );
|
||||
},
|
||||
|
||||
markUpgradeInProgress: function( upgradeKey ) {
|
||||
const upgradeRow = $( '#edd-v3-migration-' + upgradeKey );
|
||||
if ( ! upgradeRow.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const statusIcon = upgradeRow.find( '.dashicons' );
|
||||
if ( statusIcon.length ) {
|
||||
statusIcon.removeClass( 'dashicons-minus' ).addClass( 'dashicons-update' );
|
||||
}
|
||||
|
||||
upgradeRow.find( '.edd-migration-percentage' ).removeClass( 'edd-hidden' );
|
||||
},
|
||||
|
||||
updateUpgradePercentage: function( upgradeKey, newPercentage ) {
|
||||
const upgradeRow = $( '#edd-v3-migration-' + upgradeKey );
|
||||
if ( ! upgradeRow.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
upgradeRow.find( '.edd-migration-percentage-value' ).text( newPercentage );
|
||||
},
|
||||
|
||||
markUpgradeComplete: function( upgradeKey ) {
|
||||
const upgradeRow = $( '#edd-v3-migration-' + upgradeKey );
|
||||
if ( ! upgradeRow.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
upgradeRow.addClass( 'edd-upgrade-complete' );
|
||||
|
||||
const statusIcon = upgradeRow.find( '.dashicons' );
|
||||
if ( statusIcon.length ) {
|
||||
statusIcon.removeClass( 'dashicons-minus dashicons-update' ).addClass( 'dashicons-yes' );
|
||||
}
|
||||
|
||||
const statusLabel = upgradeRow.find( '.edd-migration-status .screen-reader-text' );
|
||||
if ( statusLabel.length ) {
|
||||
statusLabel.text( edd_admin_upgrade_vars.migration_complete );
|
||||
}
|
||||
|
||||
// Update percentage to 100%;
|
||||
upgradeRow.find( '.edd-migration-percentage-value' ).text( 100 );
|
||||
},
|
||||
|
||||
showLegacyDataRemoval: function() {
|
||||
// Un-spin the main submit button.
|
||||
$( '#edd-v3-migration-button' ).removeClass( 'updating-message' );
|
||||
|
||||
// Show the "migration complete" message.
|
||||
$( '#edd-v3-migration-complete' ).removeClass( 'edd-hidden' );
|
||||
|
||||
const dataRemovalWrapper = $( '#edd-v3-remove-legacy-data' );
|
||||
if ( ! dataRemovalWrapper.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataRemovalWrapper.removeClass( 'edd-hidden' );
|
||||
},
|
||||
|
||||
legacyDataRemovalComplete: function() {
|
||||
const wrapper = $( '#edd-v3-remove-legacy-data' );
|
||||
if ( ! wrapper.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wrapper.find( 'form' ).addClass( 'edd-hidden' );
|
||||
wrapper.find( '#edd-v3-legacy-data-removal-complete' ).removeClass( 'edd-hidden' );
|
||||
},
|
||||
|
||||
stopAllSpinners: function() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
EDD_v3_Upgrades.init();
|
||||
} );
|
Reference in New Issue
Block a user