installed plugin Easy Digital Downloads version 3.1.0.3

This commit is contained in:
2022-11-27 15:03:07 +00:00
committed by Gitium
parent 555673545b
commit c5dce2cec6
1200 changed files with 238970 additions and 0 deletions

View File

@ -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' );
} );
} );

View File

@ -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 ) );
} );

View File

@ -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' );
},
} );
}
} );

View File

@ -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;
} );
} );

View File

@ -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 );
} );
}
} );
} );

View File

@ -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();
}
} );
} );
} );
} );

View File

@ -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 );
},
} );
}
} );

View File

@ -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' );
}
} );

View File

@ -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' ) );
} );

View File

@ -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;
} );
} );

View File

@ -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()
} );

View File

@ -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();
} );

View File

@ -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 );
},
} );
}
} );

View File

@ -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() );
} );
} );

View File

@ -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 );
} );
} );

View File

@ -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();
} );

View File

@ -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';

View File

@ -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();
} );

View File

@ -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 );

View File

@ -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' ) );
}
} );

View File

@ -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;
});
} );

View File

@ -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 );
} );

View File

@ -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 );
}
} );
} );

View File

@ -0,0 +1,3 @@
import './address.js';
import './customer.js';
import './receipt.js';

View File

@ -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 );
} );
} );

View File

@ -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' );
} );

View File

@ -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,
} );
},
} );

View File

@ -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 );
},
} );

View File

@ -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,
} );

View File

@ -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;

View File

@ -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();
},
} );

View File

@ -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 );
} );
}
} );

View File

@ -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,
} );
}
},
} );

View File

@ -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: '',
},
} );

View File

@ -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;
}
}
);

View File

@ -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();
},
} );

View File

@ -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 ) {}
},
} );

View File

@ -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() );
},
} );

View File

@ -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;
},
} );

View File

@ -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 );
},
} );

View File

@ -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 ) ;
} );
},
} );

View File

@ -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 );
} );
},
} );

View File

@ -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,
},
};
},
} );

View File

@ -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();
},
} );

View File

@ -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();
}
},
} );

View File

@ -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' );
},
} );

View File

@ -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' ) );
}
);
},
} );

View File

@ -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();
},
} );

View File

@ -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,
} )
);
}
},
} );

View File

@ -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' ) ),
};
},
} );

View File

@ -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,
} )
)
) );
},
} );

View File

@ -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 ),
};
},
} );

View File

@ -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();
} );
},
} );

View File

@ -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 ),
};
},
} );

View File

@ -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' );
},
} );

View File

@ -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;
},
} );

View File

@ -0,0 +1,2 @@
// Empty file for backwards compatibility.
// @see assets/js/admin/orders/index.js

View File

@ -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 );
}
};

View File

@ -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 }`;
},
},
} );

View File

@ -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 }%)`;
},
},
} );

View File

@ -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';
},
};

View File

@ -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;
};

View File

@ -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();
} );

View File

@ -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();
} );

View File

@ -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;
};

View File

@ -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 );

View File

@ -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();
} );
}

View File

@ -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();
} );

View File

@ -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';
});
}

View File

@ -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;

View File

@ -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();
} );

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();
} );

View File

@ -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();
} );

View File

@ -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();
} );

View File

@ -0,0 +1 @@
import './v3';

View File

