Skip to content

Commit 090d4d6

Browse files
authored
Merge pull request #9804 from awesomemotive/release/3.6.1
Adding changes for 3.6.1
2 parents b1cbb21 + 065a32b commit 090d4d6

File tree

109 files changed

+7732
-1551
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+7732
-1551
lines changed

assets/css/admin/gateways/stripe.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,8 @@
272272
}
273273
}
274274
}
275+
276+
p.edd-stripe-payment-methods__reset {
277+
margin-top: 20px !important;
278+
text-align: right;
279+
}

assets/css/stripe-admin-rtl.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/css/stripe-admin.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/js/admin/components/conditionals/requirements.js

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,54 @@
88
* Update the visibility of elements based on requirements.
99
*
1010
* @since 3.5.0
11-
* @param {string} requires The requirement identifier (e.g., 'vat-enable').
12-
* @param {bool} enabled Whether the requirement is met.
11+
* @param {string} requires The requirement identifier (e.g., 'vat-enable').
12+
* @param {bool|string} enabled Whether the requirement is met (bool for checkboxes, string for selects).
13+
* @param {bool} inverse Whether to invert the logic (for inverse toggles).
1314
*/
14-
export function updateRequirements( requires, enabled ) {
15+
export function updateRequirements( requires, enabled, inverse = false ) {
16+
const shouldShow = inverse ? ! enabled : enabled;
1517
const elements = document.querySelectorAll( '.edd-requires__' + requires );
1618
elements.forEach( function ( element ) {
1719
element.classList.remove( 'edd-hidden--required' );
18-
element.classList.toggle( 'edd-hidden', ! enabled );
20+
element.classList.toggle( 'edd-hidden', ! shouldShow );
21+
if ( element.classList.contains( 'edd-hidden' ) ) {
22+
element.classList.add( 'edd-hidden--required' );
23+
}
24+
} );
25+
}
26+
27+
/**
28+
* Update the visibility of elements based on select value requirements.
29+
*
30+
* @since 3.6.1
31+
* @param {string} requires The requirement identifier (e.g., 'captcha-provider').
32+
* @param {string} value The current value of the select element.
33+
*/
34+
export function updateSelectRequirements( requires, value ) {
35+
// Find all elements that depend on this requirement
36+
const dependentElements = document.querySelectorAll( '[class*="edd-requires__' + requires + '-"]' );
37+
38+
dependentElements.forEach( function ( element ) {
39+
// Extract the expected value from the class name
40+
const classMatch = element.className.match( new RegExp( 'edd-requires__' + requires + '-([\\w-]+)' ) );
41+
if ( classMatch ) {
42+
const expectedValue = classMatch[1];
43+
const shouldShow = value === expectedValue;
44+
45+
element.classList.remove( 'edd-hidden--required' );
46+
element.classList.toggle( 'edd-hidden', ! shouldShow );
47+
if ( element.classList.contains( 'edd-hidden' ) ) {
48+
element.classList.add( 'edd-hidden--required' );
49+
}
50+
}
51+
} );
52+
53+
// Also handle generic requirements (elements that should show for any non-empty value)
54+
const genericElements = document.querySelectorAll( '.edd-requires__' + requires + ':not([class*="edd-requires__' + requires + '-"])' );
55+
genericElements.forEach( function ( element ) {
56+
const shouldShow = value !== '' && value !== null;
57+
element.classList.remove( 'edd-hidden--required' );
58+
element.classList.toggle( 'edd-hidden', ! shouldShow );
1959
if ( element.classList.contains( 'edd-hidden' ) ) {
2060
element.classList.add( 'edd-hidden--required' );
2161
}
@@ -26,30 +66,39 @@ export function updateRequirements( requires, enabled ) {
2666
* Initialize requirements on page load.
2767
*/
2868
export function initializeRequirements() {
29-
// Find all checkboxes with data-edd-requirement attribute
30-
const requirementCheckboxes = document.querySelectorAll( '[data-edd-requirement]' );
69+
// Find all elements with data-edd-requirement attribute (checkboxes and selects)
70+
const requirementElements = document.querySelectorAll( '[data-edd-requirement]' );
3171

32-
requirementCheckboxes.forEach( function ( checkbox ) {
33-
const requires = checkbox.getAttribute( 'data-edd-requirement' );
72+
requirementElements.forEach( function ( element ) {
73+
const requires = element.getAttribute( 'data-edd-requirement' );
3474
if ( ! requires ) {
3575
return;
3676
}
3777

38-
// Update initial state
39-
updateRequirements( requires, checkbox.checked );
78+
// Handle select elements
79+
if ( element.tagName === 'SELECT' ) {
80+
updateSelectRequirements( requires, element.value );
81+
}
82+
// Handle checkboxes
83+
else if ( element.type === 'checkbox' ) {
84+
// Check if this is an inverse toggle
85+
const inverse = element.hasAttribute( 'data-edd-requirement-inverse' );
86+
// Update initial state
87+
updateRequirements( requires, element.checked, inverse );
88+
}
4089
} );
4190
}
4291

4392
/**
44-
* Add event listeners for checkbox changes.
93+
* Add event listeners for checkbox and select changes.
4594
*/
4695
export function addRequirementListeners() {
47-
// Listen to native checkbox change events
96+
// Listen to native change events for both checkboxes and selects
4897
document.addEventListener( 'change', function ( event ) {
4998
const target = event.target;
5099

51-
// Only process checkboxes with data-edd-requirement attribute
52-
if ( target.type !== 'checkbox' || ! target.hasAttribute( 'data-edd-requirement' ) ) {
100+
// Only process elements with data-edd-requirement attribute
101+
if ( ! target.hasAttribute( 'data-edd-requirement' ) ) {
53102
return;
54103
}
55104

@@ -58,8 +107,17 @@ export function addRequirementListeners() {
58107
return;
59108
}
60109

61-
// Update dependent elements
62-
updateRequirements( requires, target.checked );
110+
// Handle select elements
111+
if ( target.tagName === 'SELECT' ) {
112+
updateSelectRequirements( requires, target.value );
113+
}
114+
// Handle checkboxes
115+
else if ( target.type === 'checkbox' ) {
116+
// Check if this is an inverse toggle
117+
const inverse = target.hasAttribute( 'data-edd-requirement-inverse' );
118+
// Update dependent elements
119+
updateRequirements( requires, target.checked, inverse );
120+
}
63121
} );
64122

65123
// Listen for custom events from AJAX toggles

assets/js/admin/components/settings/toggles.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function handleToggleChange( event ) {
1313
}
1414

1515
const setting = input.dataset.setting;
16-
if ( ! setting ) {
16+
if ( ! setting || ! input.dataset.nonce ) {
1717
return;
1818
}
1919

@@ -23,11 +23,11 @@ function handleToggleChange( event ) {
2323

2424
const body = new URLSearchParams();
2525
body.append( 'action', 'edd_toggle_ajax_setting' );
26-
body.append( 'nonce', eddSettings?.nonce || '' );
26+
body.append( 'nonce', input.dataset.nonce );
2727
body.append( 'setting', setting );
2828
body.append( 'value', desiredChecked ? '1' : '0' );
2929

30-
fetch( eddSettings.ajaxurl, {
30+
fetch( ajaxurl, {
3131
method: 'POST',
3232
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
3333
credentials: 'same-origin',

assets/js/admin/tools/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
/**
22
* Tools screen JS
33
*/
4+
import { initializeSettingsToggles } from '../components/settings/toggles';
5+
46
const EDD_Tools = {
57

68
init: function() {
79
this.revoke_api_key();
810
this.regenerate_api_key();
911
this.create_api_key();
1012
this.recount_stats();
13+
this.enable_toggles();
1114
},
1215

1316
revoke_api_key: function() {
@@ -114,6 +117,9 @@ const EDD_Tools = {
114117
}
115118
} );
116119
},
120+
enable_toggles: function() {
121+
initializeSettingsToggles();
122+
},
117123
};
118124

119125
jQuery( document ).ready( function( $ ) {

assets/js/admin/tools/labs/index.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
/**
44
* Internal dependencies
55
*/
6-
import { initializeSettingsToggles } from 'admin/components/settings/toggles';
76

87
// Requirements are auto-initialized globally when requirements.js loads
98
// but we still import it to ensure it's bundled with this script
@@ -40,9 +39,6 @@ function getVisibleProfilerIds() {
4039
* Initialize the Labs tools tab.
4140
*/
4241
function initializeLabs() {
43-
// Initialize the settings toggle functionality.
44-
initializeSettingsToggles();
45-
4642
// Handle cookie setting updates from settings toggles
4743
document.addEventListener( 'eddSettingToggled', function ( event ) {
4844
const cookieData = event?.detail?.cookie;

assets/js/captcha/captcha.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* Easy Digital Downloads CAPTCHA Core
3+
*
4+
* Provider-agnostic CAPTCHA integration for checkout and form validation.
5+
*
6+
* @package EDD\Captcha
7+
* @since 3.6.1
8+
*/
9+
10+
; ( function ( document, $ ) {
11+
'use strict';
12+
13+
/**
14+
* Executes CAPTCHA and updates the hidden input with the token.
15+
*
16+
* Delegates to the provider-specific handler defined by globalThis.EDDCaptchaHandler.
17+
*
18+
* @param {Function} onSuccess Optional callback for successful token generation.
19+
* @param {Function} onError Optional callback for errors.
20+
*/
21+
function executeCaptcha( onSuccess, onError ) {
22+
if ( ! globalThis.EDDreCAPTCHA.sitekey ) {
23+
return;
24+
}
25+
26+
// Check if handler is available.
27+
if ( typeof globalThis.EDDCaptchaHandler === 'undefined' || typeof globalThis.EDDCaptchaHandler.execute !== 'function' ) {
28+
console.error( 'CAPTCHA handler not loaded' );
29+
if ( onError ) {
30+
onError( 'Handler not loaded' );
31+
}
32+
return;
33+
}
34+
35+
// Delegate to the provider-specific handler.
36+
globalThis.EDDCaptchaHandler.execute( onSuccess, onError );
37+
}
38+
39+
/**
40+
* Initialize checkout-specific functionality.
41+
* Refreshes CAPTCHA token on a timer if the provider requires it.
42+
*/
43+
function initializeCheckout() {
44+
let tokenRefresh;
45+
46+
function clearTokenRefresh() {
47+
if ( tokenRefresh ) {
48+
clearInterval( tokenRefresh );
49+
tokenRefresh = null;
50+
}
51+
}
52+
53+
// Start token refresh when gateway loads.
54+
$( document.body ).on( 'edd_gateway_loaded', function ( e, gateway ) {
55+
clearTokenRefresh();
56+
57+
// Only set up token refresh if the provider needs it.
58+
// (e.g., reCAPTCHA v3 tokens expire after 2 minutes)
59+
if ( globalThis.EDDCaptchaHandler && globalThis.EDDCaptchaHandler.needsRefresh ) {
60+
tokenRefresh = setInterval( function () {
61+
executeCaptcha( null, function ( error ) {
62+
// On checkout, show critical error if CAPTCHA fails.
63+
const form = document.getElementById( 'edd_purchase_form' );
64+
if ( form ) {
65+
form.innerHTML = '<div class="edd_errors edd-alert edd-alert-error">' + globalThis.EDDreCAPTCHA.checkoutFailure + '</div>';
66+
}
67+
} );
68+
}, 1000 * 110 ); // Refresh every 110 seconds (before 2-minute expiry).
69+
}
70+
71+
executeCaptcha();
72+
} );
73+
74+
// Clear interval on form submission.
75+
$( document.body ).on( 'submit', '#edd_purchase_form', clearTokenRefresh );
76+
77+
// Clear interval when checkout is unloaded.
78+
$( document.body ).on( 'edd_checkout_unloaded', clearTokenRefresh );
79+
}
80+
81+
/**
82+
* Initialize form validation-specific functionality.
83+
* Handles CAPTCHA validation on form submission.
84+
*/
85+
function initializeFormValidation() {
86+
const captchaInput = document.querySelector( 'input#edd-blocks-recaptcha' );
87+
if ( ! captchaInput ) {
88+
return;
89+
}
90+
91+
// Get form action and submit button.
92+
globalThis.EDDreCAPTCHA.action = document.querySelector( 'input[name="edd_action"]' ).value;
93+
globalThis.EDDreCAPTCHA.submit = document.querySelector( 'input[name="edd_submit"]' ).value;
94+
const submitButton = document.querySelector( '#' + globalThis.EDDreCAPTCHA.submit );
95+
let isProcessing = false;
96+
97+
/**
98+
* Remove existing CAPTCHA error messages.
99+
*/
100+
function removeExistingErrors() {
101+
const existingErrors = document.querySelectorAll( '.edd_errors.invalid_captcha_bad, .edd_errors.invalid_captcha' );
102+
existingErrors.forEach( function ( error ) {
103+
error.remove();
104+
} );
105+
}
106+
107+
captchaInput.addEventListener( 'invalid', function ( e ) {
108+
e.preventDefault();
109+
110+
// Prevent multiple simultaneous executions.
111+
if ( isProcessing ) {
112+
return;
113+
}
114+
115+
isProcessing = true;
116+
117+
// Clear any existing error messages.
118+
removeExistingErrors();
119+
120+
executeCaptcha(
121+
function ( token ) {
122+
// Validate token via AJAX.
123+
$.ajax( {
124+
type: 'POST',
125+
data: {
126+
action: 'edd_captcha_validate',
127+
token: token,
128+
},
129+
url: globalThis.EDDreCAPTCHA.ajaxurl,
130+
success: function ( response ) {
131+
isProcessing = false;
132+
if ( response.success ) {
133+
captchaInput.value = token;
134+
submitButton.click();
135+
} else {
136+
captchaInput.value = '';
137+
removeExistingErrors();
138+
var errorNode = document.createElement( 'div' );
139+
errorNode.classList.add( 'edd_errors', 'edd-alert', 'edd-alert-error', 'invalid_captcha', response.data.error );
140+
errorNode.innerHTML = '<p class="edd_error"><strong>' + globalThis.EDDreCAPTCHA.error + '</strong>: ' + response.data.message + '</p>';
141+
submitButton.closest( 'form' ).after( errorNode );
142+
}
143+
},
144+
} ).fail( function ( response ) {
145+
isProcessing = false;
146+
captchaInput.value = '';
147+
console.error( 'CAPTCHA AJAX error:', response );
148+
} );
149+
},
150+
function ( error ) {
151+
isProcessing = false;
152+
// Show error to user.
153+
captchaInput.value = '';
154+
removeExistingErrors();
155+
var errorNode = document.createElement( 'div' );
156+
errorNode.classList.add( 'edd_errors', 'edd-alert', 'edd-alert-error', 'invalid_captcha_bad' );
157+
errorNode.innerHTML = '<p class="edd_error"><strong>' + globalThis.EDDreCAPTCHA.error + '</strong>: ' + globalThis.EDDreCAPTCHA.error_message + '</p>';
158+
submitButton.closest( 'form' ).after( errorNode );
159+
}
160+
);
161+
} );
162+
}
163+
164+
// Initialize based on context.
165+
if ( globalThis.EDDreCAPTCHA.context === 'checkout' ) {
166+
initializeCheckout();
167+
} else {
168+
$( document ).ready( function () {
169+
initializeFormValidation();
170+
} );
171+
}
172+
173+
} )( document, jQuery );

0 commit comments

Comments
 (0)