306 lines
7.5 KiB
JavaScript
306 lines
7.5 KiB
JavaScript
/**
|
|
* JavaScript for feedback form in wp-admin/plugins
|
|
*
|
|
* @since 5.6.2
|
|
*/
|
|
(function () {
|
|
const { __ } = wp.i18n;
|
|
|
|
const deactivationPopover = document.getElementById(
|
|
'wp-security-audit-log-popover'
|
|
);
|
|
|
|
if (!deactivationPopover) {
|
|
return;
|
|
}
|
|
|
|
let deactivateButton = null;
|
|
const feedbackFieldSelector = 'textarea, input[type="text"]';
|
|
const reasonValidationMessage = deactivationPopover.querySelector(
|
|
'.wsal-reason-validation'
|
|
);
|
|
const feedbackValidationMessage = deactivationPopover.querySelector(
|
|
'.wsal-feedback-validation'
|
|
);
|
|
|
|
/**
|
|
* Normalize free-text feedback before validation or submission.
|
|
*
|
|
* @param {string} feedbackValue The feedback field value.
|
|
* @param {boolean} trimValue Whether to trim whitespace.
|
|
*
|
|
* @since 5.6.3
|
|
*/
|
|
const sanitizeFeedbackValue = (feedbackValue, trimValue = true) => {
|
|
const sanitizedFeedbackValue = String(feedbackValue || '')
|
|
.replace(/&(?:lt|gt|#60|#62|#x3c|#x3e);/gi, '')
|
|
.replace(/[<>]/g, '');
|
|
|
|
return trimValue ? sanitizedFeedbackValue.trim() : sanitizedFeedbackValue;
|
|
};
|
|
|
|
/**
|
|
* Show a validation message, or hide all validation messages.
|
|
*
|
|
* @param {HTMLElement|null} validationElement The message element to show.
|
|
*
|
|
* @since 5.6.3
|
|
*/
|
|
const toggleValidationMessage = (validationElement = null) => {
|
|
[reasonValidationMessage, feedbackValidationMessage].forEach(
|
|
(messageElement) => {
|
|
if (messageElement) {
|
|
messageElement.hidden = messageElement !== validationElement;
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Toggle the selected feedback field and reset the others.
|
|
*
|
|
* @param {HTMLElement|null} reasonWrapper The selected reason wrapper.
|
|
*
|
|
* @since 5.6.3
|
|
*/
|
|
const toggleFeedbackFields = (reasonWrapper = null) => {
|
|
deactivationPopover
|
|
.querySelectorAll('.wsal-feedback-wrapper')
|
|
.forEach((feedbackWrapper) => {
|
|
feedbackWrapper.style.display = 'none';
|
|
|
|
const feedbackField = feedbackWrapper.querySelector(
|
|
feedbackFieldSelector
|
|
);
|
|
|
|
if (feedbackField) {
|
|
feedbackField.value = '';
|
|
}
|
|
});
|
|
|
|
if (!reasonWrapper) {
|
|
return;
|
|
}
|
|
|
|
const feedbackWrapper = reasonWrapper.querySelector(
|
|
'.wsal-feedback-wrapper'
|
|
);
|
|
if (feedbackWrapper) {
|
|
feedbackWrapper.style.display = 'block';
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the selected reason data from the popover.
|
|
*
|
|
* @return {Object|null} The selected reason data or null.
|
|
*
|
|
* @since 5.6.3
|
|
*/
|
|
const getSelectedReasonData = () => {
|
|
const checkedRadio = deactivationPopover.querySelector(
|
|
'input[name="reason"]:checked'
|
|
);
|
|
|
|
if (!checkedRadio) {
|
|
return null;
|
|
}
|
|
|
|
const reasonWrapper = checkedRadio.closest('.wsal-reason-wrapper');
|
|
const feedbackField = reasonWrapper
|
|
? reasonWrapper.querySelector(feedbackFieldSelector)
|
|
: null;
|
|
const feedbackValue = feedbackField
|
|
? sanitizeFeedbackValue(feedbackField.value)
|
|
: '';
|
|
|
|
if (feedbackField) {
|
|
feedbackField.value = feedbackValue;
|
|
}
|
|
|
|
return {
|
|
feedbackField: feedbackField,
|
|
feedbackValue: feedbackValue,
|
|
reason: checkedRadio.value
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Send a POST request to the remote server.
|
|
*
|
|
* @since 5.6.2
|
|
*/
|
|
const sendRequest = async (url, data) => {
|
|
const formData = new FormData();
|
|
|
|
for (const [key, value] of Object.entries(data)) {
|
|
formData.append(key, value);
|
|
}
|
|
|
|
const response = await fetch(url, { method: 'POST', body: formData });
|
|
|
|
try {
|
|
return await response.json();
|
|
} catch (error) {
|
|
return {};
|
|
}
|
|
};
|
|
|
|
// Intercept at document level in capture phase runs before any other handler.
|
|
document.addEventListener(
|
|
'click',
|
|
(clickEvent) => {
|
|
const target = clickEvent.target.closest('span.deactivate a');
|
|
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
// Check if this is the WSAL deactivate link, if not return early.
|
|
if (!target.href || target.href.indexOf('wp-security-audit-log') === -1) {
|
|
return;
|
|
}
|
|
|
|
deactivateButton = target;
|
|
|
|
clickEvent.preventDefault();
|
|
clickEvent.stopPropagation();
|
|
clickEvent.stopImmediatePropagation();
|
|
|
|
toggleValidationMessage();
|
|
|
|
deactivationPopover.showPopover();
|
|
},
|
|
true
|
|
);
|
|
|
|
// Show/hide the feedback fields based on the selected reason.
|
|
deactivationPopover.addEventListener('click', (clickEvent) => {
|
|
const reasonWrapper = clickEvent.target.closest('.wsal-reason-wrapper');
|
|
|
|
if (!reasonWrapper || !deactivationPopover.contains(reasonWrapper)) {
|
|
return;
|
|
}
|
|
|
|
const reasonRadio = reasonWrapper.querySelector('input[type="radio"]');
|
|
|
|
toggleValidationMessage();
|
|
|
|
if (!reasonRadio || reasonRadio.checked) {
|
|
return;
|
|
}
|
|
|
|
reasonRadio.checked = true;
|
|
toggleFeedbackFields(reasonWrapper);
|
|
});
|
|
|
|
deactivationPopover.addEventListener('change', (changeEvent) => {
|
|
if (!changeEvent.target.matches('input[name="reason"]')) {
|
|
return;
|
|
}
|
|
|
|
toggleValidationMessage();
|
|
toggleFeedbackFields(changeEvent.target.closest('.wsal-reason-wrapper'));
|
|
});
|
|
|
|
deactivationPopover.addEventListener('input', (inputEvent) => {
|
|
if (!inputEvent.target.matches(feedbackFieldSelector)) {
|
|
return;
|
|
}
|
|
|
|
inputEvent.target.value = sanitizeFeedbackValue(
|
|
inputEvent.target.value,
|
|
false
|
|
);
|
|
toggleValidationMessage();
|
|
});
|
|
|
|
// Handle clicking on the dismiss button skip feedback, deactivate directly.
|
|
deactivationPopover
|
|
.querySelector('button.wsal-dismiss')
|
|
.addEventListener('click', (dismissEvent) => {
|
|
dismissEvent.preventDefault();
|
|
|
|
if (deactivateButton) {
|
|
window.location.href = deactivateButton.href;
|
|
}
|
|
});
|
|
|
|
// Handle clicking on the close button to dismiss the popover.
|
|
deactivationPopover
|
|
.querySelector('button.wsal-close-button')
|
|
.addEventListener('click', () => {
|
|
toggleValidationMessage();
|
|
deactivationPopover.hidePopover();
|
|
});
|
|
|
|
// Handle clicking on the submit button to validate, send feedback, then deactivate.
|
|
deactivationPopover
|
|
.querySelector('button.wsal-submit')
|
|
.addEventListener('click', async (submitEvent) => {
|
|
submitEvent.preventDefault();
|
|
|
|
const selectedReasonData = getSelectedReasonData();
|
|
|
|
if (!selectedReasonData) {
|
|
toggleValidationMessage(reasonValidationMessage);
|
|
|
|
return;
|
|
}
|
|
|
|
const { feedbackField, feedbackValue, reason } = selectedReasonData;
|
|
|
|
if (feedbackField && !feedbackValue) {
|
|
toggleValidationMessage(feedbackValidationMessage);
|
|
feedbackField.focus();
|
|
|
|
return;
|
|
}
|
|
|
|
const requestData = {
|
|
action: 'plugin_deactivation',
|
|
plugin: wsalFeedbackForm.plugin,
|
|
site: wsalFeedbackForm.siteUrl,
|
|
reason: reason,
|
|
feedback: feedbackValue || 'no-feedback-detail-available'
|
|
};
|
|
|
|
// Hide the popover and show deactivating feedback after collecting form data.
|
|
deactivationPopover.hidePopover();
|
|
|
|
// Show deactivating feedback on the button.
|
|
if (deactivateButton) {
|
|
const isNetwork = window.location.pathname.indexOf('/network/') !== -1;
|
|
deactivateButton.textContent = isNetwork
|
|
? __('Network Deactivating...', 'wp-security-audit-log')
|
|
: __('Deactivating...', 'wp-security-audit-log');
|
|
}
|
|
|
|
try {
|
|
// Request 1: Get nonce from remote server.
|
|
const nonceResponse = await sendRequest(
|
|
wsalFeedbackForm.remoteUrl +
|
|
'/?rest_route=/deactivation-feedback-server/v1/get-nonce',
|
|
requestData
|
|
);
|
|
|
|
// Request 2: Submit feedback to remote server.
|
|
if (nonceResponse.nonce) {
|
|
requestData.nonce = nonceResponse.nonce;
|
|
|
|
await sendRequest(
|
|
wsalFeedbackForm.remoteUrl +
|
|
'/?rest_route=/deactivation-feedback-server/v1/submit-feedback',
|
|
requestData
|
|
);
|
|
}
|
|
} catch (error) {
|
|
// Silent fail in case this fetch fails, feedback lost, deactivation continues.
|
|
}
|
|
|
|
if (deactivateButton) {
|
|
window.location.href = deactivateButton.href;
|
|
}
|
|
});
|
|
})();
|