@ -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();
} );

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e){var t={};function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{}};return e[i].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(i,r,function(t){return e[t]}.bind(null,r));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=179)}({1:function(e,t){e.exports=jQuery},179:function(e,t,n){(function(e,t){var n={vars:{customer_card_wrap_editable:e("#edit-customer-info .editable"),customer_card_wrap_edit_item:e("#edit-customer-info .edit-item"),user_id:e('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(){e(document.body).on("click","#edit-customer",(function(e){e.preventDefault(),n.vars.customer_card_wrap_editable.hide(),n.vars.customer_card_wrap_edit_item.show().css("display","block")}))},add_email:function(){e(document.body).on("click","#add-customer-email",(function(t){t.preventDefault();var n=e(this),i=n.parent().parent().parent().parent(),r={edd_action:"customer-add-email",customer_id:i.find('input[name="customer-id"]').val(),email:i.find('input[name="additional-email"]').val(),primary:i.find('input[name="make-additional-primary"]').is(":checked"),_wpnonce:i.find('input[name="add_email_nonce"]').val()};i.parent().find(".notice-container").remove(),i.find(".spinner").css("visibility","visible"),n.attr("disabled",!0),e.post(ajaxurl,r,(function(e){setTimeout((function(){!0===e.success?window.location.href=e.redirect:(n.attr("disabled",!1),i.before('<div class="notice-container"><div class="notice notice-error inline"><p>'+e.message+"</p></div></div>"),i.find(".spinner").css("visibility","hidden"))}),342)}),"json")}))},user_search:function(){e(document.body).on("click.eddSelectUser",".edd_user_search_results a",(function(t){t.preventDefault();var i=e(this).data("userid");n.vars.user_id.val(i)}))},remove_user:function(){e(document.body).on("click","#disconnect-customer",(function(t){if(t.preventDefault(),confirm(edd_vars.disconnect_customer)){var n={edd_action:"disconnect-userid",customer_id:e('input[name="customerinfo[id]"]').val(),_wpnonce:e("#edit-customer-info #_wpnonce").val()};e.post(ajaxurl,n,(function(e){window.location.href=window.location.href}),"json")}}))},cancel_edit:function(){e(document.body).on("click","#edd-edit-customer-cancel",(function(t){t.preventDefault(),n.vars.customer_card_wrap_edit_item.hide(),n.vars.customer_card_wrap_editable.show(),e(".edd_user_search_results").html("")}))},change_country:function(){e('select[name="customerinfo[country]"]').change((function(){var t=e(this),n=e(':input[name="customerinfo[region]"]'),i={action:"edd_get_shop_states",country:t.val(),nonce:t.data("nonce"),field_name:"customerinfo[region]"};return e.post(ajaxurl,i,(function(e){console.log(e),"nostates"===e?n.replaceWith('<input type="text" name="'+i.field_name+'" value="" class="edd-edit-toggles medium-text"/>'):n.replaceWith(e)})),!1}))},delete_checked:function(){e("#edd-customer-delete-confirm").change((function(){var t=e("#edd-customer-delete-records"),n=e("#edd-delete-customer");e(this).prop("checked")?(t.attr("disabled",!1),n.attr("disabled",!1)):(t.attr("disabled",!0),t.prop("checked",!1),n.attr("disabled",!0))}))}};t(document).ready((function(e){n.init()}))}).call(this,n(1),n(1))}});

View File

@ -0,0 +1 @@
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=180)}({1:function(e,t){e.exports=jQuery},180:function(e,t,n){(function(e){e(document).ready((function(e){e("#edd_dashboard_sales").length&&e.ajax({type:"GET",data:{action:"edd_load_dashboard_widget"},url:ajaxurl,success:function(t){e("#edd_dashboard_sales .edd-loading").html(t)}})}))}).call(this,n(1))}});

View File

@ -0,0 +1 @@
!function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=181)}({1:function(t,e){t.exports=jQuery},181:function(t,e,n){"use strict";n.r(e),function(t){var e=n(3);Object(e.a)((function(){var e=t("#edd_products");e&&e.change((function(){t("#edd-discount-product-conditions").toggle(null!==e.val())}))}))}.call(this,n(1))},3:function(t,e,n){"use strict";(function(t){n.d(e,"a",(function(){return r}));var r=function(e){t(e)}}).call(this,n(1))}});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=235)}({235:function(e,t,n){"use strict";n.r(t);var r=function(e){return(e=e.toLowerCase()).trim()};document.addEventListener("DOMContentLoaded",(function(){var e,t,n,o=document.querySelector(".edd-email-tags-inserter");o&&(o.addEventListener("click",tb_position),(e=document.querySelectorAll(".edd-email-tags-list-button"))&&_.each(e,(function(e){e.addEventListener("click",(function(){tb_remove(),window.send_to_editor(e.dataset.to_insert)}))})),t=document.querySelector(".edd-email-tags-filter-search"),n=document.querySelectorAll(".edd-email-tags-list-item"),t&&t.addEventListener("keyup",(function(e){var t=e.target.value,o=function(e,t){var n=r(t),o=function(e){return-1!==r(e).indexOf(n)};return _.filter(e,(function(e){return o(e.title)||_.some(e.keywords,o)}))}(eddEmailTagsInserter.items,t);_.each(n,(function(e){var t=_.findWhere(o,{tag:e.dataset.tag});e.style.display=t?"block":"none"}))})))}))}});

View File

@ -0,0 +1 @@
!function(e){var t={};function n(a){if(t[a])return t[a].exports;var i=t[a]={i:a,l:!1,exports:{}};return e[a].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,a){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:a})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var a=Object.create(null);if(n.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(a,i,function(t){return e[t]}.bind(null,i));return a},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=219)}({1:function(e,t){e.exports=jQuery},219:function(e,t,n){(function(e){!function(e,t){"use strict";t(".edd-extension-manager__action").on("click",(function(e){e.preventDefault();var n=t(this),a=n.attr("data-action"),i=n.attr("data-plugin"),r=n.attr("data-type"),o="";if(!n.attr("disabled")){switch(a){case"activate":o="edd_activate_extension",n.text(EDDExtensionManager.activating);break;case"install":o="edd_install_extension",n.text(EDDExtensionManager.installing);break;default:return}n.removeClass("button-primary").attr("disabled",!0).addClass("updating-message");var s={action:o,nonce:EDDExtensionManager.extension_manager_nonce,plugin:i,type:r,pass:n.attr("data-pass"),id:n.attr("data-id"),product:n.attr("data-product")};t.post(ajaxurl,s).done((function(e){console.log(e);var t=n.closest(".edd-extension-manager__step");if(e.success){var a=t.next();a.length&&(t.fadeOut(),a.prepend('<div class="notice inline-notice notice-success"><p>'+e.data.message+"</p></div>"),a.fadeIn())}else{t.fadeOut();var i=e.data.message;i||(i="plugin"!==r?EDDExtensionManager.extension_install_failed:EDDExtensionManager.plugin_install_failed),t.after('<div class="notice inline-notice notice-warning"><p>'+i+"</p></div>")}}))}}))}(document,e)}).call(this,n(1))}});

View File

@ -0,0 +1 @@
!function(e){var n={};function t(o){if(n[o])return n[o].exports;var d=n[o]={i:o,l:!1,exports:{}};return e[o].call(d.exports,d,d.exports,t),d.l=!0,d.exports}t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:o})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(t.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var d in e)t.d(o,d,function(n){return e[n]}.bind(null,d));return o},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=186)}({1:function(e,n){e.exports=jQuery},186:function(e,n,t){(function(e,n){var t={init:function(){this.enter_key(),this.add_note(),this.remove_note()},enter_key:function(){e(document.body).on("keydown","#edd-note",(function(n){13===n.keyCode&&(n.metaKey||n.ctrlKey)&&(n.preventDefault(),e("#edd-add-note").click())}))},add_note:function(){e("#edd-add-note").on("click",(function(n){n.preventDefault();var t=e(this),o=e("#edd-note"),d=e(".edd-notes"),r=e(".edd-no-notes"),i=e(".edd-add-note .spinner"),a={action:"edd_add_note",nonce:e("#edd_note_nonce").val(),object_id:t.data("object-id"),object_type:t.data("object-type"),note:o.val()};if(a.note)t.prop("disabled",!0),i.css("visibility","visible"),e.ajax({type:"POST",data:a,url:ajaxurl,success:function(e){var n=wpAjax.parseAjaxResponse(e);n=n.responses[0],d.append(n.data),r.hide(),t.prop("disabled",!1),i.css("visibility","hidden"),o.val("")}}).fail((function(e){window.console&&window.console.log&&console.log(e),t.prop("disabled",!1),i.css("visibility","hidden")}));else{var c=o.css("border-color");o.css("border-color","red"),setTimeout((function(){o.css("border-color",c)}),userInteractionInterval)}}))},remove_note:function(){e(document.body).on("click",".edd-delete-note",(function(n){n.preventDefault();var t=e(this),o=e(".edd-note"),d=t.parents(".edd-note"),r=e(".edd-no-notes"),i=e("#edd_note_nonce");if(confirm(edd_vars.delete_note)){var a={action:"edd_delete_note",nonce:i.val(),note_id:t.data("note-id")};return d.addClass("deleting"),e.ajax({type:"POST",data:a,url:ajaxurl,success:function(e){return"1"===e&&d.remove(),1===o.length&&r.show(),!1}}).fail((function(e){window.console&&window.console.log&&console.log(e),d.removeClass("deleting")})),!0}}))}};n(document).ready((function(e){t.init()}))}).call(this,t(1),t(1))}});

View File

@ -0,0 +1 @@
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=220)}({1:function(e,t){e.exports=jQuery},220:function(e,t,n){(function(e){!function(e,t){"use strict";t("#edd-disable-debug-log").on("click",(function(e){e.preventDefault(),t(this).attr("disabled",!0);var n=t("#edd-debug-log-notice");t.ajax({type:"GET",data:{action:"edd_disable_debugging",nonce:t("#edd_debug_log_delete").val()},url:ajaxurl,success:function(e){n.empty().append(e.data),setTimeout((function(){n.slideUp()}),3e3)}}).fail((function(e){n.empty().append(e.responseJSON.data)}))}))}(document,e)}).call(this,n(1))}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=201)}({201:function(e,t){}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